一.UCOSIII简介
1、什么是UCOSIII?
UCOS是Micrium公司出品的RTOS类实时操作系统, UCOS目前有两个版本:UCOSII和UCOSIII。UCOSIII是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制,提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。
UCOSIII是用C和汇编来写的,其中绝大部分都是用C语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的, UCOSIII结构简洁,可读性很强!非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。
UCOS、UCOSII和UCOSIII之间的特性比较如下表所示:
2、UCOSIII的重要特性:
可剥夺多任务管理:UCOSIII和UCOSII 样都属于可剥夺的多任务内核,总是执行当前就绪的最高优先级任务。
同优先级任务的时间片轮转调度:这个是UCOSIII和UCOSII一个比较大的区别,UCOSIII允许一个任务优先级被多个任务使用,当这个优先级处于最高就绪态的时候,UCOSII就会轮流调度处于这个优先级的所有任务,让每个任务运行一段由用户指定的时间长度,叫做时间片。
极短的关中断时间:UCOSIII可以采用锁定内核调度的方式而不是关中断的方式来保护临界段代码,这样就可以将关中断的时间降到最低,使得UCOSI能够非常快速的响应中断请求。
任务数目不受限制:UCOSIII本身是没有任务数目限制的,但是从实际应用角度考虑,任务数目会受到CPU所使用的存储空间的限制,包括代码空间和数据空间。
优先级数量不受限制:UCOSII支持无限多的任务优先级。
内核对象数目不受限制:UCOSIII允许定义任意数目的内核对象。内核对象指任务、信号量、互斥信号量、事件标志组、消息队列、定时器和存储快等。
软件定时器:用户可以任意定义“单次”和“周期”型定时器,定时器是一个递减计数器,递减到零就会执行预先定义好的操作。每个定时器都可以指定所需操作,周期型定时器在递减到零时会执行指定操作,并自动重置计数器值。
同时等待多个内核对象:UCOSII允许一个任务同时等待多个事件。也就是说, 一个任务能够挂起在多个信号量或消息队列上,当其中任何 个等待的事件发生时,等待任务就会被唤醒。
直接向任务发送信号:UCOSII允许中断或任务直接给另一个任务发送信号,避免创建和使用诸如信号量或事件标志等内核对象作为向其他任务发送信号的中介,该特性有效地提高了系统性能。
直接向任务发送消息:UCOSII 允许中断或任务直接给另一个任务发送消息,避免创建和使用消息队列作为中介。
任务寄存器:每个任务都可以设定若干个“任务寄存器”,任务寄存器和CPU硬件寄存器是不同的,主要用来保存各个任务的错误信息,ID识别信息,中断关闭时间的测量结果等。
任务级时钟节拍处理:UCOSII的时钟节拍是通过一个专门任务完成的,定时中断仅触发该任务。将延迟处理和超时判断放在任务级代码完成,能极大地减少中断延迟时间。
防止死锁:所有UCOSIII的“等待”功能都提供了超时检测机制,有效地避免了死锁。
时间戳:UCOSII需要一个16位或32位的自由运行计数器(时基计数器)来实现时间测量,在系统运行时,可以通过读取该计数器来测量某一个事件的时间信息。例如,当ISR给任务发送消息时,会自动读取该计数器的数值并将其附加在消息中。当任务读取消息时,可得到该消息携带的时标,这样,再通过读取当前的时标,并计算两个时标的差值,就可以确定传递这条消息所花费的确切时间。
3、任务——线程:
任务(线程)是简单的程序。单CPU 中,在任何时刻只能是一个任务被执行。任务看起来像C 函数。在大多数嵌入式系统中,任务通常是无限循环的。任务不能像C 函数那样,它是不能return 的。
但是在UCOSIII中任务是以何种面貌存在的呢?
在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务堆栈是任务的重要部分,堆栈是在RAM中按照“先进先出(FIFO)”的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要,每个任务都应该有自己的堆栈。
#define START_STK_SIZE 512 //堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈
任务堆栈初始化
任务如何才能切换回上一个任务并且还能接着从上次被中断的地方开始运行?
恢复现场即可,现场就是CPU的内部各个寄存器。因此在创建一个新任务时,必须把系统启动这个任务时所需的CPU各个寄存器初始值事先存放在任务堆栈中。这样当任务获得CPU使用权时,就把任务堆栈的内容复制到CPU的各个寄存器,从而可以任务顺利地启动并运行。
把任务初始数据存放到任务堆栈的工作就叫做任务堆栈的初始化,UCOSIII提供了完成堆栈初始化的函数:OSTaskStkInit()。
当然,用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。
任务控制块:任务控制块用来记录任务的各个属性。
任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。我们使用OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块。任务控制块由用户自行创建。
OS_TCB StartTaskTCB; //创建一个任务控制块
USOCIII提供了用于任务控制块初始化的函数:OS_TaskInitTCB()。**但是,用户不需要自行初始化任务控制块。因为和任务堆栈初始化函数一样,函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。
任务函数:由用户编写的任务处理代码,是实实在在干活的,任务函数通常是一个无限循环,也可以是一个只执行一次的任务。任务的参数是一个void类型的,可以可以传递不同类型的数据甚至是函数。
任务函数其实就是一个C语言的函数,但是在使用UCOIII的情况下这个函数不能有用户自行调用,任务函数何时执行执行,何时停止完全有操作系统来控制。
UCOSIII支持时间片轮转调度,因此在一个优先级下会有多个任务,那么我们就要对这些任务做一个管理,这里使用OSRdyList[]数组管理这些任务。
OSRdyList[]数组中的每个元素对应一个优先级,比如OSRdyList[0]就用来管理优先级0下的所有任务。OSRdyList[0]为OS_RDY_LIST类型,从上面OS_RDY_LIST结构体可以看到成员变量:HeadPtr和TailPtr分别指向OS_TCB,我们知道OS_TCB是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的,HeadPtr和TailPtr分别指向这个链表的头和尾,NbrEntries用来记录此优先级下的任务数量,下图表示了优先级4现在有3个任务时候的就绪任务列表。
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务:
4、RTOS系统:
前后台系统
早期嵌入式开发没有嵌入式操作系统的概念 ,直接操作裸机,在裸机上写程序,比如用51单片机基本就没有操作系统的概念。通常把程序分为两部分:前台系统和后台系统。
简单的小系统通常是前后台系统,这样的程序包括一个死循环和若干个中断服务程序:应用程序是一个无限循环,循环中调用API函数完成所需的操作,这个大循环就叫做后台系统。中断服务程序用于处理系统的异步事件,也就是前台系统。前台是中断级,后台是任务级。
RTOS系统
RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性。实时操作系统又分为硬实时和软实时。硬实时要求在规定的时间内必须完成操作 ,硬实时系统不允许超时,在软实时里面处理过程超时的后果就没有那么严格。
在实时操作系统中,我们可以把要实现的功能划分为多个任务,每个任务负责实现其中的一部分,每个任务都是一个很简单的程序,通常是一个死循环。
可剥夺型内核:RTOS的内核负责管理所有的任务,内核决定了运行哪个任务,何时停止当前任务切换到其他任务,这个是内核的多任务管理能力。多任务管理给人的感觉就好像芯片有多个CPU,多任务管理实现了CPU资源的最大化利用,多任务管理有助于实现程序的模块化开发,能够实现复杂的实时应用。可剥夺内核顾名思义就是可以剥夺其他任务的CPU使用权,它总是运行就绪任务中的优先级最高的那个任务。
5、UCOSIII内核文件
如下图所示:
各编号部分介绍:
(1)应用代码包括与工程、产品相关文件。为了方便,这些被简单地叫做APP.C 和APP.H。
(2)半导体制造商通常以源代码形式提供库函数,以访问其CPU或MCU上的外围设备。
(3)板级支持包(BSP)是通常编写为与目标板上的外围设备接口的代码。 例如,此类代码可以打开和关闭LED,打开和关闭继电器或读取开关,温度传感器等。
(4)这是与μC/ OS-III处理器无关的代码。 该代码使用高度可移植的ANSI C编写。
(5)这是适用于不同CPU架构的μC/ OS-III代码,称为端口,在名为port 的文件夹中。
(6)在Micriμm,我们封装了CPU功能。 这些文件定义了用于禁用和启用中断的函数,CPU _ ??? 数据类型要独立于所使用的CPU和编译器以及更多功能,在编译时用到,而且可能非常有用。
(7)uC/LIB 是一系列的源文件,提供了常用基本的功能,如内存拷贝,字符串,ASCII 相关的函数。一些可以代替编译器提供的stdlib 的功能。提供这些文件是为了确保它们可以在应用程序之间(尤其是在编译器之间)完全可移植。uC/OS-III 不需要这些文件,但是uC/CPU 需要。
(8)uC/OS-III 功能的配置文件(OS_CFG.H)包含在应用中,OS_CFG_APP.H 定义了uC/OS-III 所需的变量类型大小、数据的结构、空闲任务堆栈的大小、时钟速率、内存池大小等。
二、利用HAL搭建keil5工程
(1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:
配置时钟,进入上面的RCC,有两个时钟,一个是hse和lse,将hse那里设Crystal/Ceramic Resonator:
设置PC13为GPIO_Output用于点亮LED灯(1s周期LED进行点亮-熄灭):
设置PA3为GPIO_Output用于点亮LED灯(3s周期LED进行点亮-熄灭):
完成创建
三、工程准备
下载uCOSIII源码
将uCOS相关文件复制到HAL工程的MDK-ARM文件夹下:
四、开始移植
打开cubeMX生成的keil文件
1、将uCOS文件添加到项目中:
(1)点击Manage Project Items,按下图所示操作,为项目新建文件夹,在对应文件夹下添加文件。
(2)点击CPU–>Add Files…
MDK-ARM\uC-CPU路径下选中以下文件,Add添加
MDK-ARM\uC-CPU\ARM-Cortex-M3\RealView路径下选中以下文件,Add添加
(3)点击LIB–>Add Files…
MDK-ARM\uC-LIB路径下选中下图红框文件,Add添加;MDK-ARM\uC-LIB\Ports\ARM-Cortex-M3\RealView路径下选中下图绿框文件,Add添加:
(4)点击PORT–>Add Files…
MDK-ARM\uCOS-III\Ports\ARM-Cortex-M3\Generic\RealView路径下选中以下文件,Add添加 :
(5)点击SOURCE–>Add Files…
MDK-ARM\uCOS-III\Source路径下选中以下全部 .c .h 文件,Add添加
(6)点击CONFIG–>Add Files…:
MDK-ARM\uC-CONFIG路径下选中以下全部文件,Add添加
(7)点击BSP–>Add Files…:
MDK-ARM\uC-BSP路径下选中以下全部文件,Add添加
(8) 到此全部添加完毕后,点击OK,一定记得点击OK,不然就白干了
(9)此时项目结构会发生变化,如下图所示
2、导入文件路径:
(1)按下图所示操作:
(2)从项目路径下寻找到对应的文件,并且添加进来,如下图所示:
五、完善keil5工程
(1) 为bsp.c和bsp.h添加代码:
点击打开bsp.c和bsp.h:
bsp.h:
#ifndef __BSP_H__
#define __BSP_H__
#include "stm32f1xx_hal.h"
void BSP_Init(void);
#endif
bsp.c:
// bsp.c
#include "includes.h"
#define DWT_CR *(CPU_REG32 *)0xE0001000
#define DWT_CYCCNT *(CPU_REG32 *)0xE0001004
#define DEM_CR *(CPU_REG32 *)0xE000EDFC
#define DBGMCU_CR *(CPU_REG32 *)0xE0042004
#define DEM_CR_TRCENA (1 << 24)
#define DWT_CR_CYCCNTENA (1 << 0)
CPU_INT32U BSP_CPU_ClkFreq (void)
{
return HAL_RCC_GetHCLKFreq();
}
void BSP_Tick_Init(void)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
cpu_clk_freq = BSP_CPU_ClkFreq();
#if(OS_VERSION>=3000u)
cnts = cpu_clk_freq/(CPU_INT32U)OSCfg_TickRate_Hz;
#else
cnts = cpu_clk_freq/(CPU_INT32U)OS_TICKS_PER_SEC;
#endif
OS_CPU_SysTickInit(cnts