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