本帖最后由 熊仔 于 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_METHOD 2 或者#define OS_CRITICAL_METHOD 3
这个时候编译运行,系统运行不正确的。
对方法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
- MOV A,SP
- CLR C
- SUBB A,#5
- MOV SP,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
- MOV A,SP
- CLR C
- SUBB A,#4
- MOV SP,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
- MOV A,SP
- CLR C
- SUBB A,#4
- MOV SP,A
- ENDIF
-
- IF OS_CRITICAL_METHOD == 2
- ;调整SP指针去掉在调用OSIntExit()、OSIntCtxSw()、OS_ENTER_CRITICAL()过程中压入堆栈的多余内容
- ;SP=SP-5
- MOV A,SP
- CLR C
- SUBB A,#5
- MOV SP,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
- MOV A,SP
- CLR C
- SUBB A,#4
- MOV SP,A
- ENDIF
复制代码
这样,STC的51单片机就能使用方法2和方法3进行临界区的保护。解决嵌套问题。
显然方法2的效率比较高,直接IE出入栈,没有变量的参与。
方法3会慢很多,因为是操作xdata进出仿真栈。
注意:通过网上查资料,方法2使用有注意问题,当用户使用的处理器有堆栈指针相对寻址模式时,可能出现严重错误。8051没有堆栈指针相对寻址模式可以放心用。
|