文章来源于正点原子:正点原子UCOSIII教程
早期的时候嵌入式并没有“嵌入式操作系统”的概念,直接跑裸机(轮询系统)。“嵌入式操作系统”通常可以把程序分为前台系统和后台系统。(应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看成后台行为。前台程序通过中断来处理事件,后台程序则掌管整个嵌入式系统软、硬件资源的分配、管理以及任务的调度,是一个系统管理调度程序。即wihle(1)中为后台系统,中断为前台系统)
RTOS全称为:Real Time OS(即实时操作系统)。在实时操作系统中,要实现的功能划分为多个任务,一个任务实现一个功能,每个任务都是一个简单程序,通常都有一个死循环。
RTOS操作系统有: UCOS, FreeRTOS, RTX, RT-Thread, DJYOS等。(核心内容:实时内核)
RTOS可分为可剥夺型和不可剥夺型。可剥夺内核顾名思义就是可以剥夺其他任务的CPU使用权,它总是运行就绪任务中的优先级最高的那个任务。μCOSII为不可剥夺型,而μCOSIII是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。(绝大部分是用C来写得,极少数与处理器密切相关部分使用汇编)μCOSIII任务处理如下图:(此图来源正点原子)
使用μCOSIII之前必须初始化系统。函数OSInit()用来完成系统初始化,且必须先于其他μCOSIII函数调用,完了之后调用OSStart()函数开启系统。
int main(void)
{
OS_ERR err;
.......................
//其他初始化函数,一般为外设初始化函数
OSInit(&err); //初始化UCOSIII
..........................
//其他函数,一般为创建任务函数
OSStart(&err); //开启UCOSIII
while(1);
}
在裸机系统中,main()函数中的无限循环部分。在多任务系统中,根据不同的功能,把整个系统分割成多个独立的且带无限循环的函数,这些函数称为任务,通常称之为线程。任务/线程实际是一个函数,函数内主体无限循环并且无退出和返回值。
μCOSIII中任务组成有三部分:
1)任务堆栈: 上下文切换的时候用来保存任务当前环境,即压栈进行现场保护(是一个结构体数组)
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈创建
CPU_STK START_TASK_STK[START_STK_SIZE];
2)任务控制块: 用来记录任务各个属性/与任务相关信息(是一个结构体)
//任务控制块创建
OS_TCB StartTaskTCB;
3)任务函数: 用户自己编写的处理函数。(是一个函数)
//开始任务函数创建
void App_start_task(void *p_arg)
{
while(1){
}
}
这些优先级都分配给了μCOSIII的5个系统任务。
4.1 任务堆栈初始化函数
把任务初始数据存放到任务堆栈的工作叫任务堆栈初始化。由创建任务初始化函数调用。由于不同CPU对于寄存器和堆栈的操作方式不同,移植过程需要用户根据CPU来编写堆栈初始化。初始化函数为:
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
{
4.2 任务创建函数
μCOSIII中要想使用任务,就要先创建一个任务,创建任务函数为OSTaskCreate (),原型如下:
void OSTaskCreate (OS_TCB *p_tcb, //指向任务控制块OS_TCB
CPU_CHAR *p_name, //指向任务的任务名字,可以给每个任务取个名字
OS_TASK_PTR p_task, //执行任务代码,即任务函数
void *p_arg, //传递给任务的参数(一般给0,表示不传参)
OS_PRIO prio, //任务优先级,数值越低优先级越高,用户不能使用系统任务使用的那些优先级
CPU_STK *p_stk_base, //指向任务堆栈的基地址
CPU_STK_SIZE stk_limit, //任务堆栈的栈深度,用来检测和确保堆栈不溢出
CPU_STK_SIZE stk_size, //任务堆栈大小
OS_MSG_QTY q_size, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
OS_TICK time_quanta, //当使能时间片轮转时的时间片长度,为0时为默认长度(时钟频率/10),(单位为时钟节拍数)
void *p_ext, //用户补充的存储区,一般是一个数据结构,用来拓展TCB
OS_OPT opt, //任务选项,有如下选项可设置
//OS_OPT_TASK_NONE (OS_OPT)(0x0000u) 表示没有任何选项
//OS_OPT_TASK_STK_CHK (OS_OPT)(0x0001u) 指定是否允许检测该任务的堆栈
//OS_OPT_TASK_STK_CLR (OS_OPT)(0x0002u) 指定是否清除该任务堆栈
//OS_OPT_TASK_SAVE_FP (OS_OPT)(0x0004u) 指定是否存储浮点寄存器,CPU需要有浮点运算硬件并且有专用代码保存浮点寄存器
//OS_OPT_TASK_NO_TLS (OS_OPT)(0x0008u) 指定不需要TLS支持的任务
OS_ERR *p_err) //存放该函数的返回错误码
{
4.3 任务删除函数
OSTaskDel() 函数用来删除任务,删除任务不是说删除任务代码,而是注销任务,在有些应用中我们只需要某个任务只运行一次,运行完成后就将其删除掉,比如外设初始化任务,OSTaskDel()函数原型如下:
void OSTaskDel (OS_TCB *p_tcb, //指向要删除的任务OS_TCB(控制块),也可以传递NULL指针来删除调用OSTaskDel()函数的任务自身
OS_ERR *p_err) //保存返回错误码
{
4.4 任务挂起函数
有时候有些任务需要停止运行,但是以后还要运行,所以不能删除任务,这样可以使用OSTaskSuspend()函数挂起任务,以便后续使用,函数原型如下:
void OSTaskSuspend (OS_TCB *p_tcb, //指向要挂起的任务OS_TCB(控制块),也可以传递NULL指针来将调用该函数的任务挂起
OS_ERR *p_err) //保存返回错误码
{
4.5 任务挂起解除函数
OSTaskResume() 函数用来恢复被OSTaskSuspend()函数挂起的任务,是唯一能恢复被挂起任务的函数。函数原型如下:
void OSTaskResume (OS_TCB *p_tcb, //指向要解挂的任务OS_TCB(控制块),指向一个NULL是无效的,因为该任务正在运行,不需要解挂
OS_ERR *p_err) //保存返回错误码
{
4.6 任务切换函数
当要切换到其他任务时,将保存当前任务的现场到当前任务的堆栈中,主要时CPU寄存器值,然后恢复新的现场去执行新的任务,这个过程叫任务切换。任务切换有两种(都是使用汇编写的,因为切换速度要快):
1)任务级调度函数:OSCtxSw()。
2)中断级切换函数:OSIntCtxSw()。
调度器,决定了任务的运行顺序。根据其重要性每个任务都被分配了一个优先级。uC/OS-III 支持多个任务拥有相同的优先级。
时间片轮转调度器用于时间片轮转调度,使用函数OS_SchedRoundRobin(),此函数由OSTimeTick()或者OS_IntQTask()调用(在os_core.c中定义)
5.2.1 OSSchedRoundRobinCfg()函数
如果想使用μCOSIII的时间片轮转调度,就要将OS_CFG_SCHED_ROUND_ROBIN_EN置1,且需要调用函数OSSchedRoundRobinCfg(),函数原型如下:
void OSSchedRoundRobinCfg (CPU_BOOLEAN en, //使能时间片轮转调度
OS_TICK dflt_time_quanta, //设置两个时间片之间为几个节拍,设为0时为默认值OSCfg_TickRate_Hz / 10
OS_ERR *p_err) //存储返回的错误码
{
5.2.2 OSSchedRoundRobinYield()函数
调用该函数使任务放弃本次时间片,去执行下一个任务。函数原型如下:
void OSSchedRoundRobinYield (OS_ERR *p_err) //存储返回的错误码
系统内部任务一共有5个,分别如下:
为了使高优先级的任务不至于独占CPU使用权,给其他优先级较低的任务获取CPU使用权的机会。这就要在空闲任务外的所有任务中合适的位置调用系统提供的延时函数 ,让当前任务暂停一段时间,并进行任务切换去执行其他任务。延时任务有两种,分别为:OSTimeDly() 和OSTimeDlyHMSM()
1)OSTimeDly()函数
该函数有三种工作模式:相对延时模式、周期延时模式、绝对延时模式。函数原型如下:
/*opt选项有:
//OS_OPT_TIME_DLY 相对模式
//OS_OPT_TIME_TIMEOUT 和OS_OPT_TIME_DLY模式一样
//OS_OPT_TIME_MATCH 绝对模式
//OS_OPT_TIME_PERIODIC 周期模式
*/
void OSTimeDly (OS_TICK dly, //延时的时间片,这里我用的一个时间片5ms
OS_OPT opt, //选择工作模式(相对模式,绝对模式,周期模式),有选项如上
OS_ERR *p_err) //存储返回的错误码
{
2)OSTimeDlyHMSM()函数
该函数只在相对延时模式下工作,但是可以设置为小时,分钟,秒,毫秒延时。函数原型如下:
/* 选项 时间单位 可设范围
OS_OPT_TIME_HMSM_STRICT hours (0...99)
minutes (0...59)
seconds (0...59)
milliseconds (0...999)
OS_OPT_TIME_HMSM_NON_STRICT hours (0...999)
minutes (0...9999)
seconds (0...65535)
milliseconds (0...4294967295)
*/
void OSTimeDlyHMSM (CPU_INT16U hours, //小时
CPU_INT16U minutes, //分钟
CPU_INT16U seconds, //秒
CPU_INT32U milli, //毫秒
OS_OPT opt, //选项,有选项如上
OS_ERR *p_err) //存储返回的错误码
{
3)取消任务的延时
延时任务可以在其他任务中调用函数OSTimeDlyResume()取消延时而进入就绪状态,函数中最后会引发一次任务调度。函数原型如下:
void OSTimeDlyResume (OS_TCB *p_tcb, //要取消延时的任务控制块
OS_ERR *p_err) //存储返回的错误码
{
{
μCOSIII中定义一个CPU_INT32U类型的全局变量OSTickCtr来记录系统时钟节拍数,在调用OSI你太()时初始化为0,以后每发生1个小时节拍,OSTickCtr+1。
定时器为递减计数器,需设置 OS_CFG.H 中的 OS_CFG_TMR_EN 为 1 时软件定时器服务被使能。当递减计数值为0时,会触发某种动作执行,即通过回调函数执行相应的操作。回调函数是用户自己定义的,当定时器满(定时器递减到0)时可以被调用。(注:不要用回调函数调用如下函数OSTimeDly(),OSTimeDlyHMSM(),OS???Pend(),或其它能导致该定时器任务被阻塞或被删除的函数),软件定时器API函数如下:
函数名 | 功能 |
---|---|
OSTmrCreate() | 创建和设置定时器模式 |
OSTmrDel() | 删除一个定时器 |
OSTmrRemainGet() | 获得定时器的剩余期限值 |
OSTmrStart() | 开始定时器运行 |
OSTmrStateGet() | 获得定时器当前的状态 |
OSTmrStop() | 暂停定时器 |
2.1 创建定时器
定时器可以被设置为 3 种模式: 一次性定时模式, 无初始定时周期模式(没有初始的定时),有初始定时周期模式(有初始的定时)。创建软件定时器函数原型如下:
//定义定时器
OS_TMR tmr;
//创建定时器
void OSTmrCreate (OS_TMR *p_tmr, //用户定义的定时器
CPU_CHAR *p_name, //定时器名字
OS_TICK dly, //初始延时,即定时器第一个周期大小(节拍数)
OS_TICK period, //重复周期,即第一个周期以外的后面的周期,
OS_OPT opt, //定时器模式(、周期定时器)
OS_TMR_CALLBACK_PTR p_callback, //定时器回调函数
void *p_callback_arg,//一般设为0
OS_ERR *p_err) //存储返回的错误码
{
2.2 删除定时器
函数原型:
CPU_BOOLEAN OSTmrDel (OS_TMR *p_tmr, //用户定义的定时器
OS_ERR *p_err) //存储返回的错误码
{
2.3 获得定时器的剩余期限值
函数原型:
OS_TICK OSTmrRemainGet (OS_TMR *p_tmr, //用户定义的定时器
OS_ERR *p_err) //存储返回的错误码
{
2.4 开启定时器
函数原型如下:
CPU_BOOLEAN OSTmrStart (OS_TMR *p_tmr, //用户定义的定时器
OS_ERR *p_err) //存储返回的错误码
{
2.5 暂停定时器
函数原型如下:
/* 选项
OS_OPT_TMR_NONE //直接停止,不做任何动作
OS_OPT_TMR_CALLBACK //停止后调用一次回调函数
OS_OPT_TMR_CALLBACK_ARG //给回调函数一个新的参数
*/
CPU_BOOLEAN OSTmrStop (OS_TMR *p_tmr, //用户定义的定时器
OS_OPT opt, //选择模式
void *p_callback_arg, //停止后给回调函数一个新的参数
OS_ERR *p_err) //存储返回的错误码
{
信号量是一个锁定机制 ,代码需要获得钥匙(信号)才能访问共享资源,否则该代码段被上锁。信号量用于控制对共享资源的保护,但是现在基本用来做任务同步 。通常有两种类型的信号量:二值信号量和多值信号量,μCOSIII由OS_SEM_CTR来设置信号量计数最大值,OS_SEM_CTR>0时信号量可用。只有任务才允许使用信号量, ISR 是不允许的,但将信号量用于标记任务时可以被 ISR 调用。 。信号量是内核对象,通过数据类型 OS_SEM 定义。信号量相关API函数如下:
函数名 | 功能 |
---|---|
OSSemCreate() | 创建一个信号量 |
OSSemDel() | 删除一个信号量 |
OSSemPend() | 等待某个信号量 |
OSSemPendAbort() | 取消等待某个信号量 |
OSSemPost() | 释放或标记或提交一个信号量 |
OSSemSet() | 强制设置信号量计数值 |
1.1 创建信号量
函数原型:
//定义信号量,用于访问共享资源区
OS_SEM Task_SEM;
//创建信号量
void OSSemCreate (OS_SEM *p_sem, //用户创建的信号量(结构体)
CPU_CHAR *p_name, //信号量名字
OS_SEM_CTR cnt, //设置信号量的初始值
OS_ERR *p_err) //存储返回的错误码
{
1.2 删除信号量
函数原型:
/* 选项 功能
OS_OPT_DEL_NO_PEND 当没有任务请求信号量时删除
OS_OPT_DEL_ALWAYS 不管还有没有任务请求,直接删除
*/
OS_OBJ_QTY OSSemDel (OS_SEM *p_sem, //用户创建的信号量(结构体)
OS_OPT opt, //选项,如上
OS_ERR *p_err) //存储返回的错误码
{
1.3 请求信号量
函数原型:
/* 选项 模式
OS_OPT_PEND_BLOCKING 信号量无效的时候,挂起任务
OS_OPT_PEND_NON_BLOCKING 信号量无效的时候,任务直接返回
*/
OS_SEM_CTR OSSemPend (OS_SEM *p_sem, //用户创建的信号量(结构体)
OS_TICK timeout, //等待超时时间,过了就继续往下运行,如果设为0则永久等待知道等到
OS_OPT opt, //模式选项,如上
CPU_TS *p_ts, //时间戳,记录接收到信号量的时刻,给NULL/0则不要求获取时间戳
OS_ERR *p_err) //存储返回的错误码
{
1.4 取消等待信号量
函数原型:
/* 选项 模式
OS_OPT_PEND_ABORT_1 仅终止等待该信号量优先级最高的任务
OS_OPT_PEND_ABORT_ALL 终止所有等待该信号量的任务
OS_OPT_POST_NO_SCHED 禁止在该函数内执行任务调度操作
*/
OS_OBJ_QTY OSSemPendAbort (OS_SEM *p_sem, //用户创建的信号量(结构体)
OS_OPT opt, //模式选项,如上
OS_ERR *p_err) //存储返回的错误码
{
1.5 发送/释放信号量
函数原型:
/*
OS_OPT_POST_1 仅向请求此信号量的最高优先级任务发送该信号量
OS_OPT_POST_ALL 向所有等待该信号量的任务发送
OS_OPT_POST_NO_SCHED 本函数不引起任务调度
*/
OS_SEM_CTR OSSemPost (OS_SEM *p_sem, //用户创建的信号量(结构体)
OS_OPT opt, //模式选项,如上
OS_ERR *p_err) //存储返回的错误码
{
uC/OS-III 支持一种特殊类型的二值信号量叫做 mutex,用于解决优先级反转问题。mutex 是一个内核对象,它被数据类型 OS_MUTEX 所定义。只有任务才可以使用 mutex(ISR 不可以使用 mutex)。 mutex的API如下:
函数名 | 功能 |
---|---|
OSMutexCreate() | 创建一个 mutex |
OSMutexDel() | 删除一个 mutex |
OSMutexPend() | 等待一个 mutex |
OSMutexPendAbort() | 任务取消等待 mutex |
OSMutexPost() | 释放 mutex |
2.1 创建互斥信号量
函数原型:
void OSMutexCreate (OS_MUTEX *p_mutex,
CPU_CHAR *p_name,
OS_ERR *p_err)
{
2.2 删除互斥信号量
函数原型:
OS_OBJ_QTY OSMutexDel (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
2.3 请求互斥信号量
函数原型:
void OSMutexPend (OS_MUTEX *p_mutex,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts, //时间戳,记录发送、终止或删除信号量的时刻,
OS_ERR *p_err)
{
2.4 取消等待互斥信号量
函数原型:
OS_OBJ_QTY OSMutexPendAbort (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
2.5 释放互斥信号量
函数原型:
void OSMutexPost (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
μCOSIII中每个任务都有内建 的信号量,通过内建信号量不仅可以简化信号量通信的代码,而且比使用独立信号量更有效。这里没有信号量创建函数,任务内建信号量API如下:
函数名 | 功能 |
---|---|
OSTaskSemPend() | 等待一个任务信号量 |
OSTaskSemPendAbort() | 取消等待 |
OSTaskSemPost() | 发送信号量给任务 |
OSTaskSemSet() | 设置信号量计数值 |
3.1 请求任务信号量
函数原型:
/* 选项 模式
OS_OPT_PEND_BLOCKING 信号量无效的时候,挂起任务
OS_OPT_PEND_NON_BLOCKING 信号量无效的时候,任务直接返回
*/
OS_SEM_CTR OSTaskSemPend (OS_TICK timeout, //等待超时时间,过了就继续往下运行,如果设为0则永久等待知道等到
OS_OPT opt, //模式选项,
CPU_TS *p_ts, //时间戳,记录发送、终止或删除信号量的时刻,
OS_ERR *p_err) //存储返回的错误码
{
3.3 发送/释放信号量
函数原型:
/*
OS_OPT_POST_NONE 不指定特定选项
OS_OPT_POST_NO_SCHED 禁止在本任务内执行任务调度操作
*/
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_tcb, //要释放的任务的内建信号量的任务的任务控制块
OS_OPT opt, //模式选项,
OS_ERR *p_err) //存储返回的错误码
{