STM32理论 —— μCOS-Ⅲ(2/2):时间管理、消息队列、信号量、任务内嵌信号量/队列、事件标志、软件定时器、内存管理

文章目录

  • 9. 时间管理
    • 9.1 `OSTimeDly()`
    • 9.2 `OSTimeDlyHMSM()`
    • 9.3 `OSTimeDlyResume()`
    • 9.4 延时函数实验
  • 10. 消息队列
    • 10.1 创建消息队列函数`OSQCreate()`
    • 10.2 发送消息到消息队列函数(写入队列)`OSQPost()`
    • 10.3 获取消息队列中的消息函数(读出队列)`OSQPend()`
    • 10.4 消息队列操作实验
  • 11. 信号量
    • 11.1 二值信号量
      • 11.1.1 创建信号量函数`OSSemCreate()`
      • 11.1.2 释放信号量函数`OSSemPost()`
      • 11.1.3 获取信号量函数`OSSemPend ()`
      • 11.1.4 二值信号量实验
    • 11.2 计数型信号量
      • 11.2.1 计数型信号量实验
    • 11.3 优先级翻转问题
      • 11.3.1 优先级翻转实验
    • 11.4 互斥信号量
      • 11.4.1 创建互斥信号量`OSMutexCreate()`
      • 11.4.2 获取互斥信号量`OSMutexPend()`
      • 11.4.3 释放互斥信号量`OSMutexPost()`
      • 11.4.4 互斥信号量实验
  • 12. 任务内嵌信号量/队列
    • 12.1 任务内嵌信号量
      • 12.1.1 获取任务信号量函数`OSTaskSemPend()`
      • 12.1.2 释放信号量函数`OSTaskSemPost()`
      • 12.1.3 强制设置指定的任务信号量为指定值函数`OSTaskSemSet()`
      • 12.1.4 任务内嵌信号量实验
    • 12.2 任务内嵌消息队列
      • 12.2.1 获取任务消息队列函数(读出队列)`OSTaskQPend()`
      • 12.2.2 发送任务消息队列函数(写入队列)`OSTaskQPost()`
      • 12.2.3 任务消息队列实验
  • 13. 事件标志
    • 13.1 创建事件标志组函数`OSFlagCreate()`
    • 13.2 设置事件标注组的事件函数`OSFlagPost()`
    • 13.3 等待事件标注组中的事件函数`OSFlagPend()`
    • 13.4 事件标注组实验
  • 14. 软件定时器
    • 14.1 创建软件定时器函数`OSTmrCreate()`
    • 14.2 删除软件定时器函数`OSTmrCreate()`
    • 14.3 开启软件定时器函数`OSTmrStart()`
    • 14.4 关闭软件定时器函数`OSTmrStop()`
    • 14.4 软件定时器实验
  • 15. 内存管理
    • 15.1 创建一个内存区函数`OSMemCreate()`
    • 15.2 获取一个内存块函数`OSMemGet()`
    • 15.3 释放内存块到内存区中的函数`OSMemPut()`
    • 15.4 内存管理实验

9. 时间管理


时间管理是一种建立在时钟节拍上,对任务运行时间管理的一种系统内核机制;

时间管理相关API 函数:

函数描述
OSTimeDly()以系统时钟节拍为单位进行任务延时
OSTimeDlyHMSM()以时、分、秒、毫秒为单位进行任务延时
OSTimeDlyResume()恢复被延时的任务

实际延时时间取决于系统时钟节拍的频率:OS_CFG_TICK_RATE_HZ,它默认为1000;

如:task1 被延时1分钟,在1分钟的延时等待过程中,可调用OSTimeDlyResume()来及时恢复被延时的task1;

9.1 OSTimeDly()

函数原型输入形参:

void  OSTimeDly (OS_TICK   dly,OS_OPT    opt,OS_ERR   *p_err)
  1. dly:任务延时的系统时钟节拍数;
  2. opt:延时选项;
opt描述
OS_OPT_TIME_DLY任务延时的结束时刻为 OSTickCtr = OSTickCtr +dly,相对延时,OSTickCtr 每进入一次滴答定时器中断加一次1
OS_OPT_TIME_TIMEOUT任务延时的结束时刻为 OSTickCtr = OSTickCtr +dly,功能与上面一样
OS_OPT_TIME_MATCH任务延时的结束时刻为 OSTickCtr = dly,绝对时间,一般不用
OS_OPT_TIME_PERIODIC任务延时的结束时刻为 OSTickCtr = OSTCBCurPtr -> TickCtrPrev + dly
  • OS_OPT_TIME_DLY :如dly 设置为1,OSTimeDly 在OSTickCtr 等于12时被调用,则任务延时的结束时刻为OSTickCtr 等于13时;
  • OS_OPT_TIME_MATCH :如dly 设置为100,OSTimeDly 在OSTickCtr 等于0时被调用,则任务延时的结束时刻为OSTickCtr 等于100时,即总延时时刻为100;若OSTimeDly 在OSTickCtr 等于101时被调用,则任务延时的结束时刻同样为OSTickCtr 等于100时,即总延时时刻为4,294,967,296+100=4,294,967,396(系统时钟节拍为32位,即2^32=4,294,967,296,OSTickCtr 在等于4,294,967,296时,溢出回零),在延时期间,该任务进入阻塞态;
  • OS_OPT_TIME_PERIODIC :控制当前任务运行的总时间为dly;注意任务运行时间不能超过dly;
  1. p_err:指向接收错误代码变量的指针;

9.2 OSTimeDlyHMSM()

使用该函数前,先把宏OS_CFG_TIME_DLY_HMSM_EN 置1;

函数原型输入形参:

void  OSTimeDlyHMSM (CPU_INT16U   hours,CPU_INT16U   minutes,CPU_INT16U   seconds,CPU_INT32U   milli,OS_OPT       opt,OS_ERR      *p_err)
  1. hours:任务延时的小时数;
  2. minutes:任务延时的分钟数;
  3. seconds:任务延时的秒数;
  4. milli:任务延时的毫秒数;
  5. opt:延时选项;
    在这里插入图片描述

9.3 OSTimeDlyResume()

使用该函数前,先把宏OS_CFG_TIME_DLY_RESUME_EN 置1;

函数原型输入形参:

void  OSTimeDlyResume (OS_TCB  *p_tcb,OS_ERR  *p_err)

在这里插入图片描述

9.4 延时函数实验

  • 实验目的:完成以下3个任务:
  1. start_task:初始化CPU,配置Systick 中断及优先级,创建其他2个任务
  2. task1:展示延时函数OSTimeDly() 的使用;
  3. task2:展示延时函数OSTimeDlyHMSM() 的使用;
  • 实验过程:
  1. 拿一个实验例程,修改task1、task2 的任务函数:
