STM32中的FreeRTOS-#3(二值信号量)

文章目录[隐藏]

本文是FreeRTOS教程系列的第三篇,将介绍“二值信号量”的使用。

信号量用于将任务与系统中的其他事件同步。在FreeRTOS中,信号量是基于队列机制(后面会有一节专门讲队列)实现的。在FreeRTOS中有4种类型的信号量:

  • 二值信号量;
  • 计数信号量;
  • 互斥锁
  • 递归

二值信号量,顾名思义,是只有两个值,“0”和“1”的信号量。一个任务要么有信号量,有么没有信号量,没有第三种情况(比如有两个信号量)。引入信号量的原因是:某些情况下单纯依靠任务优先级进行任务调度,不能满足应用要求。假设有如下场景:一个低优先级任务(LTask)负责处理数据,得到的数据将被另一个高优先级任务(HTask)使用。根据任务调度规则,LTask随时可能被HTask抢断。一旦LTask中关键代码部分被抢断,HTask可能会拿到”半成品“数据,导致计算错误。但是如果LTask拥有信号量,执行完关键部分的代码才释放信号量。在此期间,HTask即使准备就绪,也必须等待LTask释放信号量以后才能抢断LTask,潜在的错误就可以避免。


CubeMX设置


首先在CubeMX中创建三个不同的任务,任务的属性设置如下
图1.创建任务

图1.创建任务


接着,创建一个二值信号量,如下图所示
在这里插入图片描述

图2.创建信号量

其余设置与前两节类似,不再赘述。


代码部分


在CubeMX中已经创建了三个不同的任务,下面实现任务内容

