Day1--FreeRTOS简介及多任务点灯、多任务传参、Mutex

实时操作系统(Real Time Operating System,简称RTOS)

Arduino任务执行流程:单线程执行任务

 RTOS:可以同时执行所有Task,每个任务都有自己的循环

 操作系统排行:LINUX        WINDOWS        FREERTOS

ESP32架构:ESP32-IDF的底层运行的就是freestos

默认core1:编写程序        core2:蓝牙、wifi功能

任务优先级:

优先级高的任务先执行,比如中断

实时性要求比较高的任务用高优先级

实时性要求比较低的任务用低优先级,比如屏幕刷新,数据显示

多任务其实是CPU分时完成的,1ms执行一个任务,频率1Khz

freertos传递参数只能采用指针的方式,重点:结构体和指针

内存管理:任务到底需要多少内存?分配空间大小1024*N

任务优先级:

任务的绝对频率:vTaskDelayUntil

软件定时器Timer:一次性的,周期性的,非常有用

freertos提供的三种数据结构:队列单数据、流媒体缓存、消息缓存

多任务全局变量:对资源进行保护

二进制信号量:0和1

计数信号量:0~N

事件组等待:

事件组同步:

任务通知

多任务点灯

1GB(GigaByte)=1024MB

1MB(MegaByte)=1024KB

1KB(KiloByte)=1024B(字节)

1B(byte)字节=8Bit(binary digit)位

#include <Arduino.h>

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

void task2(void *pt)
{
  pinMode(21, OUTPUT);
  while (1)
  {
    digitalWrite(21, !digitalRead(21));
    vTaskDelay(700);
  }
}

void setup()
{
  // 参数1:task;  参数2:任务备注;  参数3:内存分配空间
  // 参数4:传递参数;  参数5:任务优先级; 参数6:对任务删除管理
  xTaskCreate(task1, "Blink 23", 1024, NULL, 1, NULL);
  xTaskCreate(task2, "Blink 21", 1024, NULL, 1, NULL);
}

void loop()
{
}

 通过空指针类型给task传递单个参数

#include <Arduino.h>

byte led1 = 21;
byte led2 = 22;
byte led3 = 23;

void task1(void *pt) // 接收的为空指针
{
  byte led_pin = *(byte *)pt; // 解耦
  pinMode(led_pin, OUTPUT);
  while (1)
  {
    digitalWrite(led_pin, !digitalRead(led_pin));
    vTaskDelay(500);
  }
}

void setup()
{
  // 参数1:task;  参数2:任务备注;  参数3:内存分配空间
  // 参数4:传递参数;  参数5:任务优先级; 参数6:对任务删除管理
  xTaskCreate(task1, "Blink 21", 1024, (void *)&led1, 1, NULL);
}

void loop()
{
}

给任务传递多个参数(重要)

通过空指针类型传递结构体

/*向任务中进行传多个参数*/
#include <Arduino.h>

typedef struct
{
  byte pin;
  int delayTime;
} LEDFLASH;
LEDFLASH led1, led2;

void ledFlash(void *pt)
{
  LEDFLASH *ptLedFlash = (LEDFLASH *)pt; // 数据解耦
  byte pin = ptLedFlash->pin;
  int delayTime = ptLedFlash->delayTime;

  pinMode(pin, OUTPUT);
  while (1)
  {
    digitalWrite(pin, !digitalRead(pin));
    vTaskDelay(delayTime);
  }
}

void setup()
{
  /*局部变量,结构体赋值一定要在setup里面,在外面会出错
  在外边,需要写成全局变量的形式*/
  led1.pin = 23;
  led1.delayTime = 1000;
  led2.pin = 21;
  led2.delayTime = 3000;

  xTaskCreate(ledFlash, "***", 1024, (void *)&led1, 1, NULL);
  xTaskCreate(ledFlash, "***", 1024, (void *)&led2, 1, NULL);
}
void loop() {}

可以通过void *pt空指针的方式传递单个参数,可以通过void *struct传递多个参数 