/* task1 展示延时函数`OSTimeDly()` 的使用 */
void task1(void *p_arg)
{OS_ERR err;while(1){LED0_TOGGLE();OSTimeDly(500,OS_OPT_TIME_DLY,&err); // 延时500ms,相对延时}}/* task2 展示延时函数`OSTimeDlyHMSM()` 的使用 */
void task2(void *p_arg)
{OS_ERR err;while(1){LED1_TOGGLE();OSTimeDlyHMSM (0,0,0,500,OS_OPT_TIME_HMSM_NON_STRICT|OS_OPT_TIME_DLY,&err); // 延时500ms,相对延时}
}

代码运行结果:LED0、LED1每0.5秒翻转一次;

  1. 修改task1的延时选项为OS_OPT_TIME_MATCH:
/* task1 展示延时函数`OSTimeDly()` 的使用 */
void task1(void *p_arg)
{OS_ERR err;while(1){LED0_TOGGLE();OSTimeDly(500,OS_OPT_TIME_MATCH,&err); // 延时500ms,绝对延时}}/* task2 展示延时函数`OSTimeDlyHMSM()` 的使用 */
void task2(void *p_arg)
{OS_ERR err;while(1){LED1_TOGGLE();OSTimeDlyHMSM (0,0,0,500,OS_OPT_TIME_HMSM_NON_STRICT|OS_OPT_TIME_DLY,&err);}
}

代码运行结果:LED0 翻转的时间间隔非常长;

  1. 修改task1的延时选项为OS_OPT_TIME_PERIODIC:
/* task1 展示延时函数`OSTimeDly()` 的使用,任务总延时时间为1s */
void task1(void *p_arg)
{OS_ERR err;while(1){LED0_TOGGLE();delay_ms(200); // 模拟任务运行时间OSTimeDly(1000,OS_OPT_TIME_PERIODIC,&err); // 整个任务的总延时为1s}}/* task2 展示延时函数`OSTimeDlyHMSM()` 的使用,总延时时间为700ms */
void task2(void *p_arg)
{OS_ERR err;while(1){LED1_TOGGLE();delay_ms(200); // 模拟任务运行时间OSTimeDlyHMSM (0,0,0,500,OS_OPT_TIME_HMSM_NON_STRICT|OS_OPT_TIME_DLY,&err);}
}

代码运行结果:
在这里插入图片描述

代码备份位置:百度网盘 - 知识资源 - 电子工程 - 正点原子stm32f103精英开发板V2资料 - 资料备份 - UCOSIII 时间管理实验.rar

10. 消息队列


队列:任务到任务、中断到任务的一种数据交流机制;

队列与全局变量类似都用于数据传递,但在实时系统中使用全局变量进行数据交流,会导致数据不安全,当多个任务同时对同一变量操作时,数据易受损,故在操作系统中,数据交流一般使用队列的方式;

在读写队列函数中,会先进入临界区,从而屏蔽中断与任务打断,完成数据交流后,再退出临界区;

在中断中,只可写入消息队列,不可读出消息;
在这里插入图片描述
在这里插入图片描述

UCOS-III 队列特点

  1. 数据入队出队方式:一般采用FIFO(先进先出)的数据存储缓冲机制,即先入的数据先从队列中被读取;也可以通过配置,配置成LIFO(后进先出)方式;
  2. 数据传输方式:UCOS-III 的队列数据是一种“万能指针”,可以指向任何数据,甚至是函数;
  3. 多任务访问:任何任务、中断都可以向队列发送消息,但中断不能从队列中读消息;
  4. 出队阻塞:但任务向一个队列读取消息时,可指定一个阻塞时间,用于读取等待;注意,入队(写入消息队列)不会阻塞;

使用队列的流程:创建队列 --> 写入队列(发送消息到队列) --> 读出队列(从队列中督促消息)

队列相关API 函数介绍
在这里插入图片描述

更多关于队列相关API 函数介绍,详见《UCOS-III开发指南_V1.5》第十三章;

10.1 创建消息队列函数OSQCreate()

该函数用于创建一个消息队列;

函数原型输入形参:

void  OSQCreate (OS_Q        *p_q,CPU_CHAR    *p_name,OS_MSG_QTY   max_qty,OS_ERR      *p_err)
  1. p_q:指向消息队列结构体的指针,类似于一个句柄;
  2. p_name:指向作为消息队列名的ASCII 字符串的指针;
  3. max_qty:消息队列的大小,即该消息队列可包含多少个消息成员;
  4. p_err:指向接收错误代码变量的指针;

10.2 发送消息到消息队列函数(写入队列)OSQPost()

函数原型输入形参:

void  OSQPost (OS_Q         *p_q,void         *p_void,OS_MSG_SIZE   msg_size,OS_OPT        opt,OS_ERR       *p_err)
  1. p_q:指向消息队列结构体的指针,类似于一个句柄;
  2. p_void:指向消息的指针;
  3. msg_size:消息的大小,单位:字节;
  4. opt:函数操作选项,可组合使用;
opt描述
OS_OPT_POST_FIFO将发送的消息保存在队列的末尾
OS_OPT_POST_LIFO将发送的消息保存在队列的开头
OS_OPT_POST_ALL将消息发送给所有等待消息的任务
OS_OPT_POST_NO_SCHED禁止在本函数内执行任务调度
  1. p_err:指向接收错误代码变量的指针;

10.3 获取消息队列中的消息函数(读出队列)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)
  1. p_q:指向消息队列结构体的指针,类似于一个句柄;
  2. timeout:任务挂起等待消息队列的最大允许时间,为0时,表示一直等待,知道接收到消息;
  3. opt:函数操作选项,该参数配合timeout 使用;
opt描述
OS_OPT_PEND_BLOCKING若没有任何消息存在于消息队列,则阻塞任务
OS_OPT_PEND_NON_BLOCKING若没有任何消息存在于消息队列,则直接返回
  1. p_msg_size:指向一个变量,用于表示接收到的消息长度(字节数);
  2. p_ts:指向接收消息队列接收时时间戳的变量的指针,为NULL 则表示没有使用时间戳;
  3. p_err:指向接收错误代码变量的指针;

函数的返回值:从上述函数原型可知,该函数的返回值类型为void *,即“万能指针”(任意类型数据都能指向),故消息的读与写必须保持数据类型相同,否则会导致消息读写错乱;

10.4 消息队列操作实验

  • 实验目的:完成以下4个任务:
  1. start_task:创建2个队列,并创建另外3个任务;
  2. task1:当按键key0或key1按下,将键值的地址写入到队列key_queue中(入队);当按键key_up 按下,将传输大数据,将大数据的地址写入队列big_date_queue 中(入队);
  3. task2:读取队列key_queue 中的消息(出队),打印出接收到的键值;
  4. task3:从队列big_date_queue 读取大数据地址,打印出接收到的数据;
  • 实验过程:
  1. 定义消息队列句柄:
 OS_Q key_queue;OS_Q big_date_queue;
  1. 创建2个消息队列:
	OSQCreate (&key_queue,"key_queue",1,&err); // 创建消息队列OSQCreate (&big_date_queue,"big_date_queue",1,&err); // 创建消息队列
  1. 定义大数据数组:
char buf[] = {"我是一个大数据 abc 123456789"};
  1. 编写3个任务函数
/* task1 写入队列 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key=0;while(1){key = key_scan(0);if(key == KEY0_PRES || key == KEY1_PRES){printf("发送键值!\r\n");OSQPost (&key_queue,&key,sizeof(key),OS_OPT_POST_FIFO,&err); // 写入消息队列}else if(key == WKUP_PRES){printf("发送大数据!\r\n");OSQPost (&big_date_queue,&buf[0],sizeof(buf),OS_OPT_POST_FIFO,&err); // 写入消息队列}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}/* task2 读出队列key_queue */
void task2(void *p_arg)
{OS_ERR err;uint8_t *key; // 读出的消息队列要跟写入的消息队列的类型相同OS_MSG_SIZE size = 0;while(1){key = OSQPend (&key_queue,0,OS_OPT_PEND_BLOCKING,&size,NULL,&err);printf("接收到的键值为:%d\r\n",*key);printf("接收到的数据长度为:%d字节\r\n",size);}
}/* task3 读出队列big_date_queue */
void task3(void *p_arg)
{OS_ERR err;char *buf2; // 读出的消息队列要跟写入的消息队列的类型相同OS_MSG_SIZE size = 0;while(1){buf2 = OSQPend (&big_date_queue,0,OS_OPT_PEND_BLOCKING,&size,NULL,&err);printf("接收到的大数据为:%s\r\n",buf2);printf("接收到的数据长度为:%d字节\r\n",size);}
}

代码运行结果:
按下key0:
在这里插入图片描述
按下key1:
在这里插入图片描述
按下key up:
在这里插入图片描述

代码备份位置:百度网盘 - 知识资源 - 电子工程 - 正点原子stm32f103精英开发板V2资料 - 资料备份 - UCOSIII 消息队列操作实验.rar

11. 信号量


上节的消息队列用于传递数据,而本节的信号量用于传递状态;若该信号只有2种状态,则该信号又叫做二值信号量;若该信号有多种状态,则该信号又叫做计数型信号量

信号量是一种解决任务间同步问题的机制,可实现对共享资源的有序访问,即在多任务访问统一资源时的资源管理;

11.1 二值信号量

二值信号量只有空和非空两种状态;通常用于互斥访问或信号同步,尤其是后者;

二值信号量的使用过程:创建二值信号量 -> 释放二值信号量 -> 获取二值信号量

二值信号量相关API 函数:
在这里插入图片描述

更多关于二值信号量相关API 函数的介绍,详见《UCOSIII开发指南》第十一章

11.1.1 创建信号量函数OSSemCreate()

函数原型输入形参:

void OSSemCreate( OS_SEM *p_sem,CPU_CHAR *p_name,OS_SEM_CTR cnt,OS_ERR *p_err)
  1. p_sem:指向信号量结构体的指针;
  2. p_name:指向作为信号量名的 ASCII 字符串的指针;
  3. cnt:信号量资源数的初始值,010表示二值信号量初始无资源,反之同理;
  4. p_err:指向接收错误代码变量的指针;

11.1.2 释放信号量函数OSSemPost()

函数原型输入形参:

OS_SEM_CTR  OSSemPost (OS_SEM  *p_sem,OS_OPT   opt,OS_ERR  *p_err)
  1. p_sem:指向信号量结构体的指针;
  2. opt:函数操作选项;
    • OS_OPT_POST_1:只给最高优先级的那一个任务发信号;即该信号量释放后,优先分配给到最高优先级的那一个任务,其他同样等待该信号量的任务继续保持阻塞状态;
    • OS_OPT_POST_ALL:给所有等待该信号量的任务发信号量;即该信号量释放后,所有等待该信号量的任务都解除阻塞状态;
    • OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度;该宏可与上述两个宏组合使用
  3. p_err:指向接收错误代码变量的指针;

返回值:信号量资源数更新后的值;

11.1.3 获取信号量函数OSSemPend ()

函数原型输入形参:

OS_SEM_CTR  OSSemPend (OS_SEM   *p_sem,OS_TICK   timeout,OS_OPT    opt,CPU_TS   *p_ts,OS_ERR   *p_err)
  1. p_sem:指向信号量结构体的指针;
  2. timeout:任务挂起等待信号量的最大允许时间,当为0时,表示一直等待,直到接收到信号;
  3. opt:函数操作选项;
    • OS_OPT_PEND_BLOCKING:若信号量没有资源,则阻塞任务,阻塞时间却决于参数timeout
    • OS_OPT_PEND_NON_BLOCKING:若信号量没有资源,则任务直接返回;
  4. p_ts:指向接收信号量接收时的时间戳的变量的指针,没有则写0;
  5. p_err:指向接收错误代码变量的指针;

返回值:信号量资源数更新后的值;

11.1.4 二值信号量实验

  • 实验目的:完成以下2个任务:
  1. start_task:初始化CPU,配置Systick 中断及优先级,创建1个二值信号量,创建task1任务;
  2. task1:获取二值信号量,当获取成功后打印提示信息;
  • 实验过程:
  1. 定义二值信号量句柄:
 OS_SEM binnary_sem;
  1. 创建二值信号量:
	OSSemCreate (&binnary_sem,"binnary_sem",1,&err);
  1. 编写task1 函数:
/* task1 二值信号量测试 */
void task1(void *p_arg)
{OS_ERR err;OS_SEM_CTR ctr=0;while(1){/* 获取二值信号量 */ctr = OSSemPend (&binnary_sem,0,OS_OPT_PEND_BLOCKING,0,&err);printf("task1获取信号量成功!\r\n");printf("ctr 获取信号量成功后的资源数的计数值:%d\r\n",ctr);/* 释放二值信号量 */ctr = OSSemPost (&binnary_sem,OS_OPT_POST_1,&err);printf("task1释放信号量成功!\r\n");printf("ctr 释放信号量成功后的资源数的计数值:%d\r\n",ctr);}
}

11.2 计数型信号量

计数型信号量与二值信号量类似,但二值信号量的资源数只能是0和1,而计数型信号量只要有任务释放信号量,信号量的资源数就会被加1,直到溢出,只要有任务获取信号量,那么信号量的资源数就会被减 1,直到 0;

计数型信号量适用场合

  1. 事件计数:当事件发生,在事件处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0;
  2. 资源管理:信号量表示有效的资源数目;任务必须先获取信号量(计数值-1)才能获取资源控制权,当计数值减到为0时,表示没有资源;当任务使用完资源后,必须释放信号量(计数值+1);信号量创建时计数值应等于最大资源数;

计数型信号量与二值信号量的API 函数是公用的;

11.2.1 计数型信号量实验

  • 实验目的:完成以下3个任务:
  1. start_task:初始化CPU,配置Systick 中断及优先级,创建一个计数型信号量,创建task1、task2任务;
  2. task1:用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量;
  3. task2:每过1秒获取一次计数型信号量,当成功获取后打印信号量计数值;
  • 实验过程:
  1. 定义计数型信号量句柄:
 OS_SEM count_sem; // 计数型信号量句柄
  1. 编写taks1、task2函数:
/* task1 计数型信号量释放 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key = 0;while(1){key = key_scan(0);if(key == KEY0_PRES){printf("释放计数型信号量!\r\n");OSSemPost (&count_sem,OS_OPT_POST_1,&err);}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}/* task2 计数型信号量获取 */
void task2(void *p_arg)
{OS_ERR err;OS_SEM_CTR cnt = 0;while(1){cnt = OSSemPend (&count_sem,0,OS_OPT_PEND_BLOCKING,0,&err);printf("获取计数型信号量成功,cnt 的值为%d\r\n",cnt);OSTimeDly(1000,OS_OPT_TIME_DLY,&err);}
}

代码运行结果:
按下Key0:
在这里插入图片描述
连续按下时,信号量释放,信号量增加,不按后信号量依次获取,信号量减少:
在这里插入图片描述

11.3 优先级翻转问题

优先级翻转即让高优先级的任务慢执行,低优先级的任务反而优先执行;

  • 背景
    优先级翻转在抢占式内核中非常常见,但在实时操作系统中不允许使用,因为优先级翻转会破坏任务的预期顺序,可能导致未知后果;
    在使用二值信号量时,经常会遇到优先级翻转问题;

如:任务 H 为优先级最高的任务,任务 L 为优先级最低的任务,任务 M 为优先级介于任务 H 与任务 L 之间的任务;假设任务 H与任务L 都获取同一个二值信号量;

  1. 开始时任务 H 和任务 M 为挂起状态,等待某一事件发生,而任务 L 正常运行;
  2. 任务 L 要访问共享资源,因此需要获取信号量;
  3. 任务 L 成功获取信号量,并且此时二值信号量已无资源,任务 L 开始访问共享资源;
  4. 此时任务 H 就绪,抢占任务 L 运行;
  5. 任务 H 开始运行;
  6. 此时任务 H 要访问共享资源,因此需要获取二值信号量,但二值信号量已无资源,因此任务 H挂起等待信号量资源;
  7. 任务 L 继续运行;
  8. 此时任务 M 就绪,抢占任务 L 运行;
  9. 任务 M 正在运行;
  10. 任务 M 运行完毕,继续挂起;
  11. 任务 L 继续运行;
  12. 此时任务 L 对共享资源的访问操作完成,释放信号量,虽有任务 H 因成功获取二值信号量,解除挂起状态并抢占任务 L 运行。
  13. 任务 H 得以运行;

从上面优先级翻转的示例中,可知,任务 H 为最高优先级的任务,因此任务 H 执行的操作需要有较高的实时性,但是由于优先级翻转的问题,导致了任务 H 需要等到任务 L 释放信号量才能够运行,并且,任务 L 还会被其他介于任务 H 与任务 L 任务优先级之间的任务 M 抢占,因此任务 H 还需等待任务 M 运行完毕,这显然不符合任务 H 需要的高实时性要求;

11.3.1 优先级翻转实验

  • 实验目的:完成以下4个任务:
  1. start_task:创建一个二值信号量,并创建其他任务;
  2. high_task:高优先级任务,获取二值信号量,获取成功后打印提示信息,处理完后释放信号量;
  3. middle_task:中优先级任务,简单的引用任务;
  4. low_task:低优先级任务,与高优先级任务一样的操作,但占用信号量的时间更久一点;
  • 实验过程:
    11.1.4 二值信号量实验代码为基础:
  1. 修改任务1函数名为low_task
/* 低优先级任务 配置* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数*/#define 	TASK1_PRIO       4#define 	TASK1_STACK_SIZE 256CPU_STK* task1_stack; // 申请一个内存给task1 的任务堆栈OS_TCB 	task1_tcb;void low_task(void *p_arg);
  1. 创建middle_task 和high_task:
/* 中优先级任务 配置* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数*/#define 	TASK2_PRIO       3#define 	TASK2_STACK_SIZE 256CPU_STK* task2_stack; // 申请一个内存给task1 的任务堆栈OS_TCB 	task2_tcb;void middle_task(void *p_arg);/* 高优先级任务 配置* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数*/#define 	TASK3_PRIO       2#define 	TASK3_STACK_SIZE 256CPU_STK* task3_stack; // 申请一个内存给task1 的任务堆栈OS_TCB 	task3_tcb;void high_task(void *p_arg);
	/* 创建低优先级任务 */task1_stack = (CPU_STK *)mymalloc(SRAMIN,TASK1_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存OSTaskCreate ((OS_TCB*)        &task1_tcb,(CPU_CHAR*)      "low_task",(OS_TASK_PTR)    low_task,(void*)          0,(OS_PRIO)        TASK1_PRIO,(CPU_STK*)       task1_stack,// 以申请内存方式提供任务堆栈(CPU_STK_SIZE)   TASK1_STACK_SIZE/10,(CPU_STK_SIZE)   TASK1_STACK_SIZE,(OS_MSG_QTY)     0,(OS_TICK)        0, // 若设置为0,则使用前面所设置的时间片长度(void*)          0,(OS_OPT)         (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),(OS_ERR*)        &err);/* 创建中优先级任务 */task1_stack = (CPU_STK *)mymalloc(SRAMIN,TASK2_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存OSTaskCreate ((OS_TCB*)        &task2_tcb,(CPU_CHAR*)      "middle_task",(OS_TASK_PTR)    middle_task,(void*)          0,(OS_PRIO)        TASK2_PRIO,(CPU_STK*)       task2_stack,// 以申请内存方式提供任务堆栈(CPU_STK_SIZE)   TASK2_STACK_SIZE/10,(CPU_STK_SIZE)   TASK2_STACK_SIZE,(OS_MSG_QTY)     0,(OS_TICK)        0, // 若设置为0,则使用前面所设置的时间片长度(void*)          0,(OS_OPT)         (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),(OS_ERR*)        &err);/* 创建高优先级任务 */task1_stack = (CPU_STK *)mymalloc(SRAMIN,TASK3_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存OSTaskCreate ((OS_TCB*)        &task3_tcb,(CPU_CHAR*)      "high_task",(OS_TASK_PTR)    high_task,(void*)          0,(OS_PRIO)        TASK3_PRIO,(CPU_STK*)       task3_stack,// 以申请内存方式提供任务堆栈(CPU_STK_SIZE)   TASK3_STACK_SIZE/10,(CPU_STK_SIZE)   TASK3_STACK_SIZE,(OS_MSG_QTY)     0,(OS_TICK)        0, // 若设置为0,则使用前面所设置的时间片长度(void*)          0,(OS_OPT)         (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),(OS_ERR*)        &err);
  1. 编写任务函数:
/* 低优先级任务函数 */
void low_task(void *p_arg)
{OS_ERR err;while(1){printf("low_task 获取二值信号量!");OSSemPend (&binnary_sem,0,OS_OPT_PEND_BLOCKING,0,&err);printf("low_task 正在运行!");delay_ms(3000); // 模拟任务运行时间printf("low_task 释放二值信号量!");OSSemPost (&binnary_sem,OS_OPT_POST_1,&err);OSTimeDly(1000,OS_OPT_TIME_DLY,&err);}
}/* 中优先级任务函数 */
void middle_task(void *p_arg)
{OS_ERR err;while(1){printf("middle_task 正在运行!");OSTimeDly(1000,OS_OPT_TIME_DLY,&err);}
}/* 高优先级任务函数 */
void high_task(void *p_arg)
{OS_ERR err;while(1){printf("high_task 获取二值信号量!");OSSemPend (&binnary_sem,0,OS_OPT_PEND_BLOCKING,0,&err);printf("high_task 正在运行!");delay_ms(1000); // 模拟任务运行时间printf("high_task 释放二值信号量!");OSSemPost (&binnary_sem,OS_OPT_POST_1,&err);OSTimeDly(1000,OS_OPT_TIME_DLY,&err);}
}

代码运行结果:代码开始运行 - 高优先级任务先运行(获取&释放二值信号量) - 高优先级任务运行结束后中优先级任务运行 - 低优先级任务运行(获取&释放二值信号量) - 在低优先级任务释放二值信号量之前,中优先级任务抢占运行,高优先级任务抢占运行,但由于二值信号量未被释放,故高优先级任务阻塞,直到二值信号量被释放;
在这里插入图片描述

11.4 互斥信号量

互斥信号量又叫互斥锁,是一种特殊的二值信号量,互斥信号量拥有优先级继承的机制使得互斥信号量能够在一定的程度上解决优先级翻转的问题;

优先级继承:当一个互斥信号量正被一个低优先级的任务持有时,此时有一个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会因为获取不到互斥信号量而被挂起,不过接下来,高优先级的任务会将该低优先级任务的任务优先级提到与高优先级任务的任务优先级相同的任务优先级,那么高与低优先级任务之间的任务,就不能被执行,直至低优先级任务被执行完毕;此时高优先级任务的阻塞时间仅仅是低优先级任务的执行时间,将优先级翻转带来的影响降到最低;
在这里插入图片描述

注:互斥信号量不能用于中断服务函数中,原因如下:

  1. 互斥信号量有任务优先级继承的机制,但中断不是任务,没有任务优先级;
  2. 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态;

在同步的应用中适合使用二值信号量,而那些需要互斥访问的应用中使用互斥信号量;

互斥信号量的API 函数
在这里插入图片描述

11.4.1 创建互斥信号量OSMutexCreate()

创建互斥信号量时,默认信号量有效,即可被获取;

函数原型输入形参:

void  OSMutexCreate (OS_MUTEX  *p_mutex,CPU_CHAR  *p_name,OS_ERR    *p_err)
  1. p_mutex:指向互斥信号量结构体的指针;
  2. p_name:指向作为信号量名的 ASCII 字符串的指针;
  3. p_err:指向接收错误代码变量的指针;

11.4.2 获取互斥信号量OSMutexPend()

函数原型输入形参:

void  OSMutexPend (OS_MUTEX  *p_mutex,OS_TICK    timeout,OS_OPT     opt,CPU_TS    *p_ts,OS_ERR    *p_err)
  1. p_mutex:指向互斥信号量结构体的指针;
  2. timeout:任务挂起等待信号量的最大允许时间,当为0时,表示一直等待,直到接收到信号;
  3. opt:函数操作选项;
    • OS_OPT_PEND_BLOCKING:若信号量没有资源,则阻塞任务,阻塞时间却决于参数timeout
    • OS_OPT_PEND_NON_BLOCKING:若信号量没有资源,则任务直接返回;
  4. p_ts:指向接收信号量接收时的时间戳的变量的指针,没有则写0;
  5. p_err:指向接收错误代码变量的指针;

11.4.3 释放互斥信号量OSMutexPost()

函数原型输入形参:

void  OSMutexPost (OS_MUTEX  *p_mutex,OS_OPT     opt,OS_ERR    *p_err)
  1. p_mutex:指向互斥信号量结构体的指针;
  2. timeout:任务挂起等待信号量的最大允许时间,当为0时,表示一直等待,直到接收到信号;
  3. opt:函数操作选项;
    • OS_OPT_POST_NONE:不指定特定的选项;
    • OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度;
  4. p_err:指向接收错误代码变量的指针;

11.4.4 互斥信号量实验

  • 实验过程:在优先级翻转实验的基础上,将二值信号量更改为互斥信号量:
 OS_MUTEX mutex_sem; // 互斥信号量句柄/* 创建互斥信号量 */OSMutexCreate(&mutex_sem,"mu_sem",&err);OSMutexPend (&mutex_sem,0,OS_OPT_PEND_BLOCKING,0,&err);
OSMutexPost(&mutex_sem,OS_OPT_POST_NONE,&err);

代码运行结果:
在这里插入图片描述

12. 任务内嵌信号量/队列


12.1 任务内嵌信号量

任务内嵌信号量(下称任务信号量)本质上就是一个信号量,与前面11. 信号量中的信号量是类似的,但任务信号量是分配于每一个任务的任务控制块结构体中的,因此每一个任务都有独自的任务信号量,任务信号量只能被该任务获取,但可以由其他任务或者中断释放;
在这里插入图片描述

任务信号量的优点是使用内存更小,效率更高;缺点是无法共享给多个任务;

任务信号量的API 函数
在这里插入图片描述

12.1.1 获取任务信号量函数OSTaskSemPend()

获取任务信号量函数只能被当前任务调用;

函数原型输入形参:

OS_SEM_CTR  OSTaskSemPend (OS_TICK   timeout,OS_OPT    opt,CPU_TS   *p_ts,OS_ERR   *p_err)
  1. timeout:任务挂起等待信号量的最大允许时间,当为0时,表示一直等待,直到接收到信号;
  2. opt:函数操作选项;
    • OS_OPT_PEND_BLOCKING:若信号量没有资源,则阻塞任务,阻塞时间却决于参数timeout
    • OS_OPT_PEND_NON_BLOCKING:若信号量没有资源,则任务直接返回;
  3. p_ts:指向接收信号量接收时的时间戳的变量的指针,没有则写0;
  4. p_err:指向接收错误代码变量的指针;

返回值:任务信号量资源数更新后的值;

12.1.2 释放信号量函数OSTaskSemPost()

函数原型输入形参:

OS_SEM_CTR  OSTaskSemPost (OS_TCB  *p_tcb,OS_OPT   opt,OS_ERR  *p_err)
  1. p_tcb:指向任务控制块的指针;
  2. opt:函数操作选项;
    • OS_OPT_POST_NONE:不指定特定的选项;
    • OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度;
  3. p_err:指向接收错误代码变量的指针;

返回值:任务信号量资源数更新后的值;

12.1.3 强制设置指定的任务信号量为指定值函数OSTaskSemSet()

函数原型输入形参:

OS_SEM_CTR  OSTaskSemSet (OS_TCB      *p_tcb,OS_SEM_CTR   cnt,OS_ERR      *p_err)
  1. p_tcb:指向任务控制块的指针;
  2. cnt:指定的信号量资源数;
  3. p_err:指向接收错误代码变量的指针;

返回值:任务信号量设置前的资源数;

12.1.4 任务内嵌信号量实验

  • 实验目的:完成以下3个任务:
  1. start_task:创建task1、task2任务;
  2. task1:按键扫描,当检测到按键KEY0 被按下,释放task2任务内嵌信号量;
  3. task2:获取任务信号量,并打印相关提示信息;
  • 实验过程:以11.2.1 计数型信号量实验为基础:
  1. 删除计数型信号量相关代码;
  2. 编写task1、task2任务函数:
/* task1 往task2释放任务内嵌信号 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key = 0;while(1){key = key_scan(0);if(key == KEY0_PRES){printf("释放任务信号量!\r\n");OSTaskSemPost(&task2_tcb,OS_OPT_POST_NONE,&err);}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}/* task2  获取任务信号量,并打印相关提示信息 */
void task2(void *p_arg)
{OS_ERR err;OS_SEM_CTR cnt = 0;while(1){cnt = OSTaskSemPend (0,OS_OPT_PEND_BLOCKING,0,&err);printf("获取任务信号量成功,cnt 的值为%d\r\n",cnt);OSTimeDly(1000,OS_OPT_TIME_DLY,&err);}
}

代码运行结果:

  1. 按下key0,释放task2任务内嵌信号量,task2获取任务信号量:
    在这里插入图片描述

  2. 连续按下key0,连续释放task2任务内嵌信号量,task2依次获取任务信号量:
    在这里插入图片描述

  3. 在上述基础上,强制设置task2任务信号量为指定值:

OSTaskSemSet (&task2_tcb,1,&err); // 强制设置task2的任务信号量为指定值1

代码运行结果:上电后,不需要按下key0释放task2任务信号量,因为其资源数指定为1;
在这里插入图片描述

12.2 任务内嵌消息队列

任务内嵌消息队列(下称任务消息队列)本质上就是一个消息队列,与10. 消息队列 中介绍的消息队列是类似的,任务内嵌消息队列是分配于每一个任务的任务控制块结构体中的,因此每一个任务都有独自的任务内嵌消息队列,任务内嵌消息队列只能被该任务获取(读出),但是可以由其他任务或中断释放(写入),如下图所示:
在这里插入图片描述

任务消息队列的API 函数
在这里插入图片描述

12.2.1 获取任务消息队列函数(读出队列)OSTaskQPend()

函数原型输入形参:

void  *OSTaskQPend (OS_TICK       timeout,OS_OPT        opt,OS_MSG_SIZE  *p_msg_size,CPU_TS       *p_ts,OS_ERR       *p_err)
  1. timeout:任务挂起等待消息队列的最大允许时间,为0时,表示一直等待,知道接收到消息;
  2. opt:函数操作选项,该参数配合timeout 使用;
opt描述
OS_OPT_PEND_BLOCKING若没有任何消息存在于消息队列,则阻塞任务
OS_OPT_PEND_NON_BLOCKING若没有任何消息存在于消息队列,则直接返回
  1. p_msg_size:指向一个变量,用于表示接收到的消息长度(字节数);
  2. p_ts:指向接收消息队列接收时时间戳的变量的指针,为NULL 则表示没有使用时间戳;
  3. p_err:指向接收错误代码变量的指针;

函数的返回值:从上述函数原型可知,该函数的返回值类型为void *,即“万能指针”(任意类型数据都能指向),故消息的读与写必须保持数据类型相同,否则会导致消息读写错乱;

12.2.2 发送任务消息队列函数(写入队列)OSTaskQPost()

函数原型输入形参:

void  OSTaskQPost (OS_TCB       *p_tcb,void         *p_void,OS_MSG_SIZE   msg_size,OS_OPT        opt,OS_ERR       *p_err)
  1. p_tcb:指向任务控制块的指针,类似于一个句柄;
  2. p_void:指向消息的指针;
  3. msg_size:消息的大小,单位:字节;
  4. opt:函数操作选项,可组合使用;
opt描述
OS_OPT_POST_FIFO将发送的消息保存在队列的末尾
OS_OPT_POST_LIFO将发送的消息保存在队列的开头
OS_OPT_POST_NO_SCHED禁止在本函数内执行任务调度
  1. p_err:指向接收错误代码变量的指针;

12.2.3 任务消息队列实验

  • 实验目的:完成以下3个任务:
  1. start_task:创建task1、task2任务;
  2. task1:按键扫描,将键值发送到task2任务消息队列;
  3. task2:获取任务消息队列,并打印相关提示信息;
  • 实验过程:以10.4 消息队列操作实验 为基础:
  1. 删除task3与消息队列相关代码;
  2. 编写task1、task2任务函数:
/* task1 写入任务消息队列 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key=0;while(1){key = key_scan(0);if(key == KEY0_PRES || key == KEY1_PRES|| key == WKUP_PRES){printf("发送键值!\r\n");OSTaskQPost (&task2_tcb,&key,sizeof(key),OS_OPT_POST_FIFO,&err);}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}/* task2 读出任务消息队列key_queue */
void task2(void *p_arg)
{OS_ERR err;uint8_t *key; // 读出的消息队列要跟写入的消息队列的类型相同OS_MSG_SIZE size = 0;while(1){OSTaskQPend (0,OS_OPT_PEND_BLOCKING,&size,NULL,&err);printf("接收到的数据长度为:%d字节\r\n",size);}
}

代码运行结果:
按下key0,task1发送键值1,task2接收到键值1;按下key1,task1发送键值2,task2接收到键值2;

在这里插入图片描述

13. 事件标志


事件标志与信号量一样属于任务间同步的一种机制,但是信号量一般用于任务间的单事件同步;

事件标志是一个用于指示事件是否发生的比特位,µC/OS-III 用 1表示事件发生,用 0 表示事件未发生;

事件标志组即是多个事件标志的集合;

事件标志组的特点

  1. 其每个位表示一个事件,最多可表示32个事件标志;
  2. 每一位事件的含义,由用户自己决定;
  3. 任意任务或中断都可以写这些位,但读这些位只能由任务读;
  4. 可以等待某一位成立,或等待多位同时成立;(或操作、与操作)
  5. 支持读取阻塞;

事件标志组的逻辑关系图
在这里插入图片描述
事件标志组的API 函数:更多详见《UCOS-III 开发指南》第十六章
在这里插入图片描述

使用事件标志组的流程:创建事件标志组 --> 设置事件标志 --> 获取事件标志;

13.1 创建事件标志组函数OSFlagCreate()

函数原型输入形参:

void  OSFlagCreate (OS_FLAG_GRP  *p_grp,CPU_CHAR     *p_name,OS_FLAGS      flags,OS_ERR       *p_err)
  1. p_grp:指向事件标注组结构体的指针;
  2. p_name:指向作为事件标志组名的ASCII 字符串的指针;
  3. flags:事件标志组的初始值,一般设置为0,表示初始无任务发生;
  4. p_err:指向接收错误代码变量的指针;

13.2 设置事件标注组的事件函数OSFlagPost()

函数原型输入形参:

OS_FLAGS  OSFlagPost (OS_FLAG_GRP  *p_grp,OS_FLAGS      flags,OS_OPT        opt,OS_ERR       *p_err)
  1. p_grp:指向事件标注组结构体的指针;
  2. flags:等待的事件标志;
  3. optOS_OPT_POST_FLAG_SET置1,OS_OPT_POST_FLAG_CLR清0;
  4. p_err:指向接收错误代码变量的指针;

函数的返回值:事件标志组更新后的事件标志值;

13.3 等待事件标注组中的事件函数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)
  1. p_grp:指向事件标注组结构体的指针;
  2. flags:等待的事件标志;
  3. timeout:任务挂起等待事件标志组的最大允许时间当为0时,表示一直等待,直到接收到信号;
  4. opt:函数操作选项;
opt描述
OS_OPT_PEND_FLAG_CLR_ALL等待“flags” 中的所有指定位被清0
OS_OPT_PEND_FLAG_CLR_ANY等待“flags” 中的任意指定位被清0
OS_OPT_PEND_FLAG_SET_ALL等待“flags” 中的所有指定位被置1
OS_OPT_PEND_FLAG_SET_ANY等待“flags” 中的任意指定位被置1
调用上述四个选项时,还可以搭配下面三个选项
OS_OPT_PEND_FLAG_CONSUME当等待到指定位后,清0对应位
OS_OPT_PEND_BLOCKING标志组不满足条件时挂起任务
OS_OPT_PEND_NON_BLOCKING标志组不满足条件时不挂起任务
  1. p_ts:指向接收等待到事件时的时间戳的变量的指针,一般设置为NULL
  2. p_err:指向接收错误代码变量的指针;

函数的返回值:任务实际等待到的事件标志,如果没有则返回0;

13.4 事件标注组实验

  • 实验目的:完成以下3个任务:
  1. start_task:创建task1、task2任务,创建事件标志组;
  2. task1:按键扫描,根据不同键值将事件标志组相应事件位置1,模拟事件发生;
  3. task2:同时等待事件标志组中的多个事件位,当这些事件位都置1,就打印提示信息;
  • 实验过程:以10.4 消息队列操作实验 为基础:
  1. 删除task3与消息队列相关代码;
  2. 定义定义事件标志组指针与事件标志组bit0、bit1
 OS_FLAG_GRP flag; // 定义事件标志组指针#define FLAG_BIT0 (1<<0) // 定义事件标志组bit0,0x01#define FLAG_BIT1 (1<<1) // 定义事件标志组bit1,0x02
  1. 创建一个事件标志组:
OSFlagCreate (&flag,"flag",0,&err);/* 创建一个事件标志组 */
  1. 编写task1、task2任务函数:
/* task1 设置事件标志 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key=0;while(1){key = key_scan(0);if(key == KEY0_PRES){printf("KEY0按下,bit0置1!\r\n");OSFlagPost (&flag,FLAG_BIT0,OS_OPT_POST_FLAG_SET,&err); // bit0置1}else if(key == KEY1_PRES){printf("KEY1按下,bit1置1!\r\n");OSFlagPost (&flag,FLAG_BIT1,OS_OPT_POST_FLAG_SET,&err);// bit1置1}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}/* task2 等待事件标志 */
void task2(void *p_arg)
{OS_ERR err;while(1){OSFlagPend (&flag,FLAG_BIT0|FLAG_BIT1,0,OS_OPT_PEND_FLAG_SET_ALL|OS_OPT_PEND_FLAG_CONSUME|OS_OPT_PEND_BLOCKING,NULL,&err);printf("等待到指定事件成立!\r\n");}
}

代码运行结果:

  1. 按下key0和key1:
    在这里插入图片描述
  2. 若把task2 任务函数中的OS_OPT_PEND_FLAG_SET_ALL|OS_OPT_PEND_FLAG_CONSUME|OS_OPT_PEND_BLOCKING,改为S_OPT_PEND_FLAG_SET_ALL|OS_OPT_PEND_BLOCKING,,即去掉“当等待到指定位后,清0对应位”功能,则在按下key0和key1后,由于对应位一直为1,故一直不阻塞task2:
    在这里插入图片描述
  3. 若把task2 任务函数中的OS_OPT_PEND_FLAG_SET_ALL|OS_OPT_PEND_FLAG_CONSUME|OS_OPT_PEND_BLOCKING,改为OS_OPT_PEND_FLAG_SET_ANY|OS_OPT_PEND_FLAG_CONSUME|OS_OPT_PEND_BLOCKING,,即将“等待“flags” 中的所有指定位被置1”更改为“等待“flags” 中的任意指定位被置1”,则在按下key0或key1任意一个后,即可触发“等待到指定事件”:
    在这里插入图片描述

14. 软件定时器


定时器:从指定时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期;

硬件定时器:芯片本身自带的定时器模块,硬件定时器的精度一般很高,每次在定时时间到达后就会自动触发一个定时器中断,用户在中断服务函数中处理信息;

软件定时器:指具有定时功能的软件,可设置定时周期,当指定时间到达后调用回调函数(又叫超时函数),用户在回调函数中处理信息;

软件定时器的优缺点

  1. 硬件定时器数量有限,而软件定时器理论上只需有足够多的内存,就可创建多个;
  2. 使用简单,成本低;
  3. 软件定时器相对硬件定时器,精度不高,对于需要高精度场合不适用;

单次定时器:一旦定时超时,只会执行一次其软件定时器回调函数,并且不会自动重新开启定时,只能手动重新开始;
周期定时器:一旦启动软件定时器后,就会在执行完回调函数后自动重新开始定时,从而周期地执行其软件定时器的回调函数;


软件定时器的状态

  1. 未使用态:软件定时器被定义但未被创建或软件定时器被删除时;
  2. 停止态:软件定时器被创建但未开启定时器或被停止时;新创建的软件定时器默认处于停止状态
  3. 运行态:运行态的定时器,当指定时间到达后,其回调函数会被调用;
  4. 完成态:当单次定时器定时超时后,软件定时器处于完成态;

软件定时器的特点

  1. 支持裁剪:如需使能软件定时器,需将OS_CFG_TMR_EN 配置项置1;

注:软件定时器的超时回调函数由 软件定时器服务任务 调用,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API 函数(如系统延时、等待信号量、读取队列等);

  1. 软件定时器的频率由宏OS_CFG_TMR_TASK_RATE_HZ (默认为10)决定,要注意软件定时器的定时器频率并不等于系统时钟节拍的频率,但软件定时器是依靠系统节拍实现定时的,故需要进行换算;

软件定时器时间分辨率 : OSTmrToTicksMult = OSCfg_TickRate_Hz(系统时钟节拍,默认为1000Hz,即1ms) / OSCfg_TmrTaskRate_Hz(宏,默认为10Hz);,即软件定时器时间分辨率默认为100ms;
*
如:在本教程的实验例程中将宏 OSCfg_TmrTaskRate_Hz 配置为 10,将宏OSCfg_TickRate_Hz配置为 1000,即软件定时器时间分辨率默认为100ms,那么当开启了一个定时器时长为 10 的软件定时器后,软件定时器将会在大约 1000 毫秒后超时(因为频率为 10Hz,所以周期为 100ms,那么总的定时器周期就为 10*100ms=1000ms);
*
详见《UCOS-III开发指南》17.1.6 软件定时器定时频率


软件定时器相关的API 函数:
在这里插入图片描述

更多详见《UCOS-III开发指南》17.2 µC/OS-III 软件定时器相关 API 函数;

14.1 创建软件定时器函数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)
  1. p_tmr:指向软件定时器结构体指针;
  2. p_name:指向软件定时器名的字符串指针;
  3. dly:软件定时器的开启延时时间(超时时间),单次定时器该值必须大于0,周期定时器用到该参数;
  4. period:周期定时器的定时周期时间,周期定时器该值必须大于0,单次定时器不用该参数;
  5. optOS_OPT_TMR_ONE_SHOT 使用单次定时器,OS_OPT_TMR_PERIODIC 使用周期定时器;
  6. p_callback:指向超时回调函数的指针;
  7. p_callback_arg:超时回调函数的入口参数;
  8. p_err:指向接收错误代码变量的指针;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2 删除软件定时器函数OSTmrCreate()

函数原型输入形参:

CPU_BOOLEAN  OSTmrDel (OS_TMR  *p_tmr,OS_ERR  *p_err)
  1. p_tmr:指向软件定时器结构体指针;
  2. p_err:指向接收错误代码变量的指针;

返回值:

  1. OS_TRUE:软件定时器删除成功;
  2. OS_FALSE:软件定时器删除失败;

14.3 开启软件定时器函数OSTmrStart()

函数原型输入形参:

CPU_BOOLEAN  OSTmrStart (OS_TMR  *p_tmr,OS_ERR  *p_err)
  1. p_tmr:指向软件定时器结构体指针;
  2. p_err:指向接收错误代码变量的指针;

返回值:

  1. OS_TRUE:软件定时器开启成功;
  2. OS_FALSE:软件定时器开启失败;

14.4 关闭软件定时器函数OSTmrStop()

函数原型输入形参:

CPU_BOOLEAN  OSTmrStop (OS_TMR  *p_tmr,OS_OPT   opt,void    *p_callback_arg,OS_ERR  *p_err)
  1. p_tmr:指向软件定时器结构体指针;
  2. opt
p_callback_arg描述
OS_OPT_TMR_CALLBACK关闭软件定时器前,执行一次回调函数,带入原始参数(创建软件定时器时的p_callback_arg)
OS_OPT_TMR_CALLBACK_ARG关闭软件定时器前,执行一次回调函数,带入指定参数,即下面的p_callback_arg
OS_OPT_TMR_NONE直接停止软件定时器
  1. p_callback_arg:传给软件定时器超时回调函数的参数(即opt 中的指定参数);
  2. p_err:指向接收错误代码变量的指针;

返回值:

  1. OS_TRUE:软件定时器开启成功;
  2. OS_FALSE:软件定时器开启失败;

14.4 软件定时器实验

  • 实验目的:完成以下2个任务:
  1. start_task:创建task1任务,创建软件定时器;
  2. task1:按键扫描,当KEY0按下开启软件定时器,当KEY1按下停止软件定时器;
  • 实验过程:以10.4 消息队列操作实验 为基础:
  1. 删除task2、消息队列相关代码;
  2. 确认宏OS_CFG_TMR_EN 被置1,宏OS_CFG_TICK_RATE_HZ为1000,宏OS_CFG_TMR_TASK_RATE_HZ为10;
  3. 定义软件定时器句柄:
 OS_TMR timer1; /* 单次软件定时器 */OS_TMR timer2; /* 周期软件定时器 */
  1. 创建单次定时器与周期定时器:
	/* 创建单次定时器 dly=10,即100ms */OSTmrCreate (&timer1,"timer1",10,0,OS_OPT_TMR_ONE_SHOT,(OS_TMR_CALLBACK_PTR)timer_cb,0,&err);/* 创建周期定时器 period=10,即100ms */OSTmrCreate (&timer2,"timer2",0,10,OS_OPT_TMR_PERIODIC,(OS_TMR_CALLBACK_PTR)timer_cb,0,&err);
  1. 声明与定义回调函数:
void  timer_cb (OS_TMR *p_tmr, void *p_arg) // p_tmr:定时器判断
{static uint32_t timer1_num = 0;static uint32_t timer2_num = 0;if(p_tmr == &timer1) // 若单次定时器超时{LED0_TOGGLE();printf("单次定时器运行次数:%d\r\n",timer1_num++);}else if(p_tmr == &timer2) // 若周期定时器超时{LED1_TOGGLE();printf("周期定时器运行次数:%d\r\n",timer2_num++);}
}
  1. 编写task1任务函数:
/* task1 当KEY0按下开启软件定时器,当KEY1按下停止软件定时器 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key=0;while(1){key = key_scan(0);if(key == KEY0_PRES){printf("开启软件定时器timer1和timer2!!\r\n");OSTmrStart (&timer1,&err);OSTmrStart (&timer2,&err);}else if(key == KEY1_PRES){printf("关闭软件定时器timer1和timer2!!\r\n");OSTmrStop (&timer1,OS_OPT_TMR_NONE,0,&err);OSTmrStop (&timer2,OS_OPT_TMR_NONE,0,&err);}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}

代码运行结果:

  1. 按下key0:单次定时器运行1次,周期定时器则周期运行;
    在这里插入图片描述
  2. 按下key1 则两个软件定时器停止;

15. 内存管理


内存管理是指软件运行时对内存资源的分配和使用(申请与释放)的一种技术,其最主要的目的就是为了能够高效且快速地分配,并且在释放时释放不再使用的内存空间;


UCOS-III 不直接使用C 库自带的内存管理代码的原因,主要是因为C 库自带的内存管理代码存在下列缺点:

  1. 占用大量代码空间,不适合用在资源紧缺的嵌入式系统中;
  2. 没有线程安全相关
  3. 运行有不确定性,每次调用这些函数时花费的时间可能不同;
  4. 内存碎片化:由于多次申请与释放内存导致的;

如:初始有100k 内存容量,分配了5次10k,剩下50k,释放3次10k,现想要申请20k 的内存,由于内存碎片化,已无20k 内存可分配;
在这里插入图片描述

UCOS-III 提供了一个内存管理方案,将一块大内存作为一个内存区,一个内存区中有多个大小均相同的内存块组成,其存在以下特点:
在这里插入图片描述

  1. 由于每个内存块大小相同,故分配时间一定;
  2. 由于每个内存块大小相同,故不存在内存碎片化问题;
  3. 根据实际需求,用户可创建多个不同的内存区,每个内存区中的内存块的数量和大小都可以不同;
    在这里插入图片描述
  4. 需要用户提供内存区,并保证该内存区不被释放;
static uint8_t buffer[10][32]; // 一个具有10个内存块,每个内存块的字节数为32的字节区

内存管理相关结构体:用于记录内存块的特征信息;

struct os_mem {                                           ...void                *AddrPtr;      /* 指向内存区起始地址指针 */              void                *FreeListPtr;  /* 指向空闲的内存块链表指针,由于一开始所有内存块都是空闲,故一开始都指向内存区的起始地址 */                   OS_MEM_SIZE          BlkSize;      /* 单个内存块的大小,即上面`static uint8_t buffer[10][32];`中的32 */                  OS_MEM_QTY           NbrMax;       /* 内存区中内存块总量即上面`static uint8_t buffer[10][32];`中的10 */         OS_MEM_QTY           NbrFree;      /* 内存区中空闲内存块数量 */                ...
};

内存管理相关的API 函数:更多详见《UCOS-III开发指南》19.2 µC/OS-III 内存管理相关 API 函数
在这里插入图片描述

15.1 创建一个内存区函数OSMemCreate()

函数原型输入形参:

void  OSMemCreate (OS_MEM       *p_mem,CPU_CHAR     *p_name,void         *p_addr,OS_MEM_QTY    n_blks,OS_MEM_SIZE   blk_size,OS_ERR       *p_err)
  1. p_mem:指向内存区结构体的指针;
  2. p_name:指向作为内存区名的ASCII 字符串的指针;
  3. p_addr:指向内存区起始地址的指针;
  4. n_blks:内存区中内存块的数量;
  5. blk_size:内存区中内存块的大小;
  6. p_err:指向接收错误代码变量的指针;

15.2 获取一个内存块函数OSMemGet()

从内存区中获取一个内存块;

函数原型输入形参:

void  *OSMemGet (OS_MEM  *p_mem,OS_ERR  *p_err)
  1. p_mem:指向内存区结构体的指针;
  2. p_err:指向接收错误代码变量的指针;

返回值:指向内存块的起始地址,若返回NULL,则表示申请失败;

15.3 释放内存块到内存区中的函数OSMemPut()

从内存区中获取一个内存块;

函数原型输入形参:

void  OSMemPut (OS_MEM  *p_mem,void    *p_blk,OS_ERR  *p_err)
  1. p_mem:指向内存区结构体的指针;
  2. p_blk:待释放的内存区(起始地址);
  3. p_err:指向接收错误代码变量的指针;

返回值:指向内存块的起始地址,若返回NULL,则表示申请失败;

15.4 内存管理实验

  • 实验目的:完成以下2个任务:
  1. start_task:创建task1任务,创建一个内存区;
  2. task1:按键扫描,当KEY0按下则申请内存块,当KEY1按下则释放内存块;
  • 实验过程:以10.4 消息队列操作实验 为基础:
  1. 删除task2、task3、消息队列相关代码;
  2. 创建内存区结构体指针:
OS_MEM mem; // 内存区结构体指针
static uint8_t buffer[10][100]; // 一个具有10个内存块,每个内存块的字节数为100的字节区
  1. 创建内存区:
	/* 创建一个内存区 */OSMemCreate (&mem,"mem",&buffer[0][0],10,100,&err);
  1. 编写task1任务函数:
/* task1 当KEY0按下则申请内存块,当KEY1按下则释放内存块 */
void task1(void *p_arg)
{OS_ERR err;uint8_t key=0,task1_num = 0;uint8_t * buf=0;while(1){key = key_scan(0);if(key == KEY0_PRES){buf = OSMemGet (&mem,&err); /* 申请内存块 */if(buf!=NULL){printf("内存块申请成功!并内存块的起始地址为:0x%p\r\n",buf);}else{printf("内存块申请失败!\r\n");}	}else if(key == KEY1_PRES){if(buf!=NULL) // 若申请成功{OSMemPut (&mem,buf,&err); /* 释放内存块 */printf("释放内存块!\r\n");buf=NULL;}}if(++task1_num % 100 == 0) // 1s 打印一次{printf("空闲内存块的数量为:%d\r\n",mem.NbrFree);}OSTimeDly(10,OS_OPT_TIME_DLY,&err);}
}

代码运行结果:

  1. 上电后,显示"空闲内存块的数量为:10",这是因为定义的内存区一共有10 内存块:static uint8_t buffer[10][100];
    在这里插入图片描述
  2. 按下一次KEY0,申请1次内存块:
    在这里插入图片描述
  3. 按下一次KEY1,释放1次内存块:
    在这里插入图片描述
  4. 连续按下10次KEY0,申请10个内存块,再按下KEY1,释放1次内存块,释放成功,再按下KEY1,释放1次内存块,释放失败;因为task1 任务函数中变量buf只会保存上一次申请成功的内存块首地址,释放也只能释放上一次申请成功的内存块;这么做是为了防止内存泄露
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/28430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12 款 Android 照片恢复应用程序列表

丢失难忘的照片总是令人痛苦的。如果软件崩溃或意外删除&#xff0c;Android 设备上的照片也可能会丢失。这时照片恢复应用程序就派上用场了。查看我们为 Android 收集的顶级照片恢复应用程序。 但是&#xff0c;您不会想为自己选择任何照片恢复应用程序。因此&#xff0c;我们…

Doris:冷热分层

目录 一、冷热分层介绍 二、存储策略&#xff08;Storage policy&#xff09; 2.1 创建存储资源 2.2 创建存储策略 2.3 使用存储策略 三、使用限制 一、冷热分层介绍 冷热分层支持所有 Doris 功能&#xff0c;只是把部分数据放到对象存储上&#xff0c;以节省成本&am…

openGauss 6.0.0 一主二备集群安装及使用zcbus实现Oracle到openGauss的数据同步

一、前言 openGauss 6.0.0-RC1是openGauss 2024年3月发布的创新版本&#xff0c;该版本生命周期为0.5年。根据openGauss官网介绍&#xff0c;6.0.0-RC1与之前的版本特性功能保持兼容,另外&#xff0c;在和之前版本兼容的基础上增加了很多新功能&#xff0c;比如分区表性能优化…

go的netpoll学习

go的运行时调度框架简介 Go的运行时&#xff08;runtime&#xff09;中&#xff0c;由调度器管理&#xff1a;goroutine&#xff08;G&#xff09;、操作系统线程&#xff08;M&#xff09;和逻辑处理器&#xff08;P&#xff09;之间的关系 以实现高效的并发执行 当一个gorout…

统计完全子字符串

很不错的计数问题&#xff0c;用到了分组循环技巧和滑动窗口 代码的实现方式也非常值得多看 class Solution { public:int f(string s,int k){int res 0;for(int m1;m<26&&k*m<s.size();m){int cnt[27]{};auto check[&](){for(int i0;i<26;i){if(cnt[i]…

跟着刘二大人学pytorch(第---10---节课之卷积神经网络)

文章目录 0 前言0.1 课程链接&#xff1a;0.2 课件下载地址&#xff1a; 回忆卷积卷积过程&#xff08;以输入为单通道、1个卷积核为例&#xff09;卷积过程&#xff08;以输入为3通道、1个卷积核为例&#xff09;卷积过程&#xff08;以输入为N通道、1个卷积核为例&#xff09…

计算机组成原理之定点除法

文章目录 定点除法运算原码恢复余数法原码不恢复余数法&#xff08;加减交替法&#xff09;运算规则 习题 定点除法运算 注意 &#xff08;1&#xff09;被除数小于除数的时候&#xff0c;商0 &#xff08;2&#xff09;接下来&#xff0c;有一个除数再原来的基础上&#xff0c…

springboot + Vue前后端项目(第十六记)

项目实战第十六记 写在前面1 第一个bug1.1 完整的Role.vue 2 第二个bug2.1 修改路由router下面的index.js 总结写在最后 写在前面 发现bug&#xff0c;修复bug 1 第一个bug 分配菜单时未加入父id&#xff0c;导致分配菜单失效 <!-- :check-strictly"true" 默…

图的应用之最小生成树

大纲 生成树介绍 特点 但n个 种类 最小生成树 应用 构造算法 MST性质 Prim算法 依次选择与顶点相邻的不会构成回路的最小边对应的顶点 Kruskal算法 依次选不会构成环的最小边 区别 Prim算法有n个顶点进行选择&#xff0c;每个顶点有n个选择&#xff0c;复杂度为O(n*n) K…

C51学习归纳13 --- AD/DA转换

AD/DA转换实现了计算机和模拟信号的连接&#xff0c;扩展了计算机的应用场景&#xff0c;为模拟信号数字化提供了底层支持。 AD转换通常是多个输入通道&#xff0c;使用多路选择器连接到AD开关&#xff0c;实现AD多路复用的目的&#xff0c;提高利用率。 AD/DA转换可以使用串口…

我的创作纪念日(1825天)

Ⅰ、机缘 1. 记得是大一、大二的时候就听学校的大牛说&#xff0c;可以通过写 CSDN 博客&#xff0c;来提升自己的代码和逻辑能力&#xff0c;虽然即将到了写作的第六个年头&#xff0c;但感觉这句话依旧受用; 2、今年一整年的创作都没有停止&#xff0c;本年度几乎是每周都来…

UniApp或微信小程序中scroll-view组件使用show-scrollbar在真机Android或IOS中隐藏不了滚动条的解决办法

show-scrollbar 属性 不论是使用 变量 还是直接使用 布尔值或者直接使用 css 都是在 ios、Android 上是都没有效果。。 真机中还是出现滚动条 解决办法 添加下面CSS ::-webkit-scrollbar {display: none;width: 0 !important;height: 0 !important;-webkit-appearance: no…

盛世古董乱世金-数据库稳定到底好不好?

是不是觉得这个还用问&#xff1f; 是的要问。因为这个还是一个有争议的问题。但是争议双方都没有错。这就像辩论&#xff0c;有正反双方。大家都说的有道理&#xff0c;但是很难说谁对谁错。 正方观点&#xff1a;数据库稳定好 其实这个是用户的观点&#xff0c;应用开发人…

17个关键方法指南,保护您的web站点安全!

了解如何让您的web应用程序或网站安全&#xff0c;对于网站所有者来说至关重要。以下是一些关键步骤&#xff0c;可以帮助您保护网站免受攻击和数据泄露。 1.使用公钥加密技术 当数据以明文形式传输时&#xff0c;它容易受到中间人 &#xff08;MitM&#xff09; 攻击。这意味…

北航第六次数据结构与程序设计作业(查找与排序)选填题

一、 顺序查找的平均查找长度ASL&#xff08;1 2 …… n&#xff09;/ n (n 1&#xff09;/ 2 二、 这半查找法的平均查找次数和判定树的深度有关系。若查找一个不存在的元素&#xff0c;说明进行了深度次比较。 注意&#xff0c;判定树不是满二叉树&#xff0c;因此深…

安卓网络通信(多线程、HTTP访问、图片加载、即时通信)

本章介绍App开发常用的以下网络通信技术&#xff0c;主要包括&#xff1a;如何以官方推荐的方式使用多线程技术&#xff0c;如何通过okhttp实现常见的HTTP接口访问操作&#xff0c;如何使用Dlide框架加载网络图片&#xff0c;如何分别运用SocketIO和WebSocket实现及时通信功能等…

HTTP协议 快速入门

http概述 无状态性&#xff1a;HTTP是一个无状态协议&#xff0c;这意味着服务器不会在请求之间保存任何会话信息。每个请求都是独立的&#xff0c;服务器不会记住之前的请求。 请求-响应模型&#xff1a;HTTP通信是基于客户端发送请求和服务器返回响应的模型。客户端&#xf…

Spark常见的可以优化的点

Shuffle 复用 # 1.以下操作会复用的shuffle结果&#xff0c;只会读一遍数据源 val rdd1 sc.textFile("hdfs://zjyprc-hadoop/tmp/hive-site.xml").flatMap(_.split(" ")).map(x > (x,1)).reduceByKey(_ _).filter(_._2 > 1) rdd1.count() rdd1.fil…

华为od-C卷200分题目2 - 找城市

华为od-C卷200分题目2 - 找城市 题目描述 一个城市规划问题&#xff0c;一个地图有很多城市&#xff0c;两个城市之间只有一种路径&#xff0c;切断通往一 个城市i的所有路径之后&#xff0c;其他的城市形成了独立的城市群&#xff0c;这些城市群里最大的城 市数量&#xff0…

会声会影色彩校正在哪里 会声会影色彩素材栏在哪 会声会影中文免费版下载

会声会影是一款功能强大的视频编辑软件&#xff0c;它可以帮助用户轻松地编辑和制作视频。在进行视频编辑时&#xff0c;色彩校正是一个重要的步骤&#xff0c;它可以调整视频的色调、亮度和对比度等参数&#xff0c;使视频更加生动和鲜明。在会声会影中&#xff0c;色彩校正功…