首先是中等优先级任务(MEDIUM Task

void StartMediumTask(void const *argument)
{
  /* USER CODE BEGIN StartMediumTask */
  /* Infinite loop */
  for (;;)
  {
    printf("Entered MEDIUM Task\n");
    printf("Leaving MEDIUM Task\n\n");
    osDelay(500);
  }
  /* USER CODE END StartMediumTask */
}

其实在这个示例中,运行中等优先级任务不需要信号量。我创建这个任务是为了说明,即使创建了信号量,未必每个任务都要使用它,有些任务还是可以和信号量没有任何关系。这个MEDIUM Task不会等待任何信号量,所以它可以独立运行,在需要的时候抢低优先级任务LOW Task


其次是高优先级任务(HIGH Task

void StartHighTask(void const *argument)
{
  /* USER CODE BEGIN StartHighTask */
  /* Infinite loop */
  for (;;)
  {
    printf("Entered HighTask and waiting for Semaphore\n");
    osSemaphoreWait(myBinarySem01Handle, osWaitForever);
    printf("Semaphore acquired by HIGH Task\n");
    printf("Leaving HighTask and releasing Semaphore\n\n");
    osSemaphoreRelease(myBinarySem01Handle);
    osDelay(500);
  }
  /* USER CODE END StartHighTask */
}

在第8行代码,osSemaphoreWait(myBinarySem01Handle, osWaitForever);,高优先级任务会在此处一直等待取得二值信号量,然后才能继续向下执行。函数的第一个参数是信号量句柄,第二个参数是等待时间。在这里,任务将永远等待。一旦任务执行完毕,在第11行,二元信号量被释放osSemaphoreRelease(myBinarySem01Handle);。信号量被释放以后,其他等待信号量的任务得以继续执行。


再者是低优先级任务(LOW Task)

void StartLowTask(void const *argument)
{
  /* USER CODE BEGIN StartLowTask */
  /* Infinite loop */
  for (;;)
  {

    printf("Entered LOWTask and waiting for semaphore\n");
    osSemaphoreWait(myBinarySem01Handle, osWaitForever);
    printf("Semaphore acquired by LOW Task, waitting the key being pressed \n");
    while (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))
      ; // 一直等待按键按下
    printf("Leaving LOWTask and releasing Semaphore\n\n");
    osSemaphoreRelease(myBinarySem01Handle);
    osDelay(500);
  }

与高优先级任务类似,LOW Task也在等待信号量。一旦其获得信号量,就会等待按钮被按下,然后通过串口发送消息,最后释放信号量。

接下来看一下程序运行效果


实验结果


在这里插入图片描述

图3.程序运行效果

  • 开始时,调度器有3个任务要执行,HIGH Task首先被执行;
  • HIGH Task首先获得信号量,自身执行完成后,释放信号量;
  • 然后,MEDIUM Task执行,在任务中通过延时函数挂起500毫秒;
  • 接着,LOW Task开始执行。其获得信号量以后,等待按钮被按下;
  • HIGH Task延时结束,结束被挂起的状态,抢断LOW Task,在任务中持续等待获得信号量,但是此时信号量被LOW Task占有,HIGH Task只能等待;
  • MEDIUM Task结束挂起状态,抢断LOW Task。由于无需等待获取信号量,MEDIUM Task能够按自己的节奏,每500ms执行一次;

如果不按下按钮,只有MEDIUM Task在持续执行,当按下按钮时,如下图所示,情况发生了变化
在这里插入图片描述

图4.按钮按下后程序运行情况

上图表明按钮按下后发生了以下过程

  • MEDIUM Task持续运行,抢断LOW Task
  • 按钮按下,LOW Task得以继续执行,随后释放信号量;
  • HIGH Task随后获得信号量,得以继续执行剩余代码,在任务的最后释放信号量;
  • MEDIUM Task执行;
  • HIGH TaskLOW Task等待执行,调度器让HIGH Task先执行。在任务中先获取信号量然后又释放;
  • MEDIUM Task被延时函数挂起500ms,还未恢复,此时只有LOW Task需要执行。在任务中获取信号量然后等待按钮被按下;
  • HIGH Task被延时函数挂起500ms,还未恢复。MEDIUM Task从挂起状态恢复,抢断LOW Task执行;
  • HIGH Task从挂起状态恢复,等待信号量,但是此时信号量被LOW Task占有;
  • MEDIUM Task从挂起状态恢复,抢断LOW Task执行,保持每500ms执行一次。情况又回到了按钮被按下之前的状态;

由以上分析可以看出,如果HIGH Task未能获取信号量便值能一直等待,不能能继续执行。

回到本节开头的情景,如果LOW Task有关键的数据处理任务要执行,而HIGH Task又需要使用处理完成的数据。就可以添加信号量,LOW Task在数据处理前获取信号量,数据处理完成后释放信号量。HIGH Task在使用数据前,需要先获取信号量,如果获取不到便会持续等待数据处理完成。

二值信号量带来的优先级翻转(高优先级任务需要等待低优先级任务),某些情况下会带来问题。同时二值信号量也有其局限性,不足以应对所有使用需求。我们后面会介绍其他手段,解决实际应用中可能遇到的其他问题。



【本节教程结束】
欢迎关注我的同名公众号**“早点儿毕业”**,我会在公众号同步发布教程(笔记)内容。
在这里插入图片描述

版权声明:本文为CSDN博主「早点儿毕业」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35913527/article/details/122021032

本文是FreeRTOS教程系列的第三篇,将介绍“二值信号量”的使用。

信号量用于将任务与系统中的其他事件同步。在FreeRTOS中,信号量是基于队列机制(后面会有一节专门讲队列)实现的。在FreeRTOS中有4种类型的信号量:

  • 二值信号量;
  • 计数信号量;
  • 互斥锁
  • 递归

二值信号量,顾名思义,是只有两个值,“0”和“1”的信号量。一个任务要么有信号量,有么没有信号量,没有第三种情况(比如有两个信号量)。引入信号量的原因是:某些情况下单纯依靠任务优先级进行任务调度,不能满足应用要求。假设有如下场景:一个低优先级任务(LTask)负责处理数据,得到的数据将被另一个高优先级任务(HTask)使用。根据任务调度规则,LTask随时可能被HTask抢断。一旦LTask中关键代码部分被抢断,HTask可能会拿到”半成品“数据,导致计算错误。但是如果LTask拥有信号量,执行完关键部分的代码才释放信号量。在此期间,HTask即使准备就绪,也必须等待LTask释放信号量以后才能抢断LTask,潜在的错误就可以避免。


CubeMX设置


首先在CubeMX中创建三个不同的任务,任务的属性设置如下
图1.创建任务

图1.创建任务


接着,创建一个二值信号量,如下图所示
在这里插入图片描述

图2.创建信号量

其余设置与前两节类似,不再赘述。


代码部分


在CubeMX中已经创建了三个不同的任务,下面实现任务内容

首先是中等优先级任务(MEDIUM Task

void StartMediumTask(void const *argument)
{
  /* USER CODE BEGIN StartMediumTask */
  /* Infinite loop */
  for (;;)
  {
    printf("Entered MEDIUM Task\n");
    printf("Leaving MEDIUM Task\n\n");
    osDelay(500);
  }
  /* USER CODE END StartMediumTask */
}

其实在这个示例中,运行中等优先级任务不需要信号量。我创建这个任务是为了说明,即使创建了信号量,未必每个任务都要使用它,有些任务还是可以和信号量没有任何关系。这个MEDIUM Task不会等待任何信号量,所以它可以独立运行,在需要的时候抢低优先级任务LOW Task


其次是高优先级任务(HIGH Task

void StartHighTask(void const *argument)
{
  /* USER CODE BEGIN StartHighTask */
  /* Infinite loop */
  for (;;)
  {
    printf("Entered HighTask and waiting for Semaphore\n");
    osSemaphoreWait(myBinarySem01Handle, osWaitForever);
    printf("Semaphore acquired by HIGH Task\n");
    printf("Leaving HighTask and releasing Semaphore\n\n");
    osSemaphoreRelease(myBinarySem01Handle);
    osDelay(500);
  }
  /* USER CODE END StartHighTask */
}

在第8行代码,osSemaphoreWait(myBinarySem01Handle, osWaitForever);,高优先级任务会在此处一直等待取得二值信号量,然后才能继续向下执行。函数的第一个参数是信号量句柄,第二个参数是等待时间。在这里,任务将永远等待。一旦任务执行完毕,在第11行,二元信号量被释放osSemaphoreRelease(myBinarySem01Handle);。信号量被释放以后,其他等待信号量的任务得以继续执行。


再者是低优先级任务(LOW Task)

void StartLowTask(void const *argument)
{
  /* USER CODE BEGIN StartLowTask */
  /* Infinite loop */
  for (;;)
  {

    printf("Entered LOWTask and waiting for semaphore\n");
    osSemaphoreWait(myBinarySem01Handle, osWaitForever);
    printf("Semaphore acquired by LOW Task, waitting the key being pressed \n");
    while (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))
      ; // 一直等待按键按下
    printf("Leaving LOWTask and releasing Semaphore\n\n");
    osSemaphoreRelease(myBinarySem01Handle);
    osDelay(500);
  }

与高优先级任务类似,LOW Task也在等待信号量。一旦其获得信号量,就会等待按钮被按下,然后通过串口发送消息,最后释放信号量。

接下来看一下程序运行效果


实验结果


在这里插入图片描述

图3.程序运行效果

  • 开始时,调度器有3个任务要执行,HIGH Task首先被执行;
  • HIGH Task首先获得信号量,自身执行完成后,释放信号量;
  • 然后,MEDIUM Task执行,在任务中通过延时函数挂起500毫秒;
  • 接着,LOW Task开始执行。其获得信号量以后,等待按钮被按下;
  • HIGH Task延时结束,结束被挂起的状态,抢断LOW Task,在任务中持续等待获得信号量,但是此时信号量被LOW Task占有,HIGH Task只能等待;
  • MEDIUM Task结束挂起状态,抢断LOW Task。由于无需等待获取信号量,MEDIUM Task能够按自己的节奏,每500ms执行一次;

如果不按下按钮,只有MEDIUM Task在持续执行,当按下按钮时,如下图所示,情况发生了变化
在这里插入图片描述

图4.按钮按下后程序运行情况

上图表明按钮按下后发生了以下过程

  • MEDIUM Task持续运行,抢断LOW Task
  • 按钮按下,LOW Task得以继续执行,随后释放信号量;
  • HIGH Task随后获得信号量,得以继续执行剩余代码,在任务的最后释放信号量;
  • MEDIUM Task执行;
  • HIGH TaskLOW Task等待执行,调度器让HIGH Task先执行。在任务中先获取信号量然后又释放;
  • MEDIUM Task被延时函数挂起500ms,还未恢复,此时只有LOW Task需要执行。在任务中获取信号量然后等待按钮被按下;
  • HIGH Task被延时函数挂起500ms,还未恢复。MEDIUM Task从挂起状态恢复,抢断LOW Task执行;
  • HIGH Task从挂起状态恢复,等待信号量,但是此时信号量被LOW Task占有;
  • MEDIUM Task从挂起状态恢复,抢断LOW Task执行,保持每500ms执行一次。情况又回到了按钮被按下之前的状态;

由以上分析可以看出,如果HIGH Task未能获取信号量便值能一直等待,不能能继续执行。

回到本节开头的情景,如果LOW Task有关键的数据处理任务要执行,而HIGH Task又需要使用处理完成的数据。就可以添加信号量,LOW Task在数据处理前获取信号量,数据处理完成后释放信号量。HIGH Task在使用数据前,需要先获取信号量,如果获取不到便会持续等待数据处理完成。

二值信号量带来的优先级翻转(高优先级任务需要等待低优先级任务),某些情况下会带来问题。同时二值信号量也有其局限性,不足以应对所有使用需求。我们后面会介绍其他手段,解决实际应用中可能遇到的其他问题。



【本节教程结束】
欢迎关注我的同名公众号**“早点儿毕业”**,我会在公众号同步发布教程(笔记)内容。
在这里插入图片描述

版权声明:本文为CSDN博主「早点儿毕业」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35913527/article/details/122021032

生成海报
点赞 0

早点儿毕业

我还没有学会写个人说明!

暂无评论

相关推荐

蓝桥杯STM32G431学习记录6——IIC基本原理

IIC基本原理 在学习IIC时由于用到了通信方式学习串口时只是大概看了一下,所以在这里先复习一下之前的内容 处理器与外部设备通信的两种方式: ●并行通信 -传输原理:数据各个位同时传输。-优点:速度快 -缺点:占用引脚资源多 ●

STM32 GPIO |CSDN创作打卡

GPIO结构框图 推挽输出(0-3.3): 在该结构中输入高电平时,上方的P-MOS导通,下方的N-MOS截止,对外输出高电平 。 而在该结构中输入低电平时