风筝
发表于: 2020-5-22 22:07:11 | 显示全部楼层

在之前的文章中,我们已经介绍了在Arduino中使用FreeRTOS的基础知识以及队列Queue内核对象的使用方法。在本文中,我们将继续学习有关FreeRTOS及其高级API的更多信息,帮助您更加深入地了解多任务平台。


信号量(Semaphore)和互斥量(Mutex)是用于同步、资源管理和保护资源免遭破坏的内核对象。在本文的前半部分,我们将了解信号量背后的思想,以及如何使用。在后半部分,我们将介绍如何使用互斥量。


什么是信号量?

在之前的文章中,我们已经讨论过任务优先级,并且还知道高优先级的任务会抢占低优先级的任务,因此在执行高优先级的任务时,低优先级的任务可能会发生数据损坏的情况。因为该任务尚未执行,并且数据不断从传感器传到该任务,这会导致数据丢失和整个应用程序故障。


因此,需要保护资源免受数据丢失的影响,而信号量在这里起着重要的作用。


信号量(Semaphore)是一种信令机制,其中处于等待状态的任务由另一个任务发出信号以执行。换句话说,当task1完成工作时,它将显示一个标志或将标志加1,然后另一个任务(task2)接收到该标志,表明它现在可以执行其工作。当task2完成工作后,该标志将减少1。


因此,从根本上讲,它是一种“给予”和“接受”机制,而信号量是一个整数变量,用于同步对资源的访问。


FreeRTOS中的信号量类型

信号量有两种类型。

1.  二进制信号量(Binary Semaphore)

2.  计数信号量(Counting Semaphore)


1.  二进制信号量:它具有两个整数值0和1。它与长度为1的队列有些相似。例如,我们有两个任务task1和task2。 Task1将数据发送到task2,因此task2继续检查队列项是否为1,然后它可以读取数据,否则必须等待直到变为1。在获取数据之后,task2递减队列并将其设为0,这意味着task1再次可以将数据发送到task2。


从上面的示例可以说,二进制信号量用于任务之间或任务与中断之间的同步。


2.  计数信号量:它的值大于0,并且可以认为长度大于1的队列。该信号量用于计数事件。在这种使用情况下,事件处理程序将在事件发生时“给出”信号量(增加信号量计数值),处理程序任务将在每次处理事件时“接收”信号量(减少信号量计数值)。 。


因此,计数值为发生的事件数与已处理的事件数之差。


现在,让我们看看如何在FreeRTOS代码中使用信号量。


如何在FreeRTOS中使用信号量?

FreeRTOS支持不同的API,用于创建信号量、获取信号量和提供信号量。


现在,同一内核对象可以有两种类型的API。如果必须从ISR提供信号量,则无法使用常规信号量API。您应该使用受中断保护的API。


在本文中,我们将使用二进制信号量,因为它易于理解和实现。由于此处使用了中断功能,因此您需要在ISR函数中使用受中断保护的API。当我们说用中断同步任务时,这意味着在ISR之后立即将任务置于“运行”状态。


1.  创建信号量:

要使用任何内核对象,我们必须首先创建。要创建二进制信号量,请使用vSemaphoreCreateBinary()。


该API不带任何参数,并返回SemaphoreHandle_t类型的变量。创建一个全局变量名称sema_v来存储信号量。

  1. SemaphoreHandle_t  sema_v;
  2. sema_v = xSemaphoreCreateBinary();
复制代码

2.  提供信号量

为了提供信号量,有两个API函数,-一种用于中断,另一种用于常规任务。

1.  xSemaphoreGive():此API在创建信号量时仅接受一个参数,即上述信号量的变量名,如sema_v。可以从要同步的任何常规任务中调用它。

2.  xSemaphoreGiveFromISR():该函数是xSemaphoreGive()的受中断保护的API函数。当需要同步ISR和正常任务时,应从ISR函数中使用xSemaphoreGiveFromISR()。


3.  获取信号量

要获取信号量,请使用API​​函数xSemaphoreTake()。该API带有两个参数。

  1. xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
复制代码

xSemaphore:信号量的名称,在本例中为sema_v。

xTicksToWait:这是任务在“阻塞”状态下等待信号量变为可用的最长时间。在本文中,我们将xTicksToWait设置为portMAX_DELAY,以使task_1在Blocked状态下无限期等待,直到sema_v可用。


现在,让我们使用这些API并编写代码来执行一些任务。


这里一个按钮和两个LED相连。该按钮将充当中断按钮,该按钮连接到Arduino Uno的引脚2。当按下此按钮时,将产生一个中断,并且连接到引脚8的LED指示灯将亮起,再次按下该指示灯将熄灭。


