熊仔 发表于 2023-8-8 20:21:01

关于STC单片机RTOS中的临界区保护方法的研究

本帖最后由 熊仔 于 2023-8-8 23:27 编辑

目前看到移植到STC单片机的RTOS例子,对临界区保护基本都是简单粗暴的开关中断。


#define OS_ENTER_CRITICAL()      EA=0 /* 直接禁止中断*/
#define OS_EXIT_CRITICAL()      EA=1 /* 直接允许中断*/


通过查看uCOSII移植到其他cpu的os_cpu.h文件。里面有规定单片机临界区的3种方法
方法1:简单开关中断
方法2:利用硬件栈保存中断状态,再关中断,退出临界区的时候弹出栈。
方法3:通过本地变量“cpu_sr”保存中断状态,然后禁用中断,退出临界区的时候,将“cpu_sr”写入到中断状态寄存器。

方法2和方法3都是通过保留中断状态来禁用/启用中断,这样设计临界区就可以嵌套。

对于目前移植到STC51单片机的uCOSII例子。添加方法2和方法3
#if OS_CRITICAL_METHOD==2
#include <intrins.h>
#define OS_ENTER_CRITICAL()do{_push_(IE);EA = 0;}while(0)   /* 利用堆栈保存中断状态,再关中断*/
#define OS_EXIT_CRITICAL()do{ _pop_ (IE);}while(0)          /* 将IE从堆栈弹出,恢复IE值    */
#endif

#if OS_CRITICAL_METHOD==3
#define OS_ENTER_CRITICAL() do{ cpu_sr=IE;EA = 0;}while(0)
#define OS_EXIT_CRITICAL()do{ IE = cpu_sr; }while(0)
#endif 修改宏定义#define OS_CRITICAL_METHOD2 或者#define OS_CRITICAL_METHOD3
这个时候编译运行,系统运行不正确的。

对方法2,通过调试发现中断切换任务调用OSIntExit()函数,然后OS_ENTER_CRITICAL();最后通过OSIntCtxSw();切换任务,这里没有调用OS_EXIT_CRITICAL();导致出问题。
问题分析:OS_ENTER_CRITICAL()时导致中断切换任务的时候硬件堆栈多入了一个_push_(IE),
解决方法:OSIntCtxSw()函数调整SP的时候应该多减1,原来SP=SP-4改为SP=SP-5
修改代码如下:
IF OS_CRITICAL_METHOD == 2
      ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()、OS_ENTER_CRITICAL()过程中压入堆栈的多余内容
      ;SP=SP-5
      MOVA,SP
      CLRC
      SUBB A,#5
      MOVSP,A
ENDIF

对方法3,通过调试也是中断切换任务调用OSIntExit()函数,然后OS_ENTER_CRITICAL();最后通过OSIntCtxSw();切换任务,这里没有调用OS_EXIT_CRITICAL();导致出问题。


//问题分析:可重入函数的局部变量cpu_sr会入仿真栈。导致中断切换任务的时候,仿真堆栈出问题。
//解决方法:OSIntCtxSw()函数执行时需要对仿真堆栈指针++操作。

IF OS_CRITICAL_METHOD == 3
      ;调整OS_ENTER_CRITICAL()过程中cpu_sr压入仿真栈的内容,?C_XBP++操作
      INC   (?C_XBP+1)
      MOV   A,(?C_XBP+1)
      JNZ   C_XBPADD_END
      INC   ?C_XBP
C_XBPADD_END:
      ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()过程中压入堆栈的多余内容
      ;SP=SP-4
      MOVA,SP
      CLRC
      SUBB A,#4
      MOVSP,A
ENDIF
为了方便条件编译,在os_cpu_a.A51文件的开头定义一个常量 OS_CRITICAL_METHOD
$NOMOD51
   
;注意OS_CPU.H文件也需要设置下OS_CRITICAL_METHOD 选哪种一种方法 ,需要保持两边一致。
OS_CRITICAL_METHOD      EQU   2   

OSIntCtxSw开头部分的代码修改:
OSIntCtxSw:

      USING 0
IF OS_CRITICAL_METHOD == 1
      ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()过程中压入堆栈的多余内容
      ;SP=SP-4
      MOVA,SP
      CLRC
      SUBB A,#4
      MOVSP,A
ENDIF
   
IF OS_CRITICAL_METHOD == 2
      ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()、OS_ENTER_CRITICAL()过程中压入堆栈的多余内容
      ;SP=SP-5
      MOVA,SP
      CLRC
      SUBB A,#5
      MOVSP,A
ENDIF
   
IF OS_CRITICAL_METHOD == 3
      ;调整OS_ENTER_CRITICAL()过程中cpu_sr压入仿真栈的内容,?C_XBP++操作
      INC   (?C_XBP+1)
      MOV   A,(?C_XBP+1)
      JNZ   C_XBPADD_END
      INC   ?C_XBP
C_XBPADD_END:
      ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()过程中压入堆栈的多余内容
      ;SP=SP-4
      MOVA,SP
      CLRC
      SUBB A,#4
      MOVSP,A
ENDIF

这样,STC的51单片机就能使用方法2和方法3进行临界区的保护。解决嵌套问题。
显然方法2的效率比较高,直接IE出入栈,没有变量的参与。
方法3会慢很多,因为是操作xdata进出仿真栈。


注意:通过网上查资料,方法2使用有注意问题,当用户使用的处理器有堆栈指针相对寻址模式时,可能出现严重错误。8051没有堆栈指针相对寻址模式可以放心用。


神农鼎 发表于 2023-8-8 22:07:27

这是高手必须懂汇编的铁证啊 !!!

杨为民 发表于 2023-8-8 22:40:38

把你修改后的程序传上来,供大家分享讨论

熊仔 发表于 2023-8-9 08:27:41

杨为民 发表于 2023-8-8 22:40
把你修改后的程序传上来,供大家分享讨论

临界区保护方法上面的代码都提供了,拷贝到到移植的工程就可以。
我已经移植到最新版本的uCOSII 2.93.01。还要再测一下,新版本新增的功能。都能工作了再放出来。

xhbwork 发表于 2023-8-9 09:54:40

简单应用其实RTX51-TNIY也够用了,信号量使用和传递需要量大的话做成结构加锁就行

xhbwork 发表于 2023-8-9 09:56:13

对于工程使用来说,一定需要成熟的系统,这些修改的没经过验证评估的只能作为爱好玩玩。

杨为民 发表于 2023-8-9 10:48:12

熊仔 发表于 2023-8-9 08:27
临界区保护方法上面的代码都提供了,拷贝到到移植的工程就可以。
我已经移植到最新版本的uCOSII 2.93.01 ...

期待你的移植版本
页: [1]
查看完整版本: 关于STC单片机RTOS中的临界区保护方法的研究