前言
之前大学接触过uc/os,大学开了ARM这门课程,用的是周立功的教材,好像是大四开的,你懂的,实验只带手不带脑,复制–编译–下载–嘿,灯亮了–走人。
最近在接触EFM32系列MCU,就想弥补一下大学的遗憾。为时间4天的准备和移植工作终于完成,还是,嘿灯亮了。
硬件:EFM32G890F128
IDE : IAR
准备工作
两本书:Cortex-M3权威指南,uc/OSII操作系统原理(邵贝贝)。
因为内核之前已看过了,就琢磨了下uc,大学的时候接触过点点还有点印象,所以看起来不费什么劲,确实挺小的一个核。里面关于移植的过程也挺详细的。不过那里关于移植的是ARM7内核。大同小异。原理性的东西是一样的。
其余,关于移植部分,各大芯片厂商都已经很完善了,对于求知的同学,这是一个理解CPU,操作系统的好地方。
建立模板
-
官网下载源代码
http://micrium.com/ ->downloads -> Application Notes ->
压缩包下载下来解压最主要的就是这几个文件夹了。
当然还有一个PDF文档,这个文档主要是教你怎样使用它们的官方工程模板。英文好的可以照着上面的步骤一步一步来,没什么说的,当然你要有耐心看英语。我试了下:确实规范,一目了然。
个人觉得学习的话还是自己建一个模板吧,这样有利于理解各个模块之间的关系。所以我就以自己的习惯加参考官方的自己操作了。
其实我们自己移植的话就只要uc/OSII的内核源码和三个移植文件啦。最难的移植部分人家帮我们做好了,那就是那三个移植文件了,即os_cpu.h,os_cpu_a.asm, os_cpu.c当然我们还是要看懂的。
-
接下来就是建工程,这是我自己的工程:

代码分析
这里的代码分析就是与处理器相关的代码了,最接近处理器的代码:汇编。
主要干了两个个事情:
- 任务级和中断级调度,这两个函数的内容是一样的,真正的调度则放到了PENDSVHandler。
- 系统开始启动时,启动最高优先级任务。
- PENDVHandler保存上下文切换环境。
下面分析整个OS_CPU_A.ASM文件
EXTERN OSRunning ; External references,外部标号
EXTERN OSPrioCur
EXTERN OSPrioHighRdy
EXTERN OSTCBCur
EXTERN OSTCBHighRdy
EXTERN OSIntExit
EXTERN OSTaskSwHook
EXTERN OS_CPU_ExceptStkBase
PUBLIC OS_CPU_SR_Save ; Functions declared in this file,全局函数,可被其他模块引用
PUBLIC OS_CPU_SR_Restore
PUBLIC OSStartHighRdy
PUBLIC OSCtxSw
PUBLIC OSIntCtxSw
PUBLIC PendSV_Handler
NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register. 中断控制寄存器
NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).优先级寄存器
NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest). PENDSV优先级(最低)
NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception. 触发PENDSV的值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
;**********************************************************************************************
; 代码部分
;**********************************************************************************************
RSEG CODE:CODE:NOROOT(2)
THUMB
;RSEG 是段选择指令。RSEG CODE:选择段 code。第二个 CODE 表示代码段的意思,只读。
;NOROOT 表示:如果这段中的代码没调用,则允许连接器丢弃这段.(2)表示:4 字节对齐。假如是(n),则表示 2^n 对
;齐,更多关于IAR编译指令参考IAR编译手册。
;THUMB则为代码为THUMB指令,M3为THUMB指令集。
OS_CPU_SR_Save
MRS R0, PRIMASK
CPSID I
BX LR
OS_CPU_SR_Restore
MSR PRIMASK, R0
BX LR
OSStartHighRdy
LDR R0, =NVIC_SYSPRI14
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ; 把从堆栈置0,在调度器调度时通过判断PSP是否
MSR PSP, R0
STRB R1, [R0]
LDR R0, =OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
LDR R0, =OSRunning
MOVS R1, #1
STRB R1, [R0]
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
CPSIE I
OSStartHang
B OSStartHang
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25

