前沿:

在队列、信号量和互斥量中,我写入一个数据只能唤醒一个任务,同样的我读出一个数据也只能唤醒一个任务,所以有没有一种广播能够实现,一个事件的发生能唤醒多个任务呢?有的,事件组就能解决这个问题
事件组的本质:

事件组可以简单地认为就是一个结构体,在事件组里面有一个整数:
1、事件组的每一位表示一个事件
2、每一位事件的含义由程序员决定,比如:bit0为可以在任务A设置1,然后任务B检测到任务A将bit0置1后就可以执行
3、这些位,值为1表示事件发生了,值为0表示事件没发生
4、一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
5、可以等待某一位、某些位中的任意一个,也可以等待多位
注意:等待的事件中,它们要么是或的关系,要么是与的关系。也就是可以等待若干个事件中的任一个,可以等待若干个事件中的所有。不能在若干个事件中指定某些事件
以下面图为例子解释:

可以看到事件组下面还有一个等待链表,这个等待链表就放有一个个任务,这些任务都在等待某一事件或多个事件的发生。
如图:任务A中bit0和bit3位设置为1,说明任务A在等待bit0和bit3位事件发生,同理任务B在等待bit7和bit0位事件发生
假设任务C写入了bit2事件到事件组,然后就会遍历这个链表,看看当前事件有没有满足哪个的任务,有就唤醒,显然这里没有,当任务C写入写入了bit7事件到事件组,随便遍历链表就发现满足任务A的需求,就唤醒任务A。
注意:这里高八位是设置这些任务是只需要发生一个事件就唤醒的还是要全部事件发生才能唤醒
事件组函数:
1、创建:
使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。 有两种创建方法:动态分配内存、静态分配内存。函数原型如下:
c
/* 创建一个事件组,返回它的句柄。
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 创建一个事件组,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *
pxEventGroupBuffer );
2、设置事件:
可以设置事件组的某个位、某些位,使用的函数有2个:
在任务中使用 xEventGroupSetBits()
在ISR中使用 xEventGroupSetBitsFromISR()
有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。
函数原型如下:
c
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
* 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
* 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
* 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
* 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
* pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有,
pdFALSE-没有
* 返回值: pdPASS-成功, pdFALSE-失败
*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
3、等待事件:
使用 xEventGroupWaitBits 来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位; 等到期望的事件后,还可以清除某些位。
函数原型如下:
c
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );

一个任务在等待事件发生时,它处于阻塞状态;当期望的事件发生时,这个状态就叫"unblock condition",非阻塞条件,或称为"非阻塞条件成立";当"非阻塞条件成 立"后,该任务就可以变为就绪态。
4、删除事件:
对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。
vEventGroupDelete可以用来删除事件组,函数原型如下:
c
/*
* xEventGroup: 事件组句柄,你要删除哪个事件组
*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
事件组代码示例:
本次的代码要实现这三个现象:

c
static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;
static SemaphoreHandle_t g_xSemaphoreGame; /* 游戏信号量句柄 */
static EventGroupHandle_t g_xEventGroupGameHandl; /* 事件组游戏句柄 */
struct car {
int x;
int y;
int control_key;
};
struct car g_cars[]={
{0,0,IR_KEY_1},
{0,17,IR_KEY_2},
{0,34,IR_KEY_3},
};
static const byte carImg[] PROGMEM ={
0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,
0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};
static const byte clearImg[30] ={0};
static const byte roadMarking[] PROGMEM ={
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
static void show_car(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x,pcar->y,15,16);
}
static void hide_car(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x,pcar->y,15,16);
}
static void cars1_task(void *params)
{
struct car *pcar=params;
QueueHandle_t xQueueIR; /* 游戏汽车队列(每个任务一个) */
struct ir_data idata;
/* 创建汽车队列 */
xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
// xSemaphoreTake(g_xSemaphoreGame,portMAX_DELAY); //抢票,抢不到一直等
while(1)
{
// /*读取队列数据*/
// xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
// /*处理队列数据*/
// if(idata.val==pcar->control_key)
// {
if(pcar->x < g_xres-CAR_LENGTH)
{
/*隐藏汽车位置*/
hide_car(pcar);
/*调整位置*/
pcar->x += 1;
if(pcar->x >= g_xres-CAR_LENGTH)
{
pcar->x =g_xres-CAR_LENGTH;
}
/*重新显示汽车*/
show_car(pcar);
vTaskDelay(50);
if(pcar->x == g_xres-CAR_LENGTH)
{
//xSemaphoreGive(g_xSemaphoreGame); //进城就放票
xEventGroupSetBits(g_xEventGroupGameHandl,(1<<0));
}
}
// }
}
}
static void cars2_task(void *params)
{
vTaskDelay(1000);
struct car *pcar=params;
QueueHandle_t xQueueIR; /* 游戏汽车队列(每个任务一个) */
struct ir_data idata;
/* 创建汽车队列 */
xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
//xSemaphoreTake(g_xSemaphoreGame,portMAX_DELAY); //抢票,抢不到一直等
xEventGroupWaitBits(g_xEventGroupGameHandl,(1<<0),pdTRUE,pdFALSE,portMAX_DELAY);
while(1)
{
// /*读取队列数据*/
// xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
// /*处理队列数据*/
// if(idata.val==pcar->control_key)
// {
if(pcar->x < g_xres-CAR_LENGTH)
{
/*隐藏汽车位置*/
hide_car(pcar);
/*调整位置*/
pcar->x += 1;
if(pcar->x >= g_xres-CAR_LENGTH)
{
pcar->x =g_xres-CAR_LENGTH;
}
/*重新显示汽车*/
show_car(pcar);
//vTaskDelay(50);
mdelay(50);
if(pcar->x == g_xres-CAR_LENGTH)
{
//xSemaphoreGive(g_xSemaphoreGame); //进城就放票
vTaskDelete(NULL);
}
}
// }
}
}
static void cars3_task(void *params)
{
vTaskDelay(1000);
struct car *pcar=params;
QueueHandle_t xQueueIR; /* 游戏汽车队列(每个任务一个) */
struct ir_data idata;
/* 创建汽车队列 */
xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
// xSemaphoreTake(g_xSemaphoreGame,portMAX_DELAY); //抢票,抢不到一直等
xEventGroupWaitBits(g_xEventGroupGameHandl,(1<<0),pdTRUE,pdFALSE,portMAX_DELAY);
while(1)
{
// /*读取队列数据*/
// xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
// /*处理队列数据*/
// if(idata.val==pcar->control_key)
// {
if(pcar->x < g_xres-CAR_LENGTH)
{
/*隐藏汽车位置*/
hide_car(pcar);
/*调整位置*/
pcar->x += 1;
if(pcar->x >= g_xres-CAR_LENGTH)
{
pcar->x =g_xres-CAR_LENGTH;
}
/*重新显示汽车*/
show_car(pcar);
vTaskDelay(50);
if(pcar->x == g_xres-CAR_LENGTH)
{
xSemaphoreGive(g_xSemaphoreGame); //进城就放票
}
}
// }
}
}
void car_game(void)
{
uint8_t i,j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
//g_xSemaphoreGame=xSemaphoreCreateMutex(); //创建互斥量
g_xEventGroupGameHandl=xEventGroupCreate();
// Draw car
for(i=0;i<3;i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y,15,16);
}
// Draw roadMarking
for(i=0;i<8;i++)
{
for(j=0;j<3;j++)
{
draw_bitmap(0+16*i, 16+17*j, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(0+16*i,16+17*j,8,1);
}
}
/*创建三个汽车任务*/
xTaskCreate(cars1_task, "Car1_task", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(cars2_task, "Car2_task", 128, &g_cars[1], osPriorityNormal+1, NULL);
xTaskCreate(cars3_task, "Car3_task", 128, &g_cars[2], osPriorityNormal+1, NULL);
}
该代码实现了第一辆车跑完,唤醒第二、三辆车现象
只需要在原代码基础上做这些改动就可以实现第二个现象:


小车二的速度由阻塞函数的时间长短来决定
现象三的代码改动如下:

只需要将任务三改成等待全部事件置1才唤醒就行了
创建事件组来控制接收MPU6050数据

这是之前我们在加入一个MPU6050来控制小球板的左右移动代码思路,那时我们的MPU6050没有设置中断,所以只能设置一个任务来不断读取I2C的数据,但这会造成MPU6050没有数据传来也会不断读取I2C,这就造成了CPU的资源浪费,所以怎么解决呢?
思路:我们先使能MPU6050中断,先在MPU6050.Init()函数里创建事件组 ,然后在MPU6050任务里设置等待事件组函数 ,最后在中断设置事件组函数,这样只要MPU6050传来数据,就会触发中断,然后唤醒MPU6050任务,接着读取I2C