ESP32通关学习【全网详细版】
解决方法1:在任务A进行任务通知之前,先Delay,让这个任务A先阻塞一下,以至于任务B可以运行,才可以成功创建这个taskB_handle句柄。我们创建完任务A之后,任务A就直接运行了,任务A会直接向任务B发送任务通知,由于taskB_handle是一个无效值,所以导致程序崩溃了。解决方法2:在app_main()函数中颠倒任务A 和 任务B 的创建顺序,使得taskB_handle 这个任务B
ESP32通关学习
一、ESP32架构和组件依赖
1、组件
2、组件依赖
二、ESP32任务创建
为了处理ESP32这个双核的情况 - 该函数为乐鑫自己实现的函数
1、新建文件
①在终端命令中新建文件
cd ~/ESP/v5.5/esp-idf
. ./export.sh
②我们需要添加ESP-IDF路径到工程中 在vscode中选择
这样就可以把ESP-IDF中里面的一些头文件包含路径包含到我们的工程中了,此时可以编写代码了
因为这个函数它的参数不是毫秒,而是系统节拍,那么多少系统节拍是500ms呢?那么这里FreeRTOS提供一个宏
pdMS_TO_TICKS() 里面的系数填多少,就可以把相应的时间转换为对应的系统节拍数
2、编译烧录命令
一般的命令操作 这几条命令可以让环境直接先运行get_idf - 其实就是修改这个路径问题 echo 'export IDF_PATH=~/ESP_IDF/v5.5.1/esp-idf' >> ~/.bashrc echo 'export IDF_TOOLS_PATH=~/ESP_IDF/Tools' >> ~/.bashrc echo 'source ~/ESP_IDF/v5.5.1/esp-idf/export.sh' >> ~/.bashrc source ~/.bashrc # 立即生效 cd ~/ESP/v5.5/esp-idf . ./export.sh cd ~/ESP_Code/test_task idf.py build -- 如果不行的话 替换为下面的一条命令 idf.py fullclean 【以上可以不用】 设置工具 - 修改环境变量 export IDF_TOOLS_PATH="$HOME/required_idf_tools_path" vim ~/.bashrc alias get_idf='. $HOME/esp/esp-idf/export.sh' 之后按;然后写入wq保存并退出 source ~/.bashrc 【环境变量设置】 get_idf idf.py build idf.py flash monitor - 烧录 【以后三条命令可以直接运行】
三、ESP32任务间同步
1、队列
队列在Freertos中主要是用于不同任务间的数据传输,一般来说有个负责写入数据的任务【比如:负责采集传感器数据的任务】,然后有一个不断从队列中接收数据的任务
【xQueueCreate创建一个队列,创建成功后,它会返回这个队列的句柄,后面所有的api都是基于这个句柄进行操作的】
【第一个参数为队列的最大容量、第二个参数为每个队列项所占的内存大小-单位为字节】
2、队列实验
队列代码 #include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "esp_log.h" //新建一个队列句柄 - 本质上是一个指针 QueueHandle_t queue_handle = NULL; //新建一个结构体 - 是存放在队列里面的数据单元 typedef struct { int value; }queue_data_t; void taskA(void* param) { //任务A:从队列里面接收数据,并打印 queue_data_t data; while(1) { //参数 句柄 缓存 等待时间 if(pdTRUE == xQueueReceive(queue_handle,&data,100)) { ESP_LOGI("queue","receive queue value:%d",data.value); } } } void taskB(void* param) { //任务B:每隔1s向队列里面发数据 queue_data_t data; memset(&data,0,sizeof(queue_data_t)); while(1) { xQueueSend(queue_handle,&data,100); vTaskDelay(pdMS_TO_TICKS(1000)); data.value++; } } void app_main(void) { //创建一个队列 - 参数 队列最大容量 队列单元的占用内存字节数 queue_handle = xQueueCreate(10,sizeof(queue_data_t)); //创建以下两个任务A B xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1); } 实验效果如下:
四、ESP32信号量
1、互斥锁
互斥锁实现了优先级继承机制 - 常常用于临界区资源的互斥访问
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/gpio.h" #include "esp_log.h" //定义一个信号量句柄 SemaphoreHandle_t bin_sem; void taskA(void* param) { //释放信号量 while(1) { xSemaphoreGive(bin_sem); vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* param) { //等待信号量成功后,打印提示 while(1) { if(pdTRUE == xSemaphoreTake(bin_sem,portMAX_DELAY)) { ESP_LOGI("bin","task B take binsem success"); } } } void app_main(void) { bin_sem = xSemaphoreCreateBinary(); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1); }
互斥锁使用的原则就是 - 谁拿了谁释放
2、移植dht11
最开始先设置信号线为输出模式,如上图所示: 为了主机开始发信号脉冲
信号线设置为输入模式 - 准备接收数据
#include <freertos/FreeRTOS.h> #include <freertos/task.h> #include <freertos/semphr.h> #include <nvs_flash.h> #include <soc/rmt_reg.h> #include "esp_log.h" #include "driver/gpio.h" #include "esp_system.h" #include "esp32s3/rom/ets_sys.h" #include "driver/rmt_types.h" #include "driver/rmt_rx.h" #include "driver/rmt_tx.h" #define TAG "DHT11" static uint8_t DHT11_PIN = -1; // RMT 接收通道句柄 static rmt_channel_handle_t rx_chan_handle = NULL; // 数据接收队列 static QueueHandle_t rx_receive_queue = NULL; // 互斥锁,防止多任务同时访问 static SemaphoreHandle_t dht11_mutex = NULL; // forward declaration static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10); // 接收完成回调函数 static bool IRAM_ATTR example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) { BaseType_t high_task_wakeup = pdFALSE; QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data; xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup); return high_task_wakeup == pdTRUE; } /** DHT11 初始化 */ void DHT11_Init(uint8_t dht11_pin) { DHT11_PIN = dht11_pin; rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_APB, .resolution_hz = 1000 * 1000, // 1 tick = 1us .mem_block_symbols = 64, .gpio_num = dht11_pin, .flags.invert_in = false, .flags.with_dma = false, }; ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle)); rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t)); assert(rx_receive_queue); ESP_LOGI(TAG, "register RX done callback"); rmt_rx_event_callbacks_t cbs = { .on_recv_done = example_rmt_rx_done_callback, }; ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan_handle, &cbs, rx_receive_queue)); ESP_ERROR_CHECK(rmt_enable(rx_chan_handle)); // 初始化互斥锁 if (dht11_mutex == NULL) { dht11_mutex = xSemaphoreCreateMutex(); assert(dht11_mutex); } } // RMT 脉冲解析函数(不变) static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10) { int i = 0; unsigned int rh = 0, temp = 0, checksum = 0; if (item_num < 41) { return 0; } if (item_num > 41) item++; for (i = 0; i < 16; i++, item++) { uint16_t duration = item->level0 ? item->duration0 : item->duration1; rh = (rh << 1) + (duration < 35 ? 0 : 1); } for (i = 0; i < 16; i++, item++) { uint16_t duration = item->level0 ? item->duration0 : item->duration1; temp = (temp << 1) + (duration < 35 ? 0 : 1); } for (i = 0; i < 8; i++, item++) { uint16_t duration = item->level0 ? item->duration0 : item->duration1; checksum = (checksum << 1) + (duration < 35 ? 0 : 1); } if ((((temp >> 8) + temp + (rh >> 8) + rh) & 0xFF) != checksum) { ESP_LOGI(TAG, "Checksum failure %4X %4X %2X\n", temp, rh, checksum); return 0; } rh = rh >> 8; temp = (temp >> 8) * 10 + (temp & 0xFF); if (rh <= 100) *humidity = rh; if (temp <= 600) *temp_x10 = temp; return 1; } /** 获取 DHT11 数据(加了互斥锁) */ int DHT11_StartGet(int *temp_x10, int *humidity) { int result = 0; if (xSemaphoreTake(dht11_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) { // 发送开始信号 gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT); gpio_set_level(DHT11_PIN, 1); ets_delay_us(1000); gpio_set_level(DHT11_PIN, 0); ets_delay_us(20000); gpio_set_level(DHT11_PIN, 1); ets_delay_us(20); gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT); gpio_set_pull_mode(DHT11_PIN, GPIO_PULLUP_ONLY); // 启动 RMT 接收器 rmt_receive_config_t receive_config = { .signal_range_min_ns = 100, .signal_range_max_ns = 1000 * 1000, }; static rmt_symbol_word_t raw_symbols[128]; static rmt_rx_done_event_data_t rx_data; ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config)); if (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) { result = parse_items(rx_data.received_symbols, rx_data.num_symbols, humidity, temp_x10); } xSemaphoreGive(dht11_mutex); // 释放互斥锁 } else { ESP_LOGW(TAG, "DHT11 busy, another task is reading"); } return result; }
#ifndef _DHT11_H_ #define _DHT11_H_ #include <stdint.h> /** DHT11初始化 * @param dht11_pin GPIO引脚 * @return 无 */ void DHT11_Init(uint8_t dht11_pin); /** 获取DHT11数据 * @param temp_x10 温度值X10 * @param humidity 湿度值 * @return 无 */ int DHT11_StartGet(int *temp_x10, int *humidity); #endif
在main目录下: CMakeLists.txt idf_component_register(SRCS "test_sem.c" "dht11.c" INCLUDE_DIRS ".")
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/gpio.h" #include "esp_log.h" #include "dht11.h" SemaphoreHandle_t dht11_mutex; void taskA(void* param) { int temp,humidity; while(1) { xSemaphoreTake(dht11_mutex,portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(500)); if(DHT11_StartGet(&temp,&humidity)) { ESP_LOGI("dht11","taskA-->temp:%d,humidity:%d%%",temp / 10,humidity); } xSemaphoreGive(dht11_mutex); vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* param) { int temp,humidity; while(1) { xSemaphoreTake(dht11_mutex,portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(500)); if(DHT11_StartGet(&temp,&humidity)) { ESP_LOGI("dht11","taskB-->temp:%d,humidity:%d%%",temp / 10,humidity); } xSemaphoreGive(dht11_mutex); vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { dht11_mutex = xSemaphoreCreateMutex(); DHT11_Init(GPIO_NUM_20); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1); }
3、RMT硬件外设
RMT 是 ESP32 系列上一个硬件外设,用来生成或采样高精度脉冲序列(常用于红外、遥控、单线协议采样等)。 优点:硬件定时,精度高,CPU 参与少;可以当作高速脉冲捕获器来读 DHT11 这类通过脉冲长度表示 0/1 的器件。 RMT 的基本概念: resolution_hz:分辨率(你设 1 MHz,即 1 tick = 1 µs)。 mem_block_symbols:每个内存块能记录多少个 symbol(符号)。一个 symbol 通常包含一对(level,duration)信息(level0/duration0, level1/duration1)。 rmt_symbol_word_t:RMT 的“符号”结构(duration0/duration1 与 level0/level1)。 驱动回调会把 rmt_rx_done_event_data_t(含 received_symbols 指针 和 num_symbols)交给用户。
五、事件组|直达任务通知【同步】
1、事件组
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_log.h" //定义两个事件位 #define NUM0_BIT BIT0 #define NUM1_BIT BIT1 //定义一个事件组的句柄 static EventGroupHandle_t test_event; void taskA(void* param) { //定时设置不同的事件位 while(1) { xEventGroupSetBits(test_event,NUM0_BIT); vTaskDelay(pdMS_TO_TICKS(1000)); xEventGroupSetBits(test_event,NUM1_BIT); vTaskDelay(pdMS_TO_TICKS(1000)); } } //任务B 等待上面的两个事件位 void taskB(void* param) { //等待事件位 EventBits_t ev; while(1) { ev = xEventGroupWaitBits(test_event,NUM0_BIT|NUM1_BIT,pdTRUE,pdFALSE,pdMS_TO_TICKS(50000)); if(ev & NUM0_BIT) { ESP_LOGI("ev","get BIT0 event"); } if(ev & NUM1_BIT) { ESP_LOGI("ev","get BIT1 event"); } } } void app_main(void) { test_event = xEventGroupCreate(); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1); }
2、直达任务通知
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_log.h" static TaskHandle_t taskA_handle; static TaskHandle_t taskB_handle; void taskA(void* param) { //定时发送一个任务通知值 uint32_t value = 0; //vTaskDelay(pdMS_TO_TICKS(200)); while(1) { xTaskNotify(taskB_handle,value,eSetValueWithOverwrite); vTaskDelay(pdMS_TO_TICKS(1000)); value++; } } void taskB(void* param) { //接收任务通知值并打印 uint32_t value; while(1) { xTaskNotifyWait(0x00,ULONG_MAX,&value,portMAX_DELAY); ESP_LOGI("ev","notify wait value:%d",value); } } void app_main(void) { //xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,&taskA_handle,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,&taskB_handle,1); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,&taskA_handle,1); } 注意:如果我们在app_main()中先创建任务A,此时taskB_handle是一个无效值 我们创建完任务A之后,任务A就直接运行了,任务A会直接向任务B发送任务通知,由于taskB_handle是一个无效值,所以导致程序崩溃了 解决方法1:在任务A进行任务通知之前,先Delay,让这个任务A先阻塞一下,以至于任务B可以运行,才可以成功创建这个taskB_handle句柄 解决方法2:在app_main()函数中颠倒任务A 和 任务B 的创建顺序,使得taskB_handle 这个任务B的句柄成功创建。
六、ESP_IDF的FreeRTOS操作系统
注意:ESP_IDF版本自动创建 5 个任务 ①空闲任务 - 在每一个核上都会有1个 优先级为0 ②FreeRTOS定时器任务 - 它的优先级任务是1 ③app_main任务 - 优先级是1 【应用入口】 ④IPC任务 - 每一个核创建一个【用于处理多核协调】 优先级是24,为最高优先级 ⑤ESP定时器任务 - 负责ESP定时器的回调 - 优先级为22
/* 注意: ①ESP_IDF FreeRTOS版本步使用原生FreeRTOS的内存管理,他实现了自己的堆 ②如果任务中使用到了浮点运算,则创建任务的时候必须指定具体运行在哪个核上,不能由系统自动安排 ③通常,负责处理无线网络的任务【WIFI 蓝牙】 将被固定到CPU0上,而处理应用程序其余部分的任务将被固定在CPU1上 */
七、ESP32外设
1、GPIO
/* 在ESP_IDF中,我们经常会看到一种代码 ①先定义一个结构体 ②我们往结构体里面填充东西 - 填充东西之后成为一个配置 ③我们将这个配置通过一个api函数 - 设置到底层去 */
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #define LED_GPIO GPIO_NUM_4 void led_task(void* param) { int gpio_level = 0; while(1) { gpio_level = gpio_level ? 0 : 1; gpio_set_level(LED_GPIO,gpio_level); vTaskDelay(pdMS_TO_TICKS(500)); } } void app_main(void) { //先定义一个结构体 - 初始化配置 gpio_config_t led_cfg = { .intr_type = GPIO_INTR_DISABLE, .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = (1 << LED_GPIO), .pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, }; //通过一个api函数 - 设置到底层去 gpio_config(&led_cfg); //新建一个任务 xTaskCreatePinnedToCore(led_task,"led_task",2048,NULL,3,NULL,1); }
2、脉冲宽度调制PWM
如上图所示: 如果我们加了IRAM_ATTR这个宏,就说明这个函数是放在内存中执行的,而不是flash中,那么他的效率也就比较高
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "driver/gpio.h" #include "driver/ledc.h" #include "esp_intr_alloc.h" // for ESP_INTR_FLAG_IRAM optional #define LED_GPIO GPIO_NUM_4 #define FULL_EV_BIT BIT0 #define EMPTY_EV_BIT BIT1 static EventGroupHandle_t ledc_event_handle = NULL; /* ISR 回调(fade 完成时被调用) * 注意:必须把局部变量初始化为 pdFALSE,并返回 (taskWoken == pdTRUE) */ bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg) { BaseType_t taskWoken = pdFALSE; // 一定要初始化 if (param->duty) { xEventGroupSetBitsFromISR(ledc_event_handle, FULL_EV_BIT, &taskWoken); } else { xEventGroupSetBitsFromISR(ledc_event_handle, EMPTY_EV_BIT, &taskWoken); } return (taskWoken == pdTRUE); // 告诉内核是否需要做上下文切换 } void led_task(void* param) { EventBits_t ev; while (1) { ev = xEventGroupWaitBits(ledc_event_handle, FULL_EV_BIT | EMPTY_EV_BIT, pdTRUE, // 取到后自动清除这些位 pdFALSE, // 不需要全部位同时置位 portMAX_DELAY); if (ev & FULL_EV_BIT) { // 从 full -> fade to 0 ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0, 2000); ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); } if (ev & EMPTY_EV_BIT) { // 从 empty -> fade to max ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1<<13)-1, 2000); // 13-bit max = 8191 ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); } // 循环继续等待事件 } } void app_main(void) { // 先创建事件组,确保回调不会在 event group 未创建时触发 ledc_event_handle = xEventGroupCreate(); // LEDC 定时器配置 ledc_timer_config_t ledc_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER_0, .duty_resolution = LEDC_TIMER_13_BIT, .freq_hz = 5000, .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&ledc_timer); // LEDC 通道配置(会设置引脚) ledc_channel_config_t ledc_channel = { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .timer_sel = LEDC_TIMER_0, .intr_type = LEDC_INTR_DISABLE, // 与 fade callback 无关(fade cb 通过 ledc_cb_register 注册) .gpio_num = LED_GPIO, .duty = 0, .hpoint = 0 }; ledc_channel_config(&ledc_channel); // 安装 fade 功能,参数为中断分配标志 // 如果你的回调需要在 IRAM 执行并且被要求中断分配在 IRAM, 可传 ESP_INTR_FLAG_IRAM ledc_fade_func_install(0); // 注册回调(一次即可) ledc_cbs_t cbs = { .fade_cb = ledc_finish_cb, }; ledc_cb_register(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, &cbs, NULL); // 启动第一个渐变(从 0 -> max) ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1<<13)-1, 2000); ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); // 创建工作任务(别忘记检查返回值和调整栈/优先级) xTaskCreatePinnedToCore(led_task, "led_task", 2048, NULL, 3, NULL, 1); }
3、RMT外设
WS2812时序 - 0码 1码 - 高低电平的占比
更多推荐
所有评论(0)