;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**********************
|