|
看到了好的UCOS文章, 僅以學(xué)習(xí)為目的。從帖子標(biāo)題同可見,這是基于宏晶單片機(jī)的,和51從內(nèi)部的結(jié)構(gòu),到語言是一樣的,內(nèi)存大了一點(diǎn)。結(jié)合前面的帖子,可以看懂模擬堆棧的使用。可重入函數(shù)的代碼依舊在ROM中的,這一點(diǎn)大家一定要明白。只是在256(0XFFH)以后的RAM(0X03FFH:1024)中開了堆棧,為各函數(shù)調(diào)用可重入函數(shù)保存變量,以避免主調(diào)函數(shù)的局部變量彼此覆蓋。而這個(gè)堆棧名稱為模擬堆棧。
$NOMOD51;不使用keil提供的51寄存器地址定義,而使用下面自己寫的寄存器地址定義
EA BIT 0A8H.7
SP DATA 081H
B DATA 0F0H
ACC DATA 0E0H
DPH DATA 083H
DPL DATA 082H
PSW DATA 0D0H
TR0 BIT 088H.4
TH0 DATA 08CH
TL0 DATA 08AH
TIMER_20MS_TH0 EQU 070H ;CPU=22.1184MHz, OS_TICKS_PER_SEC=50, TH0=0x70
TIMER_20MS_TL0 EQU 000H ;CPU=22.1184MHz, OS_TICKS_PER_SEC=50, TL0=0x00
NAME OS_CPU_A ;模塊名
;定義重定位段
;1、無參函數(shù): ?PR?函數(shù)名?文件名
;2、有參函數(shù): ?PR?_函數(shù)名?文件名
;3、再入函數(shù): ?PR?_?函數(shù)名?文件名
?PR?OSStartHighRdy?OS_CPU_A SEGMENT CODE ;聲明一個(gè)可重定位的段,段名為?PR?OSStartHighRdy?OS_CPU_A,若想使用這個(gè)段中的空間,需要使用RSEG指令表明我要對下面的匯編語句進(jìn)行重定位
?PR?OSCtxSw?OS_CPU_A SEGMENT CODE
?PR?OSIntCtxSw?OS_CPU_A SEGMENT CODE
?PR?OSTickISR?OS_CPU_A SEGMENT CODE
?PR?_?SerialISR?OS_CPU_A SEGMENT CODE
;聲明引用全局變量和外部子程序
EXTRN DATA (?C_XBP) ;仿真堆棧指針用于可重入局部變量保存
EXTRN IDATA (OSTCBCur)
EXTRN IDATA (OSTCBHighRdy)
EXTRN IDATA (OSRunning)
EXTRN IDATA (OSPrioCur)
EXTRN IDATA (OSPrioHighRdy)
EXTRN CODE (_?OSTaskSwHook)
EXTRN CODE (_?OSIntEnter)
EXTRN CODE (_?OSIntExit)
EXTRN CODE (_?OSTimeTick)
EXTRN CODE (_?Serial)
;對外聲明4個(gè)不可重入函數(shù)
PUBLIC OSStartHighRdy
PUBLIC OSCtxSw
PUBLIC OSIntCtxSw
PUBLIC OSTickISR
;PUBLIC SerialISR
;分配堆棧空間。只關(guān)心大小,堆棧起點(diǎn)由keil決定,通過標(biāo)號可以獲得keil分配的SP起點(diǎn)
?STACK SEGMENT IDATA
RSEG ?STACK
OSStack:
;DS n:保留n個(gè)存儲(chǔ)單元
DS 40H ;分配硬件堆棧的大小;STARTUP.A51啟動(dòng)文件中在?STACK段中分配了1 B,這里又分配了40H B,故?STACK段總共有41H個(gè)字節(jié)
OSStkStart IDATA OSStack-1
;定義手動(dòng)壓棧出棧宏。 有些CPU寄存器是自動(dòng)在系統(tǒng)棧中入棧出棧的,從程序中觀察不到,
;例如在調(diào)用函數(shù)前(也即JMP類指令時(shí)),會(huì)把PC入SP指向的系統(tǒng)棧,RET/RETI時(shí)會(huì)用SP把PC出棧
PUSHALL MACRO
PUSH PSW
PUSH ACC
PUSH B
PUSH DPL
PUSH DPH
MOV A,R0 ;R0~R7入棧; 工作寄存器Rn不能直接入棧,必須借助A或B等中轉(zhuǎn)一下入棧。更好的保護(hù)Rn的方法是切換工作寄存器組
PUSH ACC
MOV A,R1
PUSH ACC
MOV A,R2
PUSH ACC
MOV A,R3
PUSH ACC
MOV A,R4
PUSH ACC
MOV A,R5
PUSH ACC
MOV A,R6
PUSH ACC
MOV A,R7
PUSH ACC
;PUSH SP ;不必保存SP,任務(wù)切換時(shí)由相應(yīng)程序調(diào)整
ENDM
POPALL MACRO
;POP ACC ;不必保存SP,任務(wù)切換時(shí)由相應(yīng)程序調(diào)整
POP ACC ;R0~R7出棧
MOV R7,A
POP ACC
MOV R6,A
POP ACC
MOV R5,A
POP ACC
MOV R4,A
POP ACC
MOV R3,A
POP ACC
MOV R2,A
POP ACC
MOV R1,A
POP ACC
MOV R0,A
POP DPH
POP DPL
POP B
POP ACC
POP PSW
ENDM
;--------------------
;子程序
;--------------------
; --------------------------------------------------
; ---------------- OSStartHighRdy() ----------------
; --------------------------------------------------
RSEG ?PR?OSStartHighRdy?OS_CPU_A
;調(diào)用用戶擴(kuò)展鉤子函數(shù)OSTaskSwHook()
;OSRunning=TRUE
;取得將要恢復(fù)的任務(wù)的堆棧指針:Stack pointer=OSTCBHighRdy->OSTCBStkPtr
;將所有寄存器內(nèi)容從任務(wù)棧中彈出來
;執(zhí)行中斷返回指令
OSStartHighRdy:
USING 0 ;上電后51自動(dòng)關(guān)中斷,此處不必用CLR EA指令,因?yàn)榈酱颂庍未開中斷,本程序退出后,開中斷
LCALL _?OSTaskSwHook
;OSRunning=TRUE
MOV R0,#LOW (OSRunning)
MOV @R0,#01
;--------------------
;OSCtxSw_in的功能流程:
;從將要執(zhí)行的任務(wù)的任務(wù)棧中獲取保存的系統(tǒng)棧現(xiàn)場的長度
;根據(jù)這個(gè)長度,從將要執(zhí)行的任務(wù)的任務(wù)棧中彈出系統(tǒng)棧的上一現(xiàn)場到系統(tǒng)棧,系統(tǒng)棧恢復(fù)完成,立即設(shè)置SP
;從將要執(zhí)行的任務(wù)的任務(wù)棧中彈出
;彈出任務(wù)切換函數(shù)OSCtxSw發(fā)生時(shí)的CPU現(xiàn)場到CPU寄存器
;--------------------
OSCtxSw_in: ;找到要恢復(fù)的任務(wù)的任務(wù)棧TCB,并讀出要恢復(fù)的系統(tǒng)棧的長度
;OSTCBHighRdy ==> DPTR 獲得當(dāng)前就緒的最高優(yōu)先級任務(wù)的TCB指針.注:OSTCBHighRdy本身是個(gè)TCB指針
MOV R0,#LOW (OSTCBHighRdy) ;把指針變量OSTCBHighRdy的低地址=>R0,指針占3B。+0類型 +1高8位數(shù)據(jù) +2低8位數(shù)據(jù).
INC R0 ;
MOV DPH,@R0 ;全局變量OSTCBHighRdy在IDATA中;51為大端模式,u變量的低8位在高地址
INC R0
MOV DPL,@R0
;注意:指針變量OSTCBHighRdy占了3字節(jié),用匯編讀寫這個(gè)指針變量時(shí),建議先debug或仿真觀察一下,看看這個(gè)指針是否是占了3個(gè)字節(jié),有時(shí)keil編譯出的指針只占2個(gè)字節(jié)的地址信息,并不包含前文所述的第0字節(jié)的類型信息
;以上5行匯編用C表示:
;R0= (u8)(&OSTCBHighRdy); //OSTCBHighRdy存放的地址內(nèi)容是U16的,但指針變量OSTCBHighRdy本身所處的地址是3 byte的
;R0++;
;DPH=*R0;
;R0++;
;DPL=*R0;
;截止到這里,相當(dāng)于執(zhí)行了C語言:DPTR= (uint16_t)OSTCBHighRdy;
;OSTCBHighRdy->OSTCBStkPtr ==> DPTR 獲得用戶堆棧指針。。為了把OSTCBHighRdy->OSTCBStkPtr 送入 DPTR,必須先找到OSTCBHighRdy,OSTCBHighRdy本身
;又是個(gè)地址,通過這個(gè)地址才能找到任務(wù)的TCB,進(jìn)而找到任務(wù)的棧頂OSTCBStkPtr
INC DPTR ;指針占3B。+0類型 +1高8位數(shù)據(jù) +2低8位數(shù)據(jù)。本行INC的目的是跳過第0字節(jié),第1、2 byte開始才是OSTCBStkPtr存放的棧頂?shù)刂?br />
MOVX A,@DPTR ;OSTCBStkPtr是void指針
MOV R0,A
INC DPTR
MOVX A,@DPTR
MOV R1,A
MOV DPH,R0
MOV DPL,R1
;截止到這里,相當(dāng)于執(zhí)行了C語言:DPTR= (*DPTR).OSTCBStkPtr;//C語言取指向*DPTR,等價(jià)于匯編的取值@DPTR
;也即,相當(dāng)于:DPTR= =OSTCBHighRdy->OSTCBStkPtr;
;*UserStkPtr ==> R5 用戶堆棧起始地址內(nèi)容(即用戶堆棧長度放在此處,為什么用戶棧的起始處放的是用戶棧的長度?這個(gè)是人為的,放在起始處比較方便,見OS_CPU_C.c文件的圖示)
MOVX A,@DPTR ;用戶堆棧中是unsigned char類型數(shù)據(jù) 相當(dāng)于:A=*(OSTCBHighRdy->OSTCBStkPtr)
MOV R5,A ;R5=用戶堆棧長度 ?
;恢復(fù)現(xiàn)場堆棧內(nèi)容
MOV R0,#OSStkStart ;這時(shí)R0中存放的是系統(tǒng)棧的棧底減1 注:減1是滿棧的特性
;--------------------
;restore_stack 51是sp++的滿增棧
;--------------------
;截至本行,DPTR中存放的是“目前搜索到的優(yōu)先級最高的任務(wù)的任務(wù)棧”的棧頂?shù)刂?br />
;R0存放的是系統(tǒng)棧的棧底地址
restore_stack: ;從任務(wù)棧中復(fù)制上次保存的系統(tǒng)棧內(nèi)容到系統(tǒng)棧
INC DPTR
INC R0
MOVX A,@DPTR
MOV @R0,A
DJNZ R5,restore_stack
;以上5行對應(yīng)的C如下:
; 在下面的C代碼中,DPTR、R0、R5都看做普通的指針變量
;DPTR=OSTCBHighRdy->OSTCBStkPtr;//用戶棧的棧頂
;R0= #OSStkStart = OSStack-1;//系統(tǒng)棧的棧底
;R5= OSTCBHighRdy->OSTCBStkPtr[0];//用戶棧中已占用的字節(jié)數(shù)?
;do
; {
;DPTR++;
;R0++;
;*R0 = *DPTR;//匯編中用了兩行:A= *DPTR;
;// *R0=A; //把用戶棧的內(nèi)容從棧頂依次拷貝出R5個(gè)到系統(tǒng)棧棧底
;R5--;
; }while(R5 != 0);
;恢復(fù)堆棧指針SP
MOV SP,R0 ;R0指向了系統(tǒng)棧已存入的最后一個(gè)數(shù),符合滿棧特性,直接給SP賦值即可
;恢復(fù)仿真堆棧指針?C_XBP
INC DPTR
MOVX A,@DPTR
MOV ?C_XBP,A ;?C_XBP 仿真堆棧指針高8位
INC DPTR
MOVX A,@DPTR
MOV ?C_XBP+1,A ;?C_XBP 仿真堆棧指針低8位
POPALL
SETB EA ;開中斷
RETI ;RET和RETI有一個(gè)共同點(diǎn),就是都使硬件執(zhí)行了PC出棧指令,而RETI除了出棧PC,
;還清除了中斷狀態(tài)寄存器觸發(fā)器標(biāo)志,否則同優(yōu)先級的,和比本中斷優(yōu)先級還低的中斷講無法觸發(fā)。
;這個(gè)中斷狀態(tài)寄存器觸發(fā)器標(biāo)志無法使用軟件清除,即使用匯編也不行,只能由RETI來觸發(fā)硬件清除。
; --------------------------------------------------
; ------------------- OSCtxSw() --------------------
; --------------------------------------------------
RSEG ?PR?OSCtxSw?OS_CPU_A
;保存處理器寄存器,即CPU現(xiàn)場
;將當(dāng)前任務(wù)的堆棧指針保存到當(dāng)前任務(wù)的OS_TCB中:OSTCBCur->OSTCBStkPtr=Stack pointer
;調(diào)用用戶擴(kuò)展函數(shù)OSTaskSwHook()
;OSTCBCur=OSTCBHighRdy
;OSPrioCur=OSPrioHighRdy
;得到需要恢復(fù)的任務(wù)的堆棧指針:Stack pointer=OSTCBHighRdy->OSTCBStkPtr
;將所有處理器寄存器從新任務(wù)的堆棧中恢復(fù)出來
;執(zhí)行中斷返回指令
OSCtxSw:
USING 0
PUSHALL ;當(dāng)前任務(wù)進(jìn)行過程中執(zhí)行了OSCtxSw函數(shù),相當(dāng)于普通程序流程中發(fā)生中斷,
;需要立即保護(hù)CPU現(xiàn)場->PUSHALL,以及保護(hù)系統(tǒng)棧(必選)、仿真棧(可選,若仿真棧本身就定義在了各個(gè)任務(wù)棧中,就不必保存了)
;--------------------
;OSIntCtxSw_in
;--------------------
OSIntCtxSw_in:
;獲得系統(tǒng)堆棧長度和起始地址,任務(wù)棧結(jié)構(gòu)示意圖(見文件OS_CPU_C.C)
MOV A,SP
CLR C ;進(jìn)位標(biāo)志位Cy清零
SUBB A,#OSStkStart ;A = A-OSStkStart-Cy
MOV R5,A ;獲得系統(tǒng)堆棧長度到R5
;R5=SP-OSStkStart;
;OSTCBCur ==> DPTR 獲得當(dāng)前TCB指針
MOV R0,#LOW (OSTCBCur) ;獲得OSTCBCur指針低地址,指針占3B。+0類型 +1高8位數(shù)據(jù) +2低8位數(shù)據(jù)
INC R0
MOV DPH,@R0 ;全局變量OSTCBCur在IDATA中
INC R0
MOV DPL,@R0
;DPTR=OSTCBCur;
;OSTCBCur->OSTCBStkPtr ==> DPTR 獲得用戶堆棧指針
INC DPTR ;指針占3B。+0類型 +1高8位數(shù)據(jù) +2低8位數(shù)據(jù)
MOVX A,@DPTR ;OSTCBStkPtr是void指針
MOV R0,A
INC DPTR
MOVX A,@DPTR
MOV R1,A
MOV DPH,R0
MOV DPL,R1
;DPTR=OSTCBCur->OSTCBStkPtr;
;保存硬件堆棧的長度
MOV A,R5
MOVX @DPTR,A
; *DPTR=R5; 相當(dāng)于:OSTCBCur->OSTCBStkPtr[0] = SP - OSStkStart;
MOV R0,#OSStkStart ;獲得系統(tǒng)堆棧起始地址
;--------------------
;save_stack
;--------------------
save_stack:
INC DPTR
INC R0
MOV A,@R0
MOVX @DPTR,A
DJNZ R5,save_stack
;保存仿真堆棧指針?C_XBP
INC DPTR
MOV A,?C_XBP ;?C_XBP 仿真堆棧指針高8位
MOVX @DPTR,A
INC DPTR
MOV A,?C_XBP+1 ;?C_XBP 仿真堆棧指針低8位
MOVX @DPTR,A
;調(diào)用用戶程序
LCALL _?OSTaskSwHook
;OSTCBCur = OSTCBHighRdy
MOV R0,#OSTCBCur
MOV R1,#OSTCBHighRdy
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A
;OSPrioCur = OSPrioHighRdy 使用這兩個(gè)變量主要目的是為了使指針比較變?yōu)樽止?jié)比較,以便節(jié)省時(shí)間
MOV R0,#OSPrioCur
MOV R1,#OSPrioHighRdy
MOV A,@R1
MOV @R0,A
LJMP OSCtxSw_in ;本任務(wù)現(xiàn)場已保存完畢,跳去恢復(fù)下一任務(wù)的現(xiàn)場
; -------------------------------------------
; --------------- OSIntCtxSw() --------------
; -------------------------------------------
RSEG ?PR?OSIntCtxSw?OS_CPU_A
;調(diào)整堆棧指針來去掉在調(diào)用過程中壓入堆棧的多余內(nèi)容
;將當(dāng)前任務(wù)堆棧指針保存到當(dāng)前任務(wù)的OS_TCB中:OSTCBCur->OSTCBStkPtr=Stack pointer
;調(diào)用用戶擴(kuò)展函數(shù)OSTaskSwHook()
;OSTCBCur=OSTCBHighRdy
;OSPrioCur=OSPrioHighRdy
;得到需要恢復(fù)的任務(wù)的堆棧指針:Stack pointer=OSTCBHighRdy->OSTCBStkPtr
;將所有處理器寄存器從新任務(wù)的堆棧中恢復(fù)出來
;執(zhí)行中斷返回指令
OSIntCtxSw:
USING 0
;調(diào)整SP指針去掉在調(diào)用OSIntExit()、OSIntCtxSw()過程中壓入堆棧的多余內(nèi)容
;SP=SP-4
;為什么要調(diào)整SP呢?見本文件下面的注釋1。
MOV A,SP
CLR C
SUBB A,#4
MOV SP,A
;LCALL _?OSTaskSwHook
LJMP OSIntCtxSw_in
; -------------------------------------------
; --------------- OSTickISR() ---------------
; -------------------------------------------
CSEG AT 000BH ;定時(shí)器T0中斷入口地址
;CSEG [AT 絕對地址表達(dá)式] //絕對代碼段
;DSEG [AT 絕對地址表達(dá)式] //內(nèi)部絕對數(shù)據(jù)段
;XSEG [AT 絕對地址表達(dá)式] //外部絕對數(shù)據(jù)段
;ISEG [AT 絕對地址表達(dá)式] //內(nèi)部間接尋址絕對數(shù)據(jù)段
;BSEG [AT 絕對地址表達(dá)式] //絕對位尋址段
LJMP OSTickISR;中斷入口處絕對定位程序只有一條跳轉(zhuǎn)指令,跳到中斷服務(wù)函數(shù)中去
RSEG ?PR?OSTickISR?OS_CPU_A
;關(guān)中斷
;保存處理器寄存器的值
;調(diào)用OSIntEnter()或是將OSIntNesting加1
;關(guān)時(shí)鐘中斷
;調(diào)用OSTimeTick()
;開時(shí)鐘中斷
;調(diào)用OSIntExit()
;恢復(fù)處理器寄存器的值
;重新允許中斷
;執(zhí)行中斷返回指令
OSTickISR:
USING 0 ;工作寄存器0
CLR EA ;關(guān)中斷,防止中斷嵌套
PUSHALL ;現(xiàn)場保護(hù)
LCALL _?OSIntEnter ;通知內(nèi)核進(jìn)入中斷
CLR TR0
MOV TH0,#TIMER_20MS_TH0 ;定義Tick=50次/秒,即0.02秒/次
MOV TL0,#TIMER_20MS_TL0 ;OS_CPU_C.C 和 OS_TICKS_PER_SEC
LCALL _?OSTimeTick ;調(diào)用C語言的中斷服務(wù)子程序
SETB TR0
LCALL _?OSIntExit ;通知內(nèi)核退出中斷
POPALL ;恢復(fù)現(xiàn)場
SETB EA ;開中斷
RETI
;上面定時(shí)器服務(wù)函數(shù)的調(diào)用鏈如下:
OSTickISR -> OSIntEnter
OSTickISR -> OSTimeTick
;--------------------
;SerialISR
;--------------------
CSEG AT 0023H ;串口中斷。CSEG指代碼段絕對定位到23H(中斷向量表的串口中斷入口地址)
LJMP SerialISR
RSEG ?PR?_?SerialISR?OS_CPU_A
SerialISR:
USING 0
CLR EA ;關(guān)中斷,防止中斷嵌套
PUSHALL
LCALL _?OSIntEnter ;通知內(nèi)核進(jìn)入中斷
LCALL _?Serial ;調(diào)用中斷服務(wù)子程序
LCALL _?OSIntExit ;通知內(nèi)核退出中斷
POPALL
SETB EA
RETI
;--------------------
END
;--------------------
;注釋1:
;為什么在中斷中進(jìn)行任務(wù)切換,需要調(diào)整SP?原因如下:
;任務(wù)切換的本質(zhì),無非就是,先從當(dāng)前任務(wù)的TCB中獲取當(dāng)前任務(wù)的任務(wù)棧指針OSTCBStkPtr,再把當(dāng)前任務(wù)的現(xiàn)場保存到這個(gè)指針處(只保存系統(tǒng)棧即可),
;然后從下一個(gè)任務(wù)的TCB讀取下一個(gè)任務(wù)的任務(wù)棧指針,從這個(gè)指針處把保存的系統(tǒng)棧現(xiàn)場彈到任務(wù)棧中去。在中斷中執(zhí)行任務(wù)切換(確切的說是在定時(shí)器中
;斷服務(wù)函數(shù)中執(zhí)行切換),那么本任務(wù)的現(xiàn)場應(yīng)該是在執(zhí)行中斷服務(wù)之前的那一條匯編語句時(shí)的現(xiàn)場,而不是進(jìn)入中斷后執(zhí)行OSIntCtxSw這個(gè)函數(shù)時(shí)的現(xiàn)場
;試想,如果我們保存的是進(jìn)入中斷后的現(xiàn)場,那么等這個(gè)任務(wù)再次被切回來時(shí),豈不是會(huì)回到中斷函數(shù)中!因?yàn)檫M(jìn)入中斷服務(wù)函數(shù)(甚至中斷服務(wù)函數(shù)又調(diào)用了其他
;函數(shù)且未被調(diào)函數(shù)尚未返回時(shí))后,中斷之前的PC、中斷函數(shù)調(diào)的函數(shù)的返回PC都會(huì)已經(jīng)被壓入SP系統(tǒng)棧了,所以,在中斷中切換,我們把進(jìn)入中斷時(shí)多壓入的幾次PC都不保存,這樣保存的系統(tǒng)棧
;才模仿出了進(jìn)入中斷前的現(xiàn)場。對于本工程來做個(gè)分析,首先定時(shí)中斷調(diào)用了服務(wù)函數(shù)OSTickISR,在進(jìn)入中斷函數(shù)前假設(shè)SP=a, 進(jìn)入中斷函數(shù)時(shí)PC被自動(dòng)壓棧一次,
;SP變成了a+2,(tip:51的PC是2字節(jié)),OSTickISR函數(shù)又調(diào)用了OSIntEnter函數(shù)、OSTimeTick函數(shù),這兩個(gè)函數(shù)在執(zhí)行OSIntCtxSw時(shí)都已經(jīng)返回了,所以SP還是a+2,
;OSTickISR函數(shù)又調(diào)用了OSIntExit函數(shù),進(jìn)入OSIntExit函數(shù)后,PC壓棧,SP變成了a+4,在OSIntExit函數(shù)中執(zhí)行了“中斷中的任務(wù)切換函數(shù)OSIntCtxSw”,所以在
;OSIntCtxSw函數(shù)中保存現(xiàn)場時(shí)的系統(tǒng)棧內(nèi)容為從(#?STACK)到(SP-4)。注:(#?STACK)為系統(tǒng)棧的棧底地址。
以上的Word格式文檔51黑下載地址:
51單片機(jī)ucos ii任務(wù)切換匯編代碼分析.docx
(30.35 KB, 下載次數(shù): 15)
2020-5-10 16:36 上傳
點(diǎn)擊文件名下載附件
下載積分: 黑幣 -5
|
評分
-
查看全部評分
|