• 1.FreeRTOS简介及多任务点灯


    实时操作系统(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)位

    1. #include
    2. void task1(void *pt)
    3. {
    4. pinMode(23, OUTPUT);
    5. while (1)
    6. {
    7. digitalWrite(23, !digitalRead(23));
    8. vTaskDelay(500);
    9. }
    10. }
    11. void task2(void *pt)
    12. {
    13. pinMode(21, OUTPUT);
    14. while (1)
    15. {
    16. digitalWrite(21, !digitalRead(21));
    17. vTaskDelay(700);
    18. }
    19. }
    20. void setup()
    21. {
    22. // 参数1:task; 参数2:任务备注; 参数3:内存分配空间
    23. // 参数4:传递参数; 参数5:任务优先级; 参数6:对任务删除管理
    24. xTaskCreate(task1, "Blink 23", 1024, NULL, 1, NULL);
    25. xTaskCreate(task2, "Blink 21", 1024, NULL, 1, NULL);
    26. }
    27. void loop()
    28. {
    29. }

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

    1. #include
    2. byte led1 = 21;
    3. byte led2 = 22;
    4. byte led3 = 23;
    5. void task1(void *pt) // 接收的为空指针
    6. {
    7. byte led_pin = *(byte *)pt; // 解耦
    8. pinMode(led_pin, OUTPUT);
    9. while (1)
    10. {
    11. digitalWrite(led_pin, !digitalRead(led_pin));
    12. vTaskDelay(500);
    13. }
    14. }
    15. void setup()
    16. {
    17. // 参数1:task; 参数2:任务备注; 参数3:内存分配空间
    18. // 参数4:传递参数; 参数5:任务优先级; 参数6:对任务删除管理
    19. xTaskCreate(task1, "Blink 21", 1024, (void *)&led1, 1, NULL);
    20. }
    21. void loop()
    22. {
    23. }

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

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

    1. /*向任务中进行传多个参数*/
    2. #include
    3. typedef struct
    4. {
    5. byte pin;
    6. int delayTime;
    7. } LEDFLASH;
    8. LEDFLASH led1, led2;
    9. void ledFlash(void *pt)
    10. {
    11. LEDFLASH *ptLedFlash = (LEDFLASH *)pt; // 数据解耦
    12. byte pin = ptLedFlash->pin;
    13. int delayTime = ptLedFlash->delayTime;
    14. pinMode(pin, OUTPUT);
    15. while (1)
    16. {
    17. digitalWrite(pin, !digitalRead(pin));
    18. vTaskDelay(delayTime);
    19. }
    20. }
    21. void setup()
    22. {
    23. /*局部变量,结构体赋值一定要在setup里面,在外面会出错
    24. 在外边,需要写成全局变量的形式*/
    25. led1.pin = 23;
    26. led1.delayTime = 1000;
    27. led2.pin = 21;
    28. led2.delayTime = 3000;
    29. xTaskCreate(ledFlash, "***", 1024, (void *)&led1, 1, NULL);
    30. xTaskCreate(ledFlash, "***", 1024, (void *)&led2, 1, NULL);
    31. }
    32. void loop() {}

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

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

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

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

    任务2:显示商品的数量

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

    1. /*任务之间通过全局变量进行数据传递*/
    2. #include
    3. /*养成良好习惯,被多进程和中断调用的变量使用 volatile修饰符*/
    4. /*ESP32是32位的,一定要定义为uint32_t,因为同一个变量占用CPU同一个通道
    5. */
    6. volatile u_int32_t inventory = 100; // 总库存
    7. volatile u_int32_t retailCount = 0; // 线下销售量
    8. /*任务1:库存数量变化计算*/
    9. void retailTask(void *pt)
    10. {
    11. while (1)
    12. {
    13. /*以下实现了带有随机延迟的库存减1
    14. 等效为 inventory--; retailCount++;*/
    15. u_int32_t inv = inventory;
    16. for (byte i; i < random(10, 100); i++)
    17. {
    18. vTaskDelay(i);
    19. }
    20. if (inventory > 0)
    21. {
    22. inventory = inv - 1;
    23. retailCount++;
    24. }
    25. };
    26. vTaskDelay(10);
    27. }
    28. /*任务2:显示库存和线下销售量*/
    29. void showTask(void *pt)
    30. {
    31. while (1)
    32. {
    33. printf("Inventory : %d\n", inventory);
    34. printf(" Retail :%d\n", retailCount);
    35. if (inventory == 0)
    36. {
    37. printf("\n------sales summary-------\n");
    38. printf("totail sales: %d\n\n", retailCount);
    39. }
    40. vTaskDelay(1000);
    41. }
    42. }
    43. void setup()
    44. {
    45. Serial.begin(115200);
    46. xTaskCreate(retailTask, "库存数量变化", 1024 * 4, NULL, 1, NULL);
    47. xTaskCreate(showTask, "数量显示", 1024 * 4, NULL, 1, NULL);
    48. }
    49. 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的工作原理可以想象成

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

    1. /*
    2. 程序: Tasks之间数据传递
    3. 有多任务同时写入,或者数据大小超过cpu内存通道时,或者对共享资源的访问时候,需要有防范机制
    4. 使用MUTEX对数据对Cirtical Section的内容进行保护
    5. 可以想象成MUTEX就是一把锁
    6. 公众号:孤独的二进制
    7. 语法:
    8. SemaphoreHandle_t xHandler; 创建Handler
    9. xHandler = xSemaphoreCreateMutex(); 创建一个MUTEX 返回NULL,或者handler
    10. xSemaphoreGive(xHandler); 释放
    11. xSemaphoreTake(xHanlder, timeout); 指定时间内获取信号量 返回pdPASS, 或者pdFAIL
    12. 理解方法:
    13. MUTEX的工作原理可以想象成
    14. 共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对改资源进行访问
    15. */
    16. // 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
    17. volatile uint32_t inventory = 100; //总库存
    18. volatile uint32_t retailCount = 0; //线下销售量
    19. volatile uint32_t onlineCount = 0; //线上销售量
    20. SemaphoreHandle_t xMutexInventory = NULL; //创建信号量Handler
    21. TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks
    22. void retailTask(void *pvParam) {
    23. while (1) {
    24. // 在timeout的时间内如果能够获取就继续
    25. // 通俗一些:获取钥匙
    26. if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
    27. //被MUTEX保护的内容叫做 Critical Section
    28. //以下实现了带有随机延迟的 inventory减1;
    29. //等效为 inventory--; retailCount++;
    30. uint32_t inv = inventory;
    31. for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    32. if (inventory > 0) {
    33. inventory = inv - 1;
    34. retailCount++;
    35. //释放钥匙
    36. xSemaphoreGive(xMutexInventory);
    37. } else {
    38. //无法获取钥匙
    39. }
    40. };
    41. vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
    42. }
    43. }
    44. void onlineTask(void *pvParam) {
    45. while (1) {
    46. // 在timeout的时间内如果能够获取二进制信号量就继续
    47. // 通俗一些:获取钥匙
    48. if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
    49. //被MUTEX保护的内容叫做 Critical Section
    50. //以下实现了带有随机延迟的 inventory减1;
    51. //等效为 inventory--; retailCount++;
    52. uint32_t inv = inventory;
    53. for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    54. if (inventory > 0) {
    55. inventory = inv - 1;
    56. onlineCount++;
    57. //释放钥匙
    58. xSemaphoreGive(xMutexInventory);
    59. } else {
    60. //无法获取钥匙
    61. }
    62. };
    63. vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
    64. }
    65. }
    66. void showTask(void *pvParam) {
    67. while (1) {
    68. printf("Inventory : %d\n", inventory);
    69. printf(" Retail : %d, Online : %d\n", retailCount, onlineCount);
    70. if (inventory == 0 ) {
    71. uint32_t totalSales = retailCount + onlineCount;
    72. printf("-----SALES SUMMARY-----\n");
    73. printf(" Total Sales: %d\n", totalSales);
    74. printf(" OverSales: %d\n", 100 - totalSales);
    75. }
    76. vTaskDelay(pdMS_TO_TICKS(1000));
    77. }
    78. }
    79. void setup() {
    80. // put your setup code here, to run once:
    81. Serial.begin(115200);
    82. xMutexInventory = xSemaphoreCreateMutex(); //创建MUTEX
    83. if (xMutexInventory == NULL) {
    84. printf("No Enough Ram, Unable to Create Semaphore.");
    85. } else {
    86. xTaskCreate(onlineTask,
    87. "Online Channel",
    88. 1024 * 4,
    89. NULL,
    90. 1,
    91. NULL);
    92. xTaskCreate(retailTask,
    93. "Retail Channel",
    94. 1024 * 4,
    95. NULL,
    96. 1,
    97. NULL);
    98. xTaskCreate(showTask,
    99. "Display Inventory",
    100. 1024 * 4,
    101. NULL,
    102. 1,
    103. NULL);
    104. }
    105. }
    106. void loop() {
    107. }

     MUTEX实例

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

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

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

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

    1. /*
    2. 程序: MPU6050 & MUTEX
    3. 公众号:孤独的二进制
    4. */
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. LiquidCrystal_I2C lcd(0x27, 20, 4);
    11. Adafruit_MPU6050 mpu;
    12. Adafruit_Sensor *mpu_temp, *mpu_accel, *mpu_gyro;
    13. typedef struct
    14. {
    15. float temp;
    16. float accX;
    17. float accY;
    18. float accZ;
    19. float gyroX;
    20. float gyroY;
    21. float gyroZ;
    22. } MPU6050;
    23. MPU6050 mpu6050;
    24. SemaphoreHandle_t xMutexMPU6050 = NULL; //创建信号量Handler
    25. TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks
    26. void mpu6050Task(void *pvParam)
    27. {
    28. mpu.begin();
    29. mpu_temp = mpu.getTemperatureSensor();
    30. mpu_temp->printSensorDetails();
    31. mpu_accel = mpu.getAccelerometerSensor();
    32. mpu_accel->printSensorDetails();
    33. mpu_gyro = mpu.getGyroSensor();
    34. mpu_gyro->printSensorDetails();
    35. sensors_event_t accel;
    36. sensors_event_t gyro;
    37. sensors_event_t temp;
    38. while (1)
    39. {
    40. if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS)
    41. {
    42. //获取MPU数据
    43. mpu_temp->getEvent(&temp);
    44. mpu_accel->getEvent(&accel);
    45. mpu_gyro->getEvent(&gyro);
    46. mpu6050.temp = temp.temperature;
    47. mpu6050.accX = accel.acceleration.x;
    48. mpu6050.accY = accel.acceleration.y;
    49. mpu6050.accZ = accel.acceleration.z;
    50. mpu6050.gyroX = gyro.gyro.x;
    51. mpu6050.gyroY = gyro.gyro.y;
    52. mpu6050.gyroZ = gyro.gyro.z;
    53. xSemaphoreGive(xMutexMPU6050); //释放钥匙
    54. }
    55. else
    56. {
    57. // Unable to obtain MUTEX
    58. }
    59. vTaskDelay(500);
    60. }
    61. }
    62. void lcdTask(void *ptParam)
    63. { // LCD任务主体
    64. lcd.init();
    65. lcd.backlight();
    66. //定义是 2004 LCD
    67. byte lcdLine = 4;
    68. byte lcdChar = 20;
    69. //创建一个二维的的数组
    70. //注意长度是 lcdChar+1 最后还有一个位置要给换行符
    71. char line0[lcdChar + 1], line1[lcdChar + 1], line2[lcdChar + 1], line3[lcdChar + 1];
    72. char *line[] = {
    73. line0,
    74. line1,
    75. line2,
    76. line3,
    77. };
    78. while (1)
    79. {
    80. if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS)
    81. {
    82. // 组合数据
    83. sprintf(line0, " MPU6050 %d", xTaskGetTickCount() / 100);
    84. sprintf(line1, " Temperature %.2f", mpu6050.temp);
    85. sprintf(line2, " ACC %.2f %.2f %.2f", mpu6050.accX, mpu6050.accY, mpu6050.accZ);
    86. sprintf(line3, " GYRO %.2f %.2f %.2f", mpu6050.gyroX, mpu6050.gyroY, mpu6050.gyroZ);
    87. xSemaphoreGive(xMutexMPU6050); //释放钥匙
    88. }
    89. else
    90. {
    91. // Unable to obtain MUTEX
    92. }
    93. // 显示数据
    94. for (int i = 0; i < 4; i++)
    95. {
    96. lcd.setCursor(0, i);
    97. lcd.print(line[i]);
    98. }
    99. vTaskDelay(1000);
    100. }
    101. }
    102. void setup()
    103. {
    104. Serial.begin(115200);
    105. xMutexMPU6050 = xSemaphoreCreateMutex(); //创建MUTEX
    106. xTaskCreate(mpu6050Task, "MPU6050", 1024 * 8, NULL, 1, NULL);
    107. vTaskDelay(1000); //让MPU6050提前先运行一秒获取第一笔数据
    108. xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);
    109. }
    110. void loop() {}
  • 相关阅读:
    python数据可视化-matplotlib入门(2)-利用随机函数生成变化图形
    docker(Kubernetes)环境如何查看network namespace
    html替换指定字符串
    MindFusion.WPF Pack 2022.R1
    GPT大语言模型Alpaca-lora本地化部署实践【大语言模型实践一】
    React 组件
    STC51单片机学习笔记3——C语言流水灯
    洛谷刷题C语言:PET、KEMIJA、PROSJEK、KORNISLAV、RESETO
    MySQL查询性能优化七种武器之链路追踪
    电脑小白也能修电脑?这几个维修技巧快记住
  • 原文地址:https://blog.csdn.net/qq_45355603/article/details/126914079