ESP32学习笔记之UART

第一部分:UART 核心概念

1. 什么是 UART?

  • UART 中文常叫"通用异步收发器 ","异步"意思是通信双方不共享时钟,所以要提前约定好通信参数。 UART 本质上就是按约定好的速度,把 0 和 1 一位一位串行发出去

  • 关键特点

    • 串行通信:一位一位传,节省引脚

    • 异步通信:无时钟线,靠约定波特率

    • 全双工:可同时收发

    • 点对点:通常两个设备直连

更详细的UART知识https://blog.csdn.net/2301_81636338/article/details/157764671?spm=1011.2124.3001.6209

2. 一帧数据的组成

  • 起始位:1 位,标志传输开始

  • 数据位:5~9 位,常用 8 位

  • 校验位(可选):奇校验 / 偶校验 / 无校验

  • 停止位:1 或 2 位,标志帧结束

  • 常见配置8N1(8 数据位,无校验,1 停止位)

    • 1 起始 + 8 数据 + 1 停止 = 10 位 / 字节

3. 波特率

  • 定义:每秒传输的 bit 数

  • 例:9600 波特 = 9600 bit/s ≈ 960 字节/秒(因为 1 字节占 10 位)

4. 为什么需要起始位和停止位?

  • 因为没有时钟线,接收方需要:

    1. 通过起始位同步,知道"数据开始了"

    2. 按约定时间采样每位

    3. 通过停止位知道"这一帧结束了"

5. UART vs I2C vs SPI

特性 UART I2C SPI
同步/异步 异步 同步 同步
双工方式 全双工 半双工 全双工
线数 2 (TX, RX) + 地 2 (SDA, SCL) 4+ (SCK, MOSI, MISO, CS)
设备数 点对点 多设备(地址) 一主多从(片选)

第二部分:UART 常见问题与排查

  1. 参数不一致

    • 检查波特率、数据位、停止位、校验位是否匹配
  2. 接线问题

    • TX ↔ RX 交叉连接

    • 共地(GND 必须相连)

  3. 软件问题

    • 缓冲区溢出

    • 接收处理不及时(中断优先级、任务调度)


第三部分:ESP32 UART 实践

UART 初始化步骤

  1. 配置参数结构体 uart_config_t(波特率、数据位、停止位、校验位等)

  2. 设置引脚 uart_set_pin()

  3. 安装驱动 uart_driver_install()

  4. 收发数据


循环发送 "Hello"

复制代码
c

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "esp_err.h"

#define UART_PORT_NUM   UART_NUM_1
#define UART_BAUD_RATE  115200
#define UART_TX_PIN     17
#define UART_RX_PIN     18
#define UART_BUF_SIZE   1024

void app_main(void)
{
    uart_config_t uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .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,
    };

    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(
        UART_PORT_NUM,
        UART_TX_PIN,
        UART_RX_PIN,
        UART_PIN_NO_CHANGE,
        UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(
        UART_PORT_NUM,
        UART_BUF_SIZE,
        UART_BUF_SIZE,
        0,
        NULL,
        0));

    const char *msg = "Hello from ESP32-S3 UART1\r\n";

    while (1) {
        uart_write_bytes(UART_PORT_NUM, msg, strlen(msg));
        printf("UART1 sent: %s", msg);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

回显(Echo)

复制代码
c

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "esp_err.h"

#define UART_PORT_NUM   UART_NUM_1
#define UART_BAUD_RATE  115200
#define UART_TX_PIN     17
#define UART_RX_PIN     16
#define UART_BUF_SIZE   1024

void app_main(void)
{
    uart_config_t uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .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,
    };

    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(
        UART_PORT_NUM,
        UART_TX_PIN,
        UART_RX_PIN,
        UART_PIN_NO_CHANGE,
        UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(
        UART_PORT_NUM,
        UART_BUF_SIZE,
        UART_BUF_SIZE,
        0,
        NULL,
        0));

    uint8_t data[UART_BUF_SIZE];

    while (1) {
        int len = uart_read_bytes(
            UART_PORT_NUM,
            data,
            UART_BUF_SIZE - 1,
            pdMS_TO_TICKS(100));

        if (len > 0) {
            data[len] = '\0';
            printf("UART1 received: %s\n", (char *)data);
            uart_write_bytes(UART_PORT_NUM, (const char *)data, len);
        }
    }
}

