ESP32外设入门文档 2025-05-14
写在前边 弄这个文档的初衷是为了更快地入门ESP32,一寻思自己弄太慢了,就大家一起来完善吧。文档内容肯定会有错误,在群里说一下改正即可,如果进度比较快or有地方需要完善,按照已有格式进行完善即可,众人拾柴火焰高,对吧。
文档说明 本文档中只说明ESP32中的情况 下边的函数或者某些参数如果没有看懂,需要结合PDF和AI了解。
GPIO GPIO有四种常见的工作模式:
输出 :可以设置GPIO的高低电平
输入 :可以获取外部输入的高低电平信息,一般要设置加上拉电阻或下拉电阻
浮空输出 :可以设置GPIO的高低电平,但要在电路外部中增加上拉电阻
开漏输入 :可以获取外部输入的高低电平信息,但要在电路外部中增加上拉电阻
GPIO的使用流程:初始化->设置GPIO工作模式
GPIO初始化 定义结构体->调用API初始化GPIO
①定义结构体 结构体由以下内容组成
1 2 3 4 5 6 7 gpio_config_t led_GPIO_config = { .pin_bit_mask = (1 <<On_Board_LED_GPIO), .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_ENABLE, .mode = GPIO_MODE_OUTPUT, .intr_type = GPIO_INTR_DISABLE, };
1 2 3 4 5 6 7 gpio_config_t led_GPIO_config = { .pin_bit_mask = (1 <<On_Board_LED_GPIO), .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_ENABLE, .mode = GPIO_MODE_OUTPUT, .intr_type = GPIO_INTR_DISABLE, };
②使用API初始化GPIO 1 gpio_config(&led_GPIO_config);
1 gpio_config(&led_GPIO_config);
GPIO输出 输出的两个参数分别是:
gpio_num:GPIO引脚编号(如GPIO_NUM_2)。
level:电平值(0/1)这里level非零即为1
1 GPIO_set_level ( gpio_num ,level );
1 GPIO_set_level(gpio_num,level);
GPIO交换矩阵 GPIO交换矩阵的核心作用:
动态信号路由 :允许将90%以上的外设信号(如UART、PWM、SPI等)自由映射到 任意可用的GPIO引脚(部分特殊功能引脚除外)。
突破物理限制 :解决PCB设计时的引脚冲突问题,例如可将同一外设(如 UART0)的TX和RX分配到不同位置的引脚。
简而言之,可以让任意引脚使用任意的功能。
定时器PWM 定时器是通道的基础.通道必须绑定到一个已通过ledc_timer_config配置的定时器。定时器定义了PWM的全局参数(如频率),而通道负责将这一时间基准转化为具体GPIO的输出。共享性 :一个定时器可被多个通道共享。例如,定时器配置为1kHz频率后,多个通道可基于同一频率输出不同占空比的信号。参数一致性 :通道的speed_mode必须与绑定的定时器一致,否则PWM信号无法正确生成。
配置流程: 配置PWM,绑定通道和GPIO
①配置PWM: 创建PWM配置结构体->通过API使用结构体对PWM的定时器进行配置
②绑定: 完成通道绑定设置->通过API使用结构体对PWM的定时器进行绑定
创建PWM配置结构体 对PWM进行初始化设置,设置定时器频率、通道,时钟源,翻转频率,分辨率
1 2 3 4 5 6 7 ledc_timer_config_t On_Board_LED_GPIO_Timer_change = { .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER 0 , .clk_cfg = LEDC_AUTO_CLK, .freq_hz = 5000 , .duty_resolution = LEDC_TIMER_13_BIT, };
↓↓ ESP32的时钟源类型 ↓↓
1 2 3 4 5 6 7 ledc_timer_config_t On_Board_LED_GPIO_Timer_change = { .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER0, .clk_cfg = LEDC_AUTO_CLK, .freq_hz = 5000 , .duty_resolution = LEDC_TIMER_13_BIT, };
时钟源类型
频率范围
适用模式
特点
APB_CLK
80MHz(默认)
高速模式
高精度、低抖动
RTC8M_CLK
~8MHz(可校准)
低速模式
低功耗、可深度睡眠工作
REF_TICK
1MHz
特殊应用
同步系统时钟
此处一般为自动
使用API配置PWM ledc_timer_config用于初始化用到的定时器 ()
1 ledc_timer_config(&On_Board_LED_GPIO_Timer_change);
1 ledc_timer_config(&On_Board_LED_GPIO_Timer_change);
通道初始化 指定配置的通道,及其绑定的定时器与GPIO,并指明定时器频率,中断类型和信号占空比
1 2 3 4 5 6 7 8 ledc_channel_config_t On_Board_LED_GPIO_Timer_channel_change = { .channel = 0 , .gpio_num = On_Board_LED_GPIO, .timer_sel = 0 , .speed_mode = LEDC_LOW_SPEED_MODE, .duty = 0 , .intr_type = LEDC_INTR_DISABLE, };
1 2 3 4 5 6 7 8 ledc_channel_config_t On_Board_LED_GPIO_Timer_channel_change = { .channel = 0 , .gpio_num = On_Board_LED_GPIO, .timer_sel = 0 , .speed_mode = LEDC_LOW_SPEED_MODE, .duty = 0 , .intr_type = LEDC_INTR_DISABLE, };
使用API进行绑定 ledc_channel_config用于初始化ledc输出通道 以及将timer关联起来
1 ledc_channel_config(&ledc_channel)
1 ledc_channel_config(&ledc_channel)
这样对应的通道就能输出PWM频率了(上例 为 On_Board_LED_GPIO这个GPIO接口 输出PWM )
计时计数 IIC 协议:注意!注意!新旧I2C协议代码不通用!不兼容!旧版 “driver/i2c.h” 新版 “driver/i2c_master.h”
配置流程: 写初始化I2C函数,写I2C对寄存器读写函数(用于寄存器初始化函数中),主函数调用I2C初始化函数与寄存器初始化函数
①配置I2C: 创建I2C初始化结构体->配置参数->安装I2C驱动
②寄存器读写函数->寄存器初始化函数 需要用到寄存器值读写
③ 在主函数中调用I2C初始化函数与寄存器初始化函数 文件中创建结构体初始化I2C 1 2 3 4 5 6 7 8 9 10 i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_MASTER_SDA_IO, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = I2C_MASTER_SCL_IO, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = I2C_MASTER_FREQ_HZ, }; i2c_param_config(BSP_I2C_NUM, &i2c_conf); return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0 , 0 , 0 );
1 2 3 4 5 6 7 8 9 10 i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_MASTER_SDA_IO, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = I2C_MASTER_SCL_IO, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = I2C_MASTER_FREQ_HZ, }; i2c_param_config(BSP_I2C_NUM, &i2c_conf);return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0 , 0 , 0 );
通过I2C对寄存器值进行读取写入(用于寄存器初始化函数中) BSP_I2C_NUM依旧是使用的I2C外设编号,SENSOR_ADDR是寄存器的地址,I2C通过地址找到寄存器并进行读写操作。
1 2 3 4 5 6 7 8 9 10 esp_err_t I2C_register_read (uint8_t reg_addr, uint8_t *data, size_t len) { return i2c_master_write_read_device(BSP_I2C_NUM,SENSOR_ADDR, ®_addr, 1 , data, len, 1000 / portTICK_PERIOD_MS); }esp_err_t I2C_register_write_byte (uint8_t reg_addr, uint8_t data) { uint8_t write_buf[2 ] = {reg_addr, data}; return i2c_master_write_to_device(BSP_I2C_NUM, SENSOR_ADDR, write_buf, sizeof (write_buf), 1000 / portTICK_PERIOD_MS); }
1 2 3 4 5 6 7 8 9 esp_err_t I 2 C _register_read(uint8_t reg_addr, uint8_t *data, size_t len) { return i2c_master_write_read_device(BSP_I2C_NUM,SENSOR_ADDR, ®_addr, 1 , data, len, 1000 / portTICK_PERIOD_MS); }esp_err_t I 2 C _register_write_byte(uint8_t reg_addr, uint8_t data) { uint8_t write_buf[2 ] = {reg_addr, data}; return i2c_master_write_to_device(BSP_I2C_NUM, SENSOR_ADDR, write_buf, sizeof (write_buf), 1000 / portTICK_PERIOD_MS); }
在主函数中调用初始化函数 ESP_ERROR_CHECK 是一个宏函数,位于 esp-idf 文件中,用于检测执行函数的返回结果,如果没有错误,什么事情都不会发生,如果有错误,会引起单片机重启。使用这个宏函数,需要包含 esp_err.h 头文件。
1 2 3 ESP_ERROR_CHECK(bsp_i2c_init()); ESP_LOGI(TAG, "I2C initialized successfully" ); sensor_init();
1 ESP_ERROR_CHECK(bsp_i2c_init());ESP_LOGI(TAG, "I2C initialized successfully" );
串口(UART) 使用方法与其他的配置大同小异 使用流程:创建配置结构体->调用结构体进行配置->安装驱动(设置RX/TX缓冲区)->读取/发送
创建配置结构体
baud_rate
波特率
设置数据传输速率,单位是每秒比特数(bps)
data_bits
数据位
定义每个数据包的位数
parity
校验位
错误检测机制,检测单比特错误
stop_bits
停止位
标识数据包结束的位数
flow_ctrl
流控制
防止数据溢出的硬件流控
source_clk
时钟源
选择 UART 的基准时钟源
这里对流控制进一步解释(因为我也不会) 流控制的作用:
防止数据溢出:当接收方缓冲区满时,通过流控制信号让发送方暂停传输。
保证传输可靠性:避免高速发送导致低速接收设备无法处理数据。
适应不同硬件性能:协调不同处理速度的设备(如MCU与PC)之间的通信。
硬件流控制 (通过物理引脚信号控制数据流):
RTS(RequestToSend):接收方准备好接收数据时,拉高此信号。
CTS(ClearToSend):发送方检测到CTS有效时,才允许发送数据。
1 2 3 4 5 6 7 8 uart_config_t uart_config = { .baud_rate = 115200 , .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT };
1 2 3 4 5 6 7 8 uart_config_t uart_config = { .baud_rate = 115200 , .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT };
调用API配置结构体 这里 UART_PORT_NUM 是 串口号 ( 选择 哪个 串口 )( ESP32芯片通常包含3个独立的UART硬件控制器,串口0默认串口下载,串口1一般为FLASH的引脚(可通过GPIO交换矩阵更改引脚),串口2常用 )
1 uart_param_config(UART_PORT_NUM, &uart_config)
1 uart_param_config(UART_PORT_NUM, &uart_config)
安装UART驱动 串口号-发送缓存大小-接收缓存大小-队列长度-队列句柄(传回来用的)-中断标志(一般使用0,使用默认配置)
1 uart_driver_install(UART_PORT_NUM, BUF_SIZE * 2 , 0 , 0 , NULL , intr_alloc_flags)
1 uart_driver_install(UART_PORT_NUM, BUF_SIZE * 2 , 0 , 0 , NULL , intr_alloc_flags)
读取串口数据 这函数有返回值:如果成功,则返回读取的字节数;失败则返回-1。串口号-缓冲区-缓冲区大小-等待数据的超时时间 portMAX_DELAY表示无限等待
1 uart_read_bytes(UART_PORT_NUM,BUF_SIZE, uint32_t buf_size, TickType_t ticks_to_wait);
1 uart_read_bytes(UART_PORT_NUM,BUF_SIZE, uint32_t buf_size, TickType_t ticks_to_wait);
发送串口数据 串口号-待发送数据的源地址-发送数据的字节数
1 uart_write_bytes( U ART_PORT_NUM,src,size);
1 uart_write_bytes(UART_PORT_NUM,src,size);
更改串口引脚(可选) 通过GPIO矩阵,将默认的串口引脚更改。若更改,则需在安装驱动前配置该项。
1 uart_set_pin(UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS)
1 uart_set_pin(UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS)
WiFi STA模式 STA模式:连接已有的网络,ESP32作为终端设备 使用流程:总流程:创建并初始化函数->监听设备事件->分情况调用回调函数进行各项初始化和配置 初始化流程:初始化NVS(存储SSID和密码),初始化TCP/IP栈(使用esp_netif_init),初始化事件系统循环(存储系统事件,包括WIFI MQTT BLU等),初始化WIFI,设置事件回调函数并监听事件
初始化NVS,TCP/IP,系统事件循环 1 2 3 esp_netif_init() ; esp_event_loop_create_default() ; nvs_flash_init() ;
初始化WIFI 先定义个WIFI初始化结构体->再创建个设置STA的初始化结构体->调用API进行初始化(WIFI和WIFI设置)->启动WIFI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); wifi_config_t wifi_config ={ .sta = { .ssid = DEFAULT_WIFI_SSID, .password = DEFAULT_WIFI_PASSWORD, .threshold.authmode = WIFI_AUTH_WPA2_PSK, .pmf_cfg = { .capable = true , .required = false }, }, }; esp_wifi_set_mode(WIFI_MODE_STA) ; esp_wifi_set_config(WIFI_IF_STA, &wifi_config) esp_wifi_start() ;
1 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
设置事件回调函数 WIFI事件的回调函数 事件回调函数有以下几个要素:1. 有个能被调用的“注册回调函数的接口”(就是将回调函数与事件进行绑定)2. 有个回调函数(被调用之后产生的动作) WIFI需要注册多个回调函数:WIFI和网络(IP_EVENT) 这里的事件回调函数,主要是为了判断WIFI的各个事件段该做什么,比如未连接WiFi怎么办,获取ip之后怎么办。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static void event_handler (void * arg, esp_event_base_t event_base,int32_t event_id, void * event_data) { if ( e vent_base == WIFI_EVENT) { swit c h ( e vent_id ) { case WIFI_EVENT_STA_START: esp_wifi_connect( ); break ; case WIFI_EVENT_STA_CONNECTED: ESP_LOGI( T AG, " ESP CONNECTED TO AP " ) break ; case WIFI_EVENT_STA_ DIS CONNECTED: esp_wifi_connect(); break ; default : b reak; } if (event_base == IP _EVENT) { switch (event_id) { case IP_EVENT_STA_GOT_IP : ESP_LOGI(TAG,"ESP GET IP " ) break ; default : break ; } } } } esp_event_handler_register (WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL )); esp_event_handler_register( IP _EVENT, IP _EVENT_ STA_GOT_IP ,&event_handler,NULL ));
1 static void event_handler (void * arg, esp_event_base_t event_base,int32_t event_id, void * event_data)
一键配网模式(SmartConfig) MQTT 基于TCP/IP的低带宽需求报文传输服务 使用流程:①创建MQTT句柄(通过这个对MQTT进行操作)->创建并调用结构体(存储初始化MQTT的参数)->②->注册回调函数->启用MQTT ②配置事件回调函数,对MQTT各个状态进行反应(对订阅、发布、接收数据等进行操作)
①MQTT基础设置 创建MQTT句柄 这里实际上创建了一个MQTT客户端,名为s_mqtt_client后续所有对MQTT客户端的操作,都使用s_mqtt_client
1 esp_mqtt_client_handle_t s_mqtt_client = NULL ;
1 esp_mqtt_client_handle_t s_mqtt_client = NULL ;
创建并填充结构体 1 2 3 4 5 6 esp_mqtt_client_config_t mqtt_cfg = {0 }; mqtt_cfg.broker.address.uri = MQTT_ADDRESS; mqtt_cfg.broker.address.port = MQTT_PORT ; mqtt_cfg . credentials . client_id = MQTT_CLIENT; mqtt_cfg.credentials.username = MQTT_USERNAME; mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;
1 2 3 4 5 6 esp_mqtt_client_config_t mqtt_cfg = {0 }; mqtt_cfg.broker.address.uri = MQTT_ADDRESS; mqtt_cfg.broker.address.port = MQTT_PORT; mqtt_cfg.credentials.client_id = MQTT_CLIENT; mqtt_cfg.credentials.username = MQTT_USERNAME; mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;
调用配置结构体 并 初始化客户端 1 s_mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
1 s_mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
注册回调函数 为 MQTT 客户端注册事件回调函数。具体来说:1. 调用 esp_mqtt_client_register_event() 来关联 MQTT 客户端 s_mqtt_client 和事件回调函数aliot_mqtt_event_handler。2. 指定 ESP_EVENT_ANY_ID 表示接收所有类型的 MQTT 事件(例如连接、断开、发布、订阅、接收数据等)。3. 回调函数中可以根据事件类型(event_id)做出相应处理,例如记录日志、修改连接状态或发布配置信息。4. 最后传入 s_mqtt_client 作为用户数据,这样在回调中可以访问到该 MQTT 客户端句柄或相关信息。
1 esp_mqtt_client_register_event(s_mqtt_client, ESP_EVENT_ANY_ID, aliot_mqtt_event_handler, s_mqtt_client);
1 esp_mqtt_client_register_event(s_mqtt_client, ESP_EVENT_ANY_ID, aliot_mqtt_event_handler, s_mqtt_client);
启用MQTT 1 esp_mqtt_client_start(s_mqtt_client); ( 这里其实调用了MQTT客户端 s_mqtt_client )
1 esp_mqtt_client_start(s_mqtt_client);(这里其实调用了MQTT客户端s_mqtt_client)
②配置MQTT事件回调函数 可以用这个回调函数直接读取MQTT订阅的数据 配置方式与WIFI大差不差
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static void aliot_mqtt_event_handler (void * event_handler_arg,esp_event_base_t event_base,int32_t event_id,void * event_data) { esp_mqtt_event_handle_t event = event_data; switch ((esp_mqtt_event_id_t )event_id) { case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "mqtt connected" ); s_is_mqtt_connected = true ; esp_mqtt_client_subscribe_single(s_mqtt_client, MQTT_SUBSCRIBE_TOPIC, 1 ); publish_sensor_config(); break ; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "mqtt disconnected" ); s_is_mqtt_connected = false ; break ; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG, "mqtt subscribed, msg_id=%d" , event->msg_id); break ; case MQTT_EVENT_PUBLISHED: ESP_LOGI(TAG, "mqtt publish ack, msg_id=%d" , event->msg_id); break ; case MQTT_EVENT_DATA: ESP_LOGI(TAG, "mqtt data received" ); printf ("TOPIC=%.*s\r\n" , event->topic_len, event->topic); printf ("DATA=%.*s\r\n" , event->data_len, event->data); break ; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, "MQTT_EVENT_ERROR" ); break ; default : break ; } }
1 static void aliot_mqtt_event_handler (void * event_handler_arg,esp_event_base_t event_base,int32_t event_id,void * event_data) { esp_mqtt_event_handle_t event = event_data; / / 获取MQTT接收到的数据 / / 上方这行代码的本质是将通用指针转换为 MQTT 事件数据结构指针,以便提取事件信息。 switch ((esp_mqtt_event_id_t )event_id) { case MQTT_EVENT_CONNECTED:
注:
1 在 MQTT 事件回调函数中,传入的 event 对象其实是一个 esp_mqtt_event_t 结构体指针,它包含了多个字段,常用的有: - msg_id:消息的编号,用于标识发布、订阅等操作对应的消息。 - topic:指向 MQTT 消息主题的指针(当接收数据时有效)。 - topic_len:主题字符串的长度。 - data:指向 MQTT 消息数据的指针,对于 MQTT_EVENT_DATA 事件,该字段保存了接收到的消息内容。 - data_len:消息数据的长度。 - total_data_len:在分段接收数据的情况下,总的数据长度。 - client:MQTT 客户端的句柄。 - error_handle:在发生错误时可能包含错误相关的信息。 这些字段结合起来,可以通过 event 对象获取到当前 MQTT 事件的详细信息,从而在回调中根据不同的事件类型进行相应处理。
1 在 MQTT 事件回调函数中,传入的 event 对象其实是一个 esp_mqtt_event_t 结构体指针,它包含了多个字段,常用的有: - msg_id:消息的编号,用于标识发布、订阅等操作对应的消息。 - topic:指向 MQTT 消息主题的指针(当接收数据时有效)。 - topic_len:主题字符串的长度。 - data:指向 MQTT 消息数据的指针,对于 MQTT_EVENT_DATA 事件,该字段保存了接收到的消息内容。 - data_len:消息数据的长度。 - total_data_len:在分段接收数据的情况下,总的数据长度。 - client:MQTT 客户端的句柄。 - error_handle:在发生错误时可能包含错误相关的信息。这些字段结合起来,可以通过 event 对象获取到当前 MQTT 事件的详细信息,从而在回调中根据不同的事件类型进行相应处理。
使用MQTT 发送 数据 发送流程:NVS 是 Non-Volatile Storage(非易失性存储)的缩写,是 ESP32 用来持久化保存数据的一种机制。它类似于“小型文件系统”,常用于保存 WiFi 配置、密钥、参数等,即使断电数据也不会丢失。此处,NVS 主要用于保证系统能正常保存和读取配置 信息。
1 2 3 4 esp_err_t ret = nvs_flash_init();if ( ret == ESP_ERR_NVS_NO_FREE_PAGE||ret == ESP_ERR_NVS_NEW_VERSION_FOUND) ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); }
1 esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGE||ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
初始化WIFI 传入回调函数,用于通知连接成功事件
1 wifi_sta_init(wifi_event_handler);
1 wifi_sta_init(wifi_event_handler);
监听WIFI连接事件 直到WiFi连接成功后,才启动MQTT连接
1 2 3 4 5 ev=xEventGroupWaitBits(s_wifi_ev,WIFI_CONNECT_BIT,pdTRUE,pdFALSE,portMAX_DELAY);if (ev & WIFI_CONNECT_BIT) { mqtt_start(); }
1 ev=xEventGroupWaitBits(s_wifi_ev,WIFI_CONNECT_BIT,pdTRUE,pdFALSE,portMAX_DELAY); if (ev & WIFI_CONNECT_BIT) { mqtt_start(); })
发布数据 1 2 3 4 5 6 7 8 9 10 11 12 while (1 ) { int count = 0 ; if (s_is_mqtt_connected) { snprintf (mqtt_pub_buff,64 ,"{\"count\":\"%d\"}" ,count); esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLIC_TOPIC, mqtt_pub_buff, strlen (mqtt_pub_buff),1 , 0 ); count++; } vTaskDelay(pdMS_TO_TICKS(2000 )); }
JSON JSON是一种规范好的数据格式, 本质上就是字符串 。用于传输数据信息,基于“键-值对“,可以包含对象、数组、字符串、数字、布尔、NULL这六种最基本的数据。 ESP32中可以使用cJSON数据解析器来分析和打包JSON数据,需要调用”cJSON.h”。 JSON的基本格式如下
1 2 3 4 5 6 7 8 9 10 { "键名" : "值" , "键名" : { "键名" : "值" , "键名" : "值" , } , "键名" : "值" , "键名" : 数字, "键名" : [ "值" , "值" , "值" , ] }
1 { "键名" : "值" , "键名" : { "键名" : "值" , "键名" : "值" , } , "键名" : "值" , "键名" : 数字, "键名" : [ "值" , "值" , "值" , ] } )
解析JSON流程: 解析JSON->……>解析JSON->读取键的数据->读取数据的值
解析JSON并保存 本文档中只说明ESP32中的情况
1 cJson* js_get_from_mqtt = cJSON_Pqrse(json_str);
1 cJson* js_get_from_mqtt = cJSON_Pqrse(json_str);
读取JSON中某个键的数据 这里的数据,可以是具体的数值和字符串,也可以是对象。若JSON层层嵌套,则需层层解析。重复使用 cJSON_GetObjectItem,一层一层解析 , 以下操作也同理
1 cJson* Humidity_get_from_js =cJSON_ GetObjectItem( js_get_from_mqtt , " Humidity " ) ;
1 cJson* Humidity_get_from_js=cJSON_GetObjectItem(js_get_from_mqtt,"Humidity" );
读取获取数据的值 如果要获取数据的值为字符串,则用cJSON_GetStringValue() 如果是数值,则用cJSON_GetNumberValue()
1 Humidity _val = cJSON_Get NumberValue (Humidity_get_from_js);
1 Humidity_val = cJSON_GetNumberValue(Humidity_get_from_js);
生成JSON流程: 创建cJSON格式的对象->向对象中添加键-值
创建cJSON格式的对象 这里创建了个名为 js _obj _tx_humidity的cJSON对象。
1 cJSON *js _obj _tx_humidity = cJSON_CreatObjecte();
1 cJSON *js _obj _tx_humidity = cJSON_CreatObjecte();)
向对象中添加键-值 这里向 js _obj _tx_humidity这个对象中添加的键-值为 “Humidity”,”%d” 然后向 js _obj _tx_humidity这个对象中又添加了一个对象js _obj _sys
1 2 cJSON_ Add NumberTo Object (js_obj_tx_humidity , " H umidity " , humidity ) ; cJSON_Add Item ToObject (js_obj_tx_humidity," sys " ,js_obj_sys) ;
1 cJSON_AddNumberToObject(js_obj_tx_humidity,"Humidity" ,humidity);
WS2812 以下是调用已有配置对WS2812进行驱动单总线控制的彩色LED灯珠 ,因为对控制时序比较严格,故使用RMT红外控制。同GPIO等操作方式,需先使用结构体进行初始化,再使用API通过结构体配置。 Tips:使用”led_strip.h” 使用流程(基于官方Example):创建结构体(共两个)->调用API进行配置->使用函数配置亮度颜色等参数
结构体创建 先创建LED的配置结构体和被操控的LED对象 1 2 3 4 5 static led_strip_handle_t led_strip; led_strip_config_t WS2812_ strip_config = { .strip_gpio_num = BLINK_GPIO, .max_leds = 1 , };
1 static led_strip_handle_t led_strip; / / 创建句柄名为led_stripled_strip_config_t WS2812_strip_config = { .strip_gpio_num = BLINK_GPIO,
再创建RMT的配置结构体 1 2 3 4 led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000 , .flags.with_dma = false , };
1 led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000 ,
调用API进行配置 使用led_strip_new_rmt_device函数利用传递进来两个参数(LED和RMT的配置结构体),生成到 先前创建的 LED对象( led_strip )中
1 led_strip_new_rmt_device(&WS2812_strip_config, &rmt_config, &led_strip) ;
1 led_strip_new_rmt_device(&WS2812_strip_config, &rmt_config, &led_strip);)
配置LED亮度等参数 对生成的对象(led_strip),进行参数配置;配置后刷新LED状态;
1 2 3 4 led_strip_set_pixel(led_strip, 0 , 16 , 16 , 16 ); led_strip_refresh(led_strip);
1 led_strip_set_pixel(led_strip, 0 , 16 , 16 , 16 );led_strip_refresh(led_strip);)
这样WS2812就会常亮了(不基于BLink例程)
以下是从RMT驱动开始,驱动WS2812 基于此帖子( 【ESP32 IDF】用RMT控制 WS2812 彩色灯带 - 东邪独孤 - 博客园 )
DHT11 单总线工作的数字温湿度传感器,工作范围“-2060摄氏度””5%90%” DHT11基于单总线,同样利用RMT红外进行控制(接收数据)。 工作流程:定义RMT接收用结构体->调用RMT接收通道API->使用队列接收数据->接收完使用回调至接收模式
初始化DHT11 1 2 3 4 5 6 7 8 rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_APB, .resolution_hz = 1000 * 1000 , .mem_block_symbols = 64 , .gpio_num = dht11_pin, .flags.invert_in = false , .flags.with_dma = false , };
1 rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_APB,
调用rmt接受通道 1 2 3 4 ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle)); ESP_ERROR_CHECK(rmt_enable(rx_chan_handle))
使用队列接收数据 1 2 3 4 5 6 7 8 9 10 11 12 13 rx_receive_queue = xQueueCreate(20 , sizeof (rmt_rx_done_event_data_t )); assert(rx_receive_queue);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; }
将RMT读取到的脉冲数据处理为温度和湿度 1 static int parse_items (rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10) {};
1 static int parse_items (rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10) {};)
脉冲信号处理,复制即可 调整脉冲信号以接受DHT11数据 1 int DHT11_StartGet (int *temp_x10, int *humidity) {}
1 int DHT11_StartGet (int *temp_x10, int *humidity) {}
脉冲信号调整部分复制即可 调整后接收数据 1 2 3 4 5 6 7 8 9 10 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) { return parse_items(rx_data.received_symbols, rx_data.num_symbols,humidity, temp_x10); }return 0 ;
1 static rmt_symbol_word_t raw_symbols[128 ];
LCD 液晶显示屏 这里采用 液晶屏驱动芯片ST7789,采用SPI通信方式与ESP32连接.由于esp32的引脚数量少,可以使用寄存器 PCA9557,对远程位置上的输入输出控制和状态监控,用于解决微控制器I/O资源不足的问题,并允许通过简单的I2C接口实现复杂的I/O操作。液晶屏的LCD_CS引脚由IO扩展芯片pca9557控制,所以需要先初始化pca9557,而pca9557是i2c通信芯片,所以又需要先初始化i2c。
Comment by 刘子华: BSP_I2C_NUM依旧是使用的I2C外设编号,SENSOR_ADDR是寄存器的地址,I2C通过地址找到寄存器并进行读写操作。…ESP_ERROR_CHECK 是一个宏函数,位于 esp-idf 文件中,用于检测执行函数的返回结果,如果没有错误,什么事情都不会发生,如果有错误,会引起单片机重启。使用这个宏函数,需要包含 esp_err.h 头文件。…假设您正在开发一个项目,其中微控制器的GPIO引脚不够用,而您需要连接更多的按钮和LED。这时,您可以使用PCA9557来扩展I/O端口。将按钮连接到PCA9557的一些GPIO引脚上,并将其配置为输入模式;将LED连接到其他GPIO引脚,并将其配置为输出模式。然后,通过I2C总线与PCA9557通信,以读取按钮状态并控制LED。
Comment by 高希来: 能不能直接加到文档里QAQ
初始化PCA8559 首先需要设置PCA9557的工作模式(如输入或输出)。这是通过向特定寄存器写入配置数据完成的。
1 2 3 4 5 6 7 8 9 void pca9557_init (void ) { DVP_PWDN=1 PA_EN = 0 LCD_CS = 1 pca9557_register_write_byte(PCA9557_OUTPUT_PORT, 0x05 ); pca9557_register_write_byte(PCA9557_CONFIGURATION_PORT, 0xf8 ); }
1 void pca9557_init (void ) {
读写PCA8559 1 2 3 4 5 6 7 8 9 10 11 12 esp_err_t pca9557_register_read (uint8_t reg_addr, uint8_t *data, size_t len) { return i2c_master_write_read_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, ®_addr, 1 , data, len, 1000 / portTICK_PERIOD_MS); }esp_err_t pca9557_register_write_byte (uint8_t reg_addr, uint8_t data) { uint8_t write_buf[2 ] = {reg_addr, data}; return i2c_master_write_to_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, write_buf, sizeof (write_buf), 1000 / portTICK_PERIOD_MS); }
控制PCA9557任意一个单独引脚变高变低 1 2 3 4 5 6 7 8 9 esp_err_t pca9557_set_output_state (uint8_t gpio_bit, uint8_t level) { uint8_t data; esp_err_t res = ESP_FAIL; pca9557_register_read(PCA9557_OUTPUT_PORT, &data, 1 ); res = pca9557_register_write_byte(PCA9557_OUTPUT_PORT, SET_BITS(data, gpio_bit, level)); return res; }
控制PCA9557_LCD_CS输出高低电平 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void lcd_cs (uint8_t level) { pca9557_set_output_state(LCD_CS_GPIO, level); }void pa_en (uint8_t level) { pca9557_set_output_state(PA_EN_GPIO, level); }void dvp_pwdn (uint8_t level) { pca9557_set_output_state(DVP_PWDN_GPIO, level); }
初始化I2C 1 2 3 4 5 6 7 8 9 10 11 12 13 esp_err_t bsp_i2c_init (void ) { i2c_config_t i2c_conf = { .mode = I2C_MODE_MASTER, .sda_io_num = BSP_I2C_SDA, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = BSP_I2C_SCL, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = BSP_I2C_FREQ_HZ }; i2c_param_config(BSP_I2C_NUM, &i2c_conf); return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0 , 0 , 0 ); }
1 esp_err_t bsp_i2c_init (void ) { i2c_config_t i2c_conf = { .mode = I2C_MODE_MASTER, .sda_io_num = BSP_I2C_SDA, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = BSP_I2C_SCL, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = BSP_I2C_FREQ_HZ }; i2c_param_config(BSP_I2C_NUM, &i2c_conf); return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0 , 0 , 0 ); })
初始化LCD CMake 是什么 ? 在 ESP-IDF(ESP32 开发框架)中,CMake 负责组织和管理各个组件的编译过程。详细 内容 可 参考 小白入门笔记:CMake编译过程详解-腾讯云开发者社区-腾讯云 , 这里 只 说一下 跟 ESP 3 2 相关 的 或者 是 我遇到 的 问题 。
CMake L i s t s . t x t 是什么 , 有什么 用 ? 拿 这段 代码 举例 :
1 idf_component_register(SRCS "uart_echo_example_main.c" INCLUDE_DIRS "." )
1 idf_component_register(SRCS "uart_echo_example_main.c" INCLUDE_DIRS "." ))
简单来说,这就是告诉 ESP-IDF:“我要编译 uart_echo_example_main.c,并且头文件就在当前目录。”CMake 会自动把 uart_echo_example_main.c 加入编译,并把当前目录作为头文件搜索路径。
使用ESP32中遇到的奇奇怪怪的问题 明明添加了”esp_log.h”这种头文件,但是根本识别不到该怎么办?
ESP-IDF的路径设置对了吗。
需要用“ ESP-IDF: 添加 VS Code 配置文件夹“给这个项目添加以下配置文件夹
编译卡在CMake过不去,但是检查了一圈程序发现没问题
删掉build文件夹,再重新编译一下
明明头文件设置好了,但是头文件中的函数无法调用怎么办?
在 CMakeLists.txt中,按照格式把需要编译的.C程序添加进去(根据已有的格式设置就行)