arduino使用FreeRTOS笔记

作用

一般的代码执行逻辑如下:

请添加图片描述

先在Setup中完成初始化,再在Loop中执行所有任务的大循环,如果有需要则会进入中断函数解决突发事件。

而RTOS可以将所有任务分离(多任务),分别运行。

请添加图片描述

Task任务

任务函数

不同的任务需要不同的task函数,函数中应该有一个while死循环相当于原来的loop函数,而while循环外的相当于原来的setup函数

void task1(void *pt){
  pinMode(23,OUTPUT);
  while(1){
    digitalWrite(23,!digitalRead(23));
    vTaskDelay(1000);
  }
}

任务状态

请添加图片描述

开启任务

创建完函数后要在setup函数中开启任务

TaskHandle_t Handle = NULL;

void setup() {
  xTaskCreate(task1,		/*任务函数*/
              "Blink23",	/*任务名*/
              1024,			/*内存大小,内存大小以字节为单位*/
              NULL,			/*传入的参数*/
              1,			/*任务的优先级*/
              Handle);		/*操作指针(句柄),如果不需要后续操作可以写NULL*/
}

暂停任务

if (Handle != NULL) {
	vTaskSuspend(Handle);
}

恢复任务

if (Handle != NULL) {
	vTaskResume(Handle);
}

删除任务

参数为任务句柄,如果为NULL则结束写该语句的任务。

一般可以在setup()任务运行完成后直接结束。

void setup() {	//loopBack, Priority 1, Core 1
  xTaskCreate(task1, "Blink23", 1024, NULL, 1, NULL);
  vTaskDelete(NULL);
}

在删除任务之前一定要判断任务是否存在

if (biliHandle != NULL) {
	vTaskDelete(Handle);
	Handle = NULL; //手动将handler设置为空
}

任务参数

任务传参

传递单个参数

以传入引脚点灯为例

byte LED_PIN = 23;

void setup(){
    Serial.begin(9600);
    
    if (xTaskCreate(task,
                    "Blink",
               		1024,
               		(void*)&LED_PIN,
               		1,
               		NULL) == pdPASS)
    	Serial.println("task created");
}

void loop(){

}

void task(void *pt){
	byte LEDPIN = *(byte *)pt;
	pinMode(LEDPIN, OUTPUT);
	while(1){
        digitalWrite(LEDPIN, !digitalRead(LEDPIN));
    	vTaskDelay(1000);
  	}
}

传递多个参数

使用结构体的方式,传输结构体达到传递多个参数

typedef struct {
    byte pin;
    int delayTime;
} LEDFLASH;

void setup(){
    Serial.begin(9600);
    
    LEDFLASH led1, led2;
    led1.pin = 23;
    led1.delayTime = 1000;
    led2.pin = 21;
    led2.delayTime = 3000;
    
    if (xTaskCreate(task,
                    "LED FLASH",
               		1024,
               		(void*)&led1,
               		1,
               		NULL) == pdPASS)
    	Serial.println("led1 task created");
    if (xTaskCreate(task,
                    "LED FLASH",
               		1024,
               		(void*)&led2,
               		1,
               		NULL) == pdPASS)
    	Serial.println("ltask created");
}

void loop(){

}

void task(void *pt){
	LEDFLASH ledflash = (LEDFLASH *)pt;
    byte pin = ledflash->pin;
    byte delayTime = ledflash->delayTime;
        
	pinMode(pin, OUTPUT);
	while(1){
        digitalWrite(pin, !digitalRead(pin));
    	vTaskDelay(delayTime);
  	}
}

任务共享参数

全局变量

局限:只有一个任务进行写操作,可以有多个任务进行读操作

注意:被多线程操作的变量应和单片机的字长相同(ESP32是32位的单片机,所以应使用uint32_t的变量)

//被多进程和中断调用的变量使用 volatile 修饰符
volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量

void retailTask(void *pvParam) {
  while (1) {
    //以下实现了带有随机延迟的 inventory减1;
    //等效为 inventory--; retailCount++;
    uint32_t inv = inventory;
    for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    if (inventory > 0) {
      inventory = inv - 1;
      retailCount++;
    }
  };
  vTaskDelay(10);
}

void showTask(void *pvParam) {
  while (1) {
    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d\n", retailCount);
    if (inventory == 0 ) {
      printf("\n-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n\n", retailCount);
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}

void setup() {
  Serial.begin(115200);

  xTaskCreate(retailTask,
              "Online Channel",
              1024 * 4,
              NULL,
              1,
              NULL);

  xTaskCreate(showTask,
              "Display Inventory",
              1024 * 4,
              NULL,
              1,
              NULL);

}

void loop() {
}

MUTEX

MUTEX就像一把锁,只有拿到锁的线程才能对数据进行操作

对共享的数据必须上锁

volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量
volatile uint32_t onlineCount = 0; //线上销售量

SemaphoreHandle_t xMutexInventory = NULL; //创建信号量Handler

TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks

void retailTask(void *pvParam) {
  while (1) {
    // 在timeout的时间内如果能够获取就继续
    // 获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        retailCount++;
        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }
    };
    vTaskDelay(100);
  }
}

