实时操作系统(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归还钥匙。
使用步骤:
- 创建一把锁, create
- 在指定时间内获取钥匙, take
- 归还钥匙,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() {}