;kernel: sys51 r0.99
;project name: doudou's up & down
;designer: ut
;version: 1.0
;date: 2016/11/1
;=============================================================================================
; TIME-SHARING SYSTEM FOR MCS51 RELEASE 0.99
; UT.ZUZU
; COPYRIGHT(2012/5/10-2016/11/--)
;=============================================================================================
;详细请查看手册
;硬件要求
;1、52系列兼容的51单片机,内存256字节或以上。本程序在AT89S52运行,24.576MHZ晶振,改变晶振需调整计数器值。晶振频率越高,控制器性能越好。
;2、256字节内存中,系统使用了大部分高地址部分,0-47由用户支配,具体请看内存分配说明。
;3、一共8个线程:TASK0到TASK2为3个主线程,其余为次线程;主线程对9个寄存器和PSW、AB、DPTR进行保护,并预留堆栈最大嵌套调用为7级;次线程仅保护PSW,AB,R0-R3,最大嵌套调用为2级。
;4、系统可以在调度程序中喂看门狗,时间片不可过大,超过4MS不喂狗看门狗发出系统复位信号。看门狗功能可以在配置定义中取消。
;5、分时过程通过定时器0进行,其初值定义在T0_VALUE中,目前设置的是5MS时间片。
;6、系统时间记录在SYS_TIME变量中,通过定时器2进行,目前设置是10MS加1.
;任务操作说明
;0、任务的边界应该是循环。不建议跳出边界。尽可能的使用系统提供的调用。
;1、不同任务可以调用同一个子程序,注意子程序内受保护的范围。
;2、主任务拥有独立的R0-R7、ACC、B、PSW寄存器、DPTR指针。次任务仅保护7个寄存器。
;3、任务之间可以通过内存变量来传递信息,注意在写内存时必须占用系统,写完后再释放系统,建议使用加锁和解锁调用。
;4、系统初始化后所有任务都是睡眠的,系统会唤醒任务0和任务7,其他任务的唤醒由用户操作。任务7为伺服任务,不建议休眠它或在该任务中使用系统延时调用。(有一种风险:所有任务处于休眠态,会进入待机)
;5、系统的性能与晶振频率、唤醒的任务数量、任务占用的时间片有关系。
;6、任务有权杀死或休眠任何任务,如果系统所有任务都被杀死或休眠,系统会进入节电POWER_DOWN模式,等待复位激活。
;7、系统提供10MS刻度的16位系统时间,由TIMER2来完成。任务可以根据自己需要来完成延时功能,其性能优于普通的空等待DELAY子程序。
;8、任务不可以操作TIMER0和TIMER2这两个定时器,需要时,可以使用TIMER1. 建议不要设置为高优先级,可能导致系统时间停走。
;注:杀死和休眠的区别:任务被杀死后再次唤醒从头开始运行,任务被休眠后再次唤醒是从原来休眠的地方继续运行(就像暂停)。
;用户使用注意:
;1.总计8个任务,单个线程是死循环,所有线程并发执行,可以有限调整每个线程的时间片,默认5MS时间片,合理使用可以满足实时要求。
;2.任务0到2是主线程,线程内寄存器A和B,R0-R7,DPTR都受保护,子程序嵌套调用最大达8级。
;3.任务3到7是次线程,线程内寄存器A和B,R0-R3受保护,子程序嵌套调用最大2级。注意这个限制条件。嵌套调用超限将导致堆栈过界破坏,使系统崩溃。
;4.用户只能使用0-59之间的内存空间。
;5.用户无需考虑堆栈的分配,禁止任务程序修改堆栈指针SP。
;6.中断响应程序中要注意保护现场和恢复现场。
;2012-5-22 R0.91 占用系统和释放系统改用停止和开启计时器的方式实现。UNDEBUG
;2012-5-23 R0.92 喂狗简化到CPL指令 UNDEBUG
;使用时注意:所有中断程序内要用到PSW,A,B,R0~R7,DPTR,必须事先暂存,返回前恢复,注意它们不受保护
;2016-11-08 确认BUG和注释。可以作为稳定版。
;2016-11-08 R0.99
;为了增强实用性,拟重新布局内存,改用堆栈方式保护现场,保证3个主线程,每个线程分配29字节,增加对DPTR的保护,;增加对PSW的保护
;可以最大嵌套29-2(PC)-2(DPTR)-10(AB,RG)-1(PSW)=7个CALL;阉割剩余5个次线程,每个线程分配13字节:AB,R0-R3,PC,PSW,最大嵌套2个CALL。
;总体256字节的内存:3个主线程:29*3=87;5个次线程:13*5=65;8个线程状态(优先级、SP、闹铃H、闹铃L)=32;
;系统变量:10;系统堆栈:14;R0-R7:8个除去。 剩余用户可用的内存区:40字节
;真可谓:螺蛳壳里做道场。
;20161110
;备忘:调度程序要增加加锁功能(不切换,好处是总是能进系统区做一些系统要做的事,比如喂狗)
;延时误差太大,最大误差是一个单位(不累计),考虑系统(放在时钟程序内)来负责高精度大跨度的计时,和唤醒服务,需要额外16字节用于8个线程的闹钟记录。
;看门狗使用指南:时间片调节的太大就会触发看门狗,应能根据需要关闭看门狗。13位,每一个机器周期+1
;时钟要方便配置
;注意中断嵌套的影响 ;注意测量 系统服务的时间,及其与中断时间的比重,比重和效率成正比 ;中断响应前后次序的关系分析,用户怎么用中断
;唤醒服务要注意的是:必须留一个伺服线程,该线程始终保持就绪(不能使用系统延时)。否则有一种风险:所有任务同时调用系统延时而休眠,调度程序将转入节电模式。要复位或外部中断才能恢复。
;20161111 r0.99基本调试完成
;0.99版本比较0.92版本特点如下:
;1、充分利用堆栈的特点布局内存,使得保护内容的调整变的灵活。
;2、不改变8个任务的总数,但集中资源到3个主任务上,增加对psw、dptr寄存器的保护(原来没考虑周全,如psw是必须保护的)。使主任务不再有束缚。
;3、取消原有的延时服务,增加系统时钟的定时唤醒服务功能,每个任务可以设置自己的延时时间,然后进入休眠态等待,时间到了系统时钟会唤醒你。
;4、改变了杀、休眠、唤醒的方式,采用位表示杀死信号、就绪态、唤醒服务,可以用逻辑的方法快速操作。
;5、增加了调度程序的加锁功能,加锁状态下,调度程序不进行任务切换,但继续执行其他系统功能。
;6、看门狗、初始时间片可配置。
;7、任务7作为伺服线程,可以做一些简单的脉搏动作。伺服线程必须始终就绪,否则有任务全部休眠的风险。
;0.99的篇幅反而比0.92下降了7%,除了更加实用以外,显得更加优美。
;实际应用达到3个以上时,修复一些潜在的bug之后,可以升为r1.0版本,并出一份《51多任务内核的应用手册》
;内存地图规划
;0-47 用户
;48-63 闹钟数组-每个任务2个字节,用于指示闹钟时间 /30H
;64-73 系统变量 /40H
;74-87 系统堆栈 7个CALL 包括中断 SP_SYS:73 /49H 再压缩至6个call
;88-95 任务优先状态字节 /58H
;96-103 任务SP指针 /60H
;104-132 任务0堆栈 SP0:103 /67H
;133-161 任务1堆栈 SP1:132 /84H
;162-190 任务2堆栈 SP2:161 /A1H
;191-203 任务3堆栈 SP3:190 /BEH
;204-216 任务4堆栈 SP4:203 /CBH
;217-229 任务5堆栈 SP5:216 /D8H
;230-242 任务6堆栈 SP6:229 /E5H
;243-255 任务7堆栈 SP7:242 /F2H
;NOTE:OPRATING SFR OR RAM WHERE HAVE THE SAME ADDRESS WITH EACH OTHER WILL BE ATTENTED! CARE <DATASHEET OF AT89S52>
;-------------------------------------------------------------------------------
;标号定义
PRI_BYTE EQU 0D8H ;INIT PRIORITY OF EVERY TASK ;时间片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H 这里是参考值,初始化时间片请定义在PRI_BYTE
SYS_SP EQU 4bH ;SYSTEM STACK HEAD
START_TASK_SP EQU 67H
TAB_PRI EQU 58H ;基址 见内存分配规划
TAB_SP EQU 60H ;基址
TAB_CLK EQU 30H ;BASE
WDT_PIE EQU 00H ;设置为1E,看门狗开启,其他值则关闭看门狗 NO TEST 13位计时器1FFF复位,合计4MS :意味着启用看门狗时,时间片必须小于4MS,占用系统时也要注意这个问题,建议用加锁功能代替占用系统
;系统全局变量定义
sys_bit_byte equ 2fh ;留给系统的8个标志位 位地址78-7fh
TMP_A EQU 40H
TASK_CURT_P EQU 41H ;当前的任务指针
task_sch_p equ 4ah ;调度任务指针
CLK_ALARM EQU 42H ;闹钟字节 从左到右每一位依次标志任务0到7的闹铃请求,1为有闹铃请求
DEAD_SIG EQU 43H ;从左到右每一位依次标志任务0到7的杀死请求,1为有杀死请求
READY_BYTE EQU 44H ;从左到右每一位依次标志任务0到7的就绪状态,1为就绪
LOCK_BYTE EQU 45H ;5A表示加锁,其他值表示解锁
TMP_SP EQU 46H
WDT_BYTE EQU 47H ;狗盆子
SYS_TIME_H EQU 48H ;系统时钟高8位
SYS_TIME_L EQU 49H ;系统时钟低8位
nouse equ 4bh ;预留
preempt_bit bit 78h ;是否抢占
delay_sv_bit bit 79h ;定时器1中断服务 标志 用于小刻度的延时需求
preempt_task EQU 2eh ;抢占任务号 仅0-7有效,抢占后作废,用于调度程序切换到指定的任务去。
delay_times equ 2dh ;用于timer1计时刻度的次数
;系统晶振:24.576MHZ
T0_VALUE_H EQU 0D8H ;时间片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H 这里是初始赋值,初始化时间片请定义在PRI_BYTE
T0_VALUE_L EQU 00H
T2_VALUE_H EQU 0B0H ;时钟刻度 参考上面
T2_VALUE_L EQU 00H
T1_VALUE_H EQU 0fcH ;时钟刻度 参考上面 500us
T1_VALUE_L EQU 00h ;
;------------------------------规划程序入口
ORG 00H
JMP SYS_START
ORG 03H
;LJMP INT_INT0 ;(INT0)
RETI
ORG 0BH
;LJMP INT_T0 ;(IF0)
LJMP SHARE_SYS
RETI
ORG 13H
;LJMP INT_INT1 ;(INT1)
RETI
ORG 1BH
;LJMP INT_T1 ;(IF1)
JMP sys_ms_svrs
RETI
ORG 23H
;LJMP INT_RTX ;(RI,TI)
RETI
ORG 2BH
;LJMP INT_T2 ;(IF2)
JMP SYS_TIME_RUN
RETI
;标记中断返回:如果意外中断,直接返回,不至于跳飞;-)
;以下是任务的入口,应和表格中定义一致
ORG 30H
LJMP TASK_0
ORG 38H
LJMP TASK_1
ORG 40H
LJMP TASK_2
ORG 48H
LJMP TASK_3
ORG 50H
LJMP TASK_4
ORG 58H
LJMP TASK_5
ORG 60H
LJMP TASK_6
ORG 68H
LJMP TASK_7
;;开机,从00H跳过来*******************************************
SYS_START:
MOV SP,#SYS_SP ;SYSTEM STACK
MOV WDT_BYTE,#WDT_PIE ;准备好狗粮
clr preempt_bit
clr delay_sv_bit
mov preempt_task,#0
CALL INIT_RAM ;初始化系统内存
CALL INIT_TIMER ;初始化定时器
CALL USER_INIT ;用户初始化程序
CALL SYS_TIMER_START ;启动系统定时器
MOV DEAD_SIG,#0 ;清空杀手信号
MOV R1,#0F8H;
MOV R0,#7;
CALL SET_PRIBYTE ;任务7时间片设置为1MS
MOV READY_BYTE,#10000001B ;任务0就绪,任务7当作伺服线程,如果没有一个线程就绪,会进待机
MOV TASK_CURT_P,#0
MOV TASK_sch_P,#0
MOV TAB_PRI,#PRI_BYTE ;任务正常运行的要素:不被杀,就绪,优先级(时间片)不要太长(看门狗会叫),SP状态
MOV SP,#START_TASK_SP
LJMP TASK_0 ;进入任务0,启动分时,START SHARE
;;上面用到的子程序:任务1到7依次初始化各自内存空间-----------------------------
INIT_RAM:
MOV R0,#7 ;以此对各任务进行内存初始化赋值
ITR0:
CALL TASKRAM_INIT
DJNZ R0,ITR0 ;TASK0任务作为系统启动的入口,可以不用初始,其内容会在第一个时间片中断后调度程序会给予。
;CALL TASKRAM_INIT ;JUST FOR TEST TASK0 RAM INIT
RET
;以下表格用于初始化内存用
TAB_1:
DB 067H,084H,0A1H,0BEH,0CBH,0D8H,0E5H,0F2H,00H ;任务栈顶地址
TAB_2:
DB 030H,038H,040H,048H,050H,058H,060H,068H,00H ;任务入口地址 和ORG 30H.. 对应
;上面用到的子程序:开机初始化任务内存操作:1、根据任务号查表得栈顶位置、入口位置;2、在栈顶压入:入口、现场;3、将SP存到SP_I; 4、清就绪态
;初始化任务内存分主次 ;任务号先存R0
TASKRAM_INIT:
MOV A,R0
MOV DPTR,#TAB_1
MOVC A,@A+DPTR ;查表得初始SP
MOV TMP_SP,SP
MOV SP,A ;开始压栈
MOV A,R0
MOV DPTR,#TAB_2
MOVC A,@A+DPTR ;查表得初始PC
MOV 02H,A
PUSH 02H ;PUSH PC_L
MOV 02H,#0
PUSH 02H ;PUSH PC_H PC是16位的
PUSH 02H ;PSW,AB,R0-R7,DPTR
PUSH 02H
PUSH 02H
PUSH 02H
PUSH 02H
PUSH 02H
PUSH 02H
;区分主次任务
;任务号大于2则跳过以下步骤
CLR C
MOV A,#2
SUBB A,R0
JC TKI00
PUSH 02H ;R4 R5 R6 R7 DPL DPH
PUSH 02H
PUSH 02H
PUSH 02H
PUSH 02H
PUSH 02H
TKI00:
;保存SP到数组,SP--> SP_I
MOV A,#TAB_SP
ADD A,R0
MOV R1,A ;这个是指针变量,指向当前SP的存放地址
MOV @R1,SP ;记录SP
MOV SP,TMP_SP ;压栈完成,恢复SP
;优先级字节赋值初始值
MOV A,#TAB_PRI
ADD A,R0
MOV R1,A
MOV @R1,#PRI_BYTE
;清就绪态
CALL CLR_READY_BIT
RET
;子程序:以下初始化系统定时器 TIMER2 DEBUGED 120516 --------------------------
INIT_TIMER:
;TIMER2 SETUP
MOV 0C8H,#00H ;MOV T2CON,#00H
MOV 0C9H,#00H ;MOV T2MOD,#00H
MOV 0CCH,#T2_VALUE_L ;MOV TL2,#T2_VALUE_L
MOV 0CDH,#T2_VALUE_H ;MOV TH2,#T2_VALUE_H
MOV 0CAH,0CCH ;MOV RCAP2L,TL2
MOV 0CBH,0CDH ;MOV RCAP2H,TH2
;TIMER0 SETUP
ANL 88H,#11101111B;TCON CLR TR0 : STOP TIMER0
ANL 89H,#11110000B ;TMOD(SET TIMER0)
ORL 89H,#00000001B ;TMOD(SET TIMER0) MODE:01 16BIT COUNT UP
MOV 8AH,#T0_VALUE_L ;TL0
MOV 8CH,#T0_VALUE_H ;TH0
;TIMER1 SETUP
ANL 88H,#10111111B;TCON CLR TR0 : STOP TIMER1
ANL 89H,#00001111B ;TMOD(SET TIMER0)
ORL 89H,#00010000B ;TMOD(SET TIMER0)MODE:01 16BIT COUNT UP MODE:02 8BIT autoCOUNT UP
MOV 8bH,#T1_VALUE_L ;TL0
MOV 8dH,#T1_VALUE_H ;TH0
RET
;子程序:启动TIMER0和TIMER2 ;DEBUGED 120516---------------------------------
SYS_TIMER_START:
MOV SYS_TIME_H,#00
MOV SYS_TIME_L,#00
MOV IP,#00000000B ;SET PRIORITY
MOV IE,#10101010B ;SETB EA ;SETB ET2 ;SETB ET0 ET1 TO ENABLE INTERUPT OF TIMER2 AND TIMER0 AND TIMER1
ORL 88H,#01010000B ;TCON SETB TR0,TR1 START TIMER0 TIMER1
ORL 0C8H,#00000100B ;ORL T2CON,#00000100B ;SETB TR2 TO START TIMER2
RET
;;系统时间处理,在TIMER2中断后跳进来
;系统时间处理有2大内容:1、比较各闹钟的目标时间是否到达,到达并且该任务有唤醒服务,就执行唤醒;2、时钟刻度加一。
SYS_TIME_RUN:
CLR EA
MOV TMP_SP,SP ;保存A 保护现场
MOV SP,#SYS_SP ;--------------------------------界面,以下系统区
MOV TMP_A,PSW
PUSH TMP_A
MOV TMP_A,A ;PUSH A
PUSH TMP_A
MOV TMP_A,B ;PUSH B
PUSH TMP_A
PUSH 00H ;PUSH R0
PUSH 01H ;PUSH R1
PUSH 02H ;PUSH R2
PUSH 03H ;PUSH R3
;处理CLK_ALARM字节、TAB_CLK数组
MOV A,CLK_ALARM
JZ STR00 ;没有服务时跳过
MOV R0,#TAB_CLK
MOV R3,SYS_TIME_L
CALL PROC_CMP_BYTE ;低8位比较
MOV R1,A
MOV R0,#TAB_CLK
DEC R0
MOV R3,SYS_TIME_H
CALL PROC_CMP_BYTE ;高8位比较,对比结果保存到A 1表示相等 0表示不等
ANL A,R1 ;H和L的比较结果合并
MOV R1,A
MOV A,CLK_ALARM
ANL A,R1 ;与唤醒服务合并
ORL READY_BYTE,A ;执行唤醒
MOV A,R1
CPL A
ANL CLK_ALARM,A ;清唤醒标志,表示完成唤醒
STR00:
;16位系统时钟+1 放在后面处理,延时00时可立即生效
MOV A,SYS_TIME_L
INC SYS_TIME_L
INC A
JNZ $+4
INC SYS_TIME_H
ANL 0C8H,#01111111B ;ANL T2CON,#01111111B ;CLEAR TF2 清TIMER2中断标志
POP 03H ;POP R3
POP 02H ;POP R2
POP 01H ;POP R1
POP 00H ;POP R0
POP TMP_A
MOV B,TMP_A ;POP B
POP TMP_A
MOV A,TMP_A ;POP A ;恢复现场
POP TMP_A
MOV PSW,TMP_A
MOV SP,TMP_SP ;---------------------------------------------界面,以上系统区
SETB EA
RETI
;;;;;;;中断返回
;用于刻度为500us,次数255的等待服务。只提供一个线程使用,出于系统消耗的考虑,500us中断必须篇幅足够小。
;定时器1中断服务:500us中断一次,无服务直接返回。有服务:次数(time_us字节)为0则让waiting_task_p任务抢占(标志完成)。不为0时,减一。
;系统需要用一个字节的标志位2fh,用户要避开。
;preempt_bit bit 78h ;是否抢占
;delay_sv_bit bit 79h ;定时器1中断服务 标志 用于小刻度的延时需求
;preempt_task EQU 3fh ;抢占任务号 仅0-7有效,抢占后作废,用于调度程序切换到指定的任务去。
;delay_times equ 3eh ;用于timer1计时刻度的次数
sys_ms_svrs:
jb delay_sv_bit,smsv0 ;无服务直接返回
MOV 8bH,#T1_VALUE_L ;TL1
MOV 8dH,#T1_VALUE_H ;TH1
reti
smsv0:
;保护现场
mov tmp_a,a
;查delay_times次数:等于0时,置抢占任务preempt_bit
mov a,delay_times
jnz smsv1
setb preempt_bit ;置抢占位
clr delay_sv_bit ;清服务位
ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
;中断不嵌套,本次中断返回后进入系统中断
MOV 8bH,#T1_VALUE_L ;TL1
MOV 8dH,#T1_VALUE_H ;TH1
mov tmp_a,a
reti
;次数减一
smsv1: dec delay_times
;恢复现场
mov a,tmp_a
MOV 8bH,#T1_VALUE_L ;TL1
MOV 8dH,#T1_VALUE_H ;TH1
reti
;;上面要用到
;;子程序:基地址存R0(间隔1个字节的8个数组),与系统时钟(H或L字节)R3进行比较,8次,结果存放在ACC对应的位里面,1表示相等
PROC_CMP_BYTE:
MOV B,#0
MOV R2,#8
MOV A,#15
ADD A,R0
MOV R0,A
PCB00:
MOV A,@R0
CJNE A,03H,PCB01
MOV A,B
SETB C
RRC A
JMP PCB02
PCB01:
MOV A,B
CLR C
RRC A
PCB02: MOV B,A
DEC R0
DEC R0 ;间隔1字节的指针,从右到左
DJNZ R2,PCB00
MOV A,B
RET
;;调度程序,在timer0中断后跳过来。
;;调度程序的内容:1、保护现场;2、存sp;3、喂狗、执行任务死刑、判断加锁;4、切换下一个就绪的任务指针;5、调取新任务的时间片设置到定时器;6、调取新sp;7、恢复现场;8、返回到新任务。
SHARE_SYS: ;保护现场先
MOV TMP_A,PSW ;PUSH PSW
PUSH TMP_A
MOV TMP_A,A ;PUSH A
PUSH TMP_A
MOV TMP_A,B ;PUSH B
PUSH TMP_A
PUSH 00H ;PUSH R0
PUSH 01H ;PUSH R1
PUSH 02H ;PUSH R2
PUSH 03H ;PUSH R3
;区分主次任务
;TASK_CURT_P 大于2则跳过以下步骤
CLR C
MOV A,#2
SUBB A,TASK_CURT_P
JC SS00
PUSH 04H ;PUSH R4
PUSH 05H ;PUSH R5
PUSH 06H ;PUSH R6
PUSH 07H ;PUSH R7
PUSH DPL
PUSH DPH
SS00: ;存SP到数组SP
MOV A,#TAB_SP
ADD A,TASK_CURT_P
MOV R0,A ;这个是指针变量,指向当前SP的存放地址
MOV @R0,SP ;记录SP
;切换SP,以下进入系统区----------------------------------------------------------------INTERFACE
MOV SP,#SYS_SP ;SP指向系统SP
CALL WDT ;喂狗
CALL KILL_TASK ;根据DEAD_SIG字节,执行任务的死刑 ;-*
;是否上锁,如果上锁 LOCK_BYTE= 5AH 则不执行任务切换
MOV A,LOCK_BYTE
CJNE A,#5AH,SS04
JMP SS05
SS04:
mov r1,task_sch_p ;暂存
MOV R6,#10
SELECT_P: ;选择下一个任务
DJNZ R6,SS01 ;选择次数计时,如果连续选择超10次就得进节电模式了
MOV P1,#0FFH
ORL 87H,#02H ;INTO POWER-DOWN MODE
LJMP SYS_START ;醒来的话就重新开机咯
;切换任务指针(0-7) 全局变量TASK_sch_P 任务指针,仅此进行写操作
SS01:
INC TASK_sch_P
MOV R0,TASK_sch_P
CJNE R0,#8,SS02 ;超限
MOV TASK_sch_P,#0
;判就绪位,不在就绪态就跳回 SELECT_P,重复以上步骤
SS02:
MOV R0,TASK_sch_P
CALL GET_READY_BIT
JNC SELECT_P
;调度结束,新的指针在task_sch_p
;是否有抢占信号
jnb preempt_bit,ss06
mov a,preempt_task
clr c
subb a,#8
jnc ss06 ;抢占任务号无效(大于7)
mov a,preempt_task
cjne a,task_sch_p,ss07 ;如果抢占任务和本次应该调度的任务相同,则下一次不要再调这个任务了。(本次调度生效,否则退回上一次调度指针)。
jmp ss08
ss07:
mov task_sch_p,r1 ;恢复调度指针
ss08:
mov r0,preempt_task
call set_ready_bit ;抢占任务就绪位
mov task_curt_p,preempt_task ;直接指定任务号,切换
clr preempt_bit
jmp ss05
ss06: mov task_curt_p,task_sch_p ;调度盘指针 确定调度指针和实际任务指针分离,解决抢占后调度不公平问题
SS05:
;取优先字节地址
MOV A,#TAB_PRI
ADD A,TASK_CURT_P
MOV R0,A
;时间片赋值 ;RESET THE TIMER0
MOV 8AH,#T0_VALUE_L ;TL0
MOV 8CH,@R0 ;TH0 ;MOV TH0,@R0;选中后,优先级设置到时间片
;取SP_I --> SP
MOV A,#TAB_SP
ADD A,TASK_CURT_P
MOV R0,A
MOV SP,@R0
;以下退出系统态,回到新的任务态,恢复现场-------------------------------------------INTERFACE
;区分主次任务
;TASK_CURT_P 大于2则跳过以下步骤
CLR C
MOV A,#2
SUBB A,TASK_CURT_P
JC SS03
POP DPH
POP DPL
POP 07H ;POP R7
POP 06H ;POP R6
POP 05H ;POP R5
POP 04H ;POP R4
SS03:
POP 03H ;POP R3
POP 02H ;POP R2
POP 01H ;POP R1
POP 00H ;POP R0
POP TMP_A
MOV B,TMP_A ;POP B
POP TMP_A
MOV A,TMP_A ;POP A
POP TMP_A
MOV PSW,TMP_A
;此时堆栈内当前应是中断返回时的PC值,RETI可以返回。
;ANL 88H,#11011111B ;CLR TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
RETI
;;子程序:根据被杀任务信号字节8位,从左到右每一位代表任务0-7是否要杀掉,1为杀死,0为不杀 来执行死刑
;执行内容:将该任务的内存区重新初始化(初始化后为休眠态),下次再轮到时,从头开始。
KILL_TASK:
MOV R3,#8
KTA00:
MOV A,DEAD_SIG
RRC A
MOV DEAD_SIG,A
JNC KTA01
MOV a,R3
DEC a
mov r0,a
CALL TASKRAM_INIT
KTA01:
DJNZ R3,KTA00
MOV DEAD_SIG,#0 ;清掉所有DEAD信息
RET
;;喂狗子程序
WDT:
MOV 0A6H,WDT_BYTE ;MOV WDTRST,WDT_BYTE WDT_BYTE= 1EH OR E1H
MOV A,WDT_BYTE
CPL A ;取反
MOV WDT_BYTE,A
RET
;;获取就绪位:在调度程序中用到
GET_READY_BIT: ;任务号R0, 执行结束后,结果的位在C
MOV B,R0
INC B
MOV A,READY_BYTE
GRB00: RLC A
DJNZ B,GRB00
RET
;提供的系统调用
;-----------------------------------------------------------------------------------------------
;子程序:修改任务的时间片,任务号在R0,优先字节(时间片)在R1,将优先字节写入到数组
SET_PRIBYTE:
MOV A,#TAB_PRI
ADD A,R0
MOV R0,A
MOV A,R1
MOV @R0,A
RET
;子程序:回到调度程序 DEBUGED 120516
WAITING:
NOP ;留给中断响应的间隙
ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
RET
;子程序:占用系统,任务在读写的时候不允许系统中断,和frees配套使用
OCCUPY:
;ORL IE,#00000010B ;ENABLE INTERRUPT OF TIMER0 方法1:关闭timer0的中断
ANL 88H,#11101111B ;TCON CLR TR0, STOP TIMER0 方法2:关闭timer0的计时
RET
;子程序:释放系统 和occupy配套使用,任务占用系统后应及时释放
FREES:
;ANL IE,#11111101B ;DISABLE INTERUPT OF TIMER0
ORL 88H,#00010000B ;TCON SETB TR0, START TIMER0
RET
;注意:occupy和free要配套用,他们之间就是临界区,然而occupy会导致不进调度程序,不建议使用。建议用加锁和解锁来实现临界区的操作。
LOCK_SYS:
MOV LOCK_BYTE,#5AH
RET
UNLOCK_SYS:
MOV LOCK_BYTE,#0EEH
RET
;精确的系统延时-将16位的延时数,每一位为一个时刻,存放在DPTR,计算目标时间,设置唤醒任务,休眠自己。等待系统时钟在时间到了再唤醒你,误差为一个调度周期。
;任务号为全局变量指针TASK_CURT_P
;延时步骤:1、将dptr个刻度和当前时间相加得到目标时间,存入到闹铃数组当前任务位置;2、设置本任务的唤醒服务位,当目标时间到达,系统时钟会唤醒你;3、进入休眠态;
DELAY_SYS:
;计算目标16位目标值,存放在TAB_CLK对应的位置
MOV A,SYS_TIME_L
ADD A,DPL
MOV DPL,A
MOV A,SYS_TIME_H
ADDC A,DPH ;带进位
MOV DPH,A
MOV A,#TAB_CLK
MOV R0,TASK_CURT_P
ADD A,R0
ADD A,R0
;双字节指针
MOV R0,A
MOV @R0,DPH
INC R0
MOV @R0,DPL
;设置唤醒位,在CLK_ALARM字节,8个位标志8个任务的唤醒服务,1为有服务。
MOV R0,TASK_CURT_P
CALL SET_ALARM_BIT
;清就绪位,在READY_BYTE
MOV R0,TASK_CURT_P
CALL CLR_READY_BIT
;回调度
ORL 88H,#00100000B
RET
;上面用到的子程序:设置唤醒服务的位,任务号预先放在R0
SET_ALARM_BIT:
MOV B,R0
MOV A,#10000000B
INC B ;最小任务号为1
SAB00: DJNZ B,SAB01 ;循环左移
ORL CLK_ALARM,A
JMP SAB02
SAB01: RR A
JMP SAB00
SAB02:
RET
;子程序:任务自杀
KILL_SELF:
MOV B,TASK_CURT_P
MOV A,#10000000B
INC B ;最小任务号为1
KSF00: DJNZ B,KSF01 ;循环左移
ORL DEAD_SIG,A
JMP KSF02
KSF01: RR A
JMP KSF00
KSF02:
RET
;子程序:杀死,任务号存R0
KILL_TASK_CALL:
MOV B,R0
MOV A,#10000000B
INC B ;最小任务号为1
KTSK00: DJNZ B,KTSK01 ;循环左移
ORL DEAD_SIG,A
JMP KTSK02
KTSK01: RR A
JMP KTSK00
KTSK02:
RET
;子程序:清就绪位,就绪态字节 8位 从左到右每一位分别代表任务0-7是否就绪,1为就绪,0为休眠
;任务号存在R0
CLR_READY_BIT:
MOV B,R0
MOV A,#01111111B
INC B ;最小任务号为1
CRB00: DJNZ B,CRB01 ;循环左移
ANL READY_BYTE,A
JMP CRB02
CRB01: RR A
JMP CRB00
CRB02:
RET
;子程序:置就绪位,上面的相反操作 ;任务号存在R0
SET_READY_BIT:
MOV B,R0
MOV A,#10000000B
INC B ;最小任务号为1
SRB00: DJNZ B,SRB01 ;循环左移
ORL READY_BYTE,A
JMP SRB02
SRB01: RR A
JMP SRB00
SRB02:
RET
;子程序:小刻度的延时功能(通过定时器1和抢占机制完成),次数放在r0
delay_sys_us:
mov delay_times,r0
mov preempt_task,task_curt_p ;占用的任务号预存
mov r0,task_curt_p
call clr_ready_bit ;延时期间要休眠
setb delay_sv_bit ;开启延时服务
ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
nop
nop ;中断响应
ret
;;;;;;;;;;;;;;;;;;伺服线程:任务7
task_7:
mov r2,#77h
mov r3,#77h
tk700:
mov r0,#01h
mov r1,#0eh
call delay16b
setb p1.7 ;led 熄灭100ms
mov r0,#0dh
mov r1,#0bdh
call delay16b
clr p1.7 ;led 点亮900ms
jmp tk700
jmp task_7
;r0:h r1:l 16位数的nop延时 一个周期为10.25us(全速) ,高8位放在r0,低8位放在r1
delay16b:
dll00:
mov a,r1
clr c
subb a,#1
mov r1,a
mov a,r0
subb a,#0 ;进位 16位数减一
mov r0,a
div ab ;纯粹为了延时
div ab
nop
nop
mov a,r0
orl a,r1
jnz dll00
ret
;注意:以上仅做了关于杀死、休眠、唤醒任务的调用,仅为了使用方便,实际使用时推荐使用更高效的逻辑方法:
;比如:要杀任务3和6,可以将dead_sig ORL 00010010 即可
;要休眠任务2和4,可以将ready_byte ANL 11010111 即可
;要唤醒任务1和7,可以将ready_byte ORL 10000001 即可
;SYSTEM END==============================================================line number of r0.92 is 750
;User's code
;*********************************************************************
;project name: 360度指示器 豆豆的打开关上
;designer: ut
;version: 1.0
;date: 16-11-16
;*********************************************************************
;用户在此定义自己的变量地址 及 标号 0-46d 0-2cH :一共45个字节,除去0-7,可以用8-2cH这37个字节
;需求:
;1\ 16位数字键,0-9 abcd * #
; 2\ 3位段码LCD显示
; 3\ 按下数字,插入到LCD的左侧。
; 4\ 按下D(回车),LCD的数字作为角度值,电机转动到指定角度,三位数字范围0-999,对360取模,执行完毕后再输入数字时LCD清零再插入。
; 5\ 按下A电机向上微调,按下B电机向下微调
; 6\ 按下C,LCD清零。
; 7\ 关机时,壁板归位至270度位置,再切断电源
; 8\ 开机时,壁板开启到0度位置。
;task0: 主线程,开机初始化,启动其他任务,主循环是不停的取键、取键值成功后处理键值。
;task1: 电机移动,始终试图将当前位置靠近目标位置,直到达到为止。
;task2: 按键扫描,转换为键值存入到缓冲区。
;处理键值:0-9,执行循环插入 BCD 数组,如果有清屏标志,则先清屏再插入。
;处理键值:A-B, 执行电机走12拍,约1度,A为正方向,B为反方向。
;处理键值:C, 将BCD全部设置为0
;处理键值:D,将BCD转成一个16位数,再mod360运算,将结果写到电机目标值。设置清屏标志。
;关于显示:在主线程的循环中,涉及到BCD变化时,才会触发显示,显示过程:将BCD码转换成段码,将段码输出到HT1621驱动器。
WR_1621 BIT P3.6
;RD_1621 BIT P3.7
DATA_1621 BIT P3.5
CS_1621 BIT P3.7
BIAS EQU 52H; //0B1000 0101 0010 1/3DUTY 4COM
SYSDIS EQU 0; //0B1000 0000 0000 关振系统荡器和LCD偏压发生器
SYSEN EQU 02H; //0B1000 0000 0010 打开系统振荡器
LCDOFF EQU 04H; //0B1000 0000 0100 关LCD偏压
LCDON EQU 06H; //0B1000 0000 0110 打开LCD偏压
XTAL EQU 28H; //0B1000 0010 1000 外部接时钟
RC256 EQU 30H; //0B1000 0011 0000 内部时钟
TONEON EQU 12H; //0B1000 0001 0010 打开声音输出
TONEOFF EQU 10H; //0B1000 0001 0000 关闭声音输出
WDTDIS EQU 0AH; //0B1000 0000 1010 禁止看门狗
;;;内存变量,范围(8-2cH)
nouse1 equ 20h ;预留给可寻址的位
nouse2 equ 21h
pool_key equ 21h ;22,23,24;类似堆栈指针,前推一位
p_key equ 25h
pool_bcd equ 26h ;26,27,28;存放bcd码 循环覆盖
p_bcd equ 29h ;存放bcd指针,0-2 循环
pool_print equ 8h ;8 9 10 存放3位数码管的段码
key_value equ 0bh
p_moto_H equ 0ch
p_moto_L equ 0dh
targ_moto_H equ 0eh
targ_moto_L equ 0fh
p_step equ 10h ;表的指针
p_deg equ 11h
;定义位
key_catched bit 00h ;获取到一个按键后置位
bcd_ready bit 01h ;bcd插入新值时置位
moto_dir bit 02h ;电机方向
tmp_dir bit 03h
reset_bcd bit 04h ;重置bcd
;;用户在这里写初始化程序,在系统开机初始化时,被调用,注意:此处不可进行系统功能的调用
user_init:
mov p1,#0f0h ;关电机
mov 08h,#0 ;段码区
mov 09h,#0
mov 0ah,#0
mov 26h,#0 ;bcd区
mov 27h,#0
mov 28h,#0
mov p_key,#0 ;表示无按键
mov p_bcd,#0
mov p_step,#0
mov p_deg,#0
mov p_moto_h,#0
mov p_moto_l,#0
mov targ_moto_h,#0
mov targ_moto_l,#0
clr reset_BCD
clr p2.2 ;开启关机继电器
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;任务0 主线程 ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;主线程,1、从键盘缓冲池取一个按键; 2、处理该按键(数字键插入bcd区)其他键(步数增减、清零、回车)3、bcd转换为段码,隐去尾部0
;4、段码输出
;;;其他按键处理:步数增加一个幅度,不改变参数,步数减少一个幅度,不改变参数,bcd归零,bcd转换为目标值;;;;;;;;;;
;;-----------------------------------任务0
task_0:
CALL Ht1621_Init;() ; 上电初始化LCD驱动芯片
mov dptr,#tab_ht1621
mov r3,#0
mov r5,#16
call Ht1621WrAllData;(0,Ht1621Tab,16) ;清除1621寄存器数据,清屏
mov dptr,#tab_ht1621_dou
mov r3,#0
mov r5,#3
call Ht1621WrAllData;(0,Ht1621Tab,3);显示 ;logo
;LCD的扫描是不需要延时的
orl ready_byte,#01100000b ;开启任务2:键盘扫描程序 ;开启任务1:电机驱动:实际值逼近目标值
;臂板垂直向下270度为初始态(压缩状态,方便包装和移动)在电气驱动里面初始化。
tsk0tv0:
jb p2.1,tsk04 ;关机信号判断
clr key_catched
call catch_a_key
jnb key_catched,tsk0tv0 ;没有获取到键值
clr bcd_ready
call key_proc
jnb bcd_ready,tsk0tv0 ;不涉及到bcd变化
call bcd2print ;bcd 转换为段码
call hide_zero ;消隐尾部的0
mov r3,#0
mov r4,#pool_print
mov r5,#3
; mov lock_byte,#5ah ;加锁
call print ;输出到LCD
; mov lock_byte,#11h
jmp tsk0tv0
;;关机流程,回到270度位置
tsk04:
mov dptr,#tab_ht1621_off
mov r3,#0
mov r5,#3
call Ht1621WrAllData;(0,Ht1621Tab,3);显示off
mov targ_moto_l,#0eh
mov targ_moto_h,#01h ;电机目标为270
;等待电机到点
tsk040: mov a,P_moto_l
cjne a,targ_moto_l,tsk040
mov a,p_moto_h
cjne a,targ_moto_h,tsk040
jnb p2.1,tsk0tv0 ;最后确认是否关机
setb p2.2 ;关机
ORL 87H,#02H ;INTO POWER-DOWN MODE
LJMP SYS_START ;醒来的话就重新开机咯
;步骤:1、指针为0则无按键值,2、取键值,指针-1,(临界区);3处理键值;
;print_LCD: 函数 将3个字节的内容显示到LCD 0-f 都能显示 步奏:1、字节数转换到 段码字节 2发送给ht1621
;如何循环显示,三个字节要构成单向环,abcabcabc,始终显示指针后3位数字,加入新的字节时指针往前推。
;将上面的三个字节,转为一个整数<=999,占2个字节。
;设为目标值
;当前值与目标值比较,不等于则靠近,等于则关闭。
;关机线程
;正或反,步数n个。函数
jmp task_0
;--------task0 end----------
;任务0子程序:从pool_key,p_key取一个键值,存放在key_value,并置位key_catched
catch_a_key:
;clr key_catched
mov a,p_key
jnz cak00
ret ;p_key 为0表示键值池空
cak00:
;临界区:取一个键值
mov lock_byte,#5ah
mov a,#pool_key
add a,p_key
mov r0,a
mov a,@r0
dec p_key
mov lock_byte,#11;临界区
mov key_value,a ;键值
setb key_catched
ret
;任务0子程序:处理当前键值,小于10放到循环的bcd池里(pool_bcd,p_bcd),大于10则调用相关功能
key_proc:
mov a,key_value
clr c
subb a,#10
jc kpc00
mov a,key_value
cjne a,#0ah,kpc01
;0a键功能
call up_a_bit
kpc01: cjne a,#0bh,kpc02
;0b键功能
call down_a_bit
kpc02: cjne a,#0ch,kpc03
;0c键功能
call clr_bcd
kpc03: cjne a,#0dh,kpc04
;0d键功能
setb reset_BCD ;回车后前面的数据在下次按键输入后,清掉
call set_target
ret
kpc00: call keyv2bcd
kpc04:
ret
;;;;;;第二层子程序
clr_bcd:
mov r0,#pool_bcd
mov @r0,#0
inc r0
mov @r0,#0
inc r0
mov @r0,#0
setb bcd_ready
ret
set_target: ;将bcd里的3位数字转换16位数字,并存入到targ_moto_L, targ_moto_H 中
mov r1,p_bcd ;0-2范围
mov r3,#0
mov r4,#0
;;个位数
mov a,#pool_bcd
add a,r1
mov r0,a
mov a,@r0 ; 取到bcd值 从个位数查起
mov r4,a ;r3存H,r4存L 100* + 10* + L
;;;重复
dec r1
mov a,r1
cjne a,#0ffh,ste00;
mov r1,#2 ;过界处理
ste00:
mov a,#pool_bcd
add a,r1
mov r0,a
mov a,@r0
mov b,#10
mul ab ;十位数
clr c ;16位加法
addc a,r4
mov r4,a
mov a,b
addc a,r3
mov r3,a
;;;重复以上
dec r1
mov a,r1
cjne a,#0ffh,ste01;
mov r1,#2 ;过界处理
ste01:
mov a,#pool_bcd
add a,r1
mov r0,a
mov a,@r0
mov b,#100
mul ab ;百位数
clr c ;16位加法
addc a,r4
mov r4,a
mov a,b
addc a,r3
mov r3,a
call targ_mod360
mov lock_byte,#5ah
mov targ_moto_L,r4
mov targ_moto_H,r3
mov lock_byte,#11h
ret
;目标值在r3,r4(HL),结果调整后还是在R3 R4
targ_mod360: ;输入的bcd值(0-999)转换为0-360度范围,与360取模:求余
mov dph,r3 ;暂存
mov dpl,r4
mov a,r4 ;360d=0168h
clr c
subb a,#68h
mov r4,a
mov a,r3
subb a,#01h
mov r3,a
jc tmd00;表示过头了
jmp targ_mod360
tmd00:
mov r3,dph
mov r4,dpl
ret
up_a_bit: ;电机向上微调一个距离 ;临界区处理,禁止其他控制电机的操作
anl ready_byte,#10111111b ;休眠电机任务(task1)
mov r3,#12 ;走12拍
uab00:
dec p_step
mov a,p_step
cpl a
jnz uab03 ;过0则回到7
mov p_step,#7
uab03:
mov a,p_step
mov dptr,#tab_step
MOVC A,@a+dptr
anl P1,#11110000b ;驱动电机
orl P1,a
call waiting
djnz r3,uab00
anl p1,#11110000b ;关电机
orl ready_byte,#01000000b ;唤醒电机任务
ret
down_a_bit: ;电机向下微调一个距离 ;临界区处理,禁止其他控制电机的操作
anl ready_byte,#10111111b ;休眠电机任务(task1)
mov r3,#12 ;走12拍
dab00:
inc p_step
mov a,p_step
cjne a,#8,dab03 ;过7则回到0
mov p_step,#0
dab03:
mov a,p_step
mov dptr,#tab_step
MOVC A,@a+dptr
anl P1,#11110000b ;驱动电机
orl P1,a
call waiting
djnz r3,dab00
anl p1,#11110000b ;关电机
orl ready_byte,#01000000b ;唤醒电机任务
ret
;任务0子程序:键值key_value放到循环的bcd池里(pool_bcd,p_bcd),置位bcd_ready *****orig
keyv2bcd:
jnb reset_bcd,k2b10
call clr_bcd
clr reset_bcd
k2b10: mov b,key_value ;键值
;存放在pool_bcd
mov a,p_bcd ;容错处理:p_bcd只能0-2,超范围就置0
clr c
subb a,#3
jnc k2b01
;先推指针
inc p_bcd
mov a,p_bcd
cjne a,#3,k2b02 ;0-2循环处理
k2b01: mov p_bcd,#0
k2b02:
mov a,#pool_bcd
add a,p_bcd
mov r0,a
mov @r0,b ;再存键值
setb bcd_ready
ret
;任务0子程序:从循环的bcd池里取最近3个值(pool_bcd,p_bcd),查表转换成段码,存放到段码数组(pool_print);
bcd2print:
mov r1,p_bcd ;0-2范围
mov r2,#3 ;依次取3个数
b2p00: mov a,#pool_bcd
add a,r1
mov r0,a
mov a,@r0 ; 取到bcd值然后 查表获取段码
mov dptr,#tab_ht1621_seg
movc a,@a+dptr
mov r3,a
mov a,r2
dec a
add a,#pool_print
mov r0,a
mov a,r3
mov @r0,a
dec r1
mov a,r1
cpl a
jnz b2p01
mov r1,#2
b2p01:
djnz r2,b2p00
ret
;任务0子程序:将段码数组pool_print的3个字节前面的0隐去
hide_zero:
mov r3,#2 ;2次,个位数不管
mov r0,#pool_print
hzo00:
mov a,@r0
cjne a,#5fh,hzo01 ; 5f 为0的段码
mov @r0,#0
inc r0
djnz r3,hzo00
hzo01:
ret
;任务0子程序:将段码数组pool_print的3个字节输出到ht1621
print: ;(uchar Addr,uchar *p,uchar cnt) R3:ADDR r4:P R5:CNT
CLR CS_1621;
MOV A,#0A0H
MOV R0,#3
CALL Ht1621Wr_Data ;(0xa0,3); // - - 写入数据标志101
MOV A,R3
RLC A
RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 写入地址数据
MOV R0,#6
CALL Ht1621Wr_Data
prt00:
mov a,r4
mov r0,a
MOV A,@r0 ;取段码
MOV R0,#8
CALL Ht1621Wr_Data ;Ht1621Wr_Data(*p,8); // - - 写入数据
INC r4
DJNZ R5,prt00
SETB CS_1621
CALL delay_a_while
RET
;任务0清零表
TAB_HT1621:
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;
TAB_HT1621_off:
DB 033h,078h,078h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;
TAB_HT1621_dou:
DB 0b7h,0b3h,093h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;
;任务0段码表,依据硬件线序确定
tab_ht1621_seg:
db 5fh; 0
db 06h; 1
db 3dh; 2
db 2fh; 3
db 66h; 4
db 6bh; 5
db 7bh; 6
db 0eh; 7
db 7fh; 8
db 6fh; 9
db 7eh; A
db 73h; b
db 31h; c
db 37h; d
db 79h; E
db 78h; F
db 33h; o
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;任务1:电机驱动,实际值逼近目标值 ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;实际值:p_moto_L、p_moto_H、目标值targ_moto_L、targ_moto_H 最大360度
;步骤:目标值-实际值,记录符号(正负)到moto_dir, 结果等于0时,关闭电机,返回;结果小于180时,moto_dir反置(寻找最短路径)
;让电机逼近一步,实际值+1或-1。返回。
;;------------------------------------------任务1
task_1:
mov p_moto_L,#0eh
mov p_moto_H,#01h ; 0-359范围
mov targ_moto_L,#0
mov targ_moto_H,#0 ; 0-999范围 ;要mod360处理,变为0-359范围
;臂板垂直向下270度为初始态(压缩状态,方便包装和移动)在电气驱动里面初始化。
tsk100:
;16位减法 目标值-当前值,默认为+
clr moto_dir ;默认电机方向
clr c
mov a,targ_moto_L
subb a,P_moto_L
mov b,a
mov a,targ_moto_H
subb a,P_moto_H ;结果高位在a,低位在b
jnc tsk105 ;结果为负数的话 被减数+360,再减
clr c
mov a,targ_moto_L
addc a,#68h
mov r0,a
mov a,#01h
addc a,targ_moto_h
mov r1,a
clr c ;重新算一次
mov a,r0
subb a,P_moto_L
mov b,a
mov a,r1
subb a,P_moto_H ;结果高位在a,低位在b
tsk105:
mov r1,a ;H
mov r0,b ;L 暂存结果(正偏差:0-359)
jnz tsk104
mov a,b
jnz tsk104
;结果为0 关闭电机 并返回
anl p1,#11110000b ;驱动电机
orl ready_byte,#11100001b ;开启其他任务
jmp tsk100
tsk104: ;偏差如果大于180,电机方向反向
anl ready_byte,#11011111b ;暂停键盘线程
clr c
mov a,r0
subb a,#180
mov b,a
mov a,r1
subb a,#0 ;16位减去180
jc tsk101 ;
cpl moto_dir
tsk101:
call moto_move
;实际位置指针调整一位
jb moto_dir,tsk102
clr c
mov a,p_moto_L
addc a,#1
mov p_moto_L,a
clr a
addc a,p_moto_H
mov p_moto_H,a
cjne a,#01h,tsk100 ;如果等于360则归零
mov a,p_moto_L
cjne a,#68h,tsk100
mov p_moto_L,#0
mov p_moto_H,#0
jmp tsk100
tsk102:
clr c
mov a,p_moto_L
subb a,#1
mov p_moto_L,a
mov a,p_moto_H
subb a,#0
mov p_moto_H,a
cpl a ;如果等于ffff,则改为359
jnz tsk100
mov a,p_moto_L
cpl a
jnz tsk100
mov p_moto_L,#67h
mov p_moto_H,#01h
jmp tsk100
jmp task_1
;----------task1 end-------}}}}--
;任务1子程序:电机走一度,方向在moto_dir,
;涉及2张表:表1,45度折合512拍表,tab_deg,p_deg(0-44), 表2,8拍表tab_step,p_step(0-7)
;步骤:1根据方向调整度数指针,取一个度数拍数 2根据方向走N拍并更新拍数指针;
moto_move:
mov c,moto_dir ;防止过程中改变方向
mov tmp_dir,c
mmv00:
jb tmp_dir,mmv01 ;正方向
dec p_deg
mov a,p_deg
cpl a
jnz mmv03 ;过0则回到44
mov p_deg,#44
mmv03:
mov a,p_deg
mov dptr,#tab_deg
movc a,@a+dptr
jmp mmv02
mmv01:
inc p_deg ;反方向
mov a,p_deg
cjne a,#45,mmv04 ;过44则回到0
mov p_deg,#0
mmv04:
mov a,p_deg
mov dptr,#tab_deg
movc a,@a+dptr
mmv02:
mov r3,a
call move_n_step
ret
move_n_step:;方向在tmp_dir,步数在r3, 表2,8拍表tab_step,p_step(0-7)
mns00:
jb tmp_dir,msn01 ;正方向
dec p_step
mov a,p_step
cpl a
jnz msn03 ;过0则回到7
mov p_step,#7
msn03:
mov a,p_step
mov dptr,#tab_step
MOVC A,@a+dptr
jmp msn02
msn01:
inc p_step ;反方向
mov a,p_step
cjne a,#8,msn04 ;过7则回到0
mov p_step,#0
msn04:
mov a,p_step
mov dptr,#tab_step
MOVC A,@a+dptr
msn02:
anl p1,#11110000b ;驱动电机
orl p1,a
;至此,电机走动了一拍,下面是延时:需要2ms,采用不可重入的delay_sys_us完成
;CALL delay_a_step
;call waiting
;mov dptr,#10
;call delay_sys
mov r0,#3
call delay_sys_us
djnz r3,mns00 ;走r3步数
ret
tab_step: ;步进电机8拍表,循环使用
DB 1001B,0001B,0011B,0010B,0110B,0100B,1100B,1000B
tab_deg: ;45度折合512拍表,循环使用
db 11,11,12,11,11,12,11,11,12,11,12,12,11,11,12
db 11,11,12,11,11,12,11,11,12,11,11,12,11,11,12
db 11,11,12,12,11,12,11,11,12,11,11,12,11,11,12
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
;;;;;任务2:按键扫描 ;
;;标准的4*4按键扫描程序,键值为0-fh,设置3个字节的缓冲pool_key,设置一个缓冲指针p_key(0-3),当缓冲区满,丢弃新的按键
task_2:
;初始化
mov p_key,#0 ;0表示缓冲区空,3表示满了,类似堆栈指针,注意定义时往前推一格
scan_key:
mov a,p_key
cjne a,#3,tk301 ;缓冲区满了
jmp scan_key
tk301:
anl p0,#00001111b ;p0.7 p0.6 p0.5 p0.4 为竖线 从左到右
mov a,p2 ;p2.4 p2.5 p2.6 p2.7 为横线 从上到下
orl a,#00001111b
cpl a
jz scan_key ;快速判断,无任何按键时不要去挨个扫了,这样响应更快
mov r3,#4 ;竖线循环4次
mov a,#01111111b
tk300:
orl p0,#11110000b
anl p0,a
mov r2,a ;暂存
jb p2.4,tk303
mov r0,#0
call take_keyv
tk303:
jb p2.5,tk304
mov r0,#1
call take_keyv
tk304:
jb p2.6,tk305
mov r0,#2
call take_keyv
tk305:
jb p2.7,tk302
mov r0,#3
call take_keyv
tk302:
mov a,r2
rr a ;下一个竖线
djnz r3,tk300
jmp scan_key
jmp task_2
;----------------------------task2 end---}}}}}-----
;
tab_key16: ;二位数组的4*4键值表
db 0ah,0bh,0ch,0dh
db 03,06,09,0fh
db 02,05,08,0
db 01,04,07,0eh
;子程序:查表取键值,r3:行号,r0:列号
take_keyv:
mov a,r3 ;1-4 转为 0-3
dec a
mov dptr,#tab_key16
mov b,#4
mul ab ;调整基地址
add a,dpl
mov dpl,a
clr a
addc a,dph ;进位考虑
mov dph,a
mov a,r0
movc a,@a+dptr ;查表取到对应的键值 在b
mov b,a
;存到缓冲池
mov a,p_key
cjne a,#3,tkv00 ;缓冲区满了
ret
tkv00:
mov lock_byte,#5ah ;;临界区,加锁
inc p_key
mov a,p_key
add a,#pool_key
mov r1,a
mov @r1,b ;存缓冲
mov lock_byte,#11h ;;;;退临界区,解锁
mov a,#30
add a,sys_time_l ;设置300ms时限
mov r1,a
tkv01: ;按键释放时立即返回,连续按住时要间隔延时
;超时退出
mov a,sys_time_l
clr c
subb a,r1
clr c
subb a,#5 ;时间模糊处理,只要接近目标时间50ms以内,就算超时,担心有错过时钟刻度的考虑
jc tkv02
;anl p0,#00001111b ;p0.7 p0.6 p0.5 p0.4 为竖线 改变p0可能会扰乱竖线扫描
mov a,p2 ;p2.4 p2.5 p2.6 p2.7 为横线 从上到下
orl a,#00001111b
cpl a
jnz tkv01 ;判断是否释放(范围为本行)
tkv02:
ret
;;--------------任务3-----次任务,注意保护范围:psw\a\b\r0-r3 以及最大嵌套2个call
;;-----------------------------------任务3
;;步进电机每走一步的延时唤醒线程
task_3:
jmp task_3
;-----------------------------------------------task3 end--------------
;
;;-----------------------------------任务4
task_4:
jmp task_4
;-----------------------------------------------task5 end--------------
;
;;-----------------------------------任务5
task_5:
jmp task_5
;-----------------------------------------------task5 end--------------
;
;;-----------------------------------任务6
task_6:
jmp task_6
;-----------------------------------------------task6 end--------------
;
;
;--------------------------------------------------------------------------------------
;以下用户子程序区
;;ht1621b driver RD WR DATA CS
;/********************************************************
;函数名称:void Ht1621_Init(void)
;功能描述: HT1621初始化
Ht1621_Init:
SETB CS_1621;
SETB WR_1621;
SETB DATA_1621;
MOV dptr,#5;
CALL delay_sys; // - - 延时使LCD工作电压稳定
MOV R1,#BIAS
CALL Ht1621WrCmd;
MOV R1,#RC256
CALL Ht1621WrCmd; // - - 使用内部振荡器
MOV R1,#SYSDIS
CALL Ht1621WrCmd; // - - 关振系统荡器和LCD偏压发生器
MOV R1,#WDTDIS
CALL Ht1621WrCmd;; // - - 禁止看门狗
MOV R1,#SYSEN; // - - 打开系统振荡器
CALL Ht1621WrCmd;
MOV R1,#LCDON; // - - 打开声音输出
CALL Ht1621WrCmd;
ret
;**写数据到ht1621,数据存A,发送位数存R0*****************************************************/
Ht1621Wr_Data:;(uchar Data,uchar cnt) A:DATA R0:number of send-bit
CLR WR_1621;
CALL delay_a_while
RLC A;
MOV DATA_1621,C;
CALL delay_a_while
SETB WR_1621;
CALL delay_a_while
DJNZ R0,Ht1621Wr_Data
ret
;****写命令给HT1621****************************************************
Ht1621WrCmd: ;(uchar Cmd) cmd byte store in R1
CLR CS_1621
CALL delay_a_while
MOV A,#80H
MOV R0,#4
CALL Ht1621Wr_Data; // - - 写入命令标志1000
MOV A,R1
MOV R0,#8
CALL Ht1621Wr_Data; // - - 写入命令数据
SETB CS_1621
CALL delay_a_while
RET
;*******************************************************
;函数名称:void Ht1621WrOneData(uchar Addr,uchar Data)
;功能描述: HT1621在指定地址写入数据函数
;全局变量:无
;参数说明:Addr为写入初始地址,Data为写入数据
;返回说明:无
;说 明:因为HT1621的数据位4位,所以实际写入数据为参数的后4位
;********************************************************/
Ht1621WrOneData:;(uchar Addr,uchar Data) R2,R3
CLR CS_1621;
MOV A,#0A0H
MOV R0,#3
CALL Ht1621Wr_Data;(0xa0,3); // - - 写入数据标志101
MOV A,R2
RLC A
RLC A
MOV R0,#6
CALL Ht1621Wr_Data;(Addr<<2,6); // - - 写入地址数据
MOV A,R3
RLC A
RLC A
RLC A
RLC A
MOV R0,#4
CALL Ht1621Wr_Data;(Data<<4,4); // - - 写入数据
SETB CS_1621
CALL delay_a_while
RET
;*********函数名称:void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt)
;功能描述: HT1621连续写入方式函数
;参数说明:Addr为写入初始地址,*p为连续写入数据指针,
;cnt为写入数据总数
;返回说明:无
;说 明:HT1621的数据位4位,此处每次数据为8位,写入数据
;总数按8位计算
;********************************************************/
Ht1621WrAllData:;(uchar Addr,uchar *p,uchar cnt) R3:ADDR DPTR:P R5:CNT
CLR CS_1621;
MOV A,#0A0H
MOV R0,#3
CALL Ht1621Wr_Data ;(0xa0,3); // - - 写入数据标志101
MOV A,R3
RLC A
RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 写入地址数据
MOV R0,#6
CALL Ht1621Wr_Data
hwd00:
CLR A
MOVC A,@A+DPTR
MOV R0,#8
CALL Ht1621Wr_Data ;Ht1621Wr_Data(*p,8); // - - 写入数据
INC DPTR
DJNZ R5,hwd00
SETB CS_1621
CALL delay_a_while
RET
;;;----------------
delay_a_while:
mov r3,#50
daw00:
nop
djnz r3,daw00
ret
delay_a_step:
mov r6,#0ffh
dast00:
nop
nop
nop
djnz r6,dast00
ret
;---------------------------------------以下用户数据表区------------------
;用户可以在此定义所需要的数据表
end
;*******************************************the end**********************
|