void onlineTask(void *pvParam) {
  while (1) {
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        onlineCount++;
        xSemaphoreGive(xMutexInventory);
      } else {
          
      }
    };
    vTaskDelay(100);
  }
}


void showTask(void *pvParam) {
  while (1) {
    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d, Online : %d\n", retailCount, onlineCount);

    if (inventory == 0 ) {
      uint32_t totalSales = retailCount + onlineCount;
      printf("-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n", totalSales);
      printf("  OverSales:  %d\n", 100 - totalSales);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  Serial.begin(115200);

  xMutexInventory = xSemaphoreCreateMutex(); //创建MUTEX

  if (xMutexInventory == NULL) {
    printf("No Enough Ram, Unable to Create Semaphore.");
  } else {
    xTaskCreate(onlineTask,
                "Online Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(retailTask,
                "Retail Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(showTask,
                "Display Inventory",
                1024 * 4,
                NULL,
                1,
                NULL);
  }
}

void loop() {
}

语法

  • SemaphoreHandle_t xHandler;
    • 功能:创建Handler
  • xHandler = xSemaphoreCreateMutex();
  • 功能:创建一个MUTEX 返回NULL,或者handler
  • xSemaphoreGive(xHandler);
  • 功能: 释放Handler
  • xSemaphoreTake(xHanlder, timeout);
  • 功能:指定时间内获取信号量 返回pdPASS, 或者pdFAIL

延时

任务延时

使用不同的delay延时函数会对整个程序延时,所以需要针对任务的延时函数vTaskDelay。

但delay(ms)和vTaskDelay(ticks)的单位不同,delay以毫秒为单位,而vTaskDelay以一个ticks为单位。

请添加图片描述

ticks就像一个定时器,每过一个ticks就会暂停当前这个任务开始另一个任务。

而ESP32的一个ticks正好是1ms。

也可以使用更保险的转换方法:

//使用函数转换,将要延时的ms数填写即可
vTaskDelay(pdMS_T0_TICKS(ms));
//使用总时间除以每个ticks的时间
vTaskDelay(ms/portTICK_PERIOD_MS);

绝对任务延时

在使用vTaskDelay()延时时,其实并不是对整个任务延迟,而是程序空运行的时间。

要对一个任务整体延时多久更新一次的情况,要用vTaskDelayUntil()函数。可以通过获取程序运行的时间,补全到需要延时的时间。

以股票刷新为例

void showStockTask(void *ptParam) {
  static float stockPrice = 99.57; //股票价格

  //最后一次唤醒的tick count,第一次使用需要赋值
  //以后此变量会由vTaskDelayUntil自动更新
  TickType_t xLastWakeTime = xTaskGetTickCount();

  const TickType_t xFrequency = 3000; // 间隔 3000 ticks = 3 seconds

  for (;;) {
    vTaskDelayUntil(&xLastWakeTime, xFrequency);

    //验证当前唤醒的时刻tick count
    Serial.println(xTaskGetTickCount());
    //验证xLastWake Time是否被vTaskDelayUntil更新
    //Serial.println(xLastWakeTime);

    // ------- 很复杂的交易股票计算,时间不定 ---------
    stockPrice = stockPrice * (1 + random(1, 20) / 100.0); 
    vTaskDelay(random(500, 2000));

    Serial.print("Stock Price : $");
    Serial.println(stockPrice);
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(showStockTask,
              "Show Stock Price",
              1024 * 6,
              NULL,
              1,
              NULL);
}

void loop() {

}

软件定时器

一次性定时

TimerHandle_t lockHandle;	//创建定时器

void carKey(void *ptParam) {
  byte lockPin = 23;
  pinMode(lockPin, INPUT_PULLUP);

  for (;;) {
    //按下按钮时定时器开始计时
    if (digitalRead(lockPin) == LOW) {
      //timeout 3000 ticks
      //xTimerStart开启时间
      if (xTimerStart(lockHandle, 3000) == pdPASS) {
        Serial.println("About to lock the car");
      } else {
        Serial.println("Unable to lock the car");
      };
      vTaskDelay(100);
    }

  }
}

void lockCarCallback(TimerHandle_t xTimer) {
  Serial.println("Timer CallBack: Car is Locked");
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(carKey,
              "Check If Owner Press Lock Button",
              1024 * 1,
              NULL,
              1,
              NULL);
  //设置定时器
  lockHandle = xTimerCreate("Lock Car", 	/*描述*/
                           2000,			/*定时时间,单位为tick*/
                           pdFALSE,			/*不重复,一次性定时器*/
                           (void *)0,		/*id*/
                           lockCarCallback);/*回调函数*/
}

void loop() {

}

重复定时

TimerHandle_t checkHandle;

void checkCallback(TimerHandle_t xTimer) {
	Serial.println("Timer CallBack: Check Car");
}

void setup() {
  Serial.begin(115200);
  //设置定时器
  checkHandle = xTimerCreate("Lock Car", 	/*描述*/
                           2000,			/*定时时间,单位为tick*/
                           pdTRUE,			/*重复*/
                           (void *)0,		/*id*/
                           checkCallback);/*回调函数*/
    
  //必须要在 portMAX_DELAY 内开启 timer start
  //在此期间,此task进入Block状态
  xTimerStart(checkHandle, portMAX_DELAY);
}

void loop() {

}

内存设置

内存格式

请添加图片描述

  • Static:静态声明的变量以及字符串。
  • Stack:所有方法中的变量都会在其中生成,方法结束后会清除。
  • Heap:
    • 其中的每一个任务都有一部分用来存放TCB,用于记录任务的信息

函数

  • ESP.getHeapSize()
    • 功能:Heap一共有多少字节
  • ESP.getFreeHeap()
    • 功能:Hea还有多少空余字节
  • uxTaskGetStackHighWaterMark()
    • 功能:任务中空余的字节
    • 参数:任务的句柄,若为本任务则填写nullptr

如何分配每个任务可用空间

原则:应为任务使用的空间的2倍以上,且为1024的整数倍

TaskHandle_t taskHandle;	//任务句柄
int taskMem = 1024;

void task(void *ptParam) {
  //volatile char hello[1000] = {0}; //必须要用volatile修饰语,否则会被编译器优化掉
  while (1) {
  }
}
void setup() {
  Serial.begin(115200);

  int heapSize = ESP.getHeapSize();
  Serial.print("Total Heap Size:  ");
  Serial.print(heapSize);
  Serial.println(" Bytes");

  int heapFree = ESP.getFreeHeap();
  Serial.print("Free Heap Size:  ");
  Serial.print(heapFree);
  Serial.println(" Bytes");
  Serial.println("");


  Serial.println("Create Task ...");
  xTaskCreate(task,
              "",
              taskMem,
              NULL,
              1,
              &taskHandle);

  Serial.print("Free Heap Size:  ");
  Serial.print(ESP.getFreeHeap());
  Serial.println(" Bytes");
  Serial.println("");

  vTaskDelay(2000);
  int waterMark = uxTaskGetStackHighWaterMark(taskHandle);
  Serial.print("Task Free Memory: ");
  Serial.print(waterMark);
  Serial.println(" Bytes");
  Serial.print("Task Used Memory: ");
  Serial.print(taskMem - waterMark);
  Serial.println(" Bytes");

}

void loop() {

}

任务优先级

任务优先级可以设置在0~24之间。

问题:高优先级任务不进入Block(延时)或者Suspend状态,低优先级任务就永远不会被执行

函数

  • uxTaskPriorityGet(TaskHandle_t)

    • 功能:获取TashHandle任务优先级
    • 参数:任务的句柄,若为NULL则获得当前任务的优先级
    • 返回值:UBaseType_t 数据类型的优先级
  • vTaskPrioritySet(TaskHandle_t , UBaseType_t)

    • 功能:设置TashHandle任务优先级
    • 参数:
      • TaskHandle_t:任务的句柄
      • UBaseType_t:UBaseType_t 数据类型的优先级
  • tastYIELD()

    • 功能:把资源退让给同等级或者更高级的任务

示例

TaskHandle_t xFirstClassHandle = NULL;

void firstClass(void *ptParam) {
  while (1) {
    Serial.print("头等舱客户 - 等级");
    UBaseType_t uTaskPriority =  //显示本任务当前等级
      uxTaskPriorityGet(NULL);
    Serial.println(uTaskPriority);
    taskYIELD(); //资源退让给同等级或者更高级的任务
  }
}

void ecoClass(void *ptParam) {
  while (1) {
    Serial.print("经济舱客户 - 等级");
    UBaseType_t uTaskPriority = //显示本任务当前等级
      uxTaskPriorityGet(NULL);
    Serial.println(uTaskPriority);
    taskYIELD(); //资源退让给同等级或者更高级的任务
  }
}

void controlPanel(void *ptParam) {
  pinMode(23, INPUT_PULLUP);
  while (1) {
    if (digitalRead(23) == LOW) {
      //获取头等舱任务的当前等级
      if (xFirstClassHandle != NULL) {
        UBaseType_t uFirstClassPriority =
          uxTaskPriorityGet(xFirstClassHandle);

        switch (uFirstClassPriority) {
          case 2: //降级
            vTaskPrioritySet(xFirstClassHandle, 1);
            break;
          case 1: //升级
            vTaskPrioritySet(xFirstClassHandle, 2);
            break;
          default:
            break;
        }
      }
      vTaskDelay(120); //粗暴的防止按钮抖动
    }
  }
}

void setup() {
  Serial.begin(115200);
  /*
  三个任务都放在Core1
  任务优先等级:普通舱1级, 头等舱2级, 控制台3级
  */
  xTaskCreatePinnedToCore(ecoClass, "ecoClass", 1024 * 2, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(firstClass, "firstClass", 1024 * 2, NULL, 2, &xFirstClassHandle, 1);
  xTaskCreatePinnedToCore(controlPanel, "controlPanel", 1024 * 2, NULL, 3, NULL, 1);
}

void loop() {
}

双核

CORE0主要运行的是WIFI和蓝牙,如果不使用WIFI和蓝牙才推荐将任务分配在核心0上

xTaskCreatePinnedToCore(retailTask,
                       "Online Channel",
            			1024 * 4,
            			NULL,
            			1,
            		  	NULL,
            			0);	//运行在那个核心上,可选 0 & 1

看门狗

每一个核都有一个看门狗。ESP32的看门狗默认是5秒,Arduino目前不知道怎么改。

  • 核心0
    • IDLE任务,用于清除内存垃圾,优先级为0。(默认开启了看门狗,时间为5秒(5000 ticks))
  • 核心1
    • IDLE任务,用于清除内存垃圾,优先级为0。
    • loopBack任务,就是setup和loop函数,优先级为1。

函数

  • esp_task_wdt_add(NULL)

    • 功能:给本任务添加看门狗,NULL表示本任务。
  • esp_task_wdt_delete(NULL)

    • 功能:清除本任务的看门狗,NULL表示本任务。
  • esp_task_wdt_reset()

  • 功能:给自己任务的狗喂时间

  • disableCore0WDT()

    • 功能:手动关闭CPU上的看门狗。
    • 注意:慎重操作

示例

#include "esp_task_wdt.h" //Used by esp_task_wdt_reset()

// 永远不休息的task
void taskWithoutBreak(void * ptParam) {
  while (1) {
    //vTaskDelay(1); //让Task进入Block状态,退让资源
    //vTaskSuspend(NULL); //让Task进入Suspend状态,退让资源
  }
}

void setup() {
  Serial.begin(115200);

  //给本任务添加看门狗(NULL代表本任务)
  //这个命令需要放在demo2()的前面,否则连运行的机会都没有
  esp_task_wdt_add(NULL);

  /*
   Demo 2:
   Core 1 上有两个任务分别是: IDLE(0),  loopBack(1), taskWithoutBreak(2)
   因为taskWithoutBreak优先级高,而且永远不会退让出资源,所以IDLE(0)和loopBack(1)永远不会运行
   Core 1 IDLE(1) 在Arduino-ESP32上默认没有开启了 Watch Dog, 所以不会自动重启
   */
   xTaskCreatePinnedToCore(taskWithoutBreak, 
                           "TASK-NO-BREAK",
                           1024,
                           NULL,
                           2,
                           NULL,
                           1);

}

void loop() {
  esp_task_wdt_reset(); //给自己任务的狗喂时间

  Serial.println("loopBack Task - Priority 1");
  vTaskDelay(100);
}

通过寄存器喂狗

//feedTheDogInAllTasks()
//通过寄存器给所有任务的狗喂时
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
void feedTheDogInAllTasks() { //通过寄存器给所有任务的狗喂时
  // feed dog 0
  TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; // write enable
  TIMERG0.wdt_feed = 1;                     // feed dog
  TIMERG0.wdt_wprotect = 0;                 // write protect
  // feed dog 1
  TIMERG1.wdt_wprotect = TIMG_WDT_WKEY_VALUE; // write enable
  TIMERG1.wdt_feed = 1;                     // feed dog
  TIMERG1.wdt_wprotect = 0;                 // write protect
}

队列

函数

  • xQueueCreate(len, size)

    • 功能:创建一个队列

    • 参数:

      • len:队列的长度
      • size:队列的大小
    • 返回值:QueueHandle_t类型的队列对象

  • xQueueSend(queue, content, timeOut)

    • 功能:向一个队列中写数据

    • 参数:

      • queue:向那个队列中写

      • content:写的内容

      • timeOut:超时时间,若在该时间内还没有向队列中写入就返回pdPASS

        portMAX_DELAY:表示直接进入Suspend状态(大约49天)

  • xQueueReceive(queue, content, timeOut)

    • 功能:在一个队列中读数据

    • 参数:

      • queue:在那个队列中读

      • content:读出的内容

      • timeOut:超时时间,若在该时间内还没有从队列中读出就返回pdPASS

        portMAX_DELAY:表示直接进入Suspend状态(大约49天)

示例

读取DHT22的温湿度和光敏电阻的值

#include "DHTesp.h"
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

#define DHT22_ID 0
#define LDR_ID 1

typedef struct {
  byte deviceID;
  float value1;
  float value2;
} SENSOR;

QueueHandle_t queueSensor = xQueueCreate(8, sizeof(SENSOR));

void dht22(void *ptParam) {
  const byte dhtPin = 32;
  DHTesp dhtSensor;
  dhtSensor.setup(dhtPin, DHTesp::DHT22);

  SENSOR dht22Sensor;
  dht22Sensor.deviceID = DHT22_ID;

  while (1) {
    TempAndHumidity  data = dhtSensor.getTempAndHumidity();

    // Serial.println("Temp: " + String(data.temperature, 2) + "°C");
    // Serial.println("Humidity: " + String(data.humidity, 1) + "%");

    dht22Sensor.value1 = data.temperature;
    dht22Sensor.value2 = data.humidity;

    // TickType_t timeOut = portMAX_DELAY;
    TickType_t timeOut = 2000;
    if (xQueueSend(queueSensor, &dht22Sensor, timeOut) != pdPASS) {
      Serial.println("DHT22: Queue is full.");
    }
    vTaskDelay(1000);
  }
}

void ldr(void *ptParam) {
  const float GAMMA = 0.7;
  const float RL10 = 50;
  const byte ldrPIN = 27;
  pinMode(ldrPIN, INPUT);

  SENSOR ldrSensor;
  ldrSensor.deviceID = LDR_ID;

  while (1 ) {
    int analogValue = analogRead(ldrPIN);

    float voltage = analogValue / 4095. * 5;
    float resistance = 2000 * voltage / (1 - voltage / 5);
    float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));

    // Serial.print("LDR Light Sensor lux : ");
    // Serial.println(lux);

    ldrSensor.value1 = lux;
    ldrSensor.value2 = 0.0;

    // TickType_t timeOut = portMAX_DELAY;
    TickType_t timeOut = 2000;
    if (xQueueSend(queueSensor, &ldrSensor, timeOut) != pdPASS) {
      Serial.println("LDR: Queue is full.");
    }
    vTaskDelay(1000);
  }
}

void lcdTask(void *ptParam) {  //LCD任务主体
  lcd.init();
  lcd.backlight();

  lcd.setCursor(0, 0);
  lcd.print("   LONELY  BINARY  ");

  SENSOR data;
  while (1) {
    //TickType_t timeOut = portMAX_DELAY;
    TickType_t timeOut = 2000;
    if (xQueueReceive(queueSensor, &data, timeOut) == pdPASS) {

      switch (data.deviceID) {
        case DHT22_ID:
          lcd.setCursor(0, 1);
          lcd.print("Temp: " + String(data.value1, 2) + "c");
          lcd.setCursor(0, 2);
          lcd.print("Humidity: " + String(data.value2, 1) + "%");
          break;
        case LDR_ID:
          lcd.setCursor(0, 3);
          if (data.value1 > 50) {
            lcd.print("Bright ");
          } else {
            lcd.print("Dark ");
          }
          //lcd.setCursor(0, 3);
          lcd.print(String(data.value1, 2) + " lux");
          break;
        default:
          Serial.println("LCD: Unkown Device");
          break;
      }
    }  else {
      Serial.println("LCD: Message Queue is Empty");
    };
    vTaskDelay(2000);
  }
}

void setup(){
  Serial.begin(115200);
  xTaskCreate(dht22, "DHT22", 1024 * 4, NULL, 1, NULL);
  xTaskCreate(ldr, "LDR LIGHT", 1024 * 4, NULL, 1, NULL);
  xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);
}


void loop() {}

信号量

二进制信号量

Binary Semaphore 是一种信号机制。一个任务(生产者)发出信号,另外一个任务(消费者)接受信号。

这里不使用全局变量而是信号量是为了避免接收任务不断的刷新判断,会占用CPU资源。使用信号量可以使任务处于阻塞(挂起)的状态。如果想进一步减少CPU资源可以将发送任务用中断实现

函数

  • xSemaphoreCreateBinary()

    • 功能:创建二进制信号量

    • 参数:无

    • 返回值:SemaphoreHandle_t类型的二进制信号量对象

  • xSemaphoreGive(xHandler)

    • 功能:发送二进制信号量
    • 参数:
      • xHandler:要置1的二进制信号量
    • 返回值:无
  • xSemaphoreTake( xHandler, timeOut)

    • 功能:接收二进制信号量
    • 参数:
      • xHandler:要接收的二进制信号量
      • timeOut:超时时间,若在该时间内还没有接收到的二进制信号量就返回pdPASS
    • 返回值:成功接收返回pdTRUE

示例

按下按键后LED灯切换状态

SemaphoreHandle_t xSemaLED = NULL; //创建信号量Handler

TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks

void flashLED(void *pvParam) {
  pinMode(23, OUTPUT);
  while (1) {
    if (xSemaphoreTake( xSemaLED, timeOut) == pdTRUE )
    {
        digitalWrite(23, !digitalRead(23));
        vTaskDelay(1000);
    }
  }
}

void readBtn(void *pvParam) {
  pinMode(22, INPUT_PULLUP);
  while (1) {
    if (digitalRead(22) == LOW) {
      xSemaphoreGive(xSemaLED);
      vTaskDelay(120); //button debounce
    }
  }
}


void setup() {
  Serial.begin(115200);
  xSemaLED = xSemaphoreCreateBinary(); //创建二进制信号量

  if (xSemaLED == NULL) {
    printf("No Enough Ram, Unable to Create Semaphore.");
  } else {
    xTaskCreate(flashLED,
                "Flash LED",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(readBtn,
                "Read Button",
                1024 * 4,
                NULL,
                1,
                NULL);
  }
}

void loop() {
}

计数信号量

与二进制信号量不同的是,计数信号量不至于两种状态,而是一个数。

每个任务接收到信号量时会对信号量减1,而发生端可以不断的补充信号量。

函数

  • xSemaphoreCreateCounting(MAXSemaphore, Current)
    • 功能:创建计数信号量
    • 参数:
      • MAXSemaphore:最大可以有多少个信号量
      • Current:当前有多少信号量
    • 返回值:SemaphoreHandle_t类型的计数信号量对象
  • xSemaphoreGive(xSemaPhone)
    • 功能:增加计数信号量
    • 参数:
      • xSemaPhone:向那个计数信号量中增加
  • xSemaphoreTake(xSemaPhone, timeOut)
    • 功能:减少计数信号量
    • 参数:
      • xSemaPhone:要接收的计数信号量
      • timeOut:超时时间,若在该时间内还没有接收到的二进制信号量就返回pdPASS
    • 返回值:成功接收返回pdTRUE

示例

模拟电商平台抢手机库存

SemaphoreHandle_t xSemaPhone = NULL;

String consumerA = "JD";
String consumerB = "TMALL";
String consumerC = "PDD";

void producer(void *paParam) { //制造者 give
  while (1) {
    for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
    xSemaphoreGive(xSemaPhone);
    Serial.println("...... 手机再放出一台,");
  }
}

void consumer(void *pvParam) { //消费者 take
  String website = *(String *)pvParam;
  while (1) {
    if (xSemaphoreTake(xSemaPhone, portMAX_DELAY) == pdTRUE ) {
      for (int i = 0; i < random(200, 400); i++) vTaskDelay(10);
      Serial.print(website);
      Serial.println("抢到并销售一台: ");
    }
  }
}

void setup() {
  Serial.begin(115200);
  xSemaPhone = xSemaphoreCreateCounting(3, 0);

  xTaskCreate(consumer, "consumer a", 1024 * 6, (void *)&consumerA, 1, NULL);
  xTaskCreate(consumer, "consumer b", 1024 * 6, (void *)&consumerB, 1, NULL);
  xTaskCreate(consumer, "consumer c", 1024 * 6, (void *)&consumerC, 1, NULL);

  xTaskCreate(producer, "producer", 1024 * 6, NULL, 1, NULL);
}
void loop() {
}

事件标志组

能不用就不用,有更好的替代

本质就是:所有任务都能看到的3的字节的数据。

请添加图片描述

数据类型

  • EventGroupHandle_t:事件标志组的句柄
  • EventBits_t:事件标志组的字节

函数

  • xEventGroupCreate()

    • 功能:创建事件标志组
    • 参数:无
    • 返回值:EventGroupHandle_t数据类型的事件标志组对象
  • xEventGroupSetBits(EventGroupHandle_t, Bits)

    • 功能:将事件标志组中的某一位置1
    • 参数:
      • EventGroupHandle_t:要更改的组事件标志组的句柄
      • Bits:需要修改的位(使用二进制表示,要修改的为1,其余为0)
    • 返回值:事件标志组的的全部位(使用二进制表示)
  • xEventGroupGetBits(EventGroupHandle_t)

    • 功能:读取事件标志组中的全部位
    • 参数:
      • EventGroupHandle_t:要读取事件标志组的句柄
    • 返回值:事件标志组的的全部位(使用二进制表示)
  • xEventGroupClearBits(EventGroupHandle_t, Bits)

    • 功能:重置事件标志组

    • 参数:

      • EventGroupHandle_t:要重置的组事件标志组的句柄

      • Bits:需要重置的位(使用二进制表示,要修改的为1,其余为0)

        0xFFFFFF:全部重置

    • 返回值:重置前事件标志组的的全部位(使用二进制表示)

  • xEventGroupWaitBits(EventGroupHandle_t, Bits, Reset, Relation, TimeOut)

    • 功能:等待一位或几位置1

    • 参数:

      • EventGroupHandle_t:要等待的组事件标志组的句柄

      • Bits:需要等待的位(使用二进制表示,要修改的为1,其余为0)

        多位可以用” | “管道符分割

      • Reset:执行后,对应的Bits是否重置为 0

      pdFALSE:执行后不对Bits置0

      pdTRUE:执行后对Bits置0

      • Relation:

        pdFALSE:多位时互相的关系为OR

        pdTRUE:多位时互相的关系为AND

      • TimeOut:超时时间

    • 返回值:事件标志组的的全部位(使用二进制表示)

  • xEventGroupSync(EventGroupHandle_t, SetBits, WaitBits, TimeOut)

    • 功能:组合设置和等待两个过程
    • 参数:
      • EventGroupHandle_t:要操作的组事件标志组的句柄
      • SetBits:将设置的位置1
      • WaitBits:等待设置的位变为1
      • TimeOut:超时时间
    • 返回值:事件标志组的的全部位(使用二进制表示)

等待功能

以购物平台交易流程为例

#define BTNPIN 23 //按钮

#define ADDTOCART_0	( 1 << 0 )  //0001 bit0
#define PAYMENT_1	( 1 << 1 )  //0010 bit1
#define INVENTORY_2	( 1 << 2 )  //0100 bit2

#define ALLBITS 0xFFFFFF //24bits都是1

EventGroupHandle_t xEventPurchase = NULL; //创建event handler
const TickType_t xTimeOut = 10000 / portTICK_PERIOD_MS; // 10秒
//const TickType_t xTimeOut = portMAX_DELAY; //无限等待

void addtocartTask(void *pvParam) {
  pinMode(BTNPIN, INPUT);

  while (1) {
    if (digitalRead(BTNPIN) == LOW) {
      Serial.println("用户真心决定下单了...");

      //放一些随机的延迟,否则运行的太快了,看不出效果
      for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);

      xTaskCreate(purchaseTask, "Purchase", 1024 * 6, NULL, 1, NULL);
      xTaskCreate(paymentTask, "Payment", 1024 * 6, NULL, 1, NULL);
      xTaskCreate(inventoryTask, "Inventory", 1024 * 6, NULL, 1, NULL);
     
      vTaskDelay(120); //按钮防止抖动
    }
  }
}

void purchaseTask(void *pvParam) {
  EventBits_t uxBits;  // Event Group 24Bits 的 值

  while (1) {
    uxBits = xEventGroupSetBits(xEventPurchase, ADDTOCART_0); //将bit0设置为1
    if ((uxBits & ADDTOCART_0)) {
      Serial.println("商品已经添加到了购物车,付款中...");
      Serial.print("   Event Group Value:");
      Serial.println(uxBits, BIN);
    }

    uxBits = xEventGroupWaitBits (xEventPurchase,
                                  ADDTOCART_0 | PAYMENT_1 | INVENTORY_2,
                                  pdFALSE,
                                  pdTRUE,
                                  xTimeOut);

    if ((uxBits & ADDTOCART_0) && (uxBits & PAYMENT_1) && (uxBits & INVENTORY_2)) {
      //随机延迟, 模拟网页显示,恭喜买家入手商品
      for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);

      xEventGroupClearBits(xEventPurchase, ALLBITS); //重置
      uxBits = xEventGroupGetBits(xEventPurchase); //读取

      Serial.println("交易完成, RESET Event Group");
      Serial.print("   Event Group Value:");
      Serial.println(uxBits, BIN);
      Serial.println("");
    }
    vTaskDelete(NULL);
    //vTaskDelay(10000);
  }
}