中断方式(事件队列)

  • 使用队列接收 UART 事件(数据到达、溢出、错误等)

  • 适合需要高效处理、不丢数据的场景

    #include <stdio.h>
    #include <string.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/queue.h"
    #include "driver/uart.h"
    #include "esp_err.h"
    #include "esp_log.h"

    #define UART_PORT_NUM UART_NUM_1
    #define UART_BAUD_RATE 115200
    #define UART_TX_PIN 17
    #define UART_RX_PIN 16
    #define UART_BUF_SIZE 1024
    #define UART_QUEUE_SIZE 20

    static const char *TAG = "uart1_intr";
    static QueueHandle_t uart_queue;

    static void uart_event_task(void *pvParameters)
    {
    uart_event_t event;
    uint8_t data[UART_BUF_SIZE];

    复制代码
      while (1) {
          if (xQueueReceive(uart_queue, &event, portMAX_DELAY)) {
              switch (event.type) {
              case UART_DATA:
                  int len = uart_read_bytes(
                      UART_PORT_NUM,
                      data,
                      event.size < UART_BUF_SIZE - 1 ? event.size : UART_BUF_SIZE - 1,
                      0);
                  if (len > 0) {
                      data[len] = '\0';
                      ESP_LOGI(TAG, "recv: %s", (char *)data);
                      uart_write_bytes(UART_PORT_NUM, (const char *)data, len);
                  }
                  break;
    
              case UART_FIFO_OVF:
              case UART_BUFFER_FULL:
                  ESP_LOGW(TAG, "overflow or buffer full");
                  uart_flush_input(UART_PORT_NUM);
                  xQueueReset(uart_queue);
                  break;
    
              case UART_BREAK:
              case UART_PARITY_ERR:
              case UART_FRAME_ERR:
                  ESP_LOGW(TAG, "error event: %d", event.type);
                  break;
    
              default:
                  break;
              }
          }
      }

    }

    void app_main(void)
    {
    uart_config_t uart_config = {
    .baud_rate = UART_BAUD_RATE,
    .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,
    };

    复制代码
      ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
      ESP_ERROR_CHECK(uart_set_pin(
          UART_PORT_NUM,
          UART_TX_PIN,
          UART_RX_PIN,
          UART_PIN_NO_CHANGE,
          UART_PIN_NO_CHANGE));
    
      ESP_ERROR_CHECK(uart_driver_install(
          UART_PORT_NUM,
          UART_BUF_SIZE,
          UART_BUF_SIZE,
          UART_QUEUE_SIZE,
          &uart_queue,
          0));
    
      xTaskCreate(uart_event_task, "uart_event_task", 4096, NULL, 12, NULL);
    
      ESP_LOGI(TAG, "UART1 interrupt event echo started");

    }


DMA 方式(高性能)

