1)实验平台:Alienek NANO STM32F411 V1开发板
2)摘自《正点原子STM32F4 开发指南(HAL 库版》:更多信息请关注我们的官方微信:正点原子
第37 章UCOSII 实验2 - 信号量和邮箱
在上一章中,您学习了如何使用UCOSII及其任务调度,但没有使用任务。
任务之间的同步和通信。在本章中,您将学习任务间通信的两种最基本的方法:信号量和邮箱。本章分为:
接下来的几部分:
37.1 UCOSII信号量和邮箱概述
37.2 硬件设计
37.3 软件设计
37.4 下载验证
37.1 UCOSII信号量和邮箱概述
当多个任务在系统中运行时,各个任务往往需要访问相同的共享资源而不发生冲突,或者相互支持和依赖,在某些情况下,各个任务能够顺利运行,甚至可能会相互施加必要的限制和限制。以确保正常运行。因此,操作系统必须具有协调任务执行的能力,使它们能够同步而不发生冲突,顺利运行,而不会造成灾难性的后果。
例如,如果任务A和任务B共享一台打印机,则只有当任务A释放打印机时,任务B才会处于等待状态,因为它无法获得使用打印机的权限。系统可以启动任务B来获取打印机的使用权。如果这两个任务不这样做,就会出现大混乱。
任务之间的同步依赖于任务之间的通信。在UCOSII中,任务之间的通信是通过信号量、邮箱(消息邮箱)、消息队列等称为事件的中间链路来实现的。本章仅介绍信号量和邮箱。消息队列将在下一章介绍。
事件
两个任务通过事件进行通信的示意图如图37.1.1所示。
图37.1.1 两个任务使用事件进行通信的示意图
在图37.1.1 中,任务1 是发送者,任务2 是接收者。任务1 负责向事件发送信息。此操作称为发送事件。任务2 通过事件读取操作查询事件。如果有信息,则读取;如果没有信息,则等待。读取事件的行为称为请求事件。为了统一描述事件的数据结构,UCOSII使用了一种称为事件控制块(ECB)的数据结构来描述信号量、邮箱(消息邮箱)、消息队列等事件。事件控制块包含所有与事件相关的数据,包括待处理任务的列表。事件控制块的结构定义如下。
类型定义结构
{
INT8UOS事件类型;
//事件类型
INT16UOSEventCnt;
//信号量计数器
无效*OSEventPtr;
//指向消息或消息队列的指针
INT8UOSEventGrp;
//任务组等待事件
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
#if OS_EVENT_NAME_EN 0u
INT8U
*操作系统事件名称;
//事件名称
万一
操作系统_事件;
信号
信号量是一种事件。使用信号量的主要目的是标记共享资源。
表示共享资源的使用情况。这样,当任务访问共享资源时,它可以首先更改此标志。
通过运行查询,您可以了解资源占用情况并决定您的操作。
信号量可以分为两种类型。一种是二值信号量,另一种是N值信号量。
二进制信号量就像家里的固定电话:在任何给定时间只有一个人可以使用它。 N 值信号量就像公共信号量
共有1个电话亭可供多人(N)同时使用。
在UCOSII中,二进制信号量称为互斥信号量,N值信号量称为计数信号量。
换句话说,它是一个常规信号量。本章介绍常规信号量和互斥信号量。请参阅嵌入。
“UCOSII实时操作系统原理与应用”第5.4节。
接下来我们看一下UCOSII中一些与信号量相关的函数(未全部列出,如下)。
1)创建信号量函数
在使用信号量之前,您必须使用函数OSSemCreate 创建该函数的原型。
为了:
OS_EVENT *OSSemCreate (INT16U cnt);
该函数的返回值是指向创建的信号量的指针,参数cnt是信号量计数器(OSEventCnt)。
初始值。
2)请求信号量函数
任务调用函数OSSemPend 来请求信号量。函数原型为:
void OSSemPend (OS_EVENT *pevent, INT16U 超时, INT8U *err);
参数pevent是指向请求信号量的指针,timeout是等待时限,err是错误信息。
为了防止任务长时间等待而无法获取信号量,函数OSSemPend 接受参数。
timeout设置等待时间,当任务等待时间超过timeout时退出等待状态。
进入就绪状态。如果参数timeout设置为0,则表示任务可以无限期等待。
3)传输信号量函数
任务必须获取信号量,并在访问共享资源后释放信号量。也称为“信号量释放”。
要发送信号量,需要通过OSSemPost 函数发送信号。 OSSemPost 函数对信号量进行计数
在对信号量进行操作之前,请检查是否有任何任务正在等待该信号量。如果没有,设置信号量计数器
OSEventCnt 加一。如果存在,则调用调度程序OS_Sched() 来运行等待任务中优先级最高的任务。
任务。 OSSemPost函数原型如下:
INT8U OSSemPost(OS_EVENT *pevent);
其中,pevent是信号量指针,如果函数调用成功,返回值为OS_ON_ERR。
根据具体错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4)信号量函数的删除
当您的应用程序不再需要信号量时,您可以调用函数OSSemDel将其删除。
该函数的原型是:
OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err);
其中,pevent为要删除的信号量指针,opt为删除条件选项,err为错误信息。
邮政
多任务操作系统经常需要在任务之间传输数据(这种数据称为“消息”)。
为了达到这个目的,我们可以在内存中创建一个存储空间作为数据(以“信息”的形式进行通信)。
缓冲。该缓冲区称为消息缓冲区,是在任务之间传输数据(消息)的最简单方法。
传递的是一个指向消息缓冲区的指针。用于传输消息缓冲区指针的数据结构称为邮箱(消息邮箱)。
在UCOSII中,通过事件控制块的OSEventPrt传递消息缓冲区指针,同时创建事件控制块。
如果控制块成员OSEventType 是常量OS_EVENT_TYPE_MBOX,则事件控制块称为消息邮箱。
接下来我们看一下UCOSII中与消息邮箱相关的一些功能。
1)创建邮箱功能
邮箱的创建是通过OSMboxCreate函数实现的。该函数的原型是:
OS_EVENT *OSMboxCreate (void *msg);
函数参数msg是指向消息的指针,函数返回值是指向消息邮箱的指针。
要调用OSMboxCreate函数,必须首先为msg定义一个初始值。通常,该初始值为:
NULL; 但是,您也可以预先定义一个邮箱,并将指向该邮箱的指针作为参数传递给函数。
OSMboxCreate从一开始就指向邮箱。
2)发送消息到邮箱的功能
任务可以通过调用OSMboxPost函数向消息邮箱发送消息。该函数的原型是:
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
其中pevent是消息邮箱指针,msg是消息指针。
3)请求邮箱功能
如果任务请求邮箱,它必须调用OSMboxPend 函数。该功能主要作用是显示邮箱。
框指针OSEventPtr 是否为NULL。如果不为NULL,则将邮箱中的消息指针返回给调用函数。
当运行任务时邮箱指针出错时,使用OS_NO_ERR通过函数参数通知任务消息已成功检索。
如果OSEventPtr为NULL,则任务进入等待状态并触发任务的调度。
OSMboxPend函数的原型是:
void *OSMboxPend (OS_EVENT *pevent, INT16U 超时, INT8U *err);
其中pevent是请求邮箱指针,timeout是等待时限,err是错误信息。
4)邮箱状态查询功能
任务可以通过调用函数OSMboxQuery来查询邮箱的当前状态。函数原型为:
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);
其中,pevent是消息邮箱指针,pdata是存储邮箱信息的结构体。
5) 邮箱删除功能
当某个邮箱不再使用时,可以通过调用函数OSMboxDel将其删除。
数值原型为:
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
其中pevent是消息邮箱指针,opt是删除选项,err是错误消息。
UCOSII 信号量和邮箱的介绍就到此结束。有关更详细的介绍,请参阅嵌入式实时操作。
UCOSII 系统原理与应用》第5 章。
37.2 硬件设计
本节实验功能介绍:本章我们在UCOSII中创建6个任务:启动任务、LED0任务和LED1任务。
数码管显示任务、按键扫描任务和主任务分别用于创建信号量、创建邮箱和初始化。
统计任务和其他任务的创建,以及挂起的LED0任务,用于DS0控制来通知程序执行状态。
LED1 任务用于测试信号量。这将导致DS1 编号在每次获取信号量时闪烁。
码管显示任务用于测试数码管显示,按键扫描任务用于按键扫描,具有最高优先级检索按键值。
通过邮箱发送消息。主要任务是查询消息邮箱获取键值,并根据键值进行信号量发送。
(DS1控制),控制数码管显示变化。
使用的硬件资源如下。
1)指示灯DS0、DS1
2)两个按键(KEY0/KEY1)
3)数码管
我们已经在之前的研究中介绍过这些。
37.3 软件设计
本章我们将修改第17章的实验(实验12)。具体方法与上一章完全相同,本章不再详细介绍。
添加UCOSII代码后,只需修改main.c函数,输入以下代码:
/////////////////////////UCOSII任务设置////////////////////////////////////////////////////
//启动任务
//设置任务优先级
#定义START_TASK_PRIO
10 //设置启动任务优先级为最低
//设置任务栈大小
#定义START_STK_SIZE
64
//任务栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
无效start_task(无效* pdata);
//LED0任务
//设置任务优先级
#定义LED0_TASK_PRIO
7
//设置任务栈大小
#定义LED0_STK_SIZE
64
//任务栈
OS_STK LED0_TASK_STK[LED0_STK_SIZE];
//任务函数
无效led0_task(无效*pdata);
//乳腺导管显示任务
//设置任务优先级
定义#SMG_TASK_PRIO
6
//设置任务栈大小
定义#SMG_STK_SIZE
64
//任务栈
OS_STK SMG_TASK_STK[SMG_STK_SIZE];
//任务函数
无效smg_task(无效* pdata);
//LED1任务
//设置任务优先级
#定义LED1_TASK_PRIO
五
//设置任务栈大小
#定义LED1_STK_SIZE
128
//任务栈
OS_STK LED1_TASK_STK[LED1_STK_SIZE];
//任务函数
无效led1_task(无效*pdata);
//主线任务
//设置任务优先级
#定义MAIN_TASK_PRIO
四
//设置任务栈大小
#定义MAIN_STK_SIZE
128
//任务栈
OS_STK MAIN_TASK_STK[MAIN_STK_SIZE];
//任务函数
无效main_task(无效* pdata);
//按键扫描任务
//设置任务优先级
定义#KEY_TASK_PRIO
3
//设置任务栈大小
#定义KEY_STK_SIZE
64
//任务栈
OS_STK KEY_TASK_STK[KEY_STK_SIZE];
//任务函数
无效key_task(无效* pdata);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
OS_EVENT * msg_key;
//按钮邮箱事件块指针
OS_EVENT * sem_led1;
//LED1信号量指针
//公共负数数组
//0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,全部销毁
u8
smg_num[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6,0xee,0x3e,0x9c,0x7a,0x9e,0x8e,0
x01,0x00};
u8 smg_duan=0;//选择乳腺导管段
int 主函数(无效)
{
HAL_Init();
//初始化HAL库
Stm32_Clock_Init(96,4,2,4);
//设置时钟,96Mhz
延迟初始化(96);
//初始化延迟函数
LED_Init();
//初始化连接LED的硬件接口
KEY_Init();
//初始化按钮
LED_SMG_Init();
//初始化数码管
OSInit();
OSTaskCreate(start_task,(无效
*)0,(OS_STK
*)START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建启动任务
操作系统启动();
}
//启动任务
无效启动任务(无效*pdata)
{
OS_CPU_SR cpu_sr=0;
pdata=pdata;
msg_key=OSMboxCreate((void*)0); //创建消息邮箱
sem_led1=OSSemCreate(0);
//创建信号量
OSStatInit();
//统计任务初始化有大约1秒的延迟。
OS_ENTER_CRITICAL();
//进入临界区(不能被中断打断)
OSTaskCreate(led0_task,(void *)0,
(OS_STK*)LED0_TASK_STK[LED0_STK_SIZE-1], LED0_TASK_PRIO);
OSTaskCreate(smg_task,(void *)0,
(OS_STK*)SMG_TASK_STK[SMG_STK_SIZE-1],SMG_TASK_PRIO);
OSTaskCreate(led1_task,(void *)0,
(OS_STK*)LED1_TASK_STK[LED1_STK_SIZE-1], LED1_TASK_PRIO);
OSTaskCreate(main_task,(void *)0,
(OS_STK*)MAIN_TASK_STK[MAIN_STK_SIZE-1],MAIN_TASK_PRIO);
OSTaskCreate(key_task,(void *)0,
(OS_STK*)KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO); //暂停已启动的任务。
OS_EXIT_CRITICAL();
//退出临界区(可能会被中断中断)
}
//LED0任务
无效led0_task(无效*pdata)
{
u8t;
同时(1)
{
t++;
延迟毫秒(10);
if(t==8)LED0=1; //LED0 熄灭。
如果(t==100)
//LED0点亮
{
t=0;
LED0=0;
}
}
}
//LED1任务
无效LED1_task(无效*pdata)
{
u8错误;
同时(1)
{
OSSemPend(sem_led1,0,错误);
LED1=0;
延迟毫秒(200);
LED1=1;
延迟毫秒(800);
}
}
//乳腺导管显示任务
无效smg_task(无效* pdata)
{
同时(1)
{
LED_Write_Data(smg_num[smg_duan],7);//乳腺管显示
LED_Refresh();//更新显示
延迟毫秒(10);
}
}
//主线任务
无效主任务(无效*pdata)
{
u32 键=0;
u8错误;
同时(1)
{
key=(u32)OSMboxPend(msg_key,10,err);
开关(钥匙)
{
case KEY0_PRES://发送信号量
OSSemPost(sem_led1);
休息;
case KEY1_PRES://数码管显示加1
smg_段++;
如果(smg_duan==17)smg_duan=0;
休息;
}
延迟毫秒(10);
}
}
//按键扫描任务
无效key_task(无效* pdata)
{
u8键;
同时(1)
{
键=KEY_Scan(0);
if(key)OSMboxPost(msg_key,(void*)key);//发送消息
延迟毫秒(10);
}
}
在这部分代码中,我们创建了六个任务:start_task、led0_task、smg_task、led1_task 和main_task。
key_task 和key_task 的优先级分别为10 和7-3,堆栈大小除main_task 为128 外均为64。
运行这个程序的过程比上一章稍微复杂一些。我为按键任务创建了一个消息邮箱msg_key。
还为LED1 任务和导向器创建了一个信号量sem_led1,用于主任务之间的数据传输(传递键值)。
服务之间的通信。
这里我们介绍一下软件设计部分。
37.4 下载验证
代码编译成功后,可以通过将代码下载到NANO STM32F4 V1来查看数码管显示。
如图37.4.1所示:
图37.4.1 初始界面
数码管显示0,同时按下KEY1按键,DS0闪烁,按下KEY0按键更新显示。
DS0 键将闪烁一次。如果多次按DS0,该键将闪烁多次。
版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。