void paymentTask(void *pvParam) {
  EventBits_t uxBits;

  while (1) {
    uxBits = xEventGroupWaitBits (xEventPurchase,
                                  ADDTOCART_0,
                                  pdFALSE,
                                  pdTRUE,
                                  xTimeOut);

    // 代表ADDTOCART_0被设置为了 1
    if (uxBits & ADDTOCART_0) {
      //随机延迟, 模拟付款验证过程
      for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
      uxBits = xEventGroupSetBits(xEventPurchase, PAYMENT_1); //将bit1设置为1

      Serial.println("支付宝付款完成,可以出货...");
      Serial.print("   Event Group Value:");
      Serial.println(uxBits, BIN);

      vTaskDelete(NULL);
    }
  }
}

void inventoryTask(void *pvParam) {
  EventBits_t uxBits;

  while (1) {
    uxBits = xEventGroupWaitBits (xEventPurchase,
                                  ADDTOCART_0 | PAYMENT_1,
                                  pdFALSE,
                                  pdTRUE,
                                  xTimeOut);

    // 判断 Event Group 中 ADDTOCART_0 和 PAYMENT_1 是否被设置为了0
    if ((uxBits & ADDTOCART_0) && (uxBits & PAYMENT_1))  {

      //随机延迟, 模拟仓库出货过程
      for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
      uxBits = xEventGroupSetBits(xEventPurchase, INVENTORY_2); //将bit2设置为1

      Serial.println("仓库出货完成,快递已取货...");
      Serial.print("   Event Group Value:");
      Serial.println(uxBits, BIN);

      vTaskDelete(NULL);
    }
  }
}