通过结构体传址的方式进行数据传输

任务共享全局变量(重要)

任务1:对商品的数量进行计算

任务2:显示商品的数量

重点:写操作只能有一个,读操作可以有多个

/*任务之间通过全局变量进行数据传递*/
#include <Arduino.h>

/*养成良好习惯,被多进程和中断调用的变量使用 volatile修饰符*/
/*ESP32是32位的,一定要定义为uint32_t,因为同一个变量占用CPU同一个通道
*/
volatile u_int32_t inventory = 100; // 总库存
volatile u_int32_t retailCount = 0; // 线下销售量

/*任务1:库存数量变化计算*/
void retailTask(void *pt)
{
  while (1)
  {
    /*以下实现了带有随机延迟的库存减1
    等效为 inventory--; retailCount++;*/
    u_int32_t inv = inventory;
    for (byte i; i < random(10, 100); i++)
    {
      vTaskDelay(i);
    }
    if (inventory > 0)
    {
      inventory = inv - 1;
      retailCount++;
    }
  };
  vTaskDelay(10);
}

/*任务2:显示库存和线下销售量*/
void showTask(void *pt)
{
  while (1)
  {
    printf("Inventory : %d\n", inventory);
    printf("   Retail :%d\n", retailCount);
    if (inventory == 0)
    {
      printf("\n------sales summary-------\n");
      printf("totail sales: %d\n\n", retailCount);
    }
    vTaskDelay(1000);
  }
}

void setup()
{
  Serial.begin(115200);
  xTaskCreate(retailTask, "库存数量变化", 1024 * 4, NULL, 1, NULL);
  xTaskCreate(showTask, "数量显示", 1024 * 4, NULL, 1, NULL);
}
void loop() {}

使用相互排斥 Mutex 来解决竞争冒险Race Condition(重要)

注意:在对全局变量数据进行访问时,使用Mutex,不能在程序一开始就获取钥匙,在if里对共享资源计算完就立刻释放钥匙,不要把释放钥匙语句放到任务最后

在上面一个示例中,数据计算时容易出现竞争冒险的问题,采用Mutex对数据进行保护,解决多个任务同时对共享资源访问造成的问题

Mutex互斥锁,先把共享资源放进保险柜里(只有一把钥匙),任务(例如task1)先申请钥匙,再对共享资源进行访问,Task2如果想要访问共享资源,需要等到task1归还钥匙

使用步骤:

  1. 创建一把锁, create
  2. 在指定时间内获取钥匙, take
  3. 归还钥匙,give

  语法:

  SemaphoreHandle_t xHandler; 创建Handler

  xHandler = xSemaphoreCreateMutex(); 创建一个MUTEX 返回NULL,或者handler

  xSemaphoreGive(xHandler); 释放

  xSemaphoreTake(xHanlder, timeout); 指定时间内获取信号量 返回pdPASS, 或者pdFAIL

  理解方法:

  MUTEX的工作原理可以想象成

  共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对改资源进行访问

/*
  程序: Tasks之间数据传递
        有多任务同时写入,或者数据大小超过cpu内存通道时,或者对共享资源的访问时候,需要有防范机制
        使用MUTEX对数据对Cirtical Section的内容进行保护
        可以想象成MUTEX就是一把锁

  公众号:孤独的二进制

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

  理解方法:
  MUTEX的工作原理可以想象成
  共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对改资源进行访问
*/

// 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
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


      //以下实现了带有随机延迟的 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++;

        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }


    };

    vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
  }
}

