UCOSIII
移植:STM32F10X_MD 8M
//显示字符串
//x,y:起点坐标
//width,height:区域大小
//size:字体大小
//*p:字符串起始地址
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)
UCOS学习方法及参考书籍:
1、《嵌入式实时操作系统 uc/os-III》,作者:Jean J. Labrosse,宫辉等翻译。
2、《嵌入式实时操作系统uc/os-III应用开发》,作者:Jean J. Labrosse,何小庆等翻译
3、《嵌入式实时操作系统uc/os-II原理及应用》,作者:任哲。
UCOSIII在STM32F103上的移植
一 ROTS背景知识简介
3种物联网操作系统:Huawei LiteOS、Amazon FreeRTOS和RT-Thread
RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性。实时操作系统又分为硬实时和软实时。硬实时要求在规定的时间内必须完成操作 ,硬实时系统不允许超时,在软实时里面处理过程超时的后果就没有那么严格。
RTOS操作系统:UCOS,FreeRTOS,RTX,RT-Thread,DJYOS等。
UCOS的内核是可剥夺型的,可剥夺内核就是可以剥夺其他任务的CPU使用权,它总是运行就绪任务中的优先级最高的那个任务。
二 UCOSIII任务管理
1 UCOSIII下SYSTEM文件夹
1.1 delay.c文件夹中函数
delay.c主要是使用滴答定时器来完成高精度延时。在使用UCOS的时候还完成了针对UCDOS的相关配置,主要是滴答定时器作为CUOS系统时基的配置。
1.2 滴答定时器
#define OS_CFG_TICK_RATE_HZ 200u //心跳,每秒跳200次,5ms
delay_us是不能引起任务调度的
delay_ms如果延时小于系统的最小周期的话,也不能发起任务调度
1.3 usart.c和sys.c文件夹
使用UCOS和不使用UCOS下usart.c文件最大的不同就是串口1中断服务函数:USART1_IRQHandler(),sys.c文件则完全相同。
使用UCOS中断服务函数格式
void USART1_IRQHandler(void)
{
OSIntEnter();
//中间处理函数,和不是用UCOS时相同
OSIntExit();
}
2 任务的基本概念
2.1 什么是任务?
在我们设计复杂、大型程序的时候也是一样的,将这些负责的程序分割成许多个简单的小程序,这些小程序就是单个的任务,所有的小任务和谐的工作,最终完成复杂的功能。在操作系统中这些小任务可以并发执行,从而提高CPU的使用效率。
UCOSIII就是一个可剥夺的多任务系统,我们使用UCOSIII的一个重要 的原因就是它的多任务处理能力。
2.2 UCOSIII中的任务
在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。
UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务控制块:任务控制块用来记录任务的各个属性。
任务函数:由用户编写的任务处理代码,是实实在在干活的。
2.3 UCOSIII系统任务
1、**空闲任务**:UCOSIII创建的第一个任务,UCOSIII必须创建的任务,此任务有UCOSIII自动创建,不需要用户手动创建。
2、**时钟节拍任务**:此任务也是必须创建的任务。
3、**统计任务**:可选任务,用来统计CPU使用率和各个任务的堆栈使用量。此任务是可选任务,由宏OS_CFG_STAT_TASK_EN控制是否使用此任务。
4、**定时任务**:用来向用户提供定时服务,也是可选任务,由宏OS_CFG_TMR_EN控制是否使用此任务。
5、**中断服务管理任务**:可选任务,由宏OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任务。
将对应的宏改为1,与该任务所有的代码将会被编译。
UCOSIII中以下优先级用户程序不能使用
将这些优先级分配给了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()
UCOSIII的任务一共有5种状态:
1、休眠态:任务已经在CPU的flash中了,但是还不受UCOSIII管理。
2、就绪态:系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。
3、运行态:任务获得CPU的使用权,正在运行。
4、等待态:正在运行的任务需要等待一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务。
5、中断服务态:当发送中断,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时被打断的任务的任务状态叫做中断服务态。
3 任务堆栈
3.1 任务堆栈的创建
原则:先进先出
定义:为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要。
格式:
#define START_STK_SIZE 512 //堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈
大小:CPU_STK为CPU_INT32U类型,也就是unsigned int类型,为4字节的,那么任务堆栈START_TASK_STK的大小就为:512 X 4=2048字节!
3.2 任务堆栈初始化
用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。
自己总结——它会调用os_cpu_c.c内OSTaskStkInit的函数将堆栈的值写入cpu中。
3.3 怎么使用创建的任务堆栈
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); //存放该函数错误时的返回值
4 任务控制块
4.1 任务控制块结构
定义:任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。
格式:
OS_TCB StartTaskTCB; //创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。
4.2 任务控制块初始化
用户不需要自行初始化任务控制块。函数**OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。函数OS_TaskInitTCB()**用与初始化任务控制块。
5任务就绪表
5.1 优先级
UCOSIII中任务优先级数由宏OS_CFG_PRIO_MAX来配置,UCOSIII中数值越小,优先级越高,最低可用优先级就是OS_CFG_PRIO_MAX-1。
宏定义在os_cfg.h文件下。
#define OS_CFG_PRIO_MAX 64u
5.2 就绪表
组成部分:
1、优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。
OSPrioTbl[]在os_prio.c中有定义:
CPU_DATA OSPrioTbl[OS_PRIO_TBL_SIZE];
如何找到已经就绪了的最高优先级的任务:
OS_PRIO OS_PrioGetHighest (void)
2、就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];
struct os_rdy_list {
OS_TCB *HeadPtr; /* 指向头Pointer to task that will run at selected priority */
OS_TCB *TailPtr; /* 指向尾Pointer to last task at selected priority */
OS_OBJ_QTY NbrEntries; /* 总数Number of entries at selected priority */
};
三个就绪的任务以链表的形式连接在一起
OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务!
6 任务调度
6.1 可剥夺型任务调度
条件:当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会获得CPU的使用权!
方法:UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:任务级调度器和中断级调度器。
任务级调度器为函数OSSched()。
在UCOSIII移植程序中,OSTimeDlyHMSM函数调用了OSSched();
中断级调度器为函数OSIntExit(),当退出外部中断服务函数的时候使用中断级任务调度。
#ifdef SYSTEM_SUPPORT_OS
OSIntExit();
6.2 任务调度点
1、释放信号量或者发送消息,也可通过配置相应的参数不发生任务调度。
2、使用延时函数OSTimeDly()或者OSTimeDlyHMSM()。
3、任务等待的事情还没发生(等待信号量,消息队列等)。
4、任务取消等待。
5、创建任务。
6、删除任务。
7、删除一个内核对象。
8、任务改变自身的优先级或者其他任务的优先级。
9、任务通过调用OSTaskSuspend()将自身挂起。
10、任务解挂某个挂起的任务。
11、退出所有的嵌套中断。
12、通过OSSchedUnlock()给调度器解锁。
13、任务调用OSSchedRoundRobinYield()放弃其执行时间片。
14、用户调用OSSched()。
6.3 调度器上锁和解锁:
有时候我们并不希望发生任务调度,因为始终有一些代码的执行过程是不能被打断的,比如初始化函数。此时我们就可以使用函数**OSSchedLock()对调度器加锁,当我们想要恢复任务调度的时候就可以使用函数OSSchedUnlock()**给已经上锁的任务调度器解锁。
6.4 时间片轮转调度
UCOSIII允许一个优先级下有多个任务,每个任务可以执行指定的时间(时间片),然后轮到下一个任务,这个过程就是时间片轮转调度,当一个任务不想在运行的时候就可以放弃其时间片。
时间片轮转调度器为:OS_SchedRoundRobin()。
7 任务切换
定义:当UCOSIII需要切换到另外一个任务时,它将保存当前任务的现场到当前任务的堆栈中,主要是CPU寄存器值,然后恢复新的现场并且执行新的任务,这个过程就是任务切换。
任务切换分为两种:任务级切换和中断级切换。
任务级切换函数为:OSCtxSw()。
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
中断级切换函数为:OSIntCtxSw()。
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
8 UCOSIII系统初始化和启动
8.1、UCOSIII系统初始化
函数OSInit()用来完成UCOSIII的初始化,而且**OSInit()**必须先于其他UCOSIII函数调用,包括OSStart()。
格式:
int main(void)
{
OS_ERR err;
……
//其他函数,一般为外设初始化函数
……
OSInit(&err);
……
//其他函数,一般为创建任务函数
……
OSStart(&err);
}
在进入临界区和退出临界区之间的代码是不会被打断的
进行任务的裁剪
8.2 系统启动
void OSStart (OS_ERR *p_err)
找到当前优先级最高的任务,并从这个任务开始执行
三 UCOSIII任务创建与删除
1 任务的创建
创建任务使用函数OSTaskCreate()
//主函数
int main(void)
{
OS_ERR err;
CPU_SR_ALLOC(); //为临界区代码保护声名变量
//外设函数初始化
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数,p_arg 基本不用
(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时为默认长度,为时钟频率/10
(void * )0, //用户补充的存储区 基本不用
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
opt:
OS_OPT_TASK_NONE //表示没有任何选项
OS_OPT_TASK_STK_CHK //指定是否允许检测该任务的堆栈
OS_OPT_TASK_STK_CLR //指定是否清除该任务的堆栈
OS_OPT_TASK_SAVE_FP //指定是否存储浮点寄存器,CPU需要有浮点运算硬件并且由专用代码保存浮点寄存器。
err=0说明任务创建成功
2 任务的删除
如果我们不想使用某个任务了就可将其删除,删除任务使用函数OSTaskDel(),函数原型如下:
OSTaskDel((OS_TCB*)&Task2_TaskTCB,&err); //任务1执行5此后删除掉任务2
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
除了用延时中嵌套的任务切换函数,也可以直接使用任务切换函数切换任务。
OSSched(); //任务切换
在FWLIB添加fsmc
1、删除某个任务以后,它占用的OS_TCB和堆栈就可以再次利用来创建其他的任务。
2、应该尽量避免删除任务,如果这个任务可能占有与其他任务共享的资源,在删除此任务之前这个被占有的资源没有被释放就有可能导致奇怪的结果。
四 任务的挂起和恢复
调度枷锁、空闲任务和中断服务任务不能挂起
不属于UCOSIII所有的允许的任务、在中断服务函数中、自己和不处于等待态不能恢复
OSTaskSuspend((OS_TCB*)&Task2_TaskTCB,&err); //挂起任务2
OSTaskResume((OS_TCB*)&Task2_TaskTCB,&err); //恢复任务2
五 UCOSIII时间片轮转调度
意义:同一优先级下可以存在多个任务
#define OS_CFG_SCHED_ROUND_ROBIN_EN 1u //开启时间片轮转调度
(OS_TICK )2, //2个时间片,既2*5=10ms
六 UCOSIII系统任务
1 UCOSIII系统内部任务
优先级0:中断服务服务管理任务 OS_IntQTask()
优先级1:时钟节拍任务 OS_TickTask()
优先级2:定时任务 OS_TmrTask() 通过系统函数确定是否启用
优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
1.1 中断服务管理任务 0
宏OS_CFG_ISR_POST_DEFERRED_EN置1
意义:减少中断关闭的时间
1.2 时钟节拍任务 1
意义:时钟节拍任务用来跟踪任务延时和任务等待超时
任务函数:OS_TickTask()
任务优先级宏:OS_CFG_TICK_TASK_PRIO
1.3 定时任务 2
将宏OS_CFG_TMR_EN设置为1
在OSInit()中将会调用函数OS_TmrInit()
任务优先级宏:OS_CFG_TMR_TASK_PRIO
1.4 统计任务 MAX-2
意义:统计CPU的使用率、各个任务的CPU使用率和各任务的堆栈使用情况
创建步骤:
1、将宏OS_CFG_STAT_TASK_EN置1。
3、任务优先级宏:OS_CFG_STAT_TASK_PRIO
统计任务会调用函数OSTaskStkChk来计算当前已经创建任务的堆栈使用情况和剩余情况
使用软件定时器一定要使用定时任务
1.5 空闲任务 MAX-1 必须创建
特点:
1、空闲任务是UCOSIII创建的第一个任务。
2、空闲任务是UCOSIII必须创建的。
3、空闲任务优先级总是为OS_CFG_PRIO_MAK-1。
4、空闲任务中不能调用任何可使空闲任务进入等待态的函数!
2 UCOSIII钩子函数
2.1 UCOSIII钩子函数
作用:钩子函数一般主要是用来扩展其他函数(任务)功能的
钩子函数一般主要是用来扩展其他函数(任务)功能的,钩子函数有如下几个:
1、OSIdleTaskHook(),空闲任务调用这个函数,可以用来让CPU进入低功耗模式
2、OSInitHook(),系统初始化函数OSInit()调用此函数。
3、OSStatTaskHook(),统计任务每秒中都会调用这个函数,此函数允许你向统计任务中添加自己的应用函数。
4、OSTaskCreateHook(),任务创建的钩子函数。
5、OSTaskDelHook(),任务删除的钩子函数。
6、OSTaskReturnHook(),任务意外返回时调用的钩子函数,比如删除某个任务
7、OSTaskSwHook(),任务切换时候调用的钩子函数。
8、OSTimeTickHook(),滴答定时器调用的钩子函数。
空闲任务中的钩子函数:修改os_app_hooks.c中的文件
void App_OS_IdleTaskHook (void)
{
}
七 UCOSIII中断和时间管理
1 UCOSIII中断管理
1.1 UCOSIII中断
中断:应内部或外部异步事件的请求中止当前任务,而去处理异步事件所要求的任务的过程叫做中断。
void USART1_IRQHandler(void)
{
OSIntEnter();
//中断服务程序
OSIntExit();
}
1.2 进入和退出中断服务函数
进入中断服务函数以后使用函数OSIntEnter()
void OSIntEnter (void)
{
if (OSRunning != OS_STATE_OS_RUNNING) {
return
}
if (OSIntNestingCtr >= (OS_NESTING_CTR)250u) {
return;
}
OSIntNestingCtr++;
}
OSIntNestingCtr来记录中断嵌套次数,UCOSIII最多支持250级的中断嵌套。
退出中断会时会寻找中断嵌套个数和中断任务枷锁,并且发器中断级任务切换
2 UCOSIII临界段代码保护
当宏OS_CFG_ISR_POST_DEFERRED_EN为0时,UCOSIII使用关中断的方式来保护临界段代码,当设置为1的时候就会采用给调度器上锁的方式来保护临界段代码。
UCOSIII定义了一个进入临界段代码的宏:OS_CRITICAL_ENTER(),定义了两个退出临界段代码的宏:OS_CRITICAL_EXIT和OS_CRITICAL_EXIT_NO_SCHED()。
OS_CRITICAL_EXIT退出临界代码区中嵌套了任务切换函数
OS_CRITICAL_EXIT_NO_SCHED()中没有嵌套任务切换
3 UCOSIII时间管理
3.1 任务延时
延时函数有两种,OSTimeDly()和OSTimeDlyHMSM()。
OSTimeDly()函数有三种工作模式:相对模式、周期模式和绝对模式。
OSTimeDlyHMSM()函数仅在相对模式下工作。
3.2 取消任务的延时
延时任务任务可通过在其他任务中调用函数OSTimeDlyResume()取消延时而进入就绪状态,此函数最后会引发一次任务调度。
3.3 获取和设置系统时间
UCOSIII定义了一个CPU_INT32U类型的全局变量OSTickCtr来记录系统时钟节拍数,在调用OSInit()时被初始化为0,以后每发生1个时钟节拍,OSTickCtr加1。
OSTimeSet()允许用户改变当前时钟节拍计数器的值,慎用!!!!!
OSTimeGet()用来获取动迁时钟节拍计数器的值。
八 UCOSIII软件定时器
OS_TMR tmr1; //定时器1
void tmr1_callback(void *p_tmr, void *p_arg); //定时器1回调函数
//创建定时器1
OSTmrCreate((OS_TMR *)&tmr1, //定时器1
(CPU_CHAR *)"tmr1", //定时器名字
(OS_TICK )20, //20*10=200ms 初始化定时器的延迟值
(OS_TICK )100, //100*10=1000ms 重复周期
(OS_OPT )OS_OPT_TMR_PERIODIC, //周期模式 OS_OPT_TMR_ONE_SHOT 单次定时器
(OS_TMR_CALLBACK_PTR)tmr1_callback,//定时器1回调函数
(void *)0, //参数为0
(OS_ERR *)&err); //返回的错误码
OSTmrStart(&tmr1,&err); //开启定时器1
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err); //关闭定时器1
//定时器1的回调函数
void tmr1_callback(void *p_tmr, void *p_arg)
{
static u8 tmr1_num=0;
tmr1_num++; //定时器1执行次数加1
printf("定时器1周期%d运行结束\r\n",tmr1_num);
}
九 UCOSIII信号量和互斥信号量
1 UCOSIII信号量
1.1 信号量简介
信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。
信号量用于控制对共享资源的保护,但是现在基本用来做任务同步用。
信号量=0,不能请求到;>0,可以请求。
请求1次信号量,信号量的值-1。
发送一次信号量,信号量值+1。
信号量通常分为两种:二进制信号量和计数型信号量。
二进制信号量只能取0和1两个值,计数型信号量的信号量值大于1,计数型信号量的范围由OS_SEM_CTR决定,OS_SEM_CTR可以为8位,16位和32位,取值范围分别为:0255,065535和0~4294967295。
二值信号量用于那些一次只能一个任务使用的资源,比如I/O设备,打印机
数型信号量用于某些资源可以同时被几个任务所使用,比如一个缓存池有10个缓存块,那么同时最多可以支持10个任务来使用内存池
1.1 信号量API函数
OSSemCreate() 建立一个信号量
OSSemDel() 删除一个信号量
OSSemPend() 等待一个信号量
OSSemPendAbrot() 取消等待
OSSemPost() 释放或者发出一个信号量
OSSemSet() 强制设置一个信号量的值
OS_SEM MY_SEM; //定义一个信号量,用于访问共享资源
OSSemCreate() //建立一个信号量
//创建一个信号量函数
void OSSemCreate (OS_SEM *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
OS_ERR *p_err)
//创建一个信号量实例
OSSemCreate ((OS_SEM* )&MY_SEM,
(CPU_CHAR* )"MY_SEM",
(OS_SEM_CTR)1,
(OS_ERR* )&err);
OSSemDel() //删除一个信号量
//删除信号量函数
OS_OBJ_QTY OSSemDel (OS_SEM *p_sem,
OS_OPT opt,
//OS_OPT_DEL_NO_PEND 删除没有任务请求的信号量
//OS_OPT_DEL_ALWAYS 删除所有的
OS_ERR *p_err)
OSSemPend() //请求信号量
//请求信号量函数
OS_SEM_CTR OSSemPend (OS_SEM *p_sem,
OS_TICK timeout, //表示任务等待时间,单位为时钟节拍个数。timeout=0表示任务接不到信号量就一直挂起,timeout=其他,在这个时间内没有请求到,任务接着运行。
OS_OPT opt,
//OS_OPT_PEND_BLOCKING 请求的信号量无效时,任务就挂起等待信号量
//OS_OPT_PEND_NON_BLOCKING 任务不挂起直接返回
CPU_TS *p_ts, //记录接收信号的时刻
OS_ERR *p_err)
//请求信号量实例
OSSemPend (&MY_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量
OSSemPendAbrot() //取消等待
//取消等待信号量函数
OS_OBJ_QTY OSSemPendAbort (OS_SEM *p_sem,
OS_OPT opt,
//OS_OPT_PEND_ABORT_1 仅终止等待该信号量优先级最高的任务
//OS_OPT_PEND_ABORT_ALL 终止所有等待该信号量的任务
//OS_OPT_POST_NO_SCHED 禁止在本函数内进行执行任务调度操作
OS_ERR *p_err)
OSSemPost() //释放或者发出一个信号量
//释放信号量函数
OS_SEM_CTR OSSemPost (OS_SEM *p_sem,
OS_OPT opt,
//OS_OPT_POST_1 向等待请求此信号量的最高的任务优先级任务发送
//OS_OPT_POST_ALL 向所有等待该信号量的任务发送
//OS_OPT_POST_NO_SCHED 本函数不允许任务调度
OS_ERR *p_err)
//释放信号量实例
OSSemPost (&MY_SEM,OS_OPT_POST_1,&err); //释放信号量
OSSemSet() //强制设置一个信号量的值
2 UCOSIII互斥信号量
2.1 优先级反转
2.2 互斥信号量
意义:解决优先级反转的问题
2.3 互斥信号量API函数
OSMutexCreate() 建立一个互斥信号量
OSMutexDel() 删除一个互斥信号量
OSMutexPend() 等待一个互斥信号量
OSMutexPendAbrot() 取消等待
OSMutexPost() 释放或者发布一个互斥信号量
OS_MUTEX TEST_MUTEX; //定义一个互斥信号量
//创建一个互斥信号量
OSMutexCreate((OS_MUTEX* )&TEST_MUTEX,
(CPU_CHAR* )"TEST_MUTEX",
(OS_ERR* )&err);
OSMutexPend (&TEST_MUTEX,0,OS_OPT_PEND_BLOCKING,0,&err); //请求互斥信号量
OSMutexPost(&TEST_MUTEX,OS_OPT_POST_NONE,&err); //释放互斥信号量
3 UCOSIII任务内嵌信号量
在UCOSIII中每个任务都有自己的内嵌的信号量,这种功能不仅能够简化代码,而且比使用独立的信号量更有效。任务信号量是直接内嵌在UCOSIII中的,任务信号量相关代码在os_task.c中。
OSTaskSemPend() 等待一个任务信号量
OSTaskSemPendAbort() 取消等待任务信号量
OSTaskSemPost() 发布任务信号量
OSTaskSemSet() 强行设置任务信号量计数
OSTaskSemPend() 等待一个任务信号量
//请求任务内建的信号量函数
OS_SEM_CTR OSTaskSemPend (OS_TICK timeout,
OS_OPT opt,
//OS_OPT_PEND_BLOCKING
//OS_OPT_PEND_NON_BLOCKING
CPU_TS *p_ts,
OS_ERR *p_err)
//请求任务内建的信号量实例
OSTaskSemPend(0,OS_OPT_PEND_BLOCKING,0,&err); //请求任务内建的信号量
OSTaskSemPost() 发布任务信号量
//使用系统内建信号量向任务x发送信号量函数
//释放的任务为任务快
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_tcb,
OS_OPT opt,
OS_ERR *p_err)
//使用系统内建信号量向任务x发送信号量实例
OSTaskSemPost(&Task2_TaskTCB,OS_OPT_POST_NONE,&err); //使用系统内建信号量向任务task2发送信号量
OSTaskSemSet() 强行设置任务信号量计数
//给内嵌任务信号量赋初始值函数
OS_SEM_CTR OSTaskSemSet (OS_TCB *p_tcb,
OS_SEM_CTR cnt,
OS_ERR *p_err)
//给内嵌任务信号量赋初始值实例
OSTaskSemSet(&Task2_TaskTCB,10,&err);
不可以在自己的任务里请求其他任务的信号量
十 UCOSIII消息传递
1 任务间通信
一个任务或者中断服务程序有时候需要和另一个任务交流信息,这个就是消息传递的过程就叫做任务间通信,任务间的消息传递可以通过2种途径:一是通过全局变量,二是通过发布消息。
使用全局变量的时候每个任务或者中断服务程序都必须保证其对全局变量的独占访问。消息也可以通过消息队列作为中介发布给任务。
消息包含一下几个部分:指向数据的指针,数据的长度和记录消息发布时刻的
时间戳
指针指向的可以是一块数据区域或者甚至是一个函数
消息的内容在传递过程中不能被改变
数据采用的是指针传递,也就是引用传递,并不是值传递,发布的消息本身并不产生拷贝
我们可以使用动态内存分配的方式来给消息分配一个内存块,或者,也可以传递一个指向全局变量、全局数据结构、全局数组或者函数的指针,或者局部的静态变量。
2 UCOSIII消息队列
struct os_msg_q {
OS_MSG *InPtr; /* 下一个消息插入的地方 */
OS_MSG *OutPtr; /* 读取的时候消息从哪里读走 */
OS_MSG_QTY NbrEntriesSize; /* 消息队列的大小 */
OS_MSG_QTY NbrEntries; /* 消息队列的使用值 */
OS_MSG_QTY NbrEntriesMax; /* 消息队列历史上最大值 */
};
struct os_msg {
OS_MSG *NextPtr; /* 把消息组成表 */
void *MsgPtr; /* 指向消息的指针 */
OS_MSG_SIZE MsgSize; /* 消息的大小 */
CPU_TS MsgTS; /* 保存消息的时间戳 */
};
2.1 消息队列API函数
适用于多个任务请求一个消息队列
OSQCreate() 创建一个消息 队列
OSQDel() 删除一个消息队列
OSQFlush() 清空消息队列
OSQPend() 等待消息
OSQPendAbort() 取消等待消息
OSQPost() 向消息队列发布一则消息
OSQCreate() 创建消息队列
消息队列使得任务或中断服务程序可以向一个或多个任务发送消息
//函数
void OSQCreate (OS_Q *p_q, //指向一个消息队列,消息队列的存储空间必须由应用程序分配
CPU_CHAR *p_name, // 消息队列的名字
OS_MSG_QTY max_qty, //指定消息队列的长度,必须大于0
OS_ERR *p_err)
//实例
//创建消息队列KEY_Msg
OSQCreate ((OS_Q* )&KEY_Msg, //消息队列
(CPU_CHAR* )"KEY Msg", //消息队列名称
(OS_MSG_QTY )KEYMSG_Q_NUM, //消息队列长度,这里设置为1
(OS_ERR* )&err); //错误码
OSQPend() 等待消息队列
当一个任务想要从消息队列中接收一个消息的话需要使用此函数。如果消息队列中有至少一个消息时,这些消息就会返回给函数调用者
//函数
void *OSQPend (OS_Q *p_q, //指向一个消息队列
OS_TICK timeout, //等待消息的超时时间,如果在指定时间没有接收到消息的话,任务就会被唤醒,接着运行。这个参数也可以设置为0,表示任务将一直等待下去,直到接收到消息
OS_OPT opt,
OS_MSG_SIZE *p_msg_size, //指向一个变量用来表示接收到消息的长度(字节数)
CPU_TS *p_ts,
OS_ERR *p_err)
//实例
key=OSQPend((OS_Q* )&KEY_Msg, /* 请求的是哪一个消息队列 */
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE* )&size, /* 接收消息的大小 */
(CPU_TS* )0, /* 接收消息的时间戳 */
(OS_ERR* )&err);
OSQPost() 向消息队列发布一则消息
可以通过该函数向消息队列发送消息,如果消息队列是满的,则该函数就立刻返回,并且返回一个特定的错误代码。
//函数
void OSQPost (OS_Q *p_q, /* 向哪个消息队列发送消息 */
void *p_void, /* 发送的消息,引用传递 */
OS_MSG_SIZE msg_size, /* 发送消息的大小,单位字节 */
OS_OPT opt,
//OS_OPT_POST_ALL /* 将消息发送给所有等待消息队列的任务 */
//OS_OPT_POST_FIFO /* 待发送的消息保存在消息的末尾,先进先出 */
//OS_OPT_POST_LIFO /* 待发送的消息保存在消息的开头,先进后出 */
//OS_OPT_POST_NO_SCHED /* 不做任务切换 */
OS_ERR *p_err)
//实例
//发送消息
OSQPost((OS_Q* )&KEY_Msg,
(void* )&key,
(OS_MSG_SIZE)1,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR* )&err);
2 UCOSIII任务内建消息队列
适用于一个任务请求一个消息队列
OSTaskQPend() 等待消息
OSTaskQPost() 向任务发布一则消息
p=OSTaskQPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE* )&size,
(CPU_TS* )0,
(OS_ERR* )&err );
OSTaskQPost((OS_TCB* )&Msgdis_TaskTCB, //向任务Msgdis发送消息
(void* )pbuf,
(OS_MSG_SIZE)10,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR* )&err);
十一 UCOSIII事件标志组和同时等待多个内核对象
1 UCOSIII事件标志组
1.1 事件标志组
适用于一个任务需要与多个事件同步
事件标志组与任务之间有两种同步机制:“或”同步和“与”同步。
1.2 事件标志组API函数
OSFlagCreate() 创建事件标志组
OSFlagDel() 删除事件标志组
OSFlagPend() 等待事件标志组
OSFlagPendAbort() 取消等待事件标志
OSFlagPendGetFlagsRdy() 获取使任务就绪的事件标志
OSFlagPost() 向事件标志组发布标志
//函数
void OSFlagCreate (OS_FLAG_GRP *p_grp,
CPU_CHAR *p_name,
OS_FLAGS flags,
OS_ERR *p_err)
//实例
//创建一个事件标志组
OSFlagCreate((OS_FLAG_GRP*)&EventFlags, //指向事件标志组
(CPU_CHAR* )"Event Flags", //名字
(OS_FLAGS )KEYFLAGS_VALUE, //事件标志组初始值
(OS_ERR* )&err); //错误码
//函数
OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp, //请求的哪个事件标志组
OS_FLAGS flags, //请求的事件标志组的哪几个事件
OS_TICK timeout,
OS_OPT opt,
//OS_OPT_PEND_FLAG_CLR_ALL 等待标志组所有标志清零
//OS_OPT_PEND_FLAG_CLR_ANY 等待标志组任意标志清零
//OS_OPT_PEND_FLAG_SET_ALL 等待标志组所有标志置1
//OS_OPT_PEND_FLAG_SET_ANY 等待标志组任意标志置1
//OS_OPT_PEND_FLAG_SET_ANY + OS_OPT_PEND_FLAG_CONSUME .。。+清零
CPU_TS *p_ts,
OS_ERR *p_err)
//实例
OSFlagPend((OS_FLAG_GRP*)&EventFlags,
(OS_FLAGS )KEY0_FLAG+KEY1_FLAG,
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_FLAG_SET_ALL+OS_OPT_PEND_FLAG_CONSUME,
(CPU_TS* )0,
(OS_ERR* )&err);
//函数
OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp, //
OS_FLAGS flags, //对哪些位进行操作
OS_OPT opt,
//OS_OPT_POST_FLAG_SET
//OS_OPT_POST_FLAG_CLR
OS_ERR *p_err)
//实例
flags_num=OSFlagPost((OS_FLAG_GRP*)&EventFlags,
(OS_FLAGS )KEY0_FLAG,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR* )&err);
2 UCOSIII同时等待多个内核对象
2.1 同时等待多个内核对象
在UCOSIII中允许任务同时等待多个信号量和多个消息队列,也就是说,UCOSIII不支持同时等待多个事件标志组或互斥信号量。
第一个信号量或消息队列的发布会导致该任务进入就绪态。
一个任务可以调用函数**OSPendMulti()**函数来等待多个对象
//函数
OS_OBJ_QTY OSPendMulti (OS_PEND_DATA *p_pend_data_tbl, //执行这个类型的数组
OS_OBJ_QTY tbl_size, //这个数组的大小
OS_TICK timeout,
OS_OPT opt,
OS_ERR *p_err)
//实例
index=OSPendMulti((OS_PEND_DATA* )pend_multi_tbl,
(OS_OBJ_QTY )CORE_OBJ_NUM, //内核数量
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_ERR* )&err);
十二 UCOSIII存储管理
1 存储控制块
栈空间
Stack_Size EQU 0x00000400 ;使用EMWIN的时候应该将栈开大一点
堆空间
Heap_Size EQU 0x00000200
在程序中可以用数组来表示一个存储区,比如u8 buffer[20][10],就表示一个拥有20个存储块,每个存储块10个字节的存储区。
struct os_mem {
OS_OBJ_TYPE Type;
void *AddrPtr;
CPU_CHAR *NamePtr;
void *FreeListPtr;
OS_MEM_SIZE BlkSize; //每一个存储控制块的大小
OS_MEM_QTY NbrMax; //存储区总的存储控制块
OS_MEM_QTY NbrFree; //当前存储区剩余可用的的存储控制块
#if OS_CFG_DBG_EN > 0u
OS_MEM *DbgPrevPtr;
OS_MEM *DbgNextPtr;
#endif
};
2 存储管理相关API函数
OSMemCreate() 创建一个存储分区
OSMemGet() 从存储分区中获得一个存储块
OSMemPut() 将一个存储块归还到存储分区中
//函数
void OSMemCreate (OS_MEM *p_mem,
CPU_CHAR *p_name,
void *p_addr, // 存储区首地址
OS_MEM_QTY n_blks, // 存储区有多少个存储块,必须大于2
OS_MEM_SIZE blk_size, // 存储块的大小,不能小于4字节且是4的倍数
OS_ERR *p_err)
//实例
//创建一个存储分区
OSMemCreate((OS_MEM* )&INTERNAL_MEM,
(CPU_CHAR* )"Internal Mem",
(void* )&Internal_RamMemp[0][0],
(OS_MEM_QTY )INTERNAL_MEM_NUM,
(OS_MEM_SIZE)INTERNAL_MEMBLOCK_SIZE,
(OS_ERR* )&err);
//函数
void *OSMemGet (OS_MEM *p_mem,
OS_ERR *p_err)
//实例
internal_buf=OSMemGet((OS_MEM*)&INTERNAL_MEM,
(OS_ERR*)&err);
//函数
void OSMemPut (OS_MEM *p_mem,
void *p_blk,
OS_ERR *p_err)
//实例
OSMemPut((OS_MEM* )&INTERNAL_MEM, //释放内存
(void* )internal_buf,
(OS_ERR* )&err);