历史上的今天
返回首页

历史上的今天

今天是:2025年02月07日(星期五)

正在发生

2021年02月07日 | STM32 系统级开发之 ucosIII 或 freeRTOS 事件标志组详解

2021-02-07 来源:eefocus

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("事件标志组的值:%drn",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("事件标志组的值:%drn",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。

推荐阅读

史海拾趣

潮州三环(Three-circle)公司的发展小趣事

随着公司业务的不断拓展和市场的日益全球化,三环集团开始积极推进全球化战略。公司在广东潮州、深圳,四川成都、南充、德阳,江苏苏州,湖北武汉,中国香港,德国,泰国等地设立公司,形成了覆盖全球的产业布局。通过与国际知名企业的合作与交流,三环集团不断提升自身的技术水平和市场竞争力,逐步成为电子行业的领军企业之一。同时,公司还积极参与国际展览和论坛等活动,展示自身实力和品牌形象,为全球客户提供更优质的产品和服务。

以上五个故事分别从不同角度展现了潮州三环(Three-circle)公司在电子行业中的发展历程和成就。这些故事不仅展示了公司的创新能力和市场洞察力,也体现了公司在面对市场挑战时的勇气和决心。

富芯森美(FUXINSEMI)公司的发展小趣事

随着新能源汽车产业的快速发展,富芯森美敏锐地捕捉到了这一市场机遇。公司投入大量资源研发车规级功率半导体器件,产品设计遵循APQP标准,制造管理符合IATF 16949要求,质量检验则严格遵循AEC-Q101标准。这些举措使得富芯森美的车规级产品能够满足汽车行业的严苛要求,成功打入新能源汽车供应链体系,为公司带来了新的增长点。

Excelsys公司的发展小趣事

人才是企业发展的核心驱动力。Excelsys公司高度重视人才培养和团队建设。公司建立了完善的人才培养机制,为员工提供系统的培训和学习机会;同时,公司还注重团队文化的建设,营造积极向上、团结协作的工作氛围。这些措施为公司的持续发展提供了有力的人才保障。

广州盛炬(GZSJ)公司的发展小趣事

品质是Excelsys公司的生命线。公司始终坚持严格的品质控制标准,从原材料采购到生产制造的每一个环节,都经过严格的质量检测。这种对品质的执着追求,让Excelsys的产品在市场上赢得了良好的口碑。同时,公司还建立了完善的售后服务体系,为客户提供及时、专业的技术支持,进一步提升了品牌的信誉度。

厦门法拉(faratronic)公司的发展小趣事

在追求商业成功的同时,FMI公司也积极履行社会责任,致力于环保和可持续发展。公司制定了一系列环保计划和程序,以确保在所有业务运营中保持环保意识。FMI的产品从设计到生产都遵循环保原则,采用环保材料和工艺,减少对环境的影响。此外,公司还积极参与行业内的环保活动,推动整个电子行业的绿色发展。这种负责任的企业形象为FMI赢得了社会的广泛赞誉和尊重。

DEWALT公司的发展小趣事

DEWALT公司非常注重产品品质和用户体验。公司建立了严格的质量控制体系,确保每一件产品都符合高质量标准。同时,DEWALT还积极倾听用户反馈,不断改进产品和服务。这些努力使DEWALT公司的电动工具在市场上获得了广泛认可,并赢得了大量忠实用户。

问答坊 | AI 解惑

PCB设计中可能遇到的问题及解答

问题: Query:在从原理图更新到PCB的时候,如何保持原有器件的布局? 在Protel中,通常都会遇到需要修改原理图时,如何保持原有PCB板中器件的布局的问题。下面讲述一种有效的方式就是在PCB编辑窗口中使用菜单命令Project » Component Links来完成 ...…

查看全部问答>

创建Windows CE操作系统(二)

之前介绍过如何创建一个基本的Windows CE的平台,现在咱就上一次没有提到的部分进行一下补充定制并build OS。 首先,在VS2005 IDE中的View -> Other Windows -> Catalog Items中,添加或者删除相应的模块来完成OS定制,选项如下: 然后配置buil ...…

查看全部问答>

模拟乘法器(checked)

本帖最后由 辛昕 于 2018-4-8 22:59 编辑 关于这个东西,我后来其实从来没捡起过。 但是,也就没有必要惦记着了。 当然了,每次说到这个东西,都会想起,故人已去~ 在做运放的过程中,知道了一个叫做模拟乘法器的东西。 但是想上网看看这个东 ...…

查看全部问答>

calibrate_delay 的头文件是什么

嵌入式开发,Linux系统,是可以在驱动里面调用calibrate_delay 这个函数的吧 不知道需要包含什么头文件…

查看全部问答>

我的移动硬盘使用时,怎么显示:"本地磁盘"啊?

求救:     我的移动硬盘使用时,怎么显示:\"本地磁盘\"啊?     而且打不开! 谁知道 怎么解决? 谢谢!!!…

查看全部问答>

Windows CE 5.0的ARMV4I补丁求种

Windows CE 5.0的ARMV4I补丁,名称如下:    WinCEPB50-051231-Product-Update-Rollup-Armv4I.msi    WinCEPB50-060131-2006M01-Armv4I.msi    WinCEPB50-060228-2006M02-Armv4I.msi    WinCEPB50-06 ...…

查看全部问答>

wince下的CMMB播放程序

谁有这样的程序,急求,可以和我联系. email:gcgaoxj@yahoo.com [ 本帖最后由 dreaming123 于 2011-3-16 21:42 编辑 ]…

查看全部问答>

刚刚看到可以用:用社区芯币兑换51开发板PCB板!

刚刚看到可以用:用社区芯币兑换51开发板PCB板!早知道在这里换了,自己花了150快大洋买了一个 能不能用社区芯币兑换ARM的开发板呢…

查看全部问答>

microblaze的一些问题探讨下

请问有没有建立工程的教程,我按照官方网站的教程建立,(用的是ise13.1), 各种无奈啊,遇到的问题: 1、头文件的处理,我把用到的头文件拷到板级包的include文件夹,开始时可以,但是后面工程关闭,又出现 没找到头文件,那不是又得拷贝一次, ...…

查看全部问答>

【信号处理】一种雷达通用信号处理系统的实现与应用

摘要:鉴于FPGA和DSP各自的优势,FPGA+DSP信号处理架构,已成为信号处理系统的常用结构。但目前此结构处理平台功能固定、通用性差,或对平台的介绍缺乏具体实现。文中针对以上两点提出一种通用信号处理系统。该系统不仅将两种处理器的优点集于一身 ...…

查看全部问答>