;************************************************************************************************
任务级切换与中断级切换
;************************************************************************************************
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; 触发一次PENDSV
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
;********************************************************************************************************
; void OS_CPU_PendSVHandler(void)
;
; a) 获取SP, 通过判断sp是否为第一次任务切换,如果不是则跳过如果是则跳转到
OS_CPU_PendSVHandler_nosave,第一次任务不需要进行现场保护。
; b) 如果不是第一次任务切换,需保存R4-R11的值至PSP;
; c) 将上一任务的SP保存在OSTCBCur->OSTCBStkPtr = SP;
; d) 调用钩子函数 OSTaskSwHook();
; e) 获取最高优先级任务, OSPrioCur = OSPrioHighRdy;
; f) 获得最高优先级任务控制块, OSTCBCur = OSTCBHighRdy;
; g) 从任务快做获取其SP堆栈指针, SP = OSTCBHighRdy->OSTCBStkPtr;
; h) 从起任务堆栈SP中恢复R4-R11;
; i) 启动异常返回序列xPSR, PC, LR, R12, R0-R3
;
; 3) PendSV handler中断发生时:
; a) xPSR, PC, LR, R12, R0-R3自动入栈进程堆栈PSP
; b) 处理器模式从线程模式转换到异常模式
; c) 此时堆栈使用主堆栈MSP
; d) OSTCBCur 指针指向的为被挂起的任务
; OSTCBHighRdy 指针指向将被调度的任务
;
; 4) 因为PendSV为最低优先级,所以只有当没有任何异常中断的时候PendSV才会相应。
;
;
;************************************************************************************************
PendSV_Handler
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
CPSID I
MRS R0, PSP
CBZ R0, OS_CPU_PendSVHandler_nosave
SUBS R0, R0, #0x20 ; 总共需要保存8个寄存器的值,即 8个字*4=32字节=0x20,将
STM R0, {R4-R11}
LDR R1, =OSTCBCur
LDR R1, [R1]
STR R0, [R1] ```CPU_PendSVHandler_nosave
PUSH {R14}
LDR R0, =OSTaskSwHook
BLX R0
POP {R14}
LDR R0, =OSPrioCur
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2]
LDM R0, {R4-R11}
ADDS R0, R0, #0x20
MSR PSP, R0
ORR LR, LR, #0x04 ;确保异常返回后使用PSP(详见M3异常返回)
CPSIE I
BX LR
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33

代码修改
这里的代码修改主要是开发板上的启动代码和OS内核的挂接了。因为我们下载下来的代码本身是IAR下的代码,所以很多关于编译器的代码我们就可以省了,至于什么是与编译器相关的代码,那只能翻书了。
ARM芯片都有一个启动代码.S文件,就是放置向量表的地方。
1、函数名称的对应
在OS内核os_cpu_a_asm中PENDSV中断的名称为OS_CPU_PendSvHandler.。而开发板启动代码的PENDSV中断标号为PendSV_Handler,保证两者标号一直即可,我这里将OS_CPU_PendSvHandler.改为PendSV_Handler。
Os_cpu_a_asm:


因为我自己开发板工程目录下core_cm3.h下有关于SysTick的函数:所以我们得把os_cpu_c.c关于SystemTick的函数和宏定义都注释掉:
自己工程下的SysTick_Config()
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = ticks - 1;
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
将os_cpu_c.c关于SystemTick的函数和宏定义都注释掉:
#if 0
void OS_CPU_SysTickInit (INT32U cnts)
#endif
#if 0
#define OS_CPU_CM3_NVIC_ST_CTRL 0xE000E010uL))
#define OS_CPU_CM3_NVIC_ST_RELOAD 0xE000E014uL))
#define OS_CPU_CM3_NVIC_ST_CURRENT 0xE000E018uL))
#define OS_CPU_CM3_NVIC_ST_CAL 0xE000E01CuL))
#define OS_CPU_CM3_NVIC_PRIO_ST 0xE000ED23uL))
#define OS_CPU_CM3_NVIC_ST_CTRL_COUNT 0x00010000uL
#define OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004uL
#define OS_CPU_CM3_NVIC_ST_CTRL_INTEN 0x00000002uL
#define OS_CPU_CM3_NVIC_ST_CTRL_ENABLE 0x00000001uL
#define OS_CPU_CM3_NVIC_PRIO_MIN 0xFFu
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
总结
1、光知道移植这个操作肯定是不行的,还要知道为什么这样移植。这就要参考很多资料了,我是读了邵贝贝的那本书,其实也不难,当然,我是怀着一股好奇的态度去阅读的,我就想搞清楚,操作系统到底是个啥玩意。
2、了解Cortex-M3内核,只要接触过单片机,不管是大学里微机原理中的8086还是C51,只要仔细研究过单片机的工作原理,其实也无非就是指令集,中断,内存映射。。。至于外设操作大同小异。对于M3应该是很简单的。
3、此次移植的收获很大,人家的编程方式,人家的代码一看就舒服,人家的文件structure。当然以前许多C语言教程微微提到过的东西,也终于见到他的影子了,总之,学习一个操作系统的源码,对自己的编程能力和见识是很有帮助的。
4、英文真的很重要,其实向这种移植过程micrim的网站上都有其应用文档。自己能看懂,就不必去羡慕那些辅导机构的人为什么知道的那么多了哦。。。他们也是从国外的网站上借鉴过来的。做技术的,英语无处不在啊!
5、中国的科技道路还有很长的路要走啊。
最后,说一下,关于ucos方面的移植,网上的文章讲烂了都,但扪心自问一下,自己是否真正彻底弄明白过!