void onlineTask(void *pvParam) {
  while (1) {

    // 在timeout的时间内如果能够获取二进制信号量就继续
    // 通俗一些:获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section
      //以下实现了带有随机延迟的 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;
        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() {
  // put your setup code here, to run once:
  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() {
}

 MUTEX实例

使用MPU6050传感器时,可以创建一个结构体存储7个数据(芯片温度、3轴角速度、3轴角度)

MPU6050的数据写进结构体中,然后屏幕进行读取

两个任务,一个读,一个写,一定要用MUTEX进行数据保护

原因:不管是读操作还是写操作,它都是一个独立的task,这样用freertos运行多任务就会出现某个任务因为分配的时间到了,对数据的处理被迫中断,然后另一个任务又开始对数据进行操作,而这时的数据很可能只有一半是操作完成,另一半还未完成的状态,这样的数据状态会产生很大的运算错误,非常危险。所以一个参数只要有两个或以上task要对其进行操作,就必须上钥匙

/*
   程序:  MPU6050 & MUTEX
   公众号:孤独的二进制
*/
#include <Arduino.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

Adafruit_MPU6050 mpu;
Adafruit_Sensor *mpu_temp, *mpu_accel, *mpu_gyro;

typedef struct
{
  float temp;
  float accX;
  float accY;
  float accZ;
  float gyroX;
  float gyroY;
  float gyroZ;
} MPU6050;

MPU6050 mpu6050;

SemaphoreHandle_t xMutexMPU6050 = NULL; //创建信号量Handler
TickType_t timeOut = 1000;              //用于获取信号量的Timeout 1000 ticks

void mpu6050Task(void *pvParam)
{

  mpu.begin();

  mpu_temp = mpu.getTemperatureSensor();
  mpu_temp->printSensorDetails();

  mpu_accel = mpu.getAccelerometerSensor();
  mpu_accel->printSensorDetails();

  mpu_gyro = mpu.getGyroSensor();
  mpu_gyro->printSensorDetails();

  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;

  while (1)
  {

    if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS)
    {

      //获取MPU数据
      mpu_temp->getEvent(&temp);
      mpu_accel->getEvent(&accel);
      mpu_gyro->getEvent(&gyro);

      mpu6050.temp = temp.temperature;
      mpu6050.accX = accel.acceleration.x;
      mpu6050.accY = accel.acceleration.y;
      mpu6050.accZ = accel.acceleration.z;
      mpu6050.gyroX = gyro.gyro.x;
      mpu6050.gyroY = gyro.gyro.y;
      mpu6050.gyroZ = gyro.gyro.z;

      xSemaphoreGive(xMutexMPU6050); //释放钥匙
    }
    else
    {
      // Unable to obtain MUTEX
    }

    vTaskDelay(500);
  }
}

void lcdTask(void *ptParam)
{ // LCD任务主体

  lcd.init();
  lcd.backlight();

  //定义是 2004 LCD
  byte lcdLine = 4;
  byte lcdChar = 20;

  //创建一个二维的的数组
  //注意长度是 lcdChar+1 最后还有一个位置要给换行符
  char line0[lcdChar + 1], line1[lcdChar + 1], line2[lcdChar + 1], line3[lcdChar + 1];
  char *line[] = {
      line0,
      line1,
      line2,
      line3,
  };

  while (1)
  {

    if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS)
    {

      // 组合数据
      sprintf(line0, "     MPU6050 %d", xTaskGetTickCount() / 100);
      sprintf(line1, " Temperature  %.2f", mpu6050.temp);
      sprintf(line2, " ACC  %.2f %.2f %.2f", mpu6050.accX, mpu6050.accY, mpu6050.accZ);
      sprintf(line3, " GYRO %.2f %.2f %.2f", mpu6050.gyroX, mpu6050.gyroY, mpu6050.gyroZ);

      xSemaphoreGive(xMutexMPU6050); //释放钥匙
    }
    else
    {
      // Unable to obtain MUTEX
    }

    // 显示数据
    for (int i = 0; i < 4; i++)
    {
      lcd.setCursor(0, i);
      lcd.print(line[i]);
    }

    vTaskDelay(1000);
  }
}

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

  xMutexMPU6050 = xSemaphoreCreateMutex(); //创建MUTEX

  xTaskCreate(mpu6050Task, "MPU6050", 1024 * 8, NULL, 1, NULL);
  vTaskDelay(1000); //让MPU6050提前先运行一秒获取第一笔数据
  xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);
}

void loop() {}
  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值