void setup()
{
  Serial.begin(115200);
  xEventPurchase = xEventGroupCreate(); //创建 event group

  xTaskCreate(addtocartTask, "Add To Cart", 1024 * 4, NULL, 1, NULL);
}

void loop() {}

同步功能

和上面使用同一个例子,但使用同步会简化胆码量

#define BTNPIN 23 //按钮

#define ADDTOCART_0	( 1 << 0 ) //0001 bit0
#define PAYMENT_1	( 1 << 1 )  //0010 bit1
#define INVENTORY_2	( 1 << 2 ) //0100 bit2

#define ALLBITS 0xFFFFFF //24bits都是1

#define BOUGHT_PAID_SENT (ADDTOCART_0 | PAYMENT_1 | INVENTORY_2)

EventGroupHandle_t xEventPurchase = NULL; //创建event handler
//const TickType_t xTimeOut = 10000 / portTICK_PERIOD_MS; // 10秒
const TickType_t xTimeOut = portMAX_DELAY; //无限等待

void addtocartTask(void *pvParam) {
  pinMode(BTNPIN, INPUT);

  while (1) {
    if (digitalRead(BTNPIN) == LOW) {
      xEventGroupClearBits(xEventPurchase, ALLBITS); //重置
      Serial.println("用户真心决定下单了...");

      //放一些随机的延迟,否则运行的太快了,看不出效果
      for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
      Serial.println("商品已经添加到了购物车,付款中...");

      xTaskCreate(purchaseTask, "Purchase", 1024 * 6, NULL, 1, NULL);
      xTaskCreate(paymentTask, "Payment", 1024 * 6, NULL, 1, NULL);
      xTaskCreate(inventoryTask, "Inventory", 1024 * 6, NULL, 1, NULL);

      vTaskDelay(120); //按钮防止抖动
    }
  }
}

