可以把代码也贴一下

EldridgeBear
@EldridgeBear
Posts made by EldridgeBear
-
RE: UCOS学习笔记
十三、 任务内嵌信号量
前面我们使用信号量时都需要先创建一个信号量,不过在UCOSIII 中每个任务都有自己的内嵌的信号量,这种功能不仅能够简化代码,而且比使用独立的信号量更有效。任务信号量是直接内嵌在UCOSIII 中的
-
等待任务信号量OSTaskSemPend
OStaskSemPend()允许一个任务等待由其他任务或者 ISR 直接发送的信号,使用过程基本和独立的信号量相同
函数原型:
OS_SEM_CTR OSTaskSemPend (OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
-
发布任务信号量OSTaskSemPost
函数原型:
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_tcb, OS_OPT opt, OS_ERR *p_err)
- *p_tcb: 指向要用信号通知的任务的 TCB,当设置为NULL 的时候可以向自己发送信量
-
-
RE: UCOS学习笔记
十五、 事件标志组
有时候一个任务可能需要和多个事件同步,这个时候就需要使用事件标志组
使用步骤
- 在my_os_msg.c文件中全局定义一个事件标志组
OS_FLAG_GRP ctrl_data_event;
- 在my_os_msg.h头文件中定义事件和事件标志组初始值
#define VISUAL_DATA_FLAG 0x01 //视觉信息到来事件 #define FORCE_DBUS_DATA_FLAG 0x02 //强制遥控事件 #define CTRL_DATA_FLAG_VALUE 0x00 //事件标志组初始值
事件由比特序列表示,事件1为b'0000 0001',即16进制1;事件2为b'0000 0010',即16进制2;事件3为b'0000 0100',即16进制4,依次类推
-
等待事件OSFlagPend
函数原型:
OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp, OS_FLAGS flags, OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
-
*p_grp: 指向事件标志组
-
flags: bit 序列,任务需要等待事件标志组的哪个位就把这个序列对应的位置 1,根据设置这个序列可以是 8bit、16bit 或者32 比特,比如任务需要等待时间标志组的bit0和bit1 时(无论是等待置位还是清零),flag 是的值就为0X03
-
timeout: 指定等待事件标志组的超时时间(时钟节拍数),如果在指定的超时时间内所等待的一个或多个事件没有发生,那么任务恢复运行。如果此值设置为 0,则任务就将一直等待下去,直到一个或多个事件发生
-
opt: 决定任务等待的条件是所有标志置位、所有标志清零、任意一个标志置位还是任意一个标志清零,具体的定义如下
-
OS_OPT_PEND_FLAG_CLR_ALL: 等待事件标志组所有的位清零
-
OS_OPT_PEND_FLAG_CLR_ANY: 等待事件标志组中任意一个标志清零
-
OS_OPT_PEND_FLAG_SET_ALL: 等待事件标志组中所有的位置位
-
OS_OPT_PEND_FLAG_SET_ANY: 等待事件标志组中任意一个标志置位
调用上面四个选项的时候还可以搭配下面三个选项
-
OS_OPT_PEND_FLAG_CONSUME: 用来设置是否继续保留该事件标志的状态
-
OS_OPT_PEND_NON_BLOCKING: 标志组不满足条件时不挂起任务
-
OS_OPT_PEND_BLOCKING: 标志组不满足条件时挂起任务
这里应该注意选项 OS_OPT_PEND_FLAG_CONSUME 的使用方法,如果我们希望任务等待事件标志组的任意一个标志置位,并在满足条件后将对应的标志清零那么就可以搭配使用选项 OS_OPT_PEND_FLAG_CONSUME
-
-
*p_ts: 指向一个时间戳,记录了发送、终止和删除事件标志组的时刻,如果为这个指针赋值NULL,则函数的调用者将不会收到时间戳
-
*p_err: 保存错误码
-
返回值为返回等待到的事件比特序列,如果等待多个事件时用于判断等到的是什么事件
如
OS_FLAGS os_flags_num; os_flags_num = OSFlagPend((OS_FLAG_GRP*)&ctrl_data_event, (OS_FLAGS )VISUAL_DATA_FLAG+FORCE_DBUS_DATA_FLAG, (OS_TICK )10, //等待10个时间片,即10ms (OS_OPT )OS_OPT_PEND_FLAG_SET_ANY+OS_OPT_PEND_FLAG_CONSUME, (CPU_TS *)0, (OS_ERR *)&err); switch (os_flags_num) { case 0: //等待超时,未等到任何事件 visual_no_use_flag++; if (visual_no_use_flag > 20) { //200ms内未传来视觉数据 visual_no_use_flag = 30; rc_ctrl_mode(); } else { visual_ctrl_mode(); } break; case VISUAL_DATA_FLAG: //视觉信息到来事件 visual_no_use_flag = 0; visual_ctrl_mode(); break; case FORCE_DBUS_DATA_FLAG: //强制进入遥控器控制模式事件 default: break; //退出至下一个循环,进入到上档强制遥控器模式 }
-
向事件标志组发布标志OSFlagPost
函数原型:
OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp, OS_FLAGS flags, OS_OPT opt, OS_ERR *p_err)
- *p_grp: 指向事件标志组
- flags: 决定对哪些位清零和置位,当 opt 参数为OS_OPT_POST_FLAG_SET 的时,参数flags 中置位的位就会在事件标志组中对应的位也将被置位。当 opt 为 OS_OPT_POST_FLAG_CLR 的时候参数 flags 中置位的位在事件标志组中对应的位将被清零
- opt: 决定对标志位的操作,有两种选项
- OS_OPT_POST_FLAG_SET: 对标志位进行置位操作
- OS_OPT_POST_FLAG_CLR: 对标志位进行清零操作
- *p_err: 保存错误码
- 返回值为返回发布标志后的事件比特序列
如
OSFlagPost( (OS_FLAG_GRP*)&ctrl_data_event, (OS_FLAGS )VISUAL_DATA_FLAG, (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err);
-
RE: UCOS学习笔记
十四、 消息队列
可以理解为UCOS自带的队列库函数,用于通过队列向别的任务传递消息
14.1. 消息队列使用步骤
- 定义一个消息队列
OS_Q Msg_Que
-
创建消息队列OSQCreate
函数原型:
void OSQCreate (OS_Q *p_q, CPU_CHAR *p_name, OS_MSG_QTY max_qty, OS_ERR *p_err)
- *p_q: 指向一个消息队列
- *p_name: 消息队列的名字
- max_qty: 指定消息队列的长度,必须大于 0。当然,如果 OS_MSGs 缓冲池中没有足够多的 OS_MSGs 可用,那么发送消息将会失败,并且返回相应的错误码,指明当前没有可用的 OS_MSGs
- *p_err: 保存错误码
-
等待消息队列OSQPend
函数原型:
void *OSQPend (OS_Q *p_q, OS_TICK timeout, OS_OPT opt, OS_MSG_SIZE *p_msg_size, CPU_TS *p_ts, OS_ERR *p_err)
- *p_q: 指向一个消息队列
- timeout: 等待消息的超时时间,如果在指定的时间没有接收到消息的话,任务就会被唤醒接着运行。这个参数也可以设置为 0,表示任务将一直等待下去,直到接收到消息
- opt: 用来选择是否使用阻塞模式,有两个选项可以选择
- OS_OPT_PEND_BLOCKING: 如果没有任何消息存在的话就阻塞任务,一直等待,直到接收到消息
- OS_OPT_PEND_NON_BLOCKING: 如果消息队列没有任何消息的话任务就直接返回
- *p_msg_size: 指向一个变量用来表示接收到的消息长度(字节数)
- *p_ts: 指向一个时间戳,表明什么时候接收到消息。如果这个指针被赋值为NULL的话,说明用户没有要求时间戳
- *p_err: 保存错误码
-
向消息队列发送消息OSQPost
函数原型:
void OSQPost (OS_Q *p_q, void *p_void, OS_MSG_SIZE msg_size, OS_OPT opt, OS_ERR *p_err)
-
*p_q: 指向一个消息队列
-
*p_void: 指向实际发送的内容,p_void 是一个执行 void 类型的指针,其具体含义由用户程序的决定
-
msg_size: 设定消息的大小,单位为字节数
-
opt: 用来选择消息发送操作的类型,基本的类型可以有下面四种
- OS_OPT_POST_ALL: 将消息发送给所有等待该消息队列的任务,需要和选项OS_OPT_POST_FIFO 或者 OS_OPT_POST_LIFO 配合使用
- OS_OPT_POST_FIFO: 待发送消息保存在消息队列的末尾
- OS_OPT_POST_LIFO: 待发送的消息保存在消息队列的开头
- OS_OPT_POST_NO_SCHED: 禁止在本函数内执行任务调度
我们可以使用上面四种基本类型来组合出其他几种类型,如下:
OS_OPT_POST_FIFO + OS_OPT_POST_ALL
OS_OPT_POST_LIFO + OS_OPT_POST_ALL
OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
-
*p_err: 保存错误码
14.2. 任务内建消息队列
和任务信号量一样,UCOSIII 中每个任务也都有其内建消息队列,这样的话用户就不需要使用外部的消息队列就可直接向任务发布消息,这个特性不仅简化了代码,而且比使用外部消息队列更加有效
使用步骤
-
开启宏定义OS_CFG_TASK_Q_EN
-
等待任务内建消息OSTaskQPend
函数原型
void *OSTaskQPend (OS_TICK timeout, OS_OPT opt, OS_MSG_SIZE *p_msg_size, CPU_TS *p_ts, OS_ERR *p_err)
-
发送任务内建消息OSTaskQPost
函数原型
void OSTaskQPost (OS_TCB *p_tcb, void *p_void, OS_MSG_SIZE msg_size, OS_OPT opt, OS_ERR *p_err)
- *p_tcb: 指向接收消息的任务的 TCB,可以通过指定一个 NULL 指针或该函数调用者的TCB 地址来向该函数的调用者自己发送一条消息
-
RE: UCOS学习笔记
十二、 互斥信号量
12.1. 优先级反转
优先级反转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果,如下图
- 任务H 和任务 M 处于挂起状态,等待某一事件的发生,任务L 正在运行
- 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量
- 任务 L 获得信号量并开始使用该共享资源
- 由于任务H 优先级高,它等待的事件发生后便剥夺了任务L 的 CPU 使用权
- 任务H 开始运行
- 任务H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务H 只能进入挂起状态,等待任务 L 释放该信号量
- 任务 L 继续运行
- 由于任务M 的优先级高于任务L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的CPU 使用权
- 任务M 处理该处理的事
- 任务M 执行完毕后,将 CPU 使用权归还给任务L
- 任务L 继续运行
- 最终任务L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换
- 任务H 得到该信号量并接着运行
在这种情况下,任务 H 的优先级实际上降到了任务L 的优先级水平。因为任务H 要一直待直到任务L 释放其占用的那个共享资源。由于任务 M 剥夺了任务L 的CPU 使用权,使得任务H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务H,导致优先级反转
12.2. 互斥信号量
为了解决使用普通信号量会出现的优先级反转问题,提出互斥信号量的概念
- 任务H 与任务 M 处于挂起状态,等待某一事件的发生,任务L 正在运行中
- 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应资源的互斥型信号量
- 任务 L 获得互斥型信号量并开始使用该共享资源
- 由于任务H 优先级高,它等待的事件发生后便剥夺了任务L 的 CPU 使用权
- 任务H 开始运行
- 任务 H 运行过程中也要使用任务 L 在使用的资源,考虑到任务 L 正在占用着资源UCOSIII 会将任务 L 的优先级升至同任务H 一样,使得任务 L 能继续执行而不被其他中等优先级的任务打断
- 任务 L 以任务H 的优先级继续运行,注意此时任务H 并没有运行,因为任务H 在等待任务L 释放掉互斥信号量
- 任务 L 完成所有的任务,并释放掉互斥型信号量,UCOSIII 会自动将任务 L 的优先级恢复到提升之前的值,然后 UCOSIII 会将互斥型信号量给正在等待着的任务H
- 任务H 获得互斥信号量开始执行
- 任务H 不再需要访问共享资源,于是释放掉互斥型信号量
- 由于没有更高优先级的任务需要执行,所以任务H 继续执行
- 任务 H 完成所有工作,并等待某一事件发生,此时 UCOSIII 开始运行在任务 H 或者任务L 运行过程中已经就绪的任务 M
- 任务M 继续执行
12.3. 互斥信号量使用步骤
- 在my_os_msg.c中全局定义一个互斥信号量
OS_MUTEX FILE_CONTRL; /* 定义一个互斥信号量 */
-
创建互斥信号量OSMutexCreate
函数原型
void OSMutexCreate (OS_MUTEX *p_mutex, CPU_CHAR *p_name, OS_ERR *p_err)
- *p_mutex: 指向互斥型信号量控制块
- *p_name: 互斥信号量的名字
- *p_err: 保存错误码
如
/*** 创建一个互斥信号量, 用来给文件读写加锁 ***/ OSMutexCreate((OS_MUTEX *)&FILE_CONTRL, (CPU_CHAR *)"FILE_CONTRL", (OS_ERR *)&err);
-
请求互斥信号量OSMutexPend
函数原型
void OSMutexPend (OS_MUTEX *p_mutex, OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
- *p_mutex: 指向互斥信号量
- timeout: 指定等待互斥信号量的超时时间(时钟节拍数),如果在指定的时间内互斥信号量没有释放,则允许任务恢复执行。该值设置为 0 的话,表示任务将会一直等待下去,直到信号量被释放掉
- opt: 用于选择是否使用阻塞模式
- OS_OPT_PEND_BLOCKING: 指定互斥信号量被占用时,任务挂起等待该互斥信号量
- OS_OPT_PEND_NON_BLOCKING: 指定当互斥信号量被占用时,直接返回任务,注意!当设置为 OS_OPT_PEND_NON_BLOCKING,是 timeout 参数就没有意义了,应该设置为 0
- *p_ts: 指向一个时间戳,记录发送、终止或删除互斥信号量的时刻
- *p_err: 保存错误码
如
OSMutexPend (&FILE_CONTRL, 0, OS_OPT_PEND_BLOCKING, 0, &err); //请求互斥信号量
-
发送互斥信号量OSMutexPost
函数原型:
void OSMutexPost (OS_MUTEX *p_mutex, OS_OPT opt, OS_ERR *p_err)
- *p_mutex: 指向互斥信号量
- opt: 用来指定是否进行任务调度操作
- OS_OPT_POST_NONE: 不指定特定的选项
- OS_OPT_POST_NO_SCHED: 禁止在本函数内执行任务调度操作
- *p_err: 保存错误码
如:
OSMutexPost(&FILE_CONTRL, OS_OPT_POST_NONE, &err); //释放互斥信号量
-
RE: UCOS学习笔记
十一、 信号量
信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。
信号量分为二进制信号量和技术型信号量
使用信号量步骤
- 在my_os_msg.c文件中全局定义一个信号量
OS_SEM Flash_write_sem; /* 定义一个信号量 */
-
创建信号量OSSemCreate
函数原型:
void OSSemCreate (OS_SEM *p_sem, CPU_CHAR *p_name, OS_SEM_CTR cnt, OS_ERR *p_err)
-
*p_sem: 指向信号量控制块
-
*p_name: 指向信号量的名字
-
cnt: 设置信号量的初始值,如果此值为 1,代表此信号量为二进制信号量,如果大于1的话就代表此信号量为计数型信号量
-
*p_err: 保存错误码
如
OSSemCreate ( (OS_SEM *)&Flash_write_sem, (CPU_CHAR *)"Flash_write_sem", (OS_SEM_CTR)0, //初始状态有0个信号量 (OS_ERR *)&err);
-
请求信号量OSSemPend
函数原型
OS_SEM_CTR OSSemPend (OS_SEM *p_sem, OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
- *p_sem: 指向一个信号量的指针
- timeout: 指定等待信号量的超时时间(时钟节拍数),如果在指定时间内没有等到信号量则允许任务恢复执行。如果指定时间为 0 的话任务就会一直等待下去,直到等到信号量
- opt: 用于设置是否使用阻塞模式,有下面两个选项
- OS_OPT_PEND_BLOCKING: 指定信号量无效时,任务挂起以等待信号量
- OS_OPT_PEND_NON_BLOCKING: 信号量无效时,任务直接返回
- *p_ts: 指向一个时间戳,用来记录接收到信号量的时刻,如果给这个参数赋值 NULL,则说明用户没有要求时间戳
- *p_err: 保存错误码
如
OSSemPend(&Flash_write_sem, 0, OS_OPT_PEND_BLOCKING, 0, &err); //请求信号量
- 发送信号量OSSemPost
OS_SEM_CTR OSSemPost (OS_SEM *p_sem, OS_OPT opt, OS_ERR *p_err)
- *p_sem: 指向一个信号量的指针
- opt: 用来选择信号量发送的方式
- OS_OPT_POST_1: 仅向等待该信号量的优先级最高的任务发送信号量
- OS_OPT_POST_ALL: 向等待该信号量的所有任务发送信号量
- OS_OPT_POST_NO_SCHED: 该选项禁止在本函数内执行任务调度操作。即使该函数使得更高优先级的任务结束挂起进入就绪状态,也不会执行任务调度,而是会在其他后续函数中完成任务调度
- *p_err: 保存错误码
-
RE: UCOS学习笔记
十、 软件定时器
10.1. 定时器工作模式
在学习单片机的时候会使用定时器来做很多定时任务,这个定时器是单片机自带的,也就是硬件定时器,在UCOSIII 中提供了软件定时器,我们可以使用这些软件定时器完成一些功能。
定时器其实就是一个递减计数器,当计数器递减到 0 的时候就会触发一个动作,这个动作就是回调函数,当定时器计时完成时就会自动的调用这个回调函数。因此我们可以使用这个回调函数来完成一些设计。比如,定时 10 秒后打开某个外设等等,在回调函数中应避免任何可以阻塞或者删除定时任务的函数。
定时器的分辨率由我们定义的系统节拍频率 OS_CFG_TICK_RATE_HZ 决定,比如我们定义为200,系统时钟周期就是5ms,定时器的最小分辨率肯定就是5ms。但是定时器的实际分辨率是通过宏 OS_CFG_TMR_TASK_RATE_HZ 定 义 的 , 这 个 值 绝 对 不 能 大 于OS_CFG_TICK_RATE_HZ。比如我们定义 OS_CFG_TMR_TASK_RATE_HZ 为 100,则定时器的时间分辨率为10ms。有关UCOSIII 定时器的函数都在 os_tmr.c 文件中。
10.1.1. 单次定时器
触发单次定时器的时序图
重复触发单词定时器的时序图
10.1.2. 周期定时器
- 无初始化延迟,使用OSTmrCreate()函数创建定时器的时候,参数dly 为0 的话,就没有初始化延迟
- 有初始化延迟,初始化延时就是OSTmrCreate()函数中的参数dly 就是初始化延迟,定时器的第一个周期就是 dly。当第一个周期完成后就是用参数period 作为周期值
10.2. 创建软件定时器
创建一个软件定时器的步骤
- 宏定义使能定时器
#define OS_CFG_TMR_EN 1u #define OS_CFG_TMR_DEL_EN 1u
- 在my_app_timer.c全局定义一个定时器
static OS_TMR mode_change_timer;
3. 使用OSTmrCreate()函数创建定期器
函数原型:
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, OS_ERR *p_err)
- *p_tmr: 指向定时器的指针,宏OS_TMR 是一个结构体
- *p_name: 定时器名称
- dly: 初始化定时器的延迟值
- period: 重复周期。
- opt: 定时器运行选项,这里有两个模式可以选择
- OS_OPT_TMR_ONE_SHOT: 单次定时器
- OS_OPT_TMR_PERIODIC: 周期定时器
- p_callback: 指向回调函数的名字
- *p_callback_arg: 回调函数的参数
- *p_err: 调用此函数以后返回的错误码
如
//创建模式切换定时器定时器 OSTmrCreate((OS_TMR *)&mode_change_timer, //定时器1 (CPU_CHAR *)"mode_change_timer", //定时器名字 (OS_TICK )2, //定时第一个周期的时间, 2*10=20ms (OS_TICK )1, //第二周期及以后的周期时间, 1*10=10ms (OS_OPT )OS_OPT_TMR_PERIODIC, //周期模式 (OS_TMR_CALLBACK_PTR)mode_change_timer_callback, //定时器1回调函数 (void *)0, //传递给回调函数的参数为0 (OS_ERR *)&err); //返回的错误码
- 编写在上一步中填入的回调函数,即用户自定义内容
static void mode_change_timer_callback(void *p_tmr, void *p_arg) { if (auto_ctrl_cnt) auto_ctrl_cnt--; }
- 开启定时器OSTmrStart
函数原型:
CPU_BOOLEAN OSTmrStart (OS_TMR *p_tmr, OS_ERR *p_err)
- *p_tmr: 定时器名称
- *p_err: 保存错误类型
- 关闭定时器OSTmrStop
函数原型:
CPU_BOOLEAN OSTmrStop (OS_TMR *p_tmr, OS_OPT opt, void *p_callback_arg, OS_ERR *p_err)
- *p_tmr: 定时器名称
- opt: 选项
- OS_OPT_TMR_NONE: 不带选项
- *p_callback_arg: 传递给回调函数的参数
- *p_err: 保存错误类型