信号量和互斥(互斥)是用于同步、资源管理和保护资源免受损坏的内核对象。在本教程的前半部分,我们将了解Semaphore背后的理念,以及如何以及在何处使用它。
什么是信号量?
信号量是一种信号机制,其中处于等待状态的任务由另一个任务发出信号以执行。换句话说,当一个task1完成它的工作时,它会显示一个标志或将一个标志加1,然后另一个任务(task2)收到这个标志,表明它现在可以执行它的工作了。当 task2 完成其工作时,标志将减 1。
因此,基本上,它是一种“给予”和“接受”机制,而信号量是一个整数变量,用于同步对资源的访问。
FreeRTOS 中的信号量类型:
信号量有两种类型。
二进制信号量
计数信号量
1、Binary Semaphore:它有两个整数值0和1。有点类似于长度为1的Queue。比如我们有两个task,task1和task2。task1向task2发送数据,因此task2不断检查队列项,如果有1,那么它可以读取数据,否则它必须等到它变成1。取完数据后,task2将队列递减,使其为0,即task1再次可以将数据发送到task2。
从上面的例子可以说,二进制信号量是用于任务之间或任务与中断之间的同步。
2. Counting Semaphore:它的值大于0,可以认为是长度大于1的队列。这个semaphore用于对事件进行计数。在这种使用场景中,事件处理程序将在每次事件发生时“给予”一个信号量(增加信号量计数值),而处理程序任务将在每次处理事件时“获取”一个信号量(减少信号量计数值) 。
因此,计数值是已发生的事件数与已处理的事件数之差。
现在,让我们看看如何在我们的 FreeRTOS 代码中使用 Semaphore。
如何在 FreeRTOS 中使用信号量?
FreeRTOS 支持用于创建信号量、获取信号量和提供信号量的不同 API。
现在,同一个内核对象可以有两种类型的 API。如果我们必须从 ISR 提供信号量,则无法使用正常的信号量 API。您应该使用受中断保护的 API。
在本教程中,我们将使用二进制信号量,因为它易于理解和实现。由于此处使用了中断功能,因此您需要在 ISR 功能中使用受中断保护的 API。当我们说将任务与中断同步时,这意味着在 ISR 之后立即将任务置于运行状态。
创建信号量:
要使用任何内核对象,我们必须首先创建它。要创建二进制信号量,请使用vSemaphoreCreateBinary()。
此 API 不接受任何参数,并返回 SemaphoreHandle_t 类型的变量。创建一个全局变量名sema_v来存储信号量。
SemaphoreHandle_t sema_v;
sema_v = xSemaphoreCreateBinary();
给出信号量:
对于提供信号量,有两种版本——一种用于中断,另一种用于正常任务。
xSemaphoreGive():这个 API 只接受一个参数,它是信号量的变量名,如上面在创建信号量时给出的 sema_v。它可以从您想要同步的任何正常任务中调用。
xSemaphoreGiveFromISR():这是 xSemaphoreGive() 的受中断保护的 API 版本。当我们需要同步 ISR 和普通任务时,应该从 ISR 函数中使用 xSemaphoreGiveFromISR()。
获取信号量:
要获取信号量,请使用 API 函数xSemaphoreTake()。这个 API 有两个参数。
xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore:在我们的案例 sema_v 中要采用的信号量的名称。
xTicksToWait:这是任务在阻塞状态下等待信号量变为可用的最长时间。在我们的项目中,我们将 xTicksToWait 设置为portMAX_DELAY以使 task_1 无限期地等待阻塞状态,直到 sema_v 可用。
现在,让我们使用这些 API 并编写代码来执行一些任务。
这里连接了一个按钮和两个 LED。按钮将充当连接到 Arduino Uno 引脚 2 的中断按钮。按下此按钮时将产生中断,连接到引脚 8 的 LED 将打开,再次按下时将关闭。
因此,当按下按钮时,将从 ISR 函数调用xSemaphoreGiveFromISR (),从 TaskLED 函数调用 xSemaphoreTake() 函数。
为了使系统看起来多任务,将其他 LED 连接到引脚 7,引脚 7 将始终处于闪烁状态。
信号量代码说明
让我们通过打开 Arduino IDE 开始编写代码
1. 首先,包含Arduino_FreeRTOS.h头文件。现在,如果使用任何内核对象,如队列信号量,则还必须包含一个头文件。
#include#include
2.声明一个SemaphoreHandle_t类型的变量来存储信号量的值。
SemaphoreHandle_t 中断信号量;
3. 在 void setup() 中,使用 xTaskCreate() API 创建两个任务(TaskLED 和 TaskBlink),然后使用 xSemaphoreCreateBinary() 创建一个信号量。创建一个具有相同优先级的任务,然后尝试使用这个数字。此外,将引脚 2 配置为输入并启用内部上拉电阻并连接中断引脚。最后,启动调度程序,如下所示。
无效设置(){ pinMode(2,INPUT_PULLUP); xTaskCreate(TaskLed, "Led", 128, NULL, 0, NULL ); xTaskCreate(TaskBlink, "LedBlink", 128, NULL, 0, NULL ); 中断信号量 = xSemaphoreCreateBinary(); if (interruptSemaphore != NULL) { attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW); } }
4. 现在,实现 ISR 功能。创建一个函数并将其命名为与attachInterrupt()函数的第二个参数相同。为了使中断正常工作,您需要使用millis或micros功能并通过调整去抖时间来消除按钮的去抖问题。从此函数调用interruptHandler() 函数,如下所示。
长去抖时间 = 150; volatile unsigned long last_micros; void debounceInterrupt() { if((long)(micros() - last_micros) >= debounce_time * 1000) { interruptHandler(); last_micros = micros(); } }
在interruptHandler()函数中,调用xSemaphoreGiveFromISR() API。
void interruptHandler() { xSemaphoreGiveFromISR(interruptSemaphore, NULL); }
这个函数会给TaskLed一个信号量来打开LED。
5. 创建一个TaskLed函数并在while循环中调用xSemaphoreTake() API 并检查信号量是否被成功获取。如果它等于 pdPASS(即 1),则使 LED 切换如下所示。
void TaskLed(void *pvParameters) { (void) pvParameters; pinMode(8,输出); while(1) { if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite(8, !digitalRead(8)); } } }
6. 另外,创建一个函数来闪烁连接到引脚 7 的其他 LED。
void TaskLed1(void *pvParameters) { (void) pvParameters; pinMode(7,输出); 而(1){ 数字写入(7,高); vTaskDelay(200 / portTICK_PERIOD_MS); 数字写入(7,低); vTaskDelay(200 / portTICK_PERIOD_MS); } }
7. void 循环函数将保持为空。不要忘记它。
无效循环(){}
就是这样,完整的代码可以在本教程的末尾找到。现在,上传此代码并根据电路图将 LED 和按钮与 Arduino UNO 连接起来。
电路原理图
上传代码后,您会看到一个 LED 在 200 毫秒后闪烁,当按下按钮时,第二个 LED 会立即发光,如最后给出的视频所示。
通过这种方式,信号量可以在带有 Arduino 的 FreeRTOS 中使用,它需要将数据从一个任务传递到另一个任务而不会造成任何损失。
现在,让我们看看什么是 Mutex 以及如何使用 FreeRTOS。
什么是互斥锁?
如上所述,信号量是一种信号机制,类似地,Mutex 是一种锁定机制,与信号量不同,信号量具有单独的递增和递减函数,但在 Mutex 中,函数本身接受和给出。这是一种避免共享资源损坏的技术。
为了保护共享资源,需要为资源分配一个令牌卡(互斥体)。拥有这张卡的人可以访问其他资源。其他人应该等到卡归还。这样,只有一个资源可以访问任务,其他资源等待机会。
让我们通过一个例子来了解FreeRTOS 中的 Mutex 。
这里我们有三个任务,一个用于在 LCD 上打印数据,第二个用于将 LDR 数据发送到 LCD 任务,最后一个任务用于在 LCD 上发送温度数据。所以这里两个任务共享相同的资源,即 LCD。如果 LDR 任务和温度任务同时发送数据,则其中一个数据可能损坏或丢失。
因此,为了防止数据丢失,我们需要锁定 task1 的 LCD 资源,直到它完成显示任务。然后 LCD 任务将解锁,然后 task2 可以执行其工作。
您可以在下图中观察互斥量和信号量的工作。
如何在 FreeRTOS 中使用互斥锁?
互斥量的使用方式也与信号量相同。首先,创建它,然后使用各自的 API 提供和获取。
创建互斥锁:
要创建互斥体,请使用xSemaphoreCreateMutex() API。顾名思义,互斥量是一种二进制信号量。它们用于不同的上下文和目的。二进制信号量用于同步任务,而 Mutex 用于保护共享资源。
此 API 不接受任何参数并返回SemaphoreHandle_t类型的变量。如果无法创建互斥锁,则xSemaphoreCreateMutex()返回 NULL。
SemaphoreHandle_t mutex_v;
mutex_v = xSemaphoreCreateMutex();
采取互斥锁:
当任务想要访问资源时,它将使用xSemaphoreTake() API 获取 Mutex。它与二进制信号量相同。它还需要两个参数。
xSemaphore:在我们的例子中使用的 Mutex 的名称mutex_v。
xTicksToWait:这是任务在阻塞状态下等待 Mutex 可用的最长时间。在我们的项目中,我们将 xTicksToWait 设置为portMAX_DELAY以使 task_1 在 Blocked 状态下无限期等待,直到mutex_v可用。
提供互斥锁:
访问共享资源后,任务应该返回 Mutex,以便其他任务可以访问它。xSemaphoreGive() API 用于返回 Mutex。
xSemaphoreGive() 函数只接受一个参数,即在我们的案例 mutex_v 中给出的 Mutex。
使用上述 API,让我们使用 Arduino IDE 在 FreeRTOS 代码中实现 Mutex。
互斥代码说明
这部分的目标是使用串行监视器作为共享资源和两个不同的任务来访问串行监视器以打印一些消息。
1. 头文件将保持与信号量相同。
#include#include
2. 声明一个SemaphoreHandle_t类型的变量来存储 Mutex 的值。
SemaphoreHandle_t mutex_v;
3. 在void setup() 中,以 9600 波特率初始化串行监视器,并使用xTaskCreate() API 创建两个任务(Task1 和 Task2)。然后使用xSemaphoreCreateMutex()创建一个 Mutex 。创建一个具有相同优先级的任务,然后尝试使用这个数字。
无效设置(){ 序列.开始(9600); mutex_v = xSemaphoreCreateMutex(); if (mutex_v == NULL) { Serial.println("无法创建互斥锁"); } xTaskCreate(Task1, "任务 1", 128, NULL, 1, NULL); xTaskCreate(Task2, "任务 2", 128, NULL, 1, NULL); }
4. 现在,为Task1 和Task2 制作任务函数。在任务函数的while循环中,在串行监视器上打印消息之前,我们必须使用xSemaphoreTake()获取 Mutex ,然后打印消息,然后使用xSemaphoreGive() 返回 Mutex。然后再拖延一些时间。
void Task1(void *pvParameters) { while(1) { xSemaphoreTake(mutex_v, portMAX_DELAY); Serial.println("来自 Task1 的您好"); xSemaphoreGive(mutex_v); vTaskDelay(pdMS_TO_TICKS(1000)); } }
同理,实现延迟500ms的Task2函数。
5. void loop()将保持为空。
现在,将此代码上传到 Arduino UNO 并打开串行监视器。
您将看到正在从 task1 和 task2 打印消息。
要测试 Mutex 的工作,只需注释xSemaphoreGive(mutex_v); 从任何任务。您可以看到程序挂在最后一条打印消息上。
这就是使用 Arduino 在 FreeRTOS 中实现信号量和互斥量的方式。
信号量代码:
#include
#include
长去抖时间 = 150;
volatile unsigned long last_micros;
SemaphoreHandle_t 中断信号量;
无效设置(){
pinMode(2, INPUT_PULLUP);
xTaskCreate(TaskLed, "Led", 128, NULL, 0, NULL );
xTaskCreate(TaskBlink, "LedBlink", 128, NULL, 0, NULL );
中断信号量 = xSemaphoreCreateBinary();
如果(中断信号量!= NULL){
attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW);
}
}
无效循环(){}
无效中断处理程序(){
xSemaphoreGiveFromISR(interruptSemaphore, NULL);
}
void TaskLed(void *pvParameters)
{
(void) pvParameters;
pinMode(8,输出);
为了 (;;) {
if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) {
数字写入(8,!数字读取(8));
}
}
}
void TaskBlink(void *pvParameters)
{
(void) pvParameters;
pinMode(7,输出);
为了 (;;) {
数字写入(7,高);
vTaskDelay(200 / portTICK_PERIOD_MS);
数字写入(7,低);
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
无效去抖中断(){
if((long)(micros() - last_micros) >= debouncing_time * 1000) {
中断处理程序();
last_micros = micros();
}
}
互斥体代码:
#include
#include
SemaphoreHandle_t mutex_v;
无效设置(){
序列号.开始(9600);
mutex_v = xSemaphoreCreateMutex();
if (mutex_v == NULL) {
Serial.println("无法创建互斥锁");
}
xTaskCreate(Task1, "Task1", 128, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", 128, NULL, 1, NULL);
}
无效任务1(无效* pvParameters){
而(1){
xSemaphoreTake(mutex_v, portMAX_DELAY);
Serial.println("来自 Task1 的您好");
xSemaphoreGive(mutex_v);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void Task2(void *pvParameters) {
而(1){
xSemaphoreTake(mutex_v, portMAX_DELAY);
Serial.println("来自 Task2 的您好");
xSemaphoreGive(mutex_v);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
无效循环(){
}
全部0条评论
快来发表一下你的评论吧 !