void purchaseTask(void *pvParam) {
  EventBits_t uxBits;  // Event Group 24Bits 的 值

  while (1) {
    uxBits = xEventGroupSync (xEventPurchase,
                              ADDTOCART_0,
                              BOUGHT_PAID_SENT,
                              xTimeOut);

    if ((uxBits & BOUGHT_PAID_SENT) == BOUGHT_PAID_SENT)  {
      Serial.println("purchaseTask,已经自我了断. ");
      vTaskDelete(NULL);
    }
  }
}

void paymentTask(void *pvParam) {
  EventBits_t uxBits;

  while (1) {
    //随机延迟, 模拟付款验证过程
    for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
    Serial.println("支付宝付款完成,可以出货...");

    uxBits = xEventGroupSync (xEventPurchase,
                              PAYMENT_1,
                              BOUGHT_PAID_SENT,
                              xTimeOut);

    if ((uxBits & BOUGHT_PAID_SENT) == BOUGHT_PAID_SENT)  {
      Serial.println("paymentTask,已经自我了断. ");
      vTaskDelete(NULL);
    }
  }
}

void inventoryTask(void *pvParam) {
  EventBits_t uxBits;

  while (1) {
    //随机延迟, 模拟仓库出货过程
    for (int i = 0; i < random(100, 200); i++) vTaskDelay(10);
    Serial.println("仓库出货完成,快递已取货...");

    uxBits = xEventGroupSync (xEventPurchase,
                              INVENTORY_2,
                              BOUGHT_PAID_SENT,
                              xTimeOut);

    if ((uxBits & BOUGHT_PAID_SENT) == BOUGHT_PAID_SENT)  {
      Serial.println("inventoryTask,已经自我了断. ");
      vTaskDelete(NULL);
    }
  }
}


void setup()
{
  Serial.begin(115200);
  xEventPurchase = xEventGroupCreate(); //创建 event group
  xTaskCreate(addtocartTask, "Add To Cart", 1024 * 4, NULL, 1, NULL);
}

void loop() {}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值