目录
uCOSIII简介:
准备工作:
准备基础工程:
UCOSIII工程源码:
UCOSIII移植:
向基础工程中添加相应的文件夹
向工程中添加分组
常见问题:
下载验证:
UCOS-III 是UCOS系统的第三代内核。
- 可剥夺式任务管理:总是执行当前就绪任务中优先级最高的任务。
- 同优先级任务的时间片轮转调度:UCOS-III允许一个任务优先级被多个任务使用,当这个优先级处在最高就绪态的时候,操作系统会轮流的调度处在这个优先级级别里面的所有任务,让优先级里面的每一个任务运行由用户指定的一段时间长度,这个时间通常称为"时间片"。这个特性也是UCOS-III与UCOS-II的一个重要区别。(II中不支持一个优先级内多个任务)。
- 极短的中断时间:UCOS-III采用的是锁定内核调度的方式而不是关中断的方式来保护临界区代码段,这样就可以把关中断的时间降至最低,使得UCOS-III能够非常快速的响应中断请求。
- 任务数目不受限制:UCOS-III自己本身是没有任务数目的限制的。但是实际情况下,任务数目会受到硬件比如CPU的影响不可能是无限制的。(每个任务都占用数据结构内存)
- 优先级数目不受限制:UCOS-III本身支持无限大的任务优先级。
- 内核对象数目不受限制:UCOS-III允许定义任意数目的内核对象。内核对象常见的包括任务,互斥信号量,事件标志组,消息队列,定时器和存储块等等。
- 软件定时器:用户可以任意定义“单次”和“周期”型定时器,定时器是一个递减计数器,递减到零就会执行预先定义好的操作。每个定时器都可以指定所需操作,周期型定时器在递减到零时会执行指定操作,并自动重置计数器值。
- 同时等待多个内核对象:UCOSIII允许一个任务同时等待多个事件,也就是说,一个任务能够挂起多个信号量或者消息队列上,当其中任何一个等待的时间发生时,等待任务就会被唤醒。
- 直接向任务发送信号:UCOSIII允许中断或者任务直接给另一个任务发送信号,避免创建和使用诸如信号量,或者事件标志等内和对象作为向其他任务发送信号的中介,该特征有效的提高了系统的性能。
- 直接向任务发送信息:UCOSIII 允许中断或任务直接给另一个任务发送消息,避免创建和使用消息队列作为中介。
- 任务寄存器:每个任务都可以设定若干个“任务定时器”任务寄存器和 CPU 硬件寄存器是不同的,主要用来保存各个任务的错误信息,ID 识别信息,中断关闭时间的测量结果等。
- 任务级时间节拍处理:UCOSIII的时钟节是通过一个专门的任务完成的,定时中断仅触发该任务。将延时处理和超时判断放在任务级代码完成。极大的减少了中断延时时间。
- 防止锁死:所有UCOSIII的 "等待"功能都提供了一个超时检测机制,有效的避免了锁死。
- 时间戳:UCOSIII需要一个16位或者32位的自由运行的计数器(时基计数器)来实现时间测量,在系统运行时,可以通过读取该计数器来测量,某一个时间信息。
首先需要准备一个移植的基础工程,使用库函数版本的跑马灯实验。
在移植uCOS-III之前,首先要获取它的源码。其源码可以从Micrium 的官方网站:www.micrium.com得到。μC/OS-III 是一个操作系统,其实也可以理解成一个软件库,它可以移植到多种硬件平台,如 M3、M4、M7 内核的 STM32,或者 ARM9 等等其他芯片。核心代码肯定是一致的,但是针对不同的处理器肯定要不同的实现部分。若要从 0 开始移植 μC/OS-III 到目标硬件平台,需要极大的精力和软件水平。为了方便移植,我们建议直接下载官网上移植好的基于目标平台的例子。
打开UCOSIII源码的压缩包:
- EvalBoards:
主要内容是基于评估板(厂商的板子)的应用实现,在我们移植中有部分文件是可以用来使用的,在路径红色方框中我们可以看到官方移植的评估板芯片是STM32F107,但是不影响我们在STM32F103开发板上进行移植
另一个红色方框中的八个文件就是我们所需要添加到工程中的文件
- uC-CPU:
这是和 CPU 紧密相关的文件,里边的一些文件很重要,都是我们需要使用的,
- cpu_core.h文件中包含了适用于所有CPU架构的C代码,也就是常说的通用代码。这是一个很重要的文件,主要包含的函数都是通过CPU进行命名的,时间戳的计算等等,跟CPU底层的移植没有太大依靠硬件实现,这里采用C语言方式,以防止某些CPU不支持前导零指令,该文件还包含用来监测中断关闭事件的函数(中断关闭和打开分别由
CPU_CRITICAL_ENTER()和 CPU_CRITICAL_EXIT()两个宏实现)。- cpu_core.h 文件包含 cpu_core.c 中函数的原型声明以及其他的一些变量的定义。
- cpu_def.h 文件包含 uC/CPU 模块使用的各种#define 常量。
- 在 ARM-Cortex-M3文件夹下,存在 cpu_c.c 一些对不同编译器移植相关的文件,有 GNU、IAR、RealView,里面都有一些很重要的文件,目前我们使用的开发环境是 MDK(keil),所以我们选择 RealView 文件夹。
点击ARM-cortex-M3文件夹中,常用MDK开发故选择Realview,可以看到如下所示
- cpu.h文件中包含了一些类型的定义,使用UCOSIII和其他的模块可以CPU架构和编译器子宽无关。
- cpu_a.asm 文件包含了一些用汇编语言编写的函数,可用来开中断和关中断,计算前导零(如果 CPU支持这条指令),以及其他一些只能用汇编语言编写的与 CPU 相关的函数,这个文件中的函数可以从 C 代码中调用。
- cpuc.c 文件包含了一些基于特定 CPU 架构但为了可移植而用 C 语言编写的函数 C 代码
- uC-LIB :
文件夹是Micrium 公司提供的官方库,诸如字符串操作、内存操作等接口,可用可不用。一般能用于代替标准库中的一些函数,使得在嵌入式中应用更加方便安全。
- uCOS-III
该文件夹是操作系统的内核文件夹,都是系统核心文件。这些文件是我们全部需要的。文件这个文件夹中有两个文件Ports 和 Sourece
μC/OS 是软件,我们的开发板是硬件,软硬件必须有桥梁来连接,这些与处理器架构相关的代码,可以称之为 RTOS 硬件接口层,它们位于 μC/OS-III->Ports 文件夹下,在不同的编译器中选择的是不同的我们不需要去理会官方已经帮我们写好了。
Source :
文件夹里面为 UCOSIII 3.0.3的源码
各个文件的作用如下表所示在学习的过程中慢慢了解:
在基础工程文件夹中新建一个UCOSIII文件夹,然后将我们下载的官方移植工程中的uC-CPU、uC-LIB 和 UCOS-III 这三个文件复制到工程中
在UCOSIII文件中在建立两个文件夹:UCOS_BSP和UCOS_CONFIG
将下边路径下的八个文件放到UCOS_CINFIG中:
同样复制 Micrium 官方移植好的工程中的相关文件到 UCOS_BSP 文件下,需要复制的文件路径为Micrium\Software\EvalBoards\Micrium\uC-Eval-STM32F107\BSP将下边两个文件移植到自己的所建的UCOSIII_BSP的目录当中。
根据个人习惯向目录中添加6个分组
工程中的分组建立完了之后,我们就要向新建的各个分组当中添加文件了
注意:此时有的文件会带一把钥匙,表示不能修改,修改方法:返回工程文件,点击属性,把只读去掉即可进入工程对其修改
上面完成了对.c文件的添加,然后是.h头文件路径的添加
做完这一步我们编辑一下整个工程,结果显示很多错误
下边是正点原子对一些错误的解释:
这里我还要补充一个错误就是下边这个问题
这个问题我推荐看一下这篇博客:http://t.csdnimg.cn/E9VvQ
最后一步就是在注意在os_cpu_c.c中添加#include "includes.h" //添加头文件
移植完成后就需要编写测试软件测试我们移植是否正确,我们建立 3 个任务,其中两个任务分别用于 LED0 和 LED1 闪烁,另外一个任务用于测试浮点计算。
例程:main.c
#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() //技术支持:www.openedv.com //淘宝店铺:http://eboard.taobao.com //广州市星翼电子科技有限公司 //作者:正点原子 @ALIENTEK //任务优先级 #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) { OS_ERR err; p_arg = p_arg; while(1) { LED0=0; OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err); //延时200ms LED0=1; OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms } } //led1任务函数 void led1_task(void *p_arg) { OS_ERR err; p_arg = p_arg; while(1) { LED1=~LED1; 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 } }
编译下载之后打开串口助手,我们可以看到LED1和led0按照设定好的时间闪烁,串口有信息输出以0.01递增
例程:
链接:https://pan.baidu.com/s/1x1foTZvqQjdjv3QAsGX5QQ
提取码:k9jr
正点原子的UCOSIII开发手册
链接:https://pan.baidu.com/s/1Za1q0H8giaA-Xkjpcw7Wrg
提取码:wt2f