ESP-IDF学习笔记-UART的使用

ESP32中串口的使用与STM32中大有不同。在ESP32中使用串口,需要配置串口,设置引脚,安装驱动之后才能使用。接收和发送数据也不是直接向寄存器写数据,而是先存到FIFO中,交由ESP32自动发送。其中断的使用更是结合了FreeRTOS的特性进行的。

先摆上官方文档

本文参考于这篇文章

一,串口的配置步骤

一个串口的典型使用步骤如下:

  1. 使用uart_param_config()设置UART参数
  2. 使用art_set_pin()分配UART引脚
  3. 使用uart_driver_install()安装UART驱动
  4. 使用uart_read_bytes()/uart_write_bytes()收发数据
  5. (可选)新建task读取事件队列处理中断事件
  6. (可选)删除驱动释放资源

其中,1、2、3步可以调换顺序

二,配置的具体步骤

同STM32HAL库的句柄一样,ESP32中使用一个uart_port_t类型的UART_NUM_x变量识别不同的UART控制器,在调用函数时需指明。

其中UART_NUM_MAX表示该芯片最大的uart控制器数,上面的x最大为UART_NUM_MAX-1

ESP32中默认使用UART_NUM_0作为log库的输出

UART参数的配置

UART参数的配置主要用两种方式,一种是使用uart_param_config+结构体一次性配置;另一种是使用分离函数进行配置。、

单步配置(结构体)

使用的函数原型:

1
esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config)

参数:

  • uart_num使用的UART编号
  • uart_config配置的结构体

返回:

  • ESP_OK Success
  • ESP_FALL Parameter error

uart_config_t结构体成员:

成员 类型 作用 可用参数
baud_rate int 波特率 宏定义UART_BITRATE_MAX为支持的最大波特率
data_bits uart_word_length_t 数据位长度 UART_DATA_X_BITS(X=5~8 )或UART_DATA_BITS_MAX
parity uart_parity_t 校验位 UART_PARITY_X(X=DISABLE,EVEN,ODD)
stop_bits uart_stop_bits_t 停止位 UART_STOP_BITS_X(X=1,1_5,2,MAX)
flow_ctrl uart_hw_flowcontrol_t 硬件流控制 UART_HW_FLOWCTRL_X(X=DISABLE,RTS,CTS,CTS_RTS,MAX)
rx_flow_ctrl_thresh uint8_t 硬件流控制阈值
source_clk uart_sclk_t 时钟源选择 UART_SCLK_X(X=APB,RTC,XTAL,DEFAULT(APB))

如果不使用硬件流控制,可以使用 uart_set_rts()uart_set_dtr()软件流控制。

使用例:

1
2
3
4
5
6
7
8
9
10
11
12
#include "driver/uart.h"
#define EX_UART_NUM UART_NUM_0

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,
};
uart_param_config(EX_UART_NUM, &uart_config);

多步配置

多步配置的函数表如下:

欲配置的参数 函数
波特率(Baud rate) uart_set_baudrate()
传输数据位长(Number of transmitted bits) uart_set_word_length() selected out of uart_word_length_t
奇偶校验(parity control) uart_set_parity() selected out of uart_parity_t
停止位数(Number of stop bits) uart_set_stop_bits() selected out of uart_stop_bits_t
硬件流控方式(Hardware flow control mode) uart_set_hw_flow_ctrl() selected out of uart_hw_flowcontrol_t
通信方式(Communication mode) uart_set_mode() selected out of uart_mode_t

同时,上面的每个函数都有一个_get_对应对象来检查当前设置的值。例如,要检查当前波特率值,调用uart_get_baudrate()

分配引脚

使用的函数原型

1
esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num)

参数:

  • uart_num使用的串口编号
  • tx_io_numTX引脚
  • rx_io_numRX引脚
  • rts_io_numRTS引脚
  • cts_io_numCTS引脚

返回:

  • ESP_OK Success
  • ESP_FALL Parameter error

对于后四个参数,可以使用宏UART_PIN_NO_CHANGE保留已经配置的引脚或者默认引脚

如果设置的GPIO有该功能的IOMUX(复用),则信号会直接接到该引脚而不通过GPIO矩阵,这样能够支持更高的速率。例如。ESP32S3中IO43是默认的U0TXD,IO44是默认的U0RXD;IO17–U1TXD,IO18–U1RXD。

ESP32提供了一些宏用来定义这些能直连的引脚,参考官方文档,或者见本章高级用法篇。

内部信号能够被输出到多个GPIO,但只有一个GPIO能输入。

经实际测试,ESP32S3的UART好像不能换引脚。

使用例:

1
2
3
#include "driver/uart.h"

uart_set_pin(UART_NUM_0,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE);

安装驱动

使用的函数原型:

1
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags);

