移植UCOS之前,你首先应该做好三件事:
1.弄懂UCOS,这是谁都知道的哦 ^_^
2. 弄懂你想要移植到的硬件平台
3. 清楚你使用的编译器是如何处理函数的局部变量和怎么样处理函数间的参数传递
这里多废话几句第三点:在UCOS里面,所有函数都要求具有重入性(除了OSSTAR()外,只有这个函数我觉得是不需要的,它仅仅使用了一次);重入性是多任务的基础,而所谓重入性从函数而言就是它的变量的保存问题,在中断这个函数的执行时它使用的变量需要得到保存,以便返回后的执行是正确的,就我使用的编译器而言,它通过堆栈传递函数参数,
重入性问题就很容易解决拉,在中断任务时,它的参数已经在堆栈里面了,只要你保存好CPU寄存器和堆栈指针SP就好
而如果你使用的是KEIL的话就比较复杂拉,首先KEIL是51单片机的东西,51只有很少的存储空间,KEIL的重入性是通过设立模拟栈实现的,当任务中断执行时,你不仅需要保存CPU寄存器,还要保存模拟栈的内容,特别是51的SP寻址能力不强,只有8位,不能在64K的空间自移动,而UCOS比较大,KEIL编译时只能用大规模方式,这就意味着所有的全局变量都会保存在片外空间,而SP无法访问到,你需要设立一个公共的系统栈,保存正在运行的任务的数据,在任务切换时把这个系统栈的内容拷贝到它的任务栈里,操作比较繁琐
注意系统的初始化问题,我在第一个任务里调用了系统初始化函数,在里面开中断允许.
任务可以是关中断时被挂起,也可以是开中断时被挂起,那么在任务切换回来后如何保证它的中断状况呢?问题可以通过给每个任务设置一个中断计数器解决,并初始化为 0,记录每个任务的开关中断次数,关一次中断加 1,开一次中断减 1, 在任务调度的时候保存当前任务的中断计数器,切换到新任务时查看新任务的中断计数器,如果为 0就开中断返回,不然就直接返回(任务级切换是在关中断下进行的);而对于中断级任务调度,肯定是发生在开中断情况下的,就要查看是否需要关中断处理
现在开始说我的移植:我移植到的平台是凌阳公司的SPCE061A 16位单片机,它有2K的RAM,32K的ROM空间,基本上够用,但不能有太多功能,我在研究了这款片子的资料后,才开始自己的移植,主要是学了一下它的汇编语言和混合编程以及CPU得硬件方面;在堆栈的问题上我发现凌阳的SP是16位的,可以寻址64K的空间,不需要设立系统栈. 在OS_CPU_C.C中注意堆栈的初始化,一定要在任务切换时严格遵守你的初始化时假定的寄存器顺序.
下面开始移植最重要的三个文件:
OS_CPU_C.C:
OSTaskStkInit():
stk=(OS_STK *)ptos;
*stk--=*((INT16U*)task+1); /*任务首地址 (PC)*/
*stk--=0x0000; /*SR(R6) */
*stk--=0x0000; /*BP(R5) */
*stk--=0x0000; /*R4 */
*stk--=0x0000; /*R3 */
*stk--=0x0000; /*R2 */
*stk--=0x0000; /*R1 */
*stk--=0x0000; /*该任务的中断状况计数器*/
return((void *)stk);
别的就是空函数,比较简单,这里略去
这个文件比较简单,唯一要注意的是堆栈初始化函数,像刚刚说到的,你假定的堆栈内容必须在任务切换时严格遵守.还有就是定义几个空函数
OS_CPU_A.ASM:
这个文件呢是移植的关键,需要用汇编语言编写,(我有个同学很能喷,认为汇编语言纯属没用,像这种话千万不要相信!),在这个文件里,你需要完成UCOS的核心代码!
首先是OSStartHighRdy():
该函数在系统开始时使最高优先级任务运行,代码如下:
_OSStartHighRdy:
CALL _OSTaskSwHook
R1=0x0001
[_OSRunning]=R1
R1=[_OSTCBCur]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1 //弹出任务中断状况计数器
POP R1,R5 FROM [SP]
RETI
接着是OSCtxSw():
完成任务切换,代码如下:
_OSCtxSw:
PUSH R1,R5 TO [SP]
R1=[_InterruptC]
PUSH R1 TO [SP] //保存任务中断状况
R2=[_OSTCBCur]
[R2]=SP
CALL _OSTaskSwHook
//OSUCBCur=OSTCBHighRdy
R1=_OSTCBCur
R2=[_OSTCBHighRdy]
[R1]=R2
//OSPrioCur=OSPrioHighRdy
R1=[_OSPrioHighRdy]
[_OSPrioCur]=R1
R1=[_OSTCBHighRdy]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1 //弹出任务中断状况
CMP R1,0 //查看是否需要开中断
JNE KAI
INT IRQ
KAI: POP R1,R5 FROM [SP]
RETI
然后是OSIntCtxSw():完成中断级任务切换,大部分和上一个函数一样
_OSIntCtxSw:
CALL _OSTaskSwHook
//OSUCBCur=OSTCBHighRdy
R1=_OSTCBCur
R2=[_OSTCBHighRdy]
[R1]=R2
//OSPrioCur=OSPrioHighRdy
R1=[_OSPrioHighRdy]
[_OSPrioCur]=R1
R1=[_OSTCBHighRdy]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1
CMP R1,0 //查看是否需要关中断处理
JE KAI1 //如果不需要则直接返回
IRQ OFF //需要就关中断
KAI1: POP R1,R5 FROM [SP]
RETI
还有一个是OSTickISR():
这个函数其实不是必须叫这个名字,可以根据具体情况改变的,在uCOS_II.h里面关于函数原型的部分可以看到,它的原型是条件编译的,只要你定义一个OS_ISR_PROTO_EXT,编译器就不会再去编译这个原型了,定义OS_ISR_PROTO_EXT意味着你告诉编译器你将自己另写中断函数的,不再使用OSTickISR(),同时由于OSCtxSw()和OSTickISR()使用相同的
条件编译,你还必须再自己在 OS_CPU.H里面写一条语句:#define OS_TASK_SW() OSCtxSw();这样的话,就在任务切换时将调用你编写的函数(不过我开始没有定义那个OS_ISR_PROTO_EXT时也没有出现问题,所以这块我也不太肯定)
我自己写的中断函数是针对凌阳的,代码如下:
_IRQ6:
PUSH R1,R5 TO [SP]
R1=0x0002
[P_INT_Clear]=R1 //清中断标志
CALL _Clear_Dog //俗称喂狗
R1=[_OSIntNesting]
R1+=1
[_OSIntNesting]=R1
CMP R1,1
JNE TCB //查看是否发生中断嵌套,
R1=[_InterruptC] //如果没有嵌套,则保存当前任务的中断计数器
PUSH R1 TO [SP] //及任务堆栈地址
R2=[_OSTCBCur]
[R2]=SP
TCB: CALL _OSTimeTick
CALL _OSIntExit
R1=[_OSIntNesting] //如果能执行到此,则说明不需要任务切换
CMP R1,0 //查看是否发生过中断嵌套
JNE TCB1 //如果发生过,则没有保存中断计数器
POP R1 FROM [SP] //否则需要弹出中断计数器调整堆栈指针
[_InterruptC]=R1
TCB1: POP R1,R5 FROM [SP]
RETI
OS_CPU.H:
在这个文件里面主要就是定义数据类型,一样的数据不同的CPU有不同的位数,在这里就是要定义移植过程中的数据位数问题,还有就是定义堆栈的生长方式,及OS_ENTER_CRITICAL( ),OS_EXIT_CRITICAL( ),OS_TASK_SW( ),也都是比较简单的问题,比较容易处理,不多废话
虽然说编译器有时候出些莫名其妙的问题,但我还是要说用好你的IDE真的是太重要了,象我在开始时老有问题,我不知道到底哪有毛病,怀疑是在保存堆栈指针时有问题,因为那一块尽是一堆的指针操作,我开始不知道怎么用那个变量查看窗口,就没办法确定我是否操作正确,最后比不得已,弄了半天学会了,通过那个窗口查看变量后,才确定自己真的是错在那了……
再有一个小小的经验就是像这种比较大的东西你要step调试真的很晕,我是自己搭了个小小的测试平台(其实就是8个发光二极管),连到B口,建立任务直接看结果,很简单,但真的很管用!移植UCOS之前,你首先应该做好三件事:
1.弄懂UCOS,这是谁都知道的哦 ^_^
2. 弄懂你想要移植到的硬件平台
3. 清楚你使用的编译器是如何处理函数的局部变量和怎么样处理函数间的参数传递
这里多废话几句第三点:在UCOS里面,所有函数都要求具有重入性(除了OSSTAR()外,只有这个函数我觉得是不需要的,它仅仅使用了一次);重入性是多任务的基础,而所谓重入性从函数而言就是它的变量的保存问题,在中断这个函数的执行时它使用的变量需要得到保存,以便返回后的执行是正确的,就我使用的编译器而言,它通过堆栈传递函数参数,
重入性问题就很容易解决拉,在中断任务时,它的参数已经在堆栈里面了,只要你保存好CPU寄存器和堆栈指针SP就好
而如果你使用的是KEIL的话就比较复杂拉,首先KEIL是51单片机的东西,51只有很少的存储空间,KEIL的重入性是通过设立模拟栈实现的,当任务中断执行时,你不仅需要保存CPU寄存器,还要保存模拟栈的内容,特别是51的SP寻址能力不强,只有8位,不能在64K的空间自移动,而UCOS比较大,KEIL编译时只能用大规模方式,这就意味着所有的全局变量都会保存在片外空间,而SP无法访问到,你需要设立一个公共的系统栈,保存正在运行的任务的数据,在任务切换时把这个系统栈的内容拷贝到它的任务栈里,操作比较繁琐
注意系统的初始化问题,我在第一个任务里调用了系统初始化函数,在里面开中断允许.
任务可以是关中断时被挂起,也可以是开中断时被挂起,那么在任务切换回来后如何保证它的中断状况呢?问题可以通过给每个任务设置一个中断计数器解决,并初始化为 0,记录每个任务的开关中断次数,关一次中断加 1,开一次中断减 1, 在任务调度的时候保存当前任务的中断计数器,切换到新任务时查看新任务的中断计数器,如果为 0就开中断返回,不然就直接返回(任务级切换是在关中断下进行的);而对于中断级任务调度,肯定是发生在开中断情况下的,就要查看是否需要关中断处理
现在开始说我的移植:我移植到的平台是凌阳公司的SPCE061A 16位单片机,它有2K的RAM,32K的ROM空间,基本上够用,但不能有太多功能,我在研究了这款片子的资料后,才开始自己的移植,主要是学了一下它的汇编语言和混合编程以及CPU得硬件方面;在堆栈的问题上我发现凌阳的SP是16位的,可以寻址64K的空间,不需要设立系统栈. 在OS_CPU_C.C中注意堆栈的初始化,一定要在任务切换时严格遵守你的初始化时假定的寄存器顺序.
下面开始移植最重要的三个文件:
OS_CPU_C.C:
OSTaskStkInit():
stk=(OS_STK *)ptos;
*stk--=*((INT16U*)task+1); /*任务首地址 (PC)*/
*stk--=0x0000; /*SR(R6) */
*stk--=0x0000; /*BP(R5) */
*stk--=0x0000; /*R4 */
*stk--=0x0000; /*R3 */
*stk--=0x0000; /*R2 */
*stk--=0x0000; /*R1 */
*stk--=0x0000; /*该任务的中断状况计数器*/
return((void *)stk);
别的就是空函数,比较简单,这里略去
这个文件比较简单,唯一要注意的是堆栈初始化函数,像刚刚说到的,你假定的堆栈内容必须在任务切换时严格遵守.还有就是定义几个空函数
OS_CPU_A.ASM:
这个文件呢是移植的关键,需要用汇编语言编写,(我有个同学很能喷,认为汇编语言纯属没用,像这种话千万不要相信!),在这个文件里,你需要完成UCOS的核心代码!
首先是OSStartHighRdy():
该函数在系统开始时使最高优先级任务运行,代码如下:
_OSStartHighRdy:
CALL _OSTaskSwHook
R1=0x0001
[_OSRunning]=R1
R1=[_OSTCBCur]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1 //弹出任务中断状况计数器
POP R1,R5 FROM [SP]
RETI
接着是OSCtxSw():
完成任务切换,代码如下:
_OSCtxSw:
PUSH R1,R5 TO [SP]
R1=[_InterruptC]
PUSH R1 TO [SP] //保存任务中断状况
R2=[_OSTCBCur]
[R2]=SP
CALL _OSTaskSwHook
//OSUCBCur=OSTCBHighRdy
R1=_OSTCBCur
R2=[_OSTCBHighRdy]
[R1]=R2
//OSPrioCur=OSPrioHighRdy
R1=[_OSPrioHighRdy]
[_OSPrioCur]=R1
R1=[_OSTCBHighRdy]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1 //弹出任务中断状况
CMP R1,0 //查看是否需要开中断
JNE KAI
INT IRQ
KAI: POP R1,R5 FROM [SP]
RETI
然后是OSIntCtxSw():完成中断级任务切换,大部分和上一个函数一样
_OSIntCtxSw:
CALL _OSTaskSwHook
//OSUCBCur=OSTCBHighRdy
R1=_OSTCBCur
R2=[_OSTCBHighRdy]
[R1]=R2
//OSPrioCur=OSPrioHighRdy
R1=[_OSPrioHighRdy]
[_OSPrioCur]=R1
R1=[_OSTCBHighRdy]
SP=[R1]
POP R1 FROM [SP]
[_InterruptC]=R1
CMP R1,0 //查看是否需要关中断处理
JE KAI1 //如果不需要则直接返回
IRQ OFF //需要就关中断
KAI1: POP R1,R5 FROM [SP]
RETI
还有一个是OSTickISR():
这个函数其实不是必须叫这个名字,可以根据具体情况改变的,在uCOS_II.h里面关于函数原型的部分可以看到,它的原型是条件编译的,只要你定义一个OS_ISR_PROTO_EXT,编译器就不会再去编译这个原型了,定义OS_ISR_PROTO_EXT意味着你告诉编译器你将自己另写中断函数的,不再使用OSTickISR(),同时由于OSCtxSw()和OSTickISR()使用相同的
条件编译,你还必须再自己在 OS_CPU.H里面写一条语句:#define OS_TASK_SW() OSCtxSw();这样的话,就在任务切换时将调用你编写的函数(不过我开始没有定义那个OS_ISR_PROTO_EXT时也没有出现问题,所以这块我也不太肯定)
我自己写的中断函数是针对凌阳的,代码如下:
_IRQ6:
PUSH R1,R5 TO [SP]
R1=0x0002
[P_INT_Clear]=R1 //清中断标志
CALL _Clear_Dog //俗称喂狗
R1=[_OSIntNesting]
R1+=1
[_OSIntNesting]=R1
CMP R1,1
JNE TCB //查看是否发生中断嵌套,
R1=[_InterruptC] //如果没有嵌套,则保存当前任务的中断计数器
PUSH R1 TO [SP] //及任务堆栈地址
R2=[_OSTCBCur]
[R2]=SP
TCB: CALL _OSTimeTick
CALL _OSIntExit
R1=[_OSIntNesting] //如果能执行到此,则说明不需要任务切换
CMP R1,0 //查看是否发生过中断嵌套
JNE TCB1 //如果发生过,则没有保存中断计数器
POP R1 FROM [SP] //否则需要弹出中断计数器调整堆栈指针
[_InterruptC]=R1
TCB1: POP R1,R5 FROM [SP]
RETI
OS_CPU.H:
在这个文件里面主要就是定义数据类型,一样的数据不同的CPU有不同的位数,在这里就是要定义移植过程中的数据位数问题,还有就是定义堆栈的生长方式,及OS_ENTER_CRITICAL( ),OS_EXIT_CRITICAL( ),OS_TASK_SW( ),也都是比较简单的问题,比较容易处理,不多废话
虽然说编译器有时候出些莫名其妙的问题,但我还是要说用好你的IDE真的是太重要了,象我在开始时老有问题,我不知道到底哪有毛病,怀疑是在保存堆栈指针时有问题,因为那一块尽是一堆的指针操作,我开始不知道怎么用那个变量查看窗口,就没办法确定我是否操作正确,最后比不得已,弄了半天学会了,通过那个窗口查看变量后,才确定自己真的是错在那了……
再有一个小小的经验就是像这种比较大的东西你要step调试真的很晕,我是自己搭了个小小的测试平台(其实就是8个发光二极管),连到B口,建立任务直接看结果,很简单,但真的很管用!
![](https://img2024.cnblogs.com/blog/35695/202408/35695-20240826070647288-243310440.jpg)