【IoT】STM32 系统级开发之 ucosIII 或 freeRTOS 事件标志组详解

1、轻型操作系统同步的方案详解


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这个事件的各个位

当然事件的标志还有一些其他的高级标志,比如说你各个位能判断某一个事件,还可以判断一个组合事件:

比如第一个按键按下了,并且第二个按键也按下了,并且第三个按键也按下了,那你们都按下以后,我才让某个灯亮

这样我们就可以通过事件标志组来通过各个标志位,来相互的判断,那这个就被称为事件标志组,它不是信号量,但是它还是属于信号量的范畴

2、freeRTOS事件标志组详解


需求:

任务可能会需要与多个事件或任务进行同步,此时信号量就无
能为力了。 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 个事件位。

3、测试试验详解


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。

 

 

 

你可能感兴趣的:(IoT产品设计理论,STM32开发设计,BLE,蓝牙,框架,stm32,freeRTOS)