0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用Arduino在FreeRTOS中实现信号量和互斥量的方式

科技观察员 来源:circuitdigest 作者:Rishabh Jain 2022-08-16 15:34 次阅读

信号量和互斥(互斥)是用于同步、资源管理和保护资源免受损坏的内核对象。在本教程的前半部分,我们将了解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 连接起来。

电路原理

poYBAGL7SCGAG-2xAAQwgqHUXRE555.png

上传代码后,您会看到一个 LED 在 200 毫秒后闪烁,当按下按钮时,第二个 LED 会立即发光,如最后给出的视频所示。

pYYBAGL7SB2AZJtlAAZ3UKhATq8320.png

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

现在,让我们看看什么是 Mutex 以及如何使用 FreeRTOS。

什么是互斥锁?

如上所述,信号量是一种信号机制,类似地,Mutex 是一种锁定机制,与信号量不同,信号量具有单独的递增和递减函数,但在 Mutex 中,函数本身接受和给出。这是一种避免共享资源损坏的技术。

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

让我们通过一个例子来了解FreeRTOS 中的 Mutex 。

这里我们有三个任务,一个用于在 LCD 上打印数据,第二个用于将 LDR 数据发送到 LCD 任务,最后一个任务用于在 LCD 上发送温度数据。所以这里两个任务共享相同的资源,即 LCD。如果 LDR 任务和温度任务同时发送数据,则其中一个数据可能损坏或丢失。

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

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

pYYBAGL7SBiAaPDtAAFXiAZad48919.png

如何在 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 打印消息。

poYBAGL7SBCAUrmqAAElUa0KgQI681.png

要测试 Mutex 的工作,只需注释xSemaphoreGive(mutex_v); 从任何任务。您可以看到程序挂在最后一条打印消息上。

poYBAGL7SAuAFdn1AAF9_V6mP5g682.png

这就是使用 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));
}
}

无效循环(){
}

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61347
  • Arduino
    +关注

    关注

    184

    文章

    6427

    浏览量

    184832
  • 信号量
    +关注

    关注

    0

    文章

    53

    浏览量

    8257
收藏 人收藏

    评论

    相关推荐

    UCOS-II:对于信号量互斥信号量,事件标志组的个人理解-转

    。也就是要独占共享资源的访问权~!ucos2通过互斥信号量来解决这个问题。简单说就是任务1开始访问打印机的时候,先去查询这个互斥信号量是否
    发表于 12-10 21:16

    转:freeRTOS信号量学习

    信号量同样是RTOS学习很重要的一节,信号量可以用在共享资源或者同步任务,对执行权的控制,谁拥有信号量谁拥有执行权,
    发表于 08-12 18:29

    转:第23章 FreeRTOS互斥信号量

    FreeRTOS互斥信号量的源码实现是基于消息队列实现的。本章教程配套的例子含Cortex
    发表于 09-06 14:58

    第15章 互斥信号量

    15.1 互斥信号量15.1.1互斥信号量的概念及其作用 互斥信号量就是
    发表于 10-06 16:40

    ucos信号量互斥信号量、事件标志组的理解

    。在此不做介绍下面这个图说明了以上的例子:(3.事件标志组: 在理解信号量互斥信号量的时候都可以类比,因为他们ucos2里面都通过相同的时间控制块即ECB这个数据结构来
    发表于 08-23 10:35

    信号量互斥信号量该怎么选择?

    既然说信号量可能会导致优先级反转,那全都在工程里使用互斥信号不就行了?还要信号量干啥?大家一起用互斥信号
    发表于 08-26 03:14

    关于UCOSIII的信号量互斥信号量的理解?

    UCOSIII延时一定会引起任务切换,如果所有任务都进入等待态,则切换到空闲任务运行?请求信号量,如果信号量值非零,不进行任务切换;为零,(等待超时后?或者一般都是设置死等)进行任
    发表于 03-13 00:11

    信号量互斥信号量理解

    UCOSIII信号量如果要PEND的话,那这个信号量的cnt必须大于等于1才可以(需要在创建的时候设置第三个参数cnt为1或者,先POST一下才可以)。这个理解对吗?
    发表于 04-21 02:46

    例程使用互斥信号量初始化如何设置?

    OS_MUTEXTEST_MUTEX; //定义一个互斥信号量//创建一个互斥信号量OSMutexCreate((OS_MUTEX*)&TEST_MUTEX, (CPU_CHAR
    发表于 06-02 16:22

    freertos互斥信号量能不能在不同任务释放和获取

    都行。而我在网上查资料的时候,看网上有人说互斥信号量的获取于释放必须在同一个任务完成,并且我看正点的例程使用互斥
    发表于 07-18 08:00

    信号量是什么?信号量怎么运作

    任务只能等待),常用于协助一组相互竞争的任务来访问临界资源。多任务系统,各任 务之间需要同步(一个任务的完成的瞬间另一个任务开始)或互斥实现临界资源的保护,
    发表于 01-05 08:09

    FreeRTOS信号量介绍

    FreeRTOS信号量 & ESP32实战阅读建议:有一定操作系统基础知识。FreeRTOS信号量1. 二值信号量  二值
    发表于 01-27 07:28

    FreeRTOS互斥信号量是怎样去控制LED亮灭的

    什么是互斥信号量呢?FreeRTOS互斥信号量是怎样去控制LED亮灭的?
    发表于 02-28 06:38

    FreeRTOS信号量的相关资料推荐

    一、互斥信号量简介互斥信号量其实就是一个拥有优先级继承的二值信号量同步的应用
    发表于 02-28 13:39

    FreeRTOS的二值信号量

    FreeRTOS中的信号量是一种任务间通信的方式信号量包括:二值信号量互斥
    的头像 发表于 02-10 15:07 936次阅读