初学者EFM32上移植uC/OSII

本文分享了作者对EFM32G890F128微控制器移植UC/OS-II操作系统的全过程,包括准备工作、代码分析及修改等内容,并总结了移植过程中的关键心得。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

之前大学接触过uc/os,大学开了ARM这门课程,用的是周立功的教材,好像是大四开的,你懂的,实验只带手不带脑,复制–编译–下载–嘿,灯亮了–走人。 
最近在接触EFM32系列MCU,就想弥补一下大学的遗憾。为时间4天的准备和移植工作终于完成,还是,嘿灯亮了。 
硬件:EFM32G890F128 
IDE : IAR

准备工作

两本书:Cortex-M3权威指南,uc/OSII操作系统原理(邵贝贝)。 
因为内核之前已看过了,就琢磨了下uc,大学的时候接触过点点还有点印象,所以看起来不费什么劲,确实挺小的一个核。里面关于移植的过程也挺详细的。不过那里关于移植的是ARM7内核。大同小异。原理性的东西是一样的。 
其余,关于移植部分,各大芯片厂商都已经很完善了,对于求知的同学,这是一个理解CPU,操作系统的好地方。

建立模板

  1. 官网下载源代码

    http://micrium.com/ ->downloads -> Application Notes -> 
    这里写图片描述 
    压缩包下载下来解压最主要的就是这几个文件夹了。 
    这里写图片描述 
    当然还有一个PDF文档,这个文档主要是教你怎样使用它们的官方工程模板。英文好的可以照着上面的步骤一步一步来,没什么说的,当然你要有耐心看英语。我试了下:确实规范,一目了然。 
    这里写图片描述 
    个人觉得学习的话还是自己建一个模板吧,这样有利于理解各个模块之间的关系。所以我就以自己的习惯加参考官方的自己操作了。 
    其实我们自己移植的话就只要uc/OSII的内核源码和三个移植文件啦。最难的移植部分人家帮我们做好了,那就是那三个移植文件了,即os_cpu.h,os_cpu_a.asm, os_cpu.c当然我们还是要看懂的。

  2. 接下来就是建工程,这是我自己的工程: 
    这里写图片描述

代码分析

这里的代码分析就是与处理器相关的代码了,最接近处理器的代码:汇编。 
主要干了两个个事情:

  1. 任务级和中断级调度,这两个函数的内容是一样的,真正的调度则放到了PENDSVHandler。
  2. 系统开始启动时,启动最高优先级任务。
  3. 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                          ; 保存PRIMASK值至R0,OS_CPU_SR_Save返回时,R0中值送入
                                                 ; cpu_sr.
    CPSID   I                                    ; PRIMASK =1 ,关中断.
    BX      LR

OS_CPU_SR_Restore
    MSR     PRIMASK, R0                         ;把R0的值加载到PRIMASK,R0为调用OS_CPU_SR_Restore的函
                                                ;数的参数cpu_sr.                                        
    BX      LR

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
/*-----启动最高优先级任务--操作系统启动第一个任务时------*/
OSStartHighRdy
    LDR     R0, =NVIC_SYSPRI14                         ; 设置PENDSV为最低优先级
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]                                      

    MOVS    R0, #0                                     ; 把从堆栈置0,在调度器调度时通过判断PSP是否 
    MSR     PSP, R0                                    ; 0来判断系统是否为首次调度。
    STRB    R1, [R0]

    LDR     R0, =OS_CPU_ExceptStkBase                  ; 初始化主堆栈 
    LDR     R1, [R0]
    MSR     MSP, R1                                            

    LDR     R0, =OSRunning                             ; OSRunning = TRUE
    MOVS    R1, #1                                             
    STRB    R1, [R0]

    LDR     R0, =NVIC_INT_CTRL                         ; 触发一次PENDSV 
    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      ; 第一次任务开始时,PSP为0,故跳过对R4-R11的保存              

    SUBS    R0, R0, #0x20                        ; 总共需要保存8个寄存器的值,即 8个字*4=32字节=0x20,将
                                                 ;r4-r11存储到psp中
    STM     R0, {R4-R11}
    LDR     R1, =OSTCBCur                       ; 保存被中断任务的堆栈
    LDR     R1, [R1]                            ; 获取堆栈指针,任务堆栈指针在TCB的顶部。                  
    STR     R0, [R1]                                       ```CPU_PendSVHandler_nosave
    PUSH    {R14}                               ; Save LR exc_return value            
    LDR     R0, =OSTaskSwHook                   ; OSTaskSwHook();调用钩子函数
    BLX     R0
    POP     {R14}

    LDR     R0, =OSPrioCur                       ; OSPrioCur = OSPrioHighRdy;获得最高优先级任务
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCur                        ; OSTCBCur  = OSTCBHighRdy;获得最高优先级任务控制块
    LDR     R1, =OSTCBHighRdy
    LDR     R2, [R1]
    STR     R2, [R0]

    LDR     R0, [R2]                             ; 获得最高优先级任务堆栈指针;
    LDM     R0, {R4-R11}                         ; 从PSP从恢复r4-11
    ADDS    R0, R0, #0x20
    MSR     PSP, R0                              ;更新PSP,已启动正确的返回序列
    ORR     LR, LR, #0x04                        ;确保异常返回后使用PSP(详见M3异常返回) 
    CPSIE   I                                    ;打开中断
    BX      LR                                   ;启动中断返回序列,获取PC到任务断点处。                                          
 
  • 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);                                                 
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

将os_cpu_c.c关于SystemTick的函数和宏定义都注释掉:

#if 0 //-------注释掉
void  OS_CPU_SysTickInit (INT32U  cnts)
{
    OS_CPU_CM3_NVIC_ST_RELOAD = cnts - 1u;
    OS_CPU_CM3_NVIC_PRIO_ST   = OS_CPU_CM3_NVIC_PRIO_MIN;
    OS_CPU_CM3_NVIC_ST_CTRL  |= OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC | OS_CPU_CM3_NVIC_ST_CTRL_ENABLE;
    OS_CPU_CM3_NVIC_ST_CTRL  |= OS_CPU_CM3_NVIC_ST_CTRL_INTEN;
}
#endif 

#if 0 //-------注释掉
#define  OS_CPU_CM3_NVIC_ST_CTRL    (*((volatile INT32U *)0xE000E010uL)) 
#define  OS_CPU_CM3_NVIC_ST_RELOAD  (*((volatile INT32U *)0xE000E014uL)) 
#define  OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018uL)) 
#define  OS_CPU_CM3_NVIC_ST_CAL     (*((volatile INT32U *)0xE000E01CuL)) 
#define  OS_CPU_CM3_NVIC_PRIO_ST    (*((volatile INT8U  *)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方面的移植,网上的文章讲烂了都,但扪心自问一下,自己是否真正彻底弄明白过!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值