因此,当按下按钮时,ISR函数将调用xSemaphoreGiveFromISR(),而TaskLED函数将调用xSemaphoreTake()函数。


为了使系统看起来具有多任务处理能力,请将其他LED与引脚7连接,该引脚将始终处于闪烁状态。


信号量代码说明

让我们打开Arduino IDE开始编写代码


1.  首先,包括Arduino_FreeRTOS.h头文件。现在,如果使用任何内核对象(例如队列信号量),那么还必须为其包含头文件。

  1. #include <Arduino_FreeRTOS.h>
  2. #include <semphr.h>
复制代码

2.  声明一个SemaphoreHandle_t类型的变量来存储信号量的值。

  1. SemaphoreHandle_t interruptSemaphore;
复制代码

3.  在void setup()中,使用xTaskCreate创建两个任务(TaskLED和TaskBlink),然后使用xSemaphoreCreateBinary()创建一个信号量。创建一个具有相同优先级的任务,稍后尝试使用此任务。另外,将引脚2配置为输入,并启用内部上拉电阻并连接中断引脚。最后,启动调度程序,如下所示。

  1. void setup() {
  2.   pinMode(2, INPUT_PULLUP);
  3.   xTaskCreate(TaskLed,  "Led", 128, NULL, 0, NULL );
  4. xTaskCreate(TaskBlink,  "LedBlink", 128, NULL, 0, NULL );
  5.   interruptSemaphore = xSemaphoreCreateBinary();
  6.   if (interruptSemaphore != NULL) {
  7.     attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW);
  8.   }
  9. }
复制代码

4.  现在,实现ISR函数。创建一个函数,并将其命名为与attachInterrupt()函数的第二个参数相同的名称。为了使中断正常工作,您需要使用毫秒函数并通过调整时间来消除按钮的抖动问题。从此函数中,如下所示调用interruptHandler()函数。

  1. long debouncing_time = 150;
  2. volatile unsigned long last_micros;
  3. void debounceInterrupt() {
  4. if((long)(micros() - last_micros) >= debouncing_time * 1000) {
  5. interruptHandler();
  6. last_micros = micros();
  7. }
  8. }
复制代码

interruptHandler()函数中,调用xSemaphoreGiveFromISR()函数。

  1. void interruptHandler() {
  2. xSemaphoreGiveFromISR(interruptSemaphore, NULL);
  3. }
复制代码

此函数将向TaskLed提供信号量以打开LED。


5.  创建一个TaskLed函数,并在while循环内,调用xSemaphoreTake()API并检查信号量是否成功获取。如果等于pdPASS(即1),则如下图所示使LED切换。

  1. void TaskLed(void *pvParameters)
  2. {
  3. (void) pvParameters;
  4. pinMode(8, OUTPUT);
  5. while(1) {
  6. if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) {
  7. digitalWrite(8, !digitalRead(8));
  8. }  
  9. }
  10. }
复制代码

6.  此外,创建一个函数来使连接到引脚7的其他LED闪烁。

  1. void TaskLed1(void *pvParameters)
  2. {
  3. (void) pvParameters;
  4. pinMode(7, OUTPUT);
  5. while(1) {
  6. digitalWrite(7, HIGH);
  7. vTaskDelay(200 / portTICK_PERIOD_MS);
  8. digitalWrite(7, LOW);
  9. vTaskDelay(200 / portTICK_PERIOD_MS);
  10. }
  11. }
复制代码

7. void loop函数保持为空。、

  1. void loop() {}
复制代码

以上就是全部代码。现在,上传此代码,并根据电路图将LED和按钮与Arduino UNO连接。


电路原理图

FreeRTOS-Circuit-Diagram.jpg

上载代码后,您将看到一个LED在200毫秒后闪烁,并且当按下按钮时,第二个LED将立即点亮。

Semaphore-in-FreeRTOS-with-Arduino.jpg


这样,信号量可以在Arduino的FreeRTOS中使用,它需要将数据从一个任务传递到另一个任务而不会造成任何损失。


现在,让我们看看什么是互斥量以及如何在FreeRTOS使用它。



什么是互斥量?

如上所述,信号量是一种信号传递机制,与互斥量不同,互斥量(Mutex)是一种锁定机制,与具有独立的增量和递减功能的信号量不同,但在互斥量中,该功能本身具有并给出。这是一种避免共享资源损坏的技术。


为了保护共享资源,可以为资源分配一个令牌卡(互斥体)。拥有此卡的人都可以访问其他资源。其他人应该等到卡归还。这样,只有一种资源可以访问任务,而其他资源则等待机会。


让我们借助示例来了解FreeRTOS中的互斥量。


