找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1922|回复: 0
打印 上一主题 下一主题
收起左侧

以STC12C5A60H单片机文处理器的UCOS代码的分析

[复制链接]
跳转到指定楼层
楼主
ID:216359 发表于 2020-5-10 16:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
看到了好的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, 下载次数: 13)



评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表