复制代码
  #include <stdio.h>
  #include <string.h>

  #include "freertos/FreeRTOS.h"
  #include "freertos/task.h"

  #include "driver/uart.h"
  #include "esp_attr.h"
  #include "esp_log.h"
  #include "esp_private/gdma.h"
  #include "esp_private/periph_ctrl.h"
  #include "hal/uhci_ll.h"
  #include "soc/lldesc.h"

  #define UART_PORT_NUM        UART_NUM_1
  #define UART_BAUD_RATE       115200
  #define UART_TX_PIN          17
  #define UART_RX_PIN          16

  #define DMA_RX_BUF_SIZE      1024
  #define DMA_TX_BUF_SIZE      1024
  #define DMA_DESC_NUM         1
  #define UART_RX_TOUT_THRESH  10
  #define TASK_STACK_SIZE      4096

  #define RX_DONE_BIT          (1U << 0)
  #define TX_DONE_BIT          (1U << 1)

  static const char *TAG = "uart_dma_echo";

  static volatile uhci_dev_t *s_uhci_hw = &UHCI0;
  static gdma_channel_handle_t s_rx_channel;
  static gdma_channel_handle_t s_tx_channel;
  static TaskHandle_t s_dma_task_hdl;

  DMA_ATTR static uint8_t s_rx_buf[DMA_RX_BUF_SIZE];
  DMA_ATTR static uint8_t s_tx_buf[DMA_TX_BUF_SIZE];

  DMA_ATTR static lldesc_t s_rx_desc[DMA_DESC_NUM];
  DMA_ATTR static lldesc_t s_tx_desc[DMA_DESC_NUM];

  static bool IRAM_ATTR rx_eof_callback(gdma_channel_handle_t dma_chan,
                                        gdma_event_data_t *event_data,
                                        void *user_data)
  {
      BaseType_t high_task_wakeup = pdFALSE;
      (void)dma_chan;
      (void)event_data;
      (void)user_data;

      xTaskNotifyFromISR(s_dma_task_hdl, RX_DONE_BIT, eSetBits, &high_task_wakeup);
      return high_task_wakeup == pdTRUE;
  }

  static bool IRAM_ATTR tx_eof_callback(gdma_channel_handle_t dma_chan,
                                        gdma_event_data_t *event_data,
                                        void *user_data)
  {
      BaseType_t high_task_wakeup = pdFALSE;
      (void)dma_chan;
      (void)event_data;
      (void)user_data;

      xTaskNotifyFromISR(s_dma_task_hdl, TX_DONE_BIT, eSetBits, &high_task_wakeup);
      return high_task_wakeup == pdTRUE;
  }

  static void start_rx_dma(void)
  {
      memset(s_rx_buf, 0, sizeof(s_rx_buf));
      memset(s_rx_desc, 0, sizeof(s_rx_desc));

      lldesc_setup_link(s_rx_desc, s_rx_buf, DMA_RX_BUF_SIZE, true);

      ESP_ERROR_CHECK(gdma_reset(s_rx_channel));
      ESP_ERROR_CHECK(gdma_start(s_rx_channel, (intptr_t)&s_rx_desc[0]));
  }

  static void start_tx_dma(const uint8_t *data, size_t len)
  {
      if (len == 0 || len > DMA_TX_BUF_SIZE) {
          return;
      }

      memcpy(s_tx_buf, data, len);
      memset(s_tx_desc, 0, sizeof(s_tx_desc));

      lldesc_setup_link(s_tx_desc, s_tx_buf, len, false);

      ESP_ERROR_CHECK(gdma_reset(s_tx_channel));
      ESP_ERROR_CHECK(gdma_start(s_tx_channel, (intptr_t)&s_tx_desc[0]));
  }

  static void uart_dma_init(void)
  {
      periph_module_enable(PERIPH_UHCI0_MODULE);
      periph_module_reset(PERIPH_UHCI0_MODULE);

      periph_module_enable(PERIPH_UART1_MODULE);
      periph_module_reset(PERIPH_UART1_MODULE);

      uart_config_t uart_config = {
          .baud_rate = UART_BAUD_RATE,
          .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,
      };

      ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
      ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM,
                                   UART_TX_PIN,
                                   UART_RX_PIN,
                                   UART_PIN_NO_CHANGE,
                                   UART_PIN_NO_CHANGE));

      // 设置 UART 空闲超时,空闲一小段时间就认为一帧结束
      ESP_ERROR_CHECK(uart_set_rx_timeout(UART_PORT_NUM, UART_RX_TOUT_THRESH));

      gdma_channel_alloc_config_t tx_config = {
          .direction = GDMA_CHANNEL_DIRECTION_TX,
          .flags.reserve_sibling = 1,
      };
      ESP_ERROR_CHECK(gdma_new_channel(&tx_config, &s_tx_channel));

      gdma_channel_alloc_config_t rx_config = {
          .direction = GDMA_CHANNEL_DIRECTION_RX,
          .sibling_chan = s_tx_channel,
      };
      ESP_ERROR_CHECK(gdma_new_channel(&rx_config, &s_rx_channel));

      ESP_ERROR_CHECK(gdma_connect(s_tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)));
      ESP_ERROR_CHECK(gdma_connect(s_rx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)));

      gdma_strategy_config_t strategy = {
          .auto_update_desc = false,
          .owner_check = false,
      };
      ESP_ERROR_CHECK(gdma_apply_strategy(s_tx_channel, &strategy));
      ESP_ERROR_CHECK(gdma_apply_strategy(s_rx_channel, &strategy));

      gdma_rx_event_callbacks_t rx_cbs = {
          .on_recv_eof = rx_eof_callback,
      };
      ESP_ERROR_CHECK(gdma_register_rx_event_callbacks(s_rx_channel, &rx_cbs, NULL));

      gdma_tx_event_callbacks_t tx_cbs = {
          .on_trans_eof = tx_eof_callback,
      };
      ESP_ERROR_CHECK(gdma_register_tx_event_callbacks(s_tx_channel, &tx_cbs, NULL));

      uhci_ll_init((uhci_dev_t *)s_uhci_hw);
      uhci_ll_set_eof_mode((uhci_dev_t *)s_uhci_hw, UHCI_RX_IDLE_EOF);
      s_uhci_hw->escape_conf.val = 0;
      uhci_ll_attach_uart_port((uhci_dev_t *)s_uhci_hw, 1);
  }

  static void dma_echo_task(void *arg)
  {
      uint32_t notify_value = 0;
      (void)arg;

      start_rx_dma();

      while (1) {
          xTaskNotifyWait(0, UINT32_MAX, &notify_value, portMAX_DELAY);

          if (notify_value & RX_DONE_BIT) {
              int rx_len = lldesc_get_received_len(s_rx_desc, NULL);

              if (rx_len > 0 && rx_len <= DMA_RX_BUF_SIZE) {
                  ESP_LOGI(TAG, "rx_len=%d", rx_len);
                  start_tx_dma(s_rx_buf, rx_len);
              }

              start_rx_dma();
          }

          if (notify_value & TX_DONE_BIT) {
              ESP_LOGI(TAG, "tx done");
          }
      }
  }

  void app_main(void)
  {
      uart_dma_init();

      xTaskCreate(dma_echo_task, "dma_echo_task", TASK_STACK_SIZE, NULL, 12, &s_dma_task_hdl);

      ESP_LOGI(TAG, "UART1 DMA echo started");
      ESP_LOGI(TAG, "TX=%d RX=%d baud=%d", UART_TX_PIN, UART_RX_PIN, UART_BAUD_RATE);
  }

代码较长,核心要点:

  • 初始化 GDMA 通道并连接到 UHCI

  • 使用 lldesc_t 链表管理内存

  • 通过中断回调通知任务处理数据

相关推荐
蓝桉~MLGT1 小时前
Ai-Agent学习历程(插播内容)—— 基于现在最新的Skills、MCP、Rules等进行详细拆解,并列举出使用场景
人工智能·学习
知识分享小能手2 小时前
Redis入门学习教程,从入门到精通,Redis进阶编程知识点详解(5)
数据库·redis·学习
诸葛思颖2 小时前
【论文阅读笔记】《Bayesian Nonparametric Federated Learning of Neural Networks》
笔记
夏日听雨眠2 小时前
Linux学习1
linux·服务器·学习
朗迹 - 张伟2 小时前
UE5 C++学习笔记
c++·学习·ue5
今天减肥吗2 小时前
前端面试学习流程
学习
庞轩px2 小时前
面经分享1
java·笔记·面试
arvin_xiaoting2 小时前
OpenClaw学习总结_I_核心架构系列_AgentLoop详解
java·学习·架构·llm·ai-agent·飞书机器人·openclaw
电科_银尘2 小时前
【书籍】-- 《小米创业思考》
经验分享·笔记·创业创新·学习方法