FreeRTOS操作系统学习
文章目录
一、消息邮箱API函数
任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消息会受到很多限制!
1、只能发送 32 位的数据值。
2、消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度为 1。因此说任务通知可以模拟一个轻量级的消息邮箱而不是轻量级的消息队列。任务通知值就是消息邮箱的值。
发送数据可以使用函数 xTaskNotify()或者 xTaskNotifyFromISR(),函数的参数 eAction 设置eSetValueWithOverwrite 或 者 eSetValueWithoutOverwrite 。 如 果 参 数 eAction 为eSetValueWithOverwrite 的话不管接收任务的通知值是否已经被处理,这个通知值都会被更新。参数 eAction 为 eSetValueWithoutOverwrite 的话如果上一个任务通知值话还没有被处理,那么新的任务通知值就不会更新。如果要读取任务通知值的话就使用函数 xTaskNotifyWait()。
二、消息邮箱实验
发送任务通知
while(1)
{
key=KEY_Scan(0); //扫描按键
if((Keyprocess_Handler!=NULL)&&(key))
{
err=xTaskNotify((TaskHandle_t )Keyprocess_Handler, //任务句柄 (1) 要发送到任务的任务句柄
(uint32_t )key, //任务通知值 发送的任务通知值
(eNotifyAction )eSetValueWithOverwrite); //覆写的方式发送任务通知
if(err==pdFAIL)
{
printf("任务通知发送失败\r\n");
}
}
接收任务通知
这里要注意接收的是32位的值
while(1)
{
err=xTaskNotifyWait((uint32_t )0x00, //进入函数的时候不清除任务 bit (2)
(uint32_t )ULONG_MAX,//退出函数的时候清除所有的 bit
(uint32_t* )&NotifyValue, //保存接收到的任务通知值,这里要取地址
(TickType_t )portMAX_DELAY); //阻塞时间(死等)
if(err==pdTRUE) //获取任务通知成功
{
switch((u8)NotifyValue) //强制转换成u8类型
{
case WKUP_PRES: //KEY_UP 控制 LED1
LED1=!LED1;
break;
case KEY2_PRES: //KEY2 控制蜂鸣器
beepsta=!beepsta;
BEEP=!BEEP;
break;
case KEY0_PRES: //KEY0 刷新 LCD 背景
num++;
LCD_Fill(6,126,233,313,lcd_discolor[num%14]);
break;
}
}
}
}
(1)、调用函数 xTaskNotify()发送任务通知,任务通知值为按键值,发送方式采用覆写的方式。
(2)、调用函数 xTaskNotifyWait()获取任务通知,获取到的任务通知其实就是(1)中发送的按键值,然后根据不同的按键值做不同的处理。
三、事件标志组实验
事件标志组其实就是一组二进制事件标志(位),每个事件标志位的具体意义由应用程序编写者来决定。当一个任务等待事件标志组中的某几个标志(位)的时候可以进入阻塞态,当任务因为等待事件标志(位)而进入阻塞态以后这个任务就不会消耗 CPU。
当任务通知用作事件标志组的话任务通知值就相当于事件组,这个时候任务通知值的每个bit 用作事件标志 ( 位 ) 。函数 xTaskNotifyWait() 替代事件标志组中的 API 函 数xEventGroupWaitBits()。函数 xTaskNotify()和 xTaskNotifyFromISR()(函数的参数 eAction 为eSetBits)替代事件标志组中的 API 函数 xEventGroupSetBits()和 xEventGroupSetBitsFromISR()。
发送任务通知
void eventsetbit_task(void *pvParameters)
{
u8 key,i;
while(1)
{
if(EventGroupTask_Handler!=NULL)
{
key=KEY_Scan(0);
switch(key)
{
case KEY1_PRES:
xTaskNotify((TaskHandle_t )EventGroupTask_Handler, (1)按下 KEY1 键以后调用函数 xTaskNotify()发生任务通知,因为是模拟事件标志组的,所以这里设置的是将任务通知值的 bit1 置 1。
(uint32_t )EVENTBIT_1,
(eNotifyAction )eSetBits);
break;
case KEY2_PRES:
xTaskNotify((TaskHandle_t )EventGroupTask_Handler, (2)同上
(uint32_t )EVENTBIT_2,
(eNotifyAction )eSetBits);
break;
} }
i++;
if(i==50)
{
i=0;
LED0=!LED0;
}
vTaskDelay(10); //延时 10ms,也就是 10 个时钟节拍
} }
接收任务通知
void eventgroup_task(void *pvParameters)
{
u8 num=0,enevtvalue;
static u8 event0flag,event1flag,event2flag;
uint32_t NotifyValue;
BaseType_t err;
while(1)
{
//获取任务通知值
err=xTaskNotifyWait((uint32_t )0x00, //进入函数的时候不清除任务 bit (3)、调用函数 xTaskNotifyWait()获取任务通知值,相当于获取事件标志组值。
(uint32_t )ULONG_MAX, //退出函数的时候清除所有的 bit
(uint32_t* )&NotifyValue, //保存任务通知值
(TickType_t )portMAX_DELAY);//阻塞时间
if(err==pdPASS) //任务通知获取成功
{
if((NotifyValue&EVENTBIT_0)!=0) //事件 0 发生 (4)
{
event0flag=1;
}
else if((NotifyValue&EVENTBIT_1)!=0) //事件 1 发生
{
event1flag=1;
}
else if((NotifyValue&EVENTBIT_2)!=0) //事件 2 发生
{
event2flag=1;
}
enevtvalue=event0flag|(event1flag<<1)|(event2flag<<2); //模拟事件标志组值(5)通过这三个事件发生标志来模拟事件标志组值。
printf("任务通知值为:%d\r\n",enevtvalue);
LCD_ShowxNum(174,110,enevtvalue,1,16,0); //在 LCD 上显示当前事件值
if((event0flag==1)&&(event1flag==1)&&(event2flag==1))//三个事件都同时发生(6) 所等待的三个事件都发生了,执行相应的处理。
{
num++;
LED1=!LED1;
LCD_Fill(6,131,233,313,lcd_discolor[num%14]);
event0flag=0; //标志清零 (7)处理完成以后将事件发生标志清零。
event1flag=0;
event2flag=0;
} }
else
{
printf("任务通知获取失败\r\n");
}
}
}
(4)、根据 NotifyValue 来判断是哪个事件发生了,如果是事件 0 发生的话(也就是任务通知值的 bit0 置一)就给 event0flag 赋 1,表示事件 0 发生了。因为任务通知不像事件标志组那样可以同时等待多个事件位。只要有任何一个事件位置 1,函数 xTaskNotifyWait()就会因为获取任务通知成功而在退出函数的时候将这个事件位清零(函数 xTaskNotifyWait()的参数设置的)。所以这里我们要对每个发生的事件做个标志,比如本试验中用到了三个事件标志位:EVENTBIT_0、EVENTBIT_1、EVENTBIT_2。这三个事件标志位代表三个事件,每个事件都有一个变量来记录此事件是否已经发生,这三个变量在任务函数 eventgroup_task()中有定义,它们分别为:event0flag、event1flag 和 event2flag。当这三个变量都为 1 的时候就说明三个事件都发生了,这不就模拟出来了事件标志组的同时等待多个事件位吗?
中断处理过程:
//事件标志组句柄
extern TaskHandle_t EventGroupTask_Handler;
//中断服务函数
void EXTI4_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
delay_xms(50); //消抖
if(KEY0==0)
{
xTaskNotifyFromISR((TaskHandle_t )EventGroupTask_Handler, //任务句柄 (1)、中断服务函数中调用 xTaskNotifyFromISR()来发送任务通知。
(uint32_t )EVENTBIT_0, //要更新的 bit
(eNotifyAction )eSetBits, //更新指定的 bit
(BaseType_t* )xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除 LINE4 上的中断标志位
}
总结
任务通知比较简单,合理使用任务通知可以节省大量CPU,提高工作效率。