|
在之前的文章中,我们已经介绍了在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来存储信号量。 - SemaphoreHandle_t sema_v;
- sema_v = xSemaphoreCreateBinary();
复制代码
2. 提供信号量 为了提供信号量,有两个API函数,-一种用于中断,另一种用于常规任务。 1. xSemaphoreGive():此API在创建信号量时仅接受一个参数,即上述信号量的变量名,如sema_v。可以从要同步的任何常规任务中调用它。 2. xSemaphoreGiveFromISR():该函数是xSemaphoreGive()的受中断保护的API函数。当需要同步ISR和正常任务时,应从ISR函数中使用xSemaphoreGiveFromISR()。
3. 获取信号量 要获取信号量,请使用API函数xSemaphoreTake()。该API带有两个参数。 - 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头文件。现在,如果使用任何内核对象(例如队列信号量),那么还必须为其包含头文件。 - #include <Arduino_FreeRTOS.h>
- #include <semphr.h>
复制代码2. 声明一个SemaphoreHandle_t类型的变量来存储信号量的值。 - SemaphoreHandle_t interruptSemaphore;
复制代码3. 在void setup()中,使用xTaskCreate创建两个任务(TaskLED和TaskBlink),然后使用xSemaphoreCreateBinary()创建一个信号量。创建一个具有相同优先级的任务,稍后尝试使用此任务。另外,将引脚2配置为输入,并启用内部上拉电阻并连接中断引脚。最后,启动调度程序,如下所示。 - void setup() {
- pinMode(2, INPUT_PULLUP);
- xTaskCreate(TaskLed, "Led", 128, NULL, 0, NULL );
- xTaskCreate(TaskBlink, "LedBlink", 128, NULL, 0, NULL );
- interruptSemaphore = xSemaphoreCreateBinary();
- if (interruptSemaphore != NULL) {
- attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW);
- }
- }
复制代码
4. 现在,实现ISR函数。创建一个函数,并将其命名为与attachInterrupt()函数的第二个参数相同的名称。为了使中断正常工作,您需要使用毫秒函数并通过调整时间来消除按钮的抖动问题。从此函数中,如下所示调用interruptHandler()函数。 - long debouncing_time = 150;
- volatile unsigned long last_micros;
- void debounceInterrupt() {
- if((long)(micros() - last_micros) >= debouncing_time * 1000) {
- interruptHandler();
- last_micros = micros();
- }
- }
复制代码在interruptHandler()函数中,调用xSemaphoreGiveFromISR()函数。 - void interruptHandler() {
- xSemaphoreGiveFromISR(interruptSemaphore, NULL);
- }
复制代码此函数将向TaskLed提供信号量以打开LED。
5. 创建一个TaskLed函数,并在while循环内,调用xSemaphoreTake()API并检查信号量是否成功获取。如果等于pdPASS(即1),则如下图所示使LED切换。 - void TaskLed(void *pvParameters)
- {
- (void) pvParameters;
- pinMode(8, OUTPUT);
- while(1) {
- if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) {
- digitalWrite(8, !digitalRead(8));
- }
- }
- }
复制代码
6. 此外,创建一个函数来使连接到引脚7的其他LED闪烁。 - void TaskLed1(void *pvParameters)
- {
- (void) pvParameters;
- pinMode(7, OUTPUT);
- while(1) {
- digitalWrite(7, HIGH);
- vTaskDelay(200 / portTICK_PERIOD_MS);
- digitalWrite(7, LOW);
- vTaskDelay(200 / portTICK_PERIOD_MS);
- }
- }
复制代码7. void loop函数保持为空。、 以上就是全部代码。现在,上传此代码,并根据电路图将LED和按钮与Arduino UNO连接。
电路原理图
上载代码后,您将看到一个LED在200毫秒后闪烁,并且当按下按钮时,第二个LED将立即点亮。
这样,信号量可以在Arduino的FreeRTOS中使用,它需要将数据从一个任务传递到另一个任务而不会造成任何损失。
现在,让我们看看什么是互斥量以及如何在FreeRTOS使用它。
什么是互斥量? 如上所述,信号量是一种信号传递机制,与互斥量不同,互斥量(Mutex)是一种锁定机制,与具有独立的增量和递减功能的信号量不同,但在互斥量中,该功能本身具有并给出。这是一种避免共享资源损坏的技术。
为了保护共享资源,可以为资源分配一个令牌卡(互斥体)。拥有此卡的人都可以访问其他资源。其他人应该等到卡归还。这样,只有一种资源可以访问任务,而其他资源则等待机会。
让我们借助示例来了解FreeRTOS中的互斥量。
这里我们有三个任务,一个任务是在LCD上打印数据,第二个任务是将LDR数据发送到LCD任务,最后一个任务是在LCD上发送温度数据。因此,这里有两个任务共享同一个资源,即LCD。如果LDR任务和温度任务同时发送数据,则数据之一可能已损坏或丢失。
因此,为了保护数据丢失,我们需要为任务1锁定LCD资源,直到它完成显示任务为止。然后LCD任务将解锁,然后task2可以执行其工作。
您可以在下图中观察互斥量和信号量的工作。
如何在FreeRTOS中使用互斥量? 互斥量也可以与信号量相同地使用。首先,创建它,然后使用相应的API进行使用。
1. 创建互斥量 要创建一个互斥量,请使用xSemaphoreCreateMutex()。顾名思义,互斥量是一种二进制信号量。它们用于不同的上下文和目的。二进制信号量用于同步任务,而互斥量用于保护共享资源。
该API没有任何参数,并返回SemaphoreHandle_t类型的变量。如果无法创建互斥量,则xSemaphoreCreateMutex()返回NULL。 - SemaphoreHandle_t mutex_v;
- 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. 头文件将保持与信号量相同。 - #include <Arduino_FreeRTOS.h>
- #include <semphr.h>
复制代码
2.声明一个SemaphoreHandle_t类型的变量来存储互斥量的值。 - SemaphoreHandle_t mutex_v;
复制代码
3.在void setup()中,以9600波特率初始化串口监视器,并使用xTaskCreate()创建两个任务(Task1和Task2)。然后使用xSemaphoreCreateMutex()创建一个互斥量。创建一个具有相同优先级的任务,稍后再尝试使用任务。 - void setup() {
- Serial.begin(9600);
- mutex_v = xSemaphoreCreateMutex();
- if (mutex_v == NULL) {
- Serial.println("Mutex can not be created");
- }
- xTaskCreate(Task1, "Task 1", 128, NULL, 1, NULL);
- xTaskCreate(Task2, "Task 2", 128, NULL, 1, NULL);
- }
复制代码
4. 现在,为Task1和Task2创建任务函数。在任务函数的while循环中,在串口监视器上打印消息之前,我们必须使用xSemaphoreTake()获取Mutex,然后打印消息,然后使用xSemaphoreGive()返回互斥量。 - void Task1(void *pvParameters) {
- while(1) {
- xSemaphoreTake(mutex_v, portMAX_DELAY);
- Serial.println("Hi from Task1");
- xSemaphoreGive(mutex_v);
- vTaskDelay(pdMS_TO_TICKS(1000));
- }
- }
复制代码同样,以500ms的延迟实现Task2函数。
5.void loop()代码保持为空。
现在,将此代码上传到Arduino UNO并打开串行监视器。
您将看到消息正在从task1和task2打印。
要测试互斥量的工作,只需在任一任务中注释xSemaphoreGive(mutex_v);即可。您可以看到该程序停止在最后一条打印消息上。
以上就是使用Arduino在FreeRTOS中实现信号量和互斥量的方式。有关信号量和互斥量的更多信息,您可以访问FreeRTOS的官方文档。
代码 以下是本文使用的完整代码:
main.rar
(823 Bytes, 下载次数: 24)
|