这里我们有三个任务,一个任务是在LCD上打印数据,第二个任务是将LDR数据发送到LCD任务,最后一个任务是在LCD上发送温度数据。因此,这里有两个任务共享同一个资源,即LCD。如果LDR任务和温度任务同时发送数据,则数据之一可能已损坏或丢失。


因此,为了保护数据丢失,我们需要为任务1锁定LCD资源,直到它完成显示任务为止。然后LCD任务将解锁,然后task2可以执行其工作。


您可以在下图中观察互斥量和信号量的工作。

Mutex-Vs-Semaphore.png


如何在FreeRTOS中使用互斥量?

互斥量也可以与信号量相同地使用。首先,创建它,然后使用相应的API进行使用。


1.  创建互斥量

要创建一个互斥量,请使用xSemaphoreCreateMutex()。顾名思义,互斥量是一种二进制信号量。它们用于不同的上下文和目的。二进制信号量用于同步任务,而互斥量用于保护共享资源。


该API没有任何参数,并返回SemaphoreHandle_t类型的变量。如果无法创建互斥量,则xSemaphoreCreateMutex()返回NULL。

  1. SemaphoreHandle_t mutex_v;
  2. mutex_v = xSemaphoreCreateMutex();
复制代码

2.  获取互斥量

当任务想要访问资源时,它将通过使用xSemaphoreTake()来获取互斥对象。它与二进制信号量相同。它还需要两个参数。


xSemaphore:将使用的互斥对象的名称,本例为Mutex_v。

xTicksToWait:这是任务在“阻止”状态下等待Mutex可用的最长时间。在本文中,我们将xTicksToWait设置为portMAX_DELAY,以使task_1在Blocked状态下无限期等待,直到mutex_v可用为止。


3.  提供互斥量

访问共享资源后,任务应返回互斥量,以便其他任务可以访问它。 xSemaphoreGive()用于将互斥量返回。


xSemaphoreGive()函数仅采用一个参数,即在给出的互斥量,本例中为mutex_v。


使用上述API,让我们使用Arduino IDE在FreeRTOS代码中实现Mutex。


互斥量代码说明

在这里,此部分的目标是使用串口监视器作为共享资源,并执行两个不同的任务来访问串口监视器以打印一些消息。


1.  头文件将保持与信号量相同。

  1. #include <Arduino_FreeRTOS.h>
  2. #include <semphr.h>
复制代码

2.声明一个SemaphoreHandle_t类型的变量来存储互斥量的值。

  1. SemaphoreHandle_t mutex_v;
复制代码

3.在void setup()中,以9600波特率初始化串口监视器,并使用xTaskCreate()创建两个任务(Task1和Task2)。然后使用xSemaphoreCreateMutex()创建一个互斥量。创建一个具有相同优先级的任务,稍后再尝试使用任务。

  1. void setup() {
  2. Serial.begin(9600);
  3. mutex_v = xSemaphoreCreateMutex();
  4. if (mutex_v == NULL) {
  5. Serial.println("Mutex can not be created");
  6. }
  7. xTaskCreate(Task1, "Task 1", 128, NULL, 1, NULL);
  8. xTaskCreate(Task2, "Task 2", 128, NULL, 1, NULL);
  9. }
复制代码

4.  现在,为Task1和Task2创建任务函数。在任务函数的while循环中,在串口监视器上打印消息之前,我们必须使用xSemaphoreTake()获取Mutex,然后打印消息,然后使用xSemaphoreGive()返回互斥量。

  1. void Task1(void *pvParameters) {
  2. while(1) {
  3. xSemaphoreTake(mutex_v, portMAX_DELAY);
  4. Serial.println("Hi from Task1");
  5. xSemaphoreGive(mutex_v);
  6. vTaskDelay(pdMS_TO_TICKS(1000));
  7. }
  8. }
复制代码

同样,以500ms的延迟实现Task2函数。


5.void loop()代码保持为空。


现在,将此代码上传到Arduino UNO并打开串行监视器。


您将看到消息正在从task1和task2打印。

Arduino-UNO-Serial-Monitor.png


要测试互斥量的工作,只需在任一任务中注释xSemaphoreGive(mutex_v);即可。您可以看到该程序停止在最后一条打印消息上。

FreeRTOS-Code.png


以上就是使用Arduino在FreeRTOS中实现信号量和互斥量的方式。有关信号量和互斥量的更多信息,您可以访问FreeRTOS的官方文档


代码

以下是本文使用的完整代码: main.rar (823 Bytes, 下载次数: 24)

跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 700 | 回复: 1477



手机版|

GMT+8, 2024-3-19 19:20 , Processed in 0.076317 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表