实时操作系统(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
-
- 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()
- {
- }
- #include
-
- 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
-
- 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
-
- /*养成良好习惯,被多进程和中断调用的变量使用 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,不能在程序一开始就获取钥匙,在if里对共享资源计算完就立刻释放钥匙,不要把释放钥匙语句放到任务最后
在上面一个示例中,数据计算时容易出现竞争冒险的问题,采用Mutex对数据进行保护,解决多个任务同时对共享资源访问造成的问题。
Mutex互斥锁,先把共享资源放进保险柜里(只有一把钥匙),任务(例如task1)先申请钥匙,再对共享资源进行访问,Task2如果想要访问共享资源,需要等到task1归还钥匙。
使用步骤:
语法:
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() {
- }
使用MPU6050传感器时,可以创建一个结构体存储7个数据(芯片温度、3轴角速度、3轴角度)
MPU6050的数据写进结构体中,然后屏幕进行读取
两个任务,一个读,一个写,一定要用MUTEX进行数据保护
原因:不管是读操作还是写操作,它都是一个独立的task,这样用freertos运行多任务就会出现某个任务因为分配的时间到了,对数据的处理被迫中断,然后另一个任务又开始对数据进行操作,而这时的数据很可能只有一半是操作完成,另一半还未完成的状态,这样的数据状态会产生很大的运算错误,非常危险。所以一个参数只要有两个或以上task要对其进行操作,就必须上钥匙。
- /*
- 程序: MPU6050 & MUTEX
- 公众号:孤独的二进制
- */
- #include
- #include
- #include
- #include
- #include
-
- 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() {}