2017-03-01 霍宏鹏
GD32 移植及ucos 笔记 ucos Version : V2.86
基础版本是基于ucos官方网站ST M3内核的 MCU的版本。
移植时,基础版本最好不要从0开始,如果从零开始将会导致很多bug,
并且将大大增加调试时间,
带来不必要的时间浪费。
由于官方版本是基于IAR的开发环境开发的,这里我们的开发环境是keil MDK5。
以下是移植过程中需要注意的地方:
1 usos相关文件将分为两个部分:
1.1 平台无关文件:
os_core.c
os_flag.c
os_mbox.c
os_mem.c
os_mutex.c
os_q.c
os_sem.c
os_task.c
os_time.c
os_tmr.c
ucos_ii.h
1.2 移植平台相关文件:
os_cfg.h
os_cpu_c.c
os_cpu_a.asm
os_dbg.c
os_cpu.h
移植过程中将对1.2中的文件进行修改。
2 文件编译通过
有了这些文件添加到工程以后。肯定是编译不通过的。进行如下更改:
2.1 钩子函数定义
根据编译报错会发现,很多钩子函数没有定义,例如 App_TaskXxxxHook,我们可以找
到他们的声明原型,然后将他们定义成空函数。也可以直接在os_cfg.h中直接关闭钩子函数
的功能。
2.2 OS_CPU_SysTickHandler()函数
在os_cpu_c.c中将此函数注释掉。并将其中的内容直接放在SysTick_Handler()函数中,不然当
systick产生中断后,中断处理函数为空。因为我们在中断向量表中定义的函数名是SysTick_Handler(),
而不是OS_CPU_SysTickHandler(),但是中断函数的处理过程uc官网已经为我们写好了,所以直接将
OS_CPU_SysTickHandler()的内容复制到SysTick_Handler()里面就可以。
2.3 OS_CPU_PendSVHandler()函数
这个是PendSV中断的处理过程,在os_cpu_a.asm中用汇编实现的,就是对任务进行切换。但是在
startup_gd32f1x0.s中定义的中断向量函数名是PendSV_Handler(), 当产生中断不会调用
OS_CPU_PendSVHandler()这个函数的,而是调用PendSV_Handler()这个函数。所以我们需要将
startup_gd32f1x0.s中中断向量表的函数PendSV_Handler()替换成为OS_CPU_PendSVHandler()。这样当发生
PendSV中断后,会直接调用OS_CPU_PendSVHandler(),进行任务切换。
3 添加必要的函数
操作系统初始化的时候必须强制关闭所有中断,因为操作系统的调度是基于中断的,
如果在os还没有被初始化,这时来了中断,在中断里面肯定会调用os的函数,或访问更改
os的变量,来进行任务切换或调度。但是os没有被初始化(初始化实际上就是os对自身的
一些全局变量,全局结构体,函数指针进行复制),却调用了一些函数,肯定会造成内存
访问异常,mcu直接就宕机了,死掉了。操作系统初始化又是MCU上电后,进入到main函数
第一件事。所以在os初始化之前必须要关闭全局中断。关闭全局中断是汇编指令来完成的。
但是我们需要在main函数中调用,封装成函数。
//关闭全局中断函数
CPU_IntDis
CPSID I
BX LR
//打开全局中断函数
CPU_IntEn
CPSIE I
BX LR
将这两个函数的声明放在os_cpu.h中,这样在main函数中就能正常调用了。
打开中断函数在这里我们并没有用到,在这里写出来,保留使用。
开中断的操作是在:
OSStart() -> OSStartHighRdy -> CPSIE I
os启动的时候是会开中断的。
4 中断函数
对于中断函数我们有两个最重要函数,一个是SysTick_Handler ,另一个是PendSV_Handler函数。
前面说过os的任务切换或任务调度实际上是基于中断和延时的。
4.1.1 基于中断的调度
在每一个中断处理函数,我们都会见到如下格式:
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */
OSIntNesting++;
OS_EXIT_CRITICAL();
/*
user code
*/
OSIntExit(); /* Tell uC/OS-II that we are leaving the ISR */
而任务调度就是在OSIntExit()函数里面进行。其实这里面并非真正的任务调度,这里面只是负责
找出最高优先级的任务,然后触发一次任务调度。
OSIntExit() -> OS_SchedNew() 找到最高优先级任务
OSIntExit() -> OSIntCtxSw() -> {LDR R0, =NVIC_INT_CTRL; LDR R1, =NVIC_PENDSVSET;
STR R1, [R0]; BX LR} 触发PendSV中断。
4.1.2 任务切换
真正的任务切换是在PendSV_Handler中断里面做的。os初始化的时候会将pendsv中断优先级设置
到最低,就是在没有任何中断需要处理的时候,cpu才会处理PendSV中断。在其他中断函数中只会找
出最高优先级任务,然后触发一次PendSV中断,在PendSV中断里面进行任务切换,也就是任务控制块的
赋值操作。
PendSV叫挂起中断还真的是名副其实。。。
4.1.3 SysTick_Handler 说明
前面说道两个最重要的中断,还就一个就是systick中断,就相当于os的心跳,很重要!!
这个中断函数里面负责两件事:
4.1.3.1 记录系统时钟
在os里面所有的延时函数OSTime这个变量的,每次tick中断都会将这个变量进行加1,
SysTick_Handler() -> OSTimeTick() -> OSTime++,
每秒钟产生中断的次数在os_cfg.h -> #define OS_TICKS_PER_SEC 1000 中进行定义。
4.1.3.2 周期性进行任务调度。
没有其他中断时os就不在进行调度了?答案是否定的,其一systic会周期性产生中断,
所以会周期性进行任务调度,防止高优先级任务不能的到执行。其二 除了这个,当在任务
中进行延时,也会引发调度。
4.2 基于延时的调度
4.1 中说到了基于中断的调度,除了中断以外也会有基于延时的调度或者基于任务级的调度。
在任务中,如果我们进行延时操作,会触发一次PendSV进行任务切换。
所有的延时函数都是基于OSTimeDly()的。
OSTimeDly()-> OS_Sched() -> OS_TASK_SW()[OSCtxSw()] -> {LDR R0, =NVIC_INT_CTRL;
LDR R1, =NVIC_PENDSVSET; STR R1, [R0]; BX LR} 触发PendSV
5 应用程序
在写应用程序的时候,这里有一点是非常需要注意的.就是每个任务里面最好有延时函数,
尤其高优先级的任务。如果高优先的任务里面,没有延时,低优先级的任务,将不会得到运
行。因为ucos是实时操作系统,基于优先级进行调度的。高优先级任务没有延时,会一直占
用CPU资源。
在有os的环境中编程,应该特别注意全局变量的使用,不可重入函数的调用等。
6 裁剪
程序编译完成后,RAM占用太大,仅usos的代码就占用了超过4.5K的RAM,这里我们的GD32总
共RAM只有8K,对于开发应用程序,RAM资源过于紧缺,FLASH资源影响不大,所以非常有必要
对ucos进行内核裁剪。
裁剪是通过os_cfg.h进行的,里面都是宏定义的编译"开关",比较消耗资源的是ucos的任务控制块,
定时器,统计任务。这里任务控制块占用多大的RAM和最大任务数量有关系,原来最大数量为16个,
我们这里面改为8个(OS_MAX_TASKS), 定时器功能基本不用,我们这里直接禁止(OS_TMR_EN), 统计
任务无关紧要,直接禁止(OS_TASK_STAT_EN),这样我们的RAM占用大概在2.1K左右,可以接受,一般8
个任务足够用了,任务太多,对于单片机的压力也很大,实时性也会大大降低。