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

news2024/10/6 22:25:01

文章目录

  • 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. 事件标志组

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 256
 CPU_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 256
 CPU_STK* task2_stack; // 申请一个内存给task1 的任务堆栈
 OS_TCB 	task2_tcb;
 void middle_task(void *p_arg);
 
/* 高优先级任务 配置
 * 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
 #define 	TASK3_PRIO       2
 #define 	TASK3_STACK_SIZE 256
 CPU_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. 事件标志组


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

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

相关文章

产品经理简要介绍主流电商平台商品API接口的重要应用价值

主流电商平台: 截至目前(2024年6月),主流电商平台通常指的是国内外知名的在线购物平台,包括但不限于以下几家: 1. 中国电商平台: - 淘宝网(taobao.com) - 天猫商…

阿里云香港服务器怎么样?

大家都知道阿里云是国内最受欢迎的云服务商,那么阿里云香港服务器究竟怎么样呢?和硅云的香港服务器用于做外贸网站等业务相比各有哪些优缺点呢? 阿里云和硅云在香港云服务领域有着广泛的应用和良好的口碑。然而,它们各自的特点和…

霍廷格TruPlasma RF 1001 to 1003 (G2/13)手侧通快电源

霍廷格TruPlasma RF 1001 to 1003 (G2/13)手侧通快电源

深入 Go 语言核心:结构体的全方位解析

Go 语言,作为一种高效、静态类型的编程语言,自其问世以来便以其并发处理能力和简洁的语法结构广受开发者欢迎。虽然 Go 不是传统意义上的面向对象语言,它却以独特的方式支持面向对象编程的核心概念,其中结构体扮演了非常关键的角色…

解决Servlet报404未找到

一、检查project struture 打开Project Struture 检查jdk 检查Modules的depencies 如果缺少servlet的jar依赖,则可以先配置tomcat,然后执行如下图的添加依赖(这种方法不适用于maven构建的项目) 把Tomcat里的jar添加进来就可…

Sqlite3入门和c/c++下使用

1. SQLite3基本介绍 1.1 数据库的数据类型 1.2 基本语法 1. 创建数据表格 create table 表名(字段名 数据类型, 字段名 数据类型); create table student(id int, name varchar(256), address text, QQ char(32)); 2. 插入数据 insert into 表名 valu…

音频处理1_基本概念

AI变声和音乐创作的基础 声音本质上是人类可察觉范围内的气压周期性波动, 即声波 声波是一种连续信号,在任意时间内的声音信号有无数个取值。对于只能读取有限长数组计算机来说,我们需要将连续的声音信号转换为一个离散的序列,即数字化表示。…

【一竞技DOTA2】Malr1ne有可能是第二个奇迹哥

1、近日Falcons战队一号位选手skiter在直播中讲到战队组建过程,并称赞了队内中单Malr1ne很强,甚至可能是第二个Miracle-。 “去年我在Tundra的时候曾经与Nemiga的Malr1ne合作过,他替补Nine来打比赛。尽管我们在比赛中没拿到好成绩&#xff0c…

IPv6 ND 协议功能概述

ND 协议功能概述 ND(Neighbor Discovery,邻居发现)协议是 IPv6 的一个关键协议,它综合了 IPv4 中的 ARP,ICMP 路由发现和 ICMP 重定向等协议,并对它们做了改进。 作为 IPv6 的基础性协议,ND 协…

MNIST手写字符分类-卷积

MNIST手写字符分类-卷积 文章目录 MNIST手写字符分类-卷积1 模型构造2 训练3 推理4 导出5 onnx测试6 opencv部署7 总结 在上一篇中,我们介绍了如何在pytorch中使用线性层ReLU非线性层堆叠的网络进行手写字符识别的网络构建、训练、模型保存、导出和推理测试。本篇文…

数字人的技术实现原理

数字人是一种利用计算机图形学、人工智能等技术创建的虚拟人物。数字人可以模拟真人进行各种动作和表情,并与用户进行交互。数字人的技术实现原理主要包括以下几个方面。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1. …

如何将 API 管理从 Postman 转移到 Apifox

上一篇推文讲到用 Swagger 管理的 API 怎么迁移到 Apifox,有许多同学反馈说能不能介绍一下 Postman 的迁移以及迁移过程中需要注意的事项。那么今天,它来了! 从 Postman 迁移到 Apifox 的方法有两种: 导出 Postman 集合 &#x…

诺派克ROPEX控制器维修RES-5008 RES-5006

德国希尔科诺派克ROPEX热封控制器维修型号包括:RES-401,RES-402,RES-403,RES-406,RES-407,RES-408,RES-409,RES-420,RES-440,RES-5008,RES-5006&a…

eNSP学习——PPP的认证

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建OSPF网络 3、配置PPP的PAP认证 4、配置PPP的CHAP认证 主要命令 //设置本端的PPP协议对对端设备的认证方式为 PAP,认证采用的域名为huawei [R3]int s4/0/0 [R…

ToF原理记录

目录 1. ToF是什么?2. ToF深度测量原理2.1 脉冲调制法2.2 连续波调制法 1. ToF是什么? 飞行时间(Time-of-Flight,ToF)基本原理是通过连续发射光脉冲(一般为不可见光)到目标物体上,然…

BGP学习

BGP是一种矢量协议,使用TCP作为传输协议 ,目的端口号是179.是触发式更新,不是周期性更新 BGP的重点是策略路由的选路,能对路由进行路由汇总。运行BGP的路由器被称为BGP发言者,两个建立BGP会话的路由器互为对等体 IBGP和EBGP的区…

EasyExcel文件导出出现有文件但没有数据的问题

一开始由于JDK版本过高,我用的17,一直excel没有数据,表头也没有,后来摸索了好久,找了资料也没有,后来改了代码后报了一个错误(com.alibaba.excel.exception.ExcelGenerateException: java.lang.…

Redis高性能原理:Redis为什么这么快?

目录 前言: 一、Redis知识系统观 二、Redis为什么这么快? 三、Redis 唯快不破的原理总结 四、Redis6.x的多线程 前言: Redis 为了高性能,从各方各面都进行了优化。学习一门技术,通常只接触了零散的技术点&#xff…

pioneer电源维修PM33213BP-10P-1-6PH-H

开关电源出现不启振的时候,我们通常需要查看开关频率是否正确、保护电路是否断路、电压反馈电路、电流反馈电路又没问题,开关管是否击穿等。 电源维修实践中,有许多开关电源采用UC38系列8脚PWM组件,大多数电源不能工作都是因为电…