参数:

  • uart_num使用的串口编号
  • rx_buffer_size接收缓冲区大小
  • tx_buffer_size发送缓冲区大小
  • queue_size事件队列大小
  • uart_queue事件队列句柄,如果成功,这里会有一个新的事件队列。如果设为NULL将不使用
  • intr_alloc_flags用于分配中断的标志。传入一个或多个ESP_INTR_FLAG_*(详见esp_intr_alloc.h),不能使用ESP_INTR_FLAG_IRAM(在meunconfig中设置)

返回:

  • ESP_OK Success
  • ESP_FALL Parameter error

安装UART驱动程序,同时,UART ISR处理器将被附加到运行该函数的同一个CPU核心上。Rx_buffer_size应该大于UART_FIFO_LENTx_buffer_size应该是**0或者大于UART_FIFO_LEN**。

UART_FIFO_LEN是一个宏定义,定义硬件FIFO的大小

如果发送缓存区为0,则使用uart_write_bytes()时会阻塞发送

例子:

1
uart_driver_install(UART_NUM_0,1024,512,0,NULL,0);

收发数据

发数据

使用的函数原型:

1
int uart_write_bytes(uart_port_t uart_num, const void *src, size_t size);

参数:

  • uart_num使用的串口编号
  • src数据地址
  • size数据长度

返回:

  • (-1) Parameter error
  • OTHERS (>=0) The number of bytes pushed to the TX FIFO

如果缓冲区为0,该函数会阻塞直到所有数据被发送或者所有数据被加入FIFO

缓冲区中的数据会被UART ISR逐步转移到FIFO。(有点像32的Transmit_IT)。

函数uart_write_bytes_with_break()与其功能类似,但会在结束时添加串行中断信号。“串行中断信号”意味着 Tx 线保持低电平的时间长于一个数据帧。

uart_tx_chars()也能将数据写入Tx FIFO,但不会等待可用空间,而是立即放入硬件FIFO

uart_wait_tx_done()用于监听发送缓冲区,在缓冲区为空时返回

1
2
3
// Wait for packet to be sent
const uart_port_t uart_num = UART_NUM_2;
ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // wait timeout is 100 RTOS ticks (TickType_t)

接数据

使用的函数原型:

1
int uart_read_bytes(uart_port_t uart_num, void *buf, uint32_t length, TickType_t ticks_to_wait)

ticks_to_wait是超时时间,值是RTOS的ticks。

使用该函数前应使用uart_get_buffered_data_len()获得缓冲区数据的长度。比如:

1
2
3
4
5
6
// Read data from UART.
const uart_port_t uart_num = UART_NUM_2;
uint8_t data[128];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length));
length = uart_read_bytes(uart_num, data, length, 100);

清除缓冲区

1
esp_err_t uart_flush(uart_port_t uart_num);

清除RX缓冲区的内容。

1
esp_err_t uart_flush_input(uart_port_t uart_num);

清除输入缓存区内容(如果想等待发送完成,请使用uart_wait_tx_done)。

使用中断

这里提到的中断与一般的中断不同,这里应该叫UART事件。中断触发后读取的中断源被当做事件添加到队列中,使用FreeRTOS的一个线程进行读取,处理,得到像中断的效果

使用事件处理需要在安装驱动时开启事件队列。

1
2
3
4
#include "driver/uart.h"
static QueueHandle_t uart0_queue;

uart_driver_install(UART_NUM_0,1024,512,20,&uart0_queue,0);//队列长度为20

UART 默认的事件处理 没有使用esp_event.h中的事件循环(EventLoop)。而是使用队列传输事件对象(一个uart_event_t类型的结构体)这个结构体包含了事件类型和UART_DATA事件携带的数据。

1
2
3
4
5
6
7
8
9
/**
* @brief Event structure used in UART event queue
*/
typedef struct {
uart_event_type_t type; /*!< UART 事件类型 */
size_t size; /*!< UART 数据长度(仅UART_DATA 事件)*/
bool timeout_flag; /*!< UART 超时标志(仅UART_DATA 事件)*/
/*!< If the event is caused by FIFO-full interrupt, then there will be no event with the timeout flag before the next byte coming.*/
} uart_event_t;

其中,uart_event_type_t包含以下事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @brief UART event types used in the ring buffer
*/
typedef enum {
UART_DATA, /*!< UART data event UART数据事件*/
UART_BREAK, /*!< UART break event UART中断事件*/
UART_BUFFER_FULL, /*!< UART RX buffer full event UART RX缓冲区满事件*/
UART_FIFO_OVF, /*!< UART FIFO overflow event UART FIFO溢出事件*/
UART_FRAME_ERR, /*!< UART RX frame error event UART RX帧错误事件*/
UART_PARITY_ERR, /*!< UART RX parity event UART奇偶检查错误*/
UART_DATA_BREAK, /*!< UART TX data and break event UART TX数据和中断事件*/
UART_PATTERN_DET, /*!< UART pattern detected UART 输入样式检测事件*/
#if SOC_UART_SUPPORT_WAKEUP_INT
UART_WAKEUP, /*!< UART wakeup event */
#endif
UART_EVENT_MAX, /*!< UART event max index*/
} uart_event_type_t;

