嵌入式实时操作系统μC/OS-II在STM32处理器移植(8)
- UID
- 1029342
- 性别
- 男
|
嵌入式实时操作系统μC/OS-II在STM32处理器移植(8)
需要修改的代码就介绍到这里,如果还有不明白之处,就再看看AN-1018.pdf,邵老师的书和<<ARMCortex-M3权威指南>>。ucosii在stm32上的移植详解4详解3中有一个问题还没解释,就是stm32f10x_it.c中已经有SysTick中断函数的定义SysTick_Handler(),为什么官方版非要弄个OS_CPU_SysTickHandler()。答案就在启动文件上,一般我们自己开发基于stm32芯片的软件,都会使用标准外设库CMSIS中提供的启动文件,而官方移植的启动文件却是自己写的,在两个文件init.s,vectors.s中(Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK)。init.s负责进入main(),vectors.s设置中断向量。OS_CPU_SysTickHandler和OS_CPU_PendSVHandler就是在vectors.s中被设置的。我的移植是使用标准外设库CMSIS中startup_stm32f10x_hd.s作为启动文件的,那该怎么在这个文件中设置OS_CPU_SysTickHandler呢,事实上在startup_stm32f10x_hd.s文件中,PendSV中断向量名为PendSV_Handler,所以只需用OS_CPU_PendSVHandler把所有出现PendSV_Handler的地方替换掉就可以了。那么为什么OS_CPU_SysTickHandler不用这种方式处理呢,这样也就不用注释os_cpu.c中的OS_CPU_SysTickHandler(),这主要是基于两个原因:1.startup_stm32f10x_hd.s尽量少该,能不改就不改。2.如果保留OS_CPU_SysTickHandler(),在以后开发过程中,改动OS_CPU_SysTickHandler()中的内容可能性是非常大的,如果一不小把该文件其他部分改了造成了问题,这个bug就非常难查了,所以我一般移植好后就把ucosii的这些文件设置为只读。对于上面的原因1,一开始移植时,我曾做过在PendSV_Handler()中调用OS_CPU_PendSVHandler(),后来发现这样不行,这是为什么呢?问题出在LR寄存器上。PendSV_Handler(){OS_CPU_PendSVHandler();}汇编出来的代码会是这样: PendSV_HandlerPROC PUSH {r4,lr} BL OS_CPU_PendSVHandler POP {r4,pc}ENDP这样在进入OS_CPU_PendSVHandler之后,LR寄存器中存放的是指令POP{r4,pc}的地址+1。在OS_CPU_PendSVHandler中的ORR LR, LR,#0x04就不会起作用,也就无法使用PSP,移植因此失败。其实在AN-1018.pdf的3.04.06中也有强调OS_CPU_PendSVHandler必须被放置在中断向量表中。一开始我也没注意。到这里移植的大部分工作都做完了,下面剩下的就是把工程配置好,SysTick中断处理好。在工程中建立ucosii组,把ucosii下的文件都加进该组。这里别忘了把os_cpu_a.asm加入。在工程的Options中,c/c++选项卡的IncludePaths中添加.\src\ucosii\src;.\src\ucosii\port。编译工程,会发现缺少app_cfg.h和os_cfg.h文件,app_cfg.h是用来配置应用软件的,主要是任务的优先级和堆栈大小,中断优先级等信息。目前还没有基于ucosii开发应用软件,所以只需在include文件夹中创建一个空的app_cfg.h文件即可。os_cfg.h是用来配置ucosii系统的。拷贝Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe\os_cfg.h到template\include,对其做如下修改:#define OS_APP_HOOKS_EN 0#define OS_DEBUG_EN 0 #define OS_EVENT_MULTI_EN 0 #defineOS_SCHED_LOCK_EN 0 #define OS_TICK_STEP_EN 0 #defineOS_TASK_CHANGE_PRIO_EN 0 #define OS_TASK_QUERY_EN 0 #defineOS_TASK_STAT_EN 0 #define OS_TASK_STAT_STK_CHK_EN 0 #defineOS_TASK_SUSPEND_EN 0 #define OS_FLAG_EN 0 #define OS_MBOX_EN 0#define OS_TIME_DLY_HMSM_EN 0 #define OS_TIME_DLY_RESUME_EN 0#define OS_TIME_GET_SET_EN 0 #define OS_TIME_TICK_HOOK_EN0所做的修改主要是把一些功能给去掉,减少内核大小,也利于调试。等移植完成后,如果需要该功能,再做开启。接下来就剩下处理好SysTick中断和启动任务了。SysTick是系统的“心跳”,本质上来说就是一个定时器。先把原来main.c中的内容删除,添加如下代码:#include "ucos_ii.h" #include"stm32f10x.h"static OS_STKstartup_task_stk[STARTUP_TASK_STK_SIZE];static voidsystick_init(void) { RCC_ClocksTypeDef rcc_clocks;RCC_GetClocksFreq(&rcc_clocks);SysTick_Config(rcc_clocks.HCLK_Frequency / OS_TICKS_PER_SEC);}static void startup_task(void*p_arg) { systick_init();#if (OS_TASK_STAT_EN> 0) OSStatInit(); #endifOSTaskDel(OS_PRIO_SELF); }intmain(void) { OSInit(); OSTaskCreate(startup_task, (void *)0,&startup_task_stk[STARTUP_TASK_STK_SIZE - 1],STARTUP_TASK_PRIO); OSStart(); return 0; }systick_init()用来初始化并启动SysTick定时器。RCC_GetClocksFreq()用来获取系统时钟。SysTick_Config()初始化并使能SysTick定时器。这里要注意的是OS_TICKS_PER_SEC,它是每秒钟的ticks数,如果为1000,就是1s中1000个ticks,也就是说1ms就会产生一个SysTick中断。系统的时间片为1ms。在邵老师的书中3.11节已有明确说明,必须在调用OSStart()之后,才能开启时钟节拍器(SysTick)。一般会把它放在第一个任务(启动任务)中。startup_task()用来创建其他应用任务,创建完其他任务后,就会自己删除自己。文件中的STARTUP_TASK_STK_SIZE,STARTUP_TASK_PRIO需要在app_cfg.h中定义。代码如下:#define STARTUP_TASK_PRIO 4#define STARTUP_TASK_STK_SIZE80在stm32f10x_it.c中,还需要添加SysTick中断的处理代码:void SysTick_Handler(void) {OSIntEnter(); OSTimeTick(); OSIntExit(); }这个代码是仿照OS_CPU_SysTickHandler()中代码的,在邵老师书的3.11节亦有说明。这里就不解释。至此ucosii在stm32上的移植已全部完成。ucosii在stm32上的移植详解5详解1-4把移植过程都已经介绍了。接下来的工作是验证移植是否ok以及如何基于移植好的ucosii开发应用程序。前一个问题可以说是后一个问题的特殊情况,一般我们会创建两个简单的任务,看看任务切换是否成功来验证移植是否ok,因为任务切换可以说是ucosii最核心的功能。任务代码(main.c):static void task1(void*p_arg) {for (;;){led_on(LED_0);OSTimeDly(500); led_off(LED_0); OSTimeDly(500); } }static void task2(void*p_arg) { for (;;) { led_on(LED_1); OSTimeDly(500); led_off(LED_1);OSTimeDly(500); } }在startup_task()创建任务: err =OSTaskCreate(task1, (void *)0,&task1_stk[TASK1_STK_SIZE-1],TASK1_PRIO);err = OSTaskCreate(task2,(void *)0, &task2_stk[TASK2_STK_SIZE-1],TASK2_PRIO);把任务的堆栈大小和优先级写入app_cfg.h,定义任务堆栈,编译调试。在任务中打断点,用模拟器调试可以发现已经可以做任务切换了。如果有板子,烧到板子中运行,可以看到两个灯会以1Hz的频率闪烁。可以认为移植初步成功,内核其他功能有待在应用中继续验证。如何基于移植好的ucosii开发应用程序呢?开发应用程序大部分都是为了处理或控制一个真实的物理系统,而真实的物理系统往往都是模拟系统,为了方便计算机处理,首先需要对系统做离散化处理。针对ucosii,离散化过程是通过系统“心跳”(SysTick)来实现的。一般应用程序都有多个任务(不多任务谁用ucosii啊),任务可以分为周期任务和非周期任务。周期任务是周期性循环地处理事情的任务,而非周期任务一般是某个条件触发才执行的任务。这里有一个问题,SysTick的时间是多少合适。SysTick的时间一般取周期性任务中周期最短的时间值。譬如说,系统里有3个周期性任务:系统主任务(如处理pid等,任务周期4ms),键盘扫描任务(任务周期16ms),通信任务(任务周期128ms),SysTick时间就取4ms。当然在SysTick时间较小时,要注意系统负荷问题,这时最好测一下cpu使用率及各个任务的时间等。周期性任务的开发套路是怎么样的呢?看看定时器任务的做法就知道了,代码在os_tmr.c。首先在OSTmr_Init()中初始化OSTmrSemSignal,然后OSTmr_Task()任务会一直等待OSTmrSemSignal,等到OSTmrSemSignal后去处理各个定时器。那么谁在释放OSTmrSemSignal呢?OSTmrSignal(),这个函数要求放在一定频率的时钟中断里,默认是在SysTick中断中(如果使能OS_TIME_TICK_HOOK_EN)。好了,现在我们可以总结总结周期性任务的一般套路了。首先在任务初始化函数中初始化一个信号量(一般会用信号量),伪代码如下:void task_init(void) { task_sem = OSSemCreate(0); }在任务中等待信号量void task (void*p_arg) { for (;;) { OSSemPend(task_sem, 0,&err);} }周期性的释放信号量OSSemPost(task_sem);对于上面所说系统主任务,OSSemPost(task_sem)可以放在SysTick_Handler()中。所以一般来说OS_CPU_SysTickHandler()改动的可能性是非常大的。非周期任务的开发套路又是怎样的呢?其实和周期性任务是差不多的,只是信号量不是周期性地释放,而是按需释放。其他内核功能就不多介绍了,大家按需使用,不是很难。本文代码:http://download.csdn.net/source/3472653该移植代码在我自己开发的一个小玩意上已得到一段时间的验证,未发现问题。但由于水平所限,并不敢保证该移植是没有任何问题的,殷切希望大家批评指正。 |
|
|
|
|
|