
一、串口原理esp32s3三个串口外设UART0 TX-IO43 RX-IO44UART1 TX-IO17 TX-IO18UART2 TX-IO7 TX-IO8ESP32S3串口独特点1.ESP32S3芯片内部有个交换矩阵任意引脚都可以配置成uart0、12.引导到main函数之前IDF框架把串口0已经初始化好3.已经做好printf的重定向 (可以直接用printf函数打印内容在串口显示)参数配置波特率 115200数据位长度 8停止位 1奇偶校验位 0ESP32-UART基本框图二、代码编写1.使用串口1普通收发数据创建文件bsp组件bsp_uart.c bsp_uart.htask线程task_uart.c task_uart.h主要记住修改CMakeLists文件uart初始化配置uart_config_t 结构体初始化uart_param_config 填充结构体参数uart_set_pin 设置外设号引脚uart_driver_install 注册驱动、分配缓冲区等收发数据uart_write_bytes 直接调用即可uart_read_bytes:(1)阻塞式单字节接收uint8_t data; while (1) { uart_read_bytes(UART_NUM_1, data, 1, portMAX_DELAY); // 一直等 // 处理收到的 data }(2)带超时的n字节读取定时轮询int len uart_read_bytes(UART_NUM_1, buf, sizeof(buf), 10 / portTICK_PERIOD_MS); if (len 0) { buf[len] \0; ESP_LOGI(TAG, len%d, UART1 Recv: %s, len, buf); }代码#include bsp_uart.h /** * brief UART1 初始化函数 * note 配置 UART1 的波特率、数据位、校验位、停止位等参数并安装驱动 * param 无 * retval 无 */ void bsp_uart1_init(void) { // UART1 配置结构体 uart_config_t uart_config { .baud_rate 115200, // 波特率115200 .data_bits UART_DATA_8_BITS, // 数据位8位 .parity UART_PARITY_DISABLE, // 校验位无校验 .stop_bits UART_STOP_BITS_1, // 停止位1位 .flow_ctrl UART_HW_FLOWCTRL_DISABLE, // 流控关闭硬件流控 .source_clk UART_SCLK_APB, // 时钟源APB总线时钟 }; // 配置 UART 参数 ESP_ERROR_CHECK(uart_param_config(UART1_PORT, uart_config)); // 设置 UART 引脚TX、RXRTS和CTS不更改 ESP_ERROR_CHECK(uart_set_pin(UART1_PORT, UART1_TX_PIN, UART1_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); // 安装 UART 驱动接收缓冲区大小1024发送缓冲区大小0不使用事件队列 ESP_ERROR_CHECK(uart_driver_install(UART1_PORT, 1024, 0, 0, NULL, 0)); }#include task_uart.h // 日志标签用于 ESP_LOGI 等日志输出时标识来源为 UART1 static const char *TAG UART1; /** * brief UART1 任务函数 * note 初始化 UART1循环检测接收缓冲区数据读取后将数据以十六进制格式回传 * param RXParameters 任务参数未使用 * retval 无 */ void task_uart1(void *RXParameters) { uint8_t data[20]; // 数据接收缓冲区最多存放20字节 char buff[128]; // 字符串格式化缓冲区 size_t ringbuf_len 0; // 接收缓冲区当前数据长度 // ESP_LOGI(TAG, UART1 initialized successfully.); bsp_uart1_init(); // 初始化 UART1 硬件 while (1) { // 获取 UART1 接收缓冲区中的数据长度 uart_get_buffered_data_len(UART1_PORT, ringbuf_len); if (ringbuf_len 0) { ESP_LOGI(TAG, ringbuf_len: %d, ringbuf_len); // 从接收缓冲区读取数据最多读取 ringbuf_len 字节超时 10ms int len uart_read_bytes(UART1_PORT, data, ringbuf_len, 10 / portTICK_PERIOD_MS); if (len 0) { ESP_LOGI(TAG, LEN: %d, len); // 逐个处理接收到的字节并回传十六进制格式的数据 for (size_t i 0; i len; i) { sprintf(buff, Received data: 0x%x\r\n, data[i]); uart_write_bytes(UART1_PORT, buff, strlen(buff)); } } } // 任务延时 10ms避免占用过多 CPU vTaskDelay(pdMS_TO_TICKS(10)); /******** 阻塞接收一个字节已废弃示例代码****** * 以下代码为阻塞方式接收单字节的示例当前未使用 * 根据接收到的数据控制 LED 状态并将数据回传 * * if(data0x0) * { * gpio_set_level(LED_GPIO,0); * printf(Received data: %x\n, data); * } * else if(data0x1) * { * gpio_set_level(LED_GPIO,1); * printf(Received data: %x\n, data); * } * * sprintf(buff,Received data: %x\n, data); * uart_write_bytes(UART1_PORT, buff, strlen(buff)); **************************************************/ } } /** * brief 创建 UART1 任务 * note 使用 FreeRTOS 的 xTaskCreate 创建 task_uart1 任务 * param 无 * retval 返回任务创建结果pdPASS 表示成功 */ BaseType_t bsp_uart1_task_create(void) { BaseType_t ret xTaskCreate(task_uart1, task_uart1, 2048, NULL, 5, NULL); return ret; }2.协议包组成(1)包头0xAA 占一个字节长度 (接收代码能识别的数据包的开始)(2)数据长度LEN 占一个字节长度(知道协议包里有几个字节的真实数据)(3)数据N Bytes 占N个字节长度(真正传输的数据)(4)校验和Byte 占一个字节长度;取数据和的最后一个字节(防止数据传输出错)解析方法1.一个字节、一个字节的接收数据2.利用标志位、状态机控制方法3.组包校验数据是否符合协议包协议:(1)找头(2)知道数据长度(3)保存有效数据(4)校验有效数据的和4.提取有用数据代码#include bsp_uart.h /** * brief 初始化UART1接口 * * 该函数配置并初始化ESP32的UART1接口设置波特率、数据位、停止位等参数 * 并安装UART驱动程序。此函数应在使用UART功能之前调用。 * * 配置参数 * - 波特率115200 bps * - 数据位8位 * - 校验位无 * - 停止位1位 * - 流控禁用 * - 时钟源默认 * - TX引脚GPIO17 * - RX引脚GPIO18 */ void bsp_uart1_init(void) { // 配置UART参数结构体 uart_config_t uart_config { .baud_rate 115200, // 波特率设置为115200 bps .data_bits UART_DATA_8_BITS, // 数据位8位 .parity UART_PARITY_DISABLE, // 校验位禁用无校验 .stop_bits UART_STOP_BITS_1, // 停止位1位 .flow_ctrl UART_HW_FLOWCTRL_DISABLE, // 流控禁用硬件流控 .source_clk UART_SCLK_DEFAULT, // 时钟源使用默认时钟 }; // 配置UART参数如果出错会触发错误检查机制 // UART_PORT在头文件中定义为UART_NUM_1 ESP_ERROR_CHECK(uart_param_config(UART_PORT, uart_config)); // 设置UART引脚分配 // TX引脚GPIO17 (UART_TX_PIN) // RX引脚GPIO18 (UART_RX_PIN) // RTS和CTS引脚不使用 (UART_PIN_NO_CHANGE) ESP_ERROR_CHECK(uart_set_pin(UART_PORT, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); // 安装UART驱动程序 // 参数说明 // - UART端口UART_PORT (UART_NUM_1) // - 接收缓冲区大小256字节 // - 发送缓冲区大小0 (使用默认值) // - 事件队列大小0 (不使用事件队列) // - 中断分配标志NULL (使用默认值) // - 中断标志0 (使用默认值) ESP_ERROR_CHECK(uart_driver_install(UART_PORT, 256, 0, 0, NULL, 0)); }#include task_uart.h static const char *TAG UART1; // 日志标签 static proto_state_t state STATE_WAIT_HEAD; // 当前协议解析状态 static uint8_t frame_buf[FRAME_MAX_LEN]; // 帧数据缓冲区 static uint16_t data_len 0; // 数据长度 static uint16_t data_cnt 0; // 已接收数据计数 /** * brief UART1任务函数 * * 此任务负责初始化UART并持续监听串口数据。当接收到数据时 * 将数据传递给协议解析函数进行处理。 * * param pvParameters 任务参数未使用 */ void uart1_task(void *pvParameters) { uint8_t data; bsp_uart1_init(); // 初始化UART1硬件 ESP_LOGI(TAG, uart1_task start\n); while (1) { // 从UART端口读取一个字节数据阻塞等待 // portMAX_DELAY表示无限期等待直到有数据到达 int ret uart_read_bytes(UART_PORT, data, 1, portMAX_DELAY); if (ret 0) { // 如果读取成功则将数据传入协议解析函数进行处理 proto_parse_byte(data); } } } /** * brief 创建UART1任务 * * 使用FreeRTOS的xTaskCreate函数创建一个新的任务来处理UART通信。 * * return BaseType_t 任务创建结果pdPASS表示成功其他值表示失败 */ BaseType_t uart1_task_create(void) { // 创建UART1任务 // - 任务函数uart1_task // - 任务名称uart1_task // - 栈深度4096字节 // - 任务参数NULL不传递参数 // - 任务优先级10数字越大优先级越高 // - 任务句柄NULL不需要保存任务句柄 return xTaskCreate(uart1_task, uart1_task, 4096, NULL, 10, NULL); } /** * brief 计算数据校验和 * * 使用简单的累加求和算法计算数据块的校验和。 * * param data 指向要计算校验和的数据的指针 * param len 数据长度字节数 * return uint8_t 校验和结果只保留低8位 */ uint8_t checksum_sum(const uint8_t *data, uint16_t len) { uint16_t sum 0; // 遍历所有数据字节并累加 for (int i 0; i len; i) { sum data[i]; } // 只保留低8位作为校验和结果 return (uint8_t)(sum 0xFF); } /** * brief 处理完整帧数据 * * 解析并执行来自上位机的完整命令帧。 * 目前支持的命令 * - 0x01: 控制LED开关 * * param data 指向帧数据的指针不包含帧头、长度和校验字段 * param len 数据长度 */ void process_frame(const uint8_t *data, uint16_t len) { // 至少需要包含命令字和参数值两个字节 if (len 2) { ESP_LOGW(TAG, 帧长度不足); return; } ESP_LOGI(TAG, 收到完整帧:); uint8_t cmd data[0]; // 提取命令字第一个字节 uint8_t val data[1]; // 提取参数值第二个字节 char buf[20] {0}; // 发送响应数据的缓冲区 // 根据命令字处理不同指令 switch (cmd) { case 0x01: // 控制 LED 命令 // 根据参数值控制LED状态 if (val 0x00) { // 参数0x00关闭LED注意硬件连接可能为低电平有效 bsp_gpio_set_level(LED_GPIO, 0); ESP_LOGI(TAG, LED 已打开); } else if (val 0x01) { // 参数0x01打开LED注意硬件连接可能为高电平有效 bsp_gpio_set_level(LED_GPIO, 1); ESP_LOGI(TAG, LED 已关闭); } else { ESP_LOGW(TAG, 无效的LED控制参数: 0x%02X, val); } // 向上位机发送确认响应 uart_write_bytes(UART_PORT, UART1 Recv Data:, strlen(UART1 Recv Data:)); sprintf(buf, 0x%X 0x%X, cmd, val); uart_write_bytes(UART_PORT, buf, strlen(buf)); uart_write_bytes(UART_PORT, \n, 1); // 退出switch语句结束本次命令处理 break; default: // 不支持的命令 ESP_LOGW(TAG, 未知命令: 0x%02X, cmd); // 退出switch语句 break; } } /** * brief 协议解析函数每接收一个字节调用一次 * * 使用有限状态机(FSM)解析自定义串口协议数据帧。 * 帧格式帧头(1字节) 长度(1字节) 数据(N字节) 校验和(1字节) * * 状态转换过程 * 1. STATE_WAIT_HEAD: 等待帧头(0xAA) * 2. STATE_WAIT_LEN: 等待数据长度字节 * 3. STATE_WAIT_DATA: 接收指定长度的数据 * 4. STATE_WAIT_CHECKSUM: 等待并验证校验和 * * param byte 接收到的单个字节数据 */ void proto_parse_byte(uint8_t byte) { // 根据当前状态处理接收到的字节 switch (state) { case STATE_WAIT_HEAD: // 等待帧头状态 // 检查是否为有效的帧头标识 if (byte FRAME_HEAD) { // 收到正确帧头进入等待长度字段状态 state STATE_WAIT_LEN; } // 如果不是帧头则继续保持在此状态丢弃该字节 // 退出当前case分支 break; case STATE_WAIT_LEN: // 等待长度字段状态 data_len byte; // 保存后续数据的长度 data_cnt 0; // 重置数据接收计数器 // 检查长度是否超出最大允许值 if (data_len FRAME_MAX_LEN) { // 长度超限回到等待帧头状态 ESP_LOGW(TAG, 数据长度超限: %d, data_len); state STATE_WAIT_HEAD; } else { // 长度合法进入等待数据状态 state STATE_WAIT_DATA; } // 退出当前case分支 break; case STATE_WAIT_DATA: // 接收数据字段状态 // 将数据存储到缓冲区中 frame_buf[data_cnt] byte; // 检查是否已接收完所有数据 if (data_cnt data_len) { // 数据接收完成进入等待校验和状态 state STATE_WAIT_CHECKSUM; } // 否则继续保持在此状态继续接收数据 // 退出当前case分支 break; case STATE_WAIT_CHECKSUM: { // 校验和验证状态使用代码块避免变量作用域问题 uint8_t check_recv byte; // 收到的校验和 uint8_t check_calc checksum_sum(frame_buf, data_len); // 根据接收数据计算的校验和 // 比较收到的校验和与计算得出的校验和 if (check_calc check_recv) { // 校验通过处理完整帧数据 ESP_LOGI(TAG, 校验成功处理数据帧); process_frame(frame_buf, data_len); } else { // 校验失败记录错误日志 ESP_LOGE(TAG, SUM校验错误: recv0x%02X calc0x%02X, check_recv, check_calc); } // 无论校验是否通过都回到等待帧头状态准备接收下一帧 state STATE_WAIT_HEAD; // 退出当前case分支 break; } default: // 异常状态处理 // 如果出现未定义的状态重置为等待帧头状态 ESP_LOGE(TAG, 未知状态: %d, state); state STATE_WAIT_HEAD; // 退出switch语句 break; } }3.串口1事件队列模式修改初始化代码(1)事件队列大小(2)队列句柄void bsp_uart1_events_init(int uart_events_queue_size, QueueHandle_t *uart_events_queue_handle) { // 配置UART参数结构体 uart_config_t uart_config { .baud_rate 115200, // 波特率设置为115200 bps .data_bits UART_DATA_8_BITS, // 数据位8位 .parity UART_PARITY_DISABLE, // 校验位禁用无校验 .stop_bits UART_STOP_BITS_1, // 停止位1位 .flow_ctrl UART_HW_FLOWCTRL_DISABLE, // 流控禁用硬件流控 .source_clk UART_SCLK_DEFAULT, // 时钟源使用默认时钟 }; ESP_ERROR_CHECK(uart_param_config(UART_PORT, uart_config)); ESP_ERROR_CHECK(uart_set_pin(UART_PORT, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_driver_install(UART_PORT, 1024, 0, uart_events_queue_size, uart_events_queue_handle, 0)); }原理1.串口接收到数据之后触发中断2.IDF在中断里创建事件队列3.在线程里接收事件,读对应长度的数据#include task_uart_events.h static const char *TAG UART1_EVENTS; QueueHandle_t uart_event_queue; void uart1_events_task(void *pvParameters) { uart_event_t event; uint8_t *data (uint8_t *)malloc(UART_BUFFER_SIZE); bsp_uart1_events_init(UART_QUEUE_SIZE, uart_event_queue); while(1) { // 等待串口事件 if (xQueueReceive(uart_event_queue, (void *)event, (TickType_t)portMAX_DELAY)) { switch (event.type) { // 接收数据 case UART_DATA: ESP_LOGI(TAG, 收到数据长度: %d, event.size); // 读取数据 int len uart_read_bytes(UART_PORT, data, event.size, 100 / portTICK_PERIOD_MS); if (len 0) { ESP_LOGI(TAG, 收到数据: ); for (int i 0; i len; i) { printf( 0x%X , data[i]); } printf(\n); // 可以在这里添加数据处理逻辑 } break; // 其他事件 case UART_FIFO_OVF: ESP_LOGI(TAG, UART FIFO 溢出\n); uart_flush_input(UART_PORT); xQueueReset(uart_event_queue); break; case UART_BUFFER_FULL: ESP_LOGI(TAG, UART 缓冲区满\n); uart_flush_input(UART_PORT); xQueueReset(uart_event_queue); break; case UART_BREAK: ESP_LOGI(TAG, UART 中断\n); break; case UART_PARITY_ERR: ESP_LOGI(TAG, UART 校验错误\n); break; case UART_FRAME_ERR: ESP_LOGI(TAG, UART 帧错误\n); break; default: ESP_LOGI(TAG, 未知 UART 事件: %d\n, event.type); break; } } } free(data); vTaskDelete(NULL); } BaseType_t uart1_events_task_create(void) { return xTaskCreate(uart1_events_task, uart1_events_task, 4096, NULL, 10, NULL); }仅本人学习他人视频做出的学习笔记。