其中,最后一个事件UART_PATTERN_DET是使用uart_enable_pattern_det_baud_intr()启用pattern detect后的触发事件,用于解析特定格式的数据如AT+CGMI

安装完驱动后,当有事件发生时,会自动向队列中填充信息,一般使用一个任务来进行及时处理

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
35
36
37
38
39
40
41
#include "driver/uart.h"

static QueueHandle_t uart0_queue;
...

static void uart_event_task(void *pv)
{
uart_event_t event;
uint8_t d;
while(1)
{
if(xQueueReceive(uart0_queue, (void * )&event, (TickType_t)portMAX_DELAY))
{
ESP_LOGI(TAG,"A event in");
switch (event.type)
{
case UART_DATA:
uart_read_bytes(UART_NUM_0,&d,1,(TickType_t)portMAX_DELAY);
uart_write_bytes(UART_NUM_0, &d, 1);
break;
case UART_BREAK:
break;
case ...:
break;
...
default:
//ESP_LOGI(TAG, "uart event type: %d", event.type);
break;
}
}
}
}

...
void app_main(void)
{
...
uart_driver_install(UART_NUM_0,1024,512,20,&uart0_queue,0);
...
xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 12, NULL);
}

删除驱动

1
esp_err_t uart_driver_delete(uart_port_t uart_num);

使用该函数释放资源。

三,高级用法

其他模式

UART 控制器支持多种通信模式,使用函数 uart_set_mode()可以选择模式。选择特定模式后,UART 驱动程序将处理已连接 UART 设备的相应行为。

1
esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode);

参数:

  • mode使用的模式

该函数必须在uart_driver_install()之后使用。

支持的参数如下:

参数 模式
UART_MODE_UART 普通UART
UART_MODE_RS485_HALF_DUPLEX half duplex RS485 UART mode control by RTS pin
UART_MODE_IRDA 类似于红外协议的模式
UART_MODE_RS485_COLLISION_DETECT RS485 collision detection UART mode (used for test purposes)
UART_MODE_RS485_APP_CTRL application control RS485 UART mode (used for test purposes)

具体使用参考官方文档

pattern_detect

在检测到重复接收/发送同一字符的“模式”时触发中断。例如,模式检测可用于检测命令字符串末尾是否存在特定数量的相同字符(“模式”)。

  • 配置并启用此中断:调用 uart_enable_pattern_det_baud_intr()
  • 禁用中断:调用 uart_disable_pattern_det_intr()

用法参见peripherals/uart/uart_events

自定义UART中断

如果不想使用默认的 UART 中断(如上文的 UART 事件 Queue 等高级API)或者自己另有别的绝妙之用,可以注册自己的UART中断。

  • 使用 uart_isr_register() 注册中断
  • 使用 uart_isr_free() 释放注册的中断

写的中断 ISR 程序需要尽可能简短,不要忘了在处理中断前后调用uart_clear_intr_status()清除中断标志。

一些宏

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
UART_GPIO43_DIRECT_CHANNEL
UART_NUM_0_TXD_DIRECT_GPIO_NUM
UART_GPIO44_DIRECT_CHANNEL
UART_NUM_0_RXD_DIRECT_GPIO_NUM
UART_GPIO16_DIRECT_CHANNEL
UART_NUM_0_CTS_DIRECT_GPIO_NUM
UART_GPIO15_DIRECT_CHANNEL
UART_NUM_0_RTS_DIRECT_GPIO_NUM
UART_TXD_GPIO43_DIRECT_CHANNEL
UART_RXD_GPIO44_DIRECT_CHANNEL
UART_CTS_GPIO16_DIRECT_CHANNEL
UART_RTS_GPIO15_DIRECT_CHANNEL

UART_GPIO17_DIRECT_CHANNEL
UART_NUM_1_TXD_DIRECT_GPIO_NUM
UART_GPIO18_DIRECT_CHANNEL
UART_NUM_1_RXD_DIRECT_GPIO_NUM
UART_GPIO20_DIRECT_CHANNEL
UART_NUM_1_CTS_DIRECT_GPIO_NUM
UART_GPIO19_DIRECT_CHANNEL
UART_NUM_1_RTS_DIRECT_GPIO_NUM
UART_TXD_GPIO17_DIRECT_CHANNEL
UART_RXD_GPIO18_DIRECT_CHANNEL
UART_CTS_GPIO20_DIRECT_CHANNEL
UART_RTS_GPIO19_DIRECT_CHANNEL