1)信号量
假设有两个任务 Task1 和 Task2,第一个任务进行按键的扫描,第二个任务进行LED灯的点亮
需求:
扫描到按键按下后点亮 LED 灯,也就是说第二个任务永远在等待第一个任务按键的扫描
实现:
首先 Task1 一直检测按键是否按下,如果按键按下以后,使用一个全局变量 flag 并设置 flag=1
而在 Task2 当中,不停检测 flag 值是否为 1,如果为 1,点亮 led 灯并把flag清零
此时 flag 提供的是一个信号量的作用,也就是说 Task1 按下按键以后,开始向 Task2 发送一个信号量 flag,Task2 接收到了 flag 信号量以后,就把LED灯点亮
2)互斥性信号量
假设有两个任务 Task1 和 Task2,都需要来访问一个共享的资源,如要访问一个共享的打印机
需求:
假设第一个任务 Task1 要打印 hello,第二个任务 Task2 要打印 world,Task1 在使用打印机的时候,Task2是绝对不能使用的,两个任务属于互斥关系
如果 Task1 在使用打印机,Task2 也在使用打印机,那打印出来的数据可能会出现乱码
实现:
为了防止 Task1 和 Task2 共同使用打印机,配置的时候就要使用一个约束,假设还是使用一个全局变量 flag 来表示
如果 flag=1,表示这个打印机处于空闲状态,假设这个时候 Task1 要使用打印机,它首先要判断 flag 的状态,如果判断 flag=1,它就开始使用打印机并且把 flag 置 0
同样,假设这个时候,Task2也来使用打印机,它同样要判断 flag 的状态是否等于 1,如果判断这个时候flag=0,它就知道这个时候打印机处于忙的状态
它就要等待 Task1 把打印机使用完毕,同时会把 flag 置为 1,这个时候 Task2 任务就可以使用打印机了
3)事件标志组
假设两个任务 Task1 和 Task2,Task1 进行按键扫描,Task2 进行 LED 灯的点亮
同样的道理,按键按下时 LED 灯点亮,但是如果是 N 个按键 控制 N 个 LED
使用一个全局变量 flag,但是使用 flag 的各个位来表明了按键按下的状态,flag 的第 0 位为 1 表明第一个按键按下
同样 flag 的第 1 位按键为 1,表明第二个按键已经按下,依次类推
此时 flag 已经不再是一个信号量了,而是一个事件的标志,它的一位标志着一个事件是否发生,比如说第0位为0,表明这个事件没发生,第1位为1,表明这个事件发生了,这个时候这个flag就被称为一个事件的标志
那Task2在使用的过程中,它就需要来判断flag这个事件的各个位
当然事件的标志还有一些其他的高级标志,比如说你各个位能判断某一个事件,还可以判断一个组合事件:
比如第一个按键按下了,并且第二个按键也按下了,并且第三个按键也按下了,那你们都按下以后,我才让某个灯亮
这样我们就可以通过事件标志组来通过各个标志位,来相互的判断,那这个就被称为事件标志组,它不是信号量,但是它还是属于信号量的范畴
需求:
任务可能会需要与多个事件或任务进行同步,此时信号量就无
能为力了。 FreeRTOS 为此提供了一个可选的解决方法,那就是事件标志组。
简介:
1)、事件位(事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志
比如下面的几个例子:
● 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有
消息需要处理的时候就可以将这个位(标志)置 0;
● 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要从网络发送出去的话就将这个位(标志)置 0;
● 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送心跳信息,这个位(标志)置 0。
2)、事件组
一个事件组就是一组的事件位, 事件组中的事件位通过位编号来访问
同样,以上面列出的三个例子为例:
● 事件标志组的 bit0 表示队列中的消息是否处理掉。
● 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
● 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
3)、事件标志组和事件位的数据类型
事件标志组的数据类型为 EventGroupHandle_t, 当 configUSE_16_BIT_TICKS 为 1 的时候事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24个事件位。
事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中, EventBits_t在 event_groups.h 中有如下定义:
typedef TickType_t EventBits_t;
数据类型 TickType_t 在文件 portmacro.h 中有如下定义:
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#define portTICK_TYPE_IS_ATOMIC 1
#endif
可以看出当 configUSE_16_BIT_TICKS 为 0 的时候 TickType_t 是个 32 位的数据类型, 因此 EventBits_t 也是个 32 位的数据类型。 EventBits_t 类型的变量可以存储 24 个事件位,另外的那高 8 位有其他用。事件位 0 存放在这个变量的 bit0 上,变量的 bit1 就是事件位 1,以此类推。对于 STM32 来说一个事件标志组最多可以存储 24 个事件位。
1)、需求
学习 FreeROTS 事件标志组的使用,包括创建事件标志组、将相应的事件位置 1、等待相应
的事件位置 1 等操作。
2)、实现
设计四个任务: start_task、 eventsetbit_task、 eventgroup_task 和 eventquery_task
这四个任务的任务功能如下:
start_task:用来创建其他三个任务和事件标志组。
eventsetbit_task: 读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1,用来
模拟事件的发生。
eventgroup_task:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中是刷新 LCD 指定区域的背景色。
eventquery_task:查询事件组的值,也就是各个事件位的值。获取到事件组值以后就将其显示到 LCD 上,并且也通过串口打印出来。
实验中还创建了一个事件标志组: EventGroupHandler,实验中用到了这个事件标志组的三个事件位,分别位 bit0, bit1 和 bit2。
实验中会用到 3 个按键: KEY0、 KEY1 和 KEY2,其中按键 KEY1 和 KEY2 为普通的输入模式。按键 KEY0 为中断输入模式,KEY0 用来演示如何在中断服务程序调用事件标志组的 API函数。
3)、工程
●任务设置
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define EVENTSETBIT_TASK_PRIO 2 //任务优先级
#define EVENTSETBIT_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventSetBit_Handler; //任务句柄
void eventsetbit_task(void *pvParameters); //任务函数
#define EVENTGROUP_TASK_PRIO 3 //任务优先级
#define EVENTGROUP_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventGroupTask_Handler; //任务句柄
void eventgroup_task(void *pvParameters); //任务函数
#define EVENTQUERY_TASK_PRIO 4 //任务优先级
#define EVENTQUERY_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventQueryTask_Handler; //任务句柄
void eventquery_task(void *pvParameters); //任务函数
EventGroupHandle_t EventGroupHandler; //事件标志组句柄
#define EVENTBIT_0 (1<<0) //事件位
#define EVENTBIT_1 (1<<1)
#define EVENTBIT_2 (1<<2)
#define EVENTBIT_ALL (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)
//LCD 刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW, BROWN,
BRRED, GRAY };
● main()函数
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
PCF8574_Init(); //初始化 PCF8574
EXTI_Init(); //初始化外部中断
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //初始化 LCD
my_mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"Apollo STM32F4/F7");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 16-1");
LCD_ShowString(30,50,200,16,16,"Event Group");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/11");
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,130,234,314); //画矩形
POINT_COLOR = BLUE;
LCD_ShowString(30,110,220,16,16,"Event Group Value:0");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
● 任务函数
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建事件标志组
EventGroupHandler=xEventGroupCreate(); //创建事件标志组 (1)
//创建设置事件位的任务
xTaskCreate((TaskFunction_t )eventsetbit_task,
(const char* )"eventsetbit_task",
(uint16_t )EVENTSETBIT_STK_SIZE,
(void* )NULL,
(UBaseType_t )EVENTSETBIT_TASK_PRIO,
(TaskHandle_t* )&EventSetBit_Handler);
//创建事件标志组处理任务
xTaskCreate((TaskFunction_t )eventgroup_task,
(const char* )"eventgroup_task",
(uint16_t )EVENTGROUP_STK_SIZE,
(void* )NULL,
(UBaseType_t )EVENTGROUP_TASK_PRIO,
(TaskHandle_t* )&EventGroupTask_Handler);
//创建事件标志组查询任务
xTaskCreate((TaskFunction_t )eventquery_task,
(const char* )"eventquery_task",
(uint16_t )EVENTQUERY_STK_SIZE,
(void* )NULL,
(UBaseType_t )EVENTQUERY_TASK_PRIO,
(TaskHandle_t* )&EventQueryTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//设置事件位的任务
void eventsetbit_task(void *pvParameters)
{
u8 key;
while(1)
{
if(EventGroupHandler!=NULL)
{
key=KEY_Scan(0);
switch(key)
{
case KEY1_PRES:
xEventGroupSetBits(EventGroupHandler,EVENTBIT_1); (2)
break;
case KEY2_PRES:
xEventGroupSetBits(EventGroupHandler,EVENTBIT_2); (3)
break;
}
}
}
//事件标志组处理任务
void eventgroup_task(void *pvParameters)
{
u8 num;
EventBits_t EventValue;
while(1)
{
if(EventGroupHandler!=NULL)
{
//等待事件组中的相应事件位
EventValue=xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, (4)
(EventBits_t ) EVENTBIT_ALL,
(BaseType_t )pdTRUE,
(BaseType_t )pdTRUE,
(TickType_t )portMAX_DELAY);
printf("事件标志组的值:%d\r\n",EventValue);ALIENTEK 阿波罗 FreeRTOS 开发教程
303
STM32F429 FreeRTOS 开发手册
LCD_ShowxNum(174,110,EventValue,1,16,0);
num++;
LED1=!LED1;
LCD_Fill(6,131,233,313,lcd_discolor[num%14]);
}
else
{
vTaskDelay(10); //延时 10ms,也就是 10 个时钟节拍
}
}
}
//事件查询任务
void eventquery_task(void *pvParameters)
{
u8 num=0;
EventBits_t NewValue,LastValue;
while(1)
{
if(EventGroupHandler!=NULL)
{
NewValue=xEventGroupGetBits(EventGroupHandler); //获取事件组的 (5)
if(NewValue!=LastValue)
{
LastValue=NewValue;
printf("事件标志组的值:%d\r\n",NewValue);
LCD_ShowxNum(174,110,NewValue,1,16,0);
}
num++;
if(num==0) //每 500msLED0 闪烁一次
{
num=0;
LED0=!LED0;
}
vTaskDelay(50); //延时 50ms,也就是 50 个时钟节拍
}
}
(1)、首先调用函数 xEventGroupCreate()创建一个事件标志组 EventGroupHandler。
(2)、按下 KEY1 键的时候就调用函数 xEventGroupSetBits()将事件标志组的 bit1 置 1。
(3)、按下 KEY2 键的时候调用函数 xEventGroupSetBits()将事件标志组的 bit2 值 1。
(4)、调用函数 xEventGroupWaitBits()同时等待事件标志组的 bit0, bit1 和 bit2,只有当这三个事件都置 1 的时候才会执行任务中的其他代码。
(5)、调用函数 xEventGroupGetBits()查询事件标志组 EventGroupHandler 的值变化,通过查看这些值的变化就可以分析出当前哪个事件位置 1 了。
● 中断初始化及处理过程
事件标志组 EventGroupHandler的事件位 bit0 是通过 KEY0 的外部中断服务函数来设置的,
注意中断优先级的设置!本例程的中断优先级设置如下:
//中断线 3-PH3
HAL_NVIC_SetPriority(EXTI3_IRQn,6,0); //抢占优先级为 6,子优先级为 0
HAL_NVIC_EnableIRQ(EXTI3_IRQn); //使能中断线 3
KEY0 的外部中断服务函数如下:
//事件标志组句柄
extern EventGroupHandle_t EventGroupHandler;
//中断服务函数
void EXTI3_IRQHandler(void)
{
BaseType_t Result,xHigherPriorityTaskWoken;
delay_xms(50); //消抖
if(KEY0==0)
{
Result=xEventGroupSetBitsFromISR(EventGroupHandler,EVENTBIT_0,\ (1)
&xHigherPriorityTaskWoken);
if(Result!=pdFAIL)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3); //清除中断标志位
}
(1)、 在中断服务函数中通过调用 xEventGroupSetBitsFromISR()来将事件标志组的事件位
bit0 置 1。