实时操作系统UCOS学习笔记5----UCOSIII移植

    技术2022-07-10  82

    在2009年Micrium公司推出了UCOSIII,相对于UCOSII性能有了进一步的提升,支持时间片轮转调度,极短的关中断事件等。本章讲解如何在STM32F103开发板上移植UCOSIII操作系统。

    1、UCOSIII简介

    UCOSIII是一个可裁剪、可固化、可剥夺的多任务操作系统,没有任务数目的限制,是UCOS的第三代内核,UCOSIII有以下几个重要的特性:

    可剥夺多任务管理:UCOSIII和UCOSII一样都属于可剥夺的多任务内核,总是执行当前就绪的最高优先级任务。同优先级的时间片轮转调度:UCOSIII和UCOSII一个比较大的区别,UCOSIII允许一个任务优先级被多个任务使用,当这个优先级处于最高就绪态的时候,UCOSIII就会轮流调度处于这个优先级的所有任务,让每个任务运行一段由用户指定的时间长度,叫做时间片。极短的关中断时间:UCOSIII可以采用锁定内核调度的方式而不是关中断的方式来保护临界段代码,这样就可以将关中断的时间降到最低,使得UCOSIII能够非常快速的响应中断请求。任务数目不受限制:UCOSIII本身是没有任务数目限制的,但是从实际应用角度考虑,任务数目会受到CPU所使用的存储空间的限制,包括代码空间和数据空间。优先级数量不受限制:UCOSIII支持无限多的任务优先级。内核对象数目不受限制:UCOSIII允许定义任意数目的内核对象。内核对象指任务、信号量、互斥信号量、事件标志组、消息队列、定时器和存储块等。软件定时器:用户可以任意定义“单次”和“周期”型定时器,定时器是一个递减计数器,递减到0就会执行预先定义好的操作。每个定时器都可以指定所需操作,周期型定时器在递减到0时会执行指定操作,并自动重置计数器值。同时等待多个内核对象:UCOSIII允许一个任务同时等待多个事件。也就是说,一个任务能够挂起在多个信号量或消息队列上,当其中任何一个等待的事件发生时,等待任务就会被唤醒。直接向任务发生信号:UCOSIII允许中断或任务直接给另一个任务发生信号,避免创建和使用诸如信号量或事件标志等内核对象作为向其他任务发生信号的中介,该特性有效地提高了系统性能。直接向任务发生消息:UCOSIII允许中断或任务世界给另一个任务发生消息,避免创建和使用消息队列作为中介。任务寄存器:每个任务都可以设定若干个“任务寄存器”,任务寄存器和CPU硬件寄存器是不同的,主要用来保存各个任务的错误信息,ID识别信息,中断关闭时间和测量结果等。任务级时钟节拍处理:UCOSIII的时钟节拍是通过一个专门任务完成的,定时中断仅触发该任务。将延迟处理和超时判断放在任务级代码完成,能极大地减少中断延迟时间。防止死锁:所有UCOSIII的“等待”功能都提供了超时检测机制,有效地避免了死锁。时间戳:UCOSIII需要一个16位或32位的自由运行计数器(时基计数器)来实现时间测量,在系统运行时,可以通过读取该计数器来测量某一个事件的时间信息。例如,当ISR给任务发生消息时,会自动读取该计数器的数值并将其附加在消息中。当任务读取消息时,可得到该消息携带的时标,这样,再通过读取当前的时标,并计算两个时标的差值,就可以确定传递这条消息所花费的确切时间。

    2、UCOS、UCOSII、UCOSIII之间的特性比较:

    3、移植准备工作

    3.1 移植准备工作 ① 准备基础工程(跑马灯实验工程) ② UCOSIII源码

    从UCOSII的移植教程中可以看出有一些中间文件需要问来实现,UCOSIII移植也是一样的,既然Micrium已经在STM32F1上移植好了UCOSIII,那么为了方便,这些中间文件我们就直接使用Micrium已经编写好的。

    3.2 EvalBoards文件夹 这个文件里面就是关于STM32F107的工程文件,我们是在STM32F103上移植的,打开这个文件如图所示:

    3.3 uC-CPU文件夹 这个文件里面是与CPU相关的代码,有下面几个文件:

    cpu_core.c文件:该文件包含了适用于所有CPU架构的C代码。该文件包含了用来测量中断关闭事件的函数(中断关闭和打开分别由CPU_CRITICAL_ENTER()和CPU_CRITICAL_EXIT()两个宏实现),还包含了一个可模仿前导码零计算的函数(以防止CPU不提供这样的指令),以及一些其他的函数。cpu_core.h文件:包含cpu_core.c中函数的原型声明,以及用来测量中断关闭时间变量的定义。cpu_def.h文件:包含uC/CPU模块使用的各种#define常量ARM-Cortex-M3文件夹:ARM-Cortex-M3文件夹下有三个文件夹:GNU,IAR,RealView,我们使用的是keil MDK编译器,所以只需要看RealView文件夹,RealView文件夹下有3个文件:cpu.h、 cpu_a.asm、 cpu_c.c文件,这3个文件的具体内容如下: ①cpu.h文件:包含了一些类型的定义,使UCOSIII和其他模块可与CPU架构和编译器字宽度无关。在该文件中用户能够找到CPU_INT16U、 CPU_INT32U、 CPU_FP32等数据类型的定义。该文件还指定了CPU使用的是大端模式还是小端模式,定义了UCOSIII使用的 CPU_STK数据类型,定义了CPU_CRITICAL_ENTER()和CPU_CRITICAL_EXTI(),还包括一些与CPU架构相关的函数声明。 ②cpu_a.asm文件:该文件包含了一些用汇编语言编写的函数,可用来开中断和关中断,计算前导零(如果CPU支持这条指令),以及其他一些只能用汇编语言编写的与CPU相关的函数,这个文件中的函数可以从C代码中调用。 ③cpu_c.c文件:包含了一些基于特定CPU架构但为了可移植而用C语言编写的函数C代码,作为一个普通原则,除非汇编语言能显著提高性能,否则尽量用C语言编写函数。 注意,上面的cpu.h 、cpu_a.asm和cpu_c.c这3个文件,是在uC_CPU文件夹中ARM-Cortex-M3文件夹下的,打开ARM-Cortex-M3文件夹如图所示:

    3.4 uC_LIB文件 uC_LIB是由一些可移植并且与编译器无关的函数组成,UCOSIII不使用uC_LIB中的函数,但是UCOSIII和uC_CPU假定lib_def.h是存在的,uC_LIB包含以下几个文件:

    lib_ascii.h和lib_ascii.c文件:提供ASCII_ToLower()、ASCII_ToUpper()、ASCII_IsAlpha()和ASCII_IsDig()等函数,他们可以分别替代标准库函数tolower()、toupper()、isalpha()和isdigit()等。lib_def.h文件:定义了许多常量,如TURE/FALSE、YES/NO、ENABLE/DISABLE,以及各种进制的常量。但是,该文件中所有#define常量都以DEF_打头,所以上述常量的名字实际上为DEF_TRUE/DEF_FALSE等。该文件还为常量数字计算定义了宏。lib_math.h和lib_math.c文件:包含了Math_Rand()、Math_SetRand()等函数的源代码,可用来替代标准库函数rand()、srand()。lib_mem.c和lib_mem.h文件:包含了Mem_Clr()、Mem_Set()、Mem_Copy和Mem_Cmp()等函数的源代码,可用于替代标准库函数memclr()、memset()、memcpy()和memcmp()等。lib_str.c和lib_str.h文件:包含了str_Len()、str_Copy()和str_Cmp()等函数的源代码,可用于替代标准库函数strlen()、strcpy()、ctrcmp()等。lib_mem_a.asm文件:lib_mem_a.asm文件在Ports->ARM_Cortex-M3->RealView下,包含了lib_mem.c函数的汇编优化版。

    3.5 uCOS-III文件 这个文件夹中有2个文件Ports和Source,Ports文件为与CPU平台有关的文件,Source文件夹里面为UCOSIII 3.03的源码,打开Source文件夹如图所示:

    4、UCOSIII移植

    4.1向工程中添加相应的文件

    5、测试

    #include "led.h" #include "delay.h" #include "sys.h" #include "usart.h" #include "includes.h" //UCOSIII中以下优先级用户程序不能使用,ALIENTEK //将这些优先级分配给了UCOSIII的5个系统内部任务 //优先级0:中断服务服务管理任务 OS_IntQTask() //优先级1:时钟节拍任务 OS_TickTask() //优先级2:定时任务 OS_TmrTask() //优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask() //优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask() //任务优先级 #define START_TASK_PRIO 3 //任务堆栈大小 #define START_STK_SIZE 512 //任务控制块 OS_TCB StartTaskTCB; //任务堆栈 CPU_STK START_TASK_STK[START_STK_SIZE]; //任务函数 void start_task(void *p_arg); //任务优先级 #define LED0_TASK_PRIO 4 //任务堆栈大小 #define LED0_STK_SIZE 128 //任务控制块 OS_TCB Led0TaskTCB; //任务堆栈 CPU_STK LED0_TASK_STK[LED0_STK_SIZE]; void led0_task(void *p_arg); //任务优先级 #define LED1_TASK_PRIO 5 //任务堆栈大小 #define LED1_STK_SIZE 128 //任务控制块 OS_TCB Led1TaskTCB; //任务堆栈 CPU_STK LED1_TASK_STK[LED1_STK_SIZE]; //任务函数 void led1_task(void *p_arg); //任务优先级 #define FLOAT_TASK_PRIO 6 //任务堆栈大小 #define FLOAT_STK_SIZE 128 //任务控制块 OS_TCB FloatTaskTCB; //任务堆栈 __align(8) CPU_STK FLOAT_TASK_STK[FLOAT_STK_SIZE]; //任务函数 void float_task(void *p_arg); int main(void) { OS_ERR err; CPU_SR_ALLOC(); delay_init(); //延时初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置 uart_init(115200); //串口波特率设置 LED_Init(); //LED初始化 OSInit(&err); //初始化UCOSIII OS_CRITICAL_ENTER();//进入临界区 //创建开始任务 OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块 (CPU_CHAR * )"start task", //任务名字 (OS_TASK_PTR )start_task, //任务函数 (void * )0, //传递给任务函数的参数 (OS_PRIO )START_TASK_PRIO, //任务优先级 (CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址 (CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位 (CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小 (OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息 (OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度, (void * )0, //用户补充的存储区 (OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项 (OS_ERR * )&err); //存放该函数错误时的返回值 OS_CRITICAL_EXIT(); //退出临界区 OSStart(&err); //开启UCOSIII while(1); } //开始任务函数 void start_task(void *p_arg) { OS_ERR err; CPU_SR_ALLOC(); p_arg = p_arg; CPU_Init(); #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); //统计任务 #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间 CPU_IntDisMeasMaxCurReset(); #endif #if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候 //使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms OSSchedRoundRobinCfg(DEF_ENABLED,1,&err); #endif OS_CRITICAL_ENTER(); //进入临界区 //创建LED0任务 OSTaskCreate((OS_TCB * )&Led0TaskTCB, (CPU_CHAR * )"led0 task", (OS_TASK_PTR )led0_task, (void * )0, (OS_PRIO )LED0_TASK_PRIO, (CPU_STK * )&LED0_TASK_STK[0], (CPU_STK_SIZE)LED0_STK_SIZE/10, (CPU_STK_SIZE)LED0_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void * )0, (OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, (OS_ERR * )&err); //创建LED1任务 OSTaskCreate((OS_TCB * )&Led1TaskTCB, (CPU_CHAR * )"led1 task", (OS_TASK_PTR )led1_task, (void * )0, (OS_PRIO )LED1_TASK_PRIO, (CPU_STK * )&LED1_TASK_STK[0], (CPU_STK_SIZE)LED1_STK_SIZE/10, (CPU_STK_SIZE)LED1_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void * )0, (OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, (OS_ERR * )&err); //创建浮点测试任务 OSTaskCreate((OS_TCB * )&FloatTaskTCB, (CPU_CHAR * )"float test task", (OS_TASK_PTR )float_task, (void * )0, (OS_PRIO )FLOAT_TASK_PRIO, (CPU_STK * )&FLOAT_TASK_STK[0], (CPU_STK_SIZE)FLOAT_STK_SIZE/10, (CPU_STK_SIZE)FLOAT_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void * )0, (OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, (OS_ERR * )&err); OS_TaskSuspend((OS_TCB*)&StartTaskTCB,&err); //挂起开始任务 OS_CRITICAL_EXIT(); //进入临界区 } //led0任务函数 void led0_task(void *p_arg) { int i = 0; OS_ERR err; p_arg = p_arg; while(1) { i++; printf("led0 = %d\r\n",i); LED0=0; OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err); //延时200ms,释放CPU资源,该函数前4个参数分别为时、分、秒、毫秒 LED0=1; OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms } } //led1任务函数 void led1_task(void *p_arg) { int i = 0; OS_ERR err; p_arg = p_arg; while(1) { LED1=~LED1; i++; printf("led1 = %d\r\n",i); if(i == 5) { OSTaskSuspend((OS_TCB*)&Led0TaskTCB,&err); printf("任务1挂起了任务0\r\n"); } if(i == 10) { OSTaskResume((OS_TCB*)&Led0TaskTCB,&err); printf("任务1恢复了任务0\r\n"); } OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms } } //浮点测试任务 void float_task(void *p_arg) { CPU_SR_ALLOC(); static float float_num=0.01; while(1) { float_num+=0.01f; OS_CRITICAL_ENTER(); //进入临界区 printf("float_num的值为: %.4f\r\n",float_num); OS_CRITICAL_EXIT(); //退出临界区 delay_ms(500); //延时500ms } }

    6、测试结果

    Processed: 0.016, SQL: 9