通用异步接收/发送器(UART)
概述
通用异步接收器/发送器(UART)是用于处理各种广泛适应的协议(RS232,RS485,RS422,…)的时序要求的组件。UART提供了一种广泛采用和廉价的方法来实现不同设备之间的全双工数据交换。
ESP32芯片上有三个UART控制器。它们与来自不同制造商的支持UART的设备兼容。集成在ESP32中的所有UART控制器都具有相同的寄存器组,便于编程和灵活性。在本文档中,这些控制器被称为UART0,UART1和UART2。
每个UART控制器都可以独立配置参数,如波特率、数据位长度、位排序、停止位数量、奇偶校验位等。所有的控制器都与不同厂家的UART设备兼容,还可以支持红外数据协会协议(lrDA)。
注意:这里UART0基本用于程序下载口和串口检测段,故基本不用于串口通信。
功能概述
以下概述描述了用于在ESP32和其他UART设备之间建立通信的功能和数据类型。概述反映了编程ESP32的UART驱动程序时的典型工作流程,并分为以下几部分:
-
设置通信参数
– 波特率,数据位,停止位等。 -
设置通信引脚
– 另一个UART连接的引脚。 -
驱动程序安装
– 为UART驱动程序分配ESP32的资源。 -
运行UART通信
– 发送/接收数据。 -
使用中断
– 触发特定通信事件的中断。 -
删除驱动程序
– 如果不再需要UART通信,释放ESP32的资源。
步骤1至3构成了配置阶段。第4步是UART开始工作的地方。步骤5和6是可选的。
驱动程序由标识
uart_port_t
,对应于树型UART控制器之一。这样的标识存在于以下所有函数调用中。
设置通信参数
UART通信参数可以在一个步骤中全部配置,也可以在多个步骤中单独配置。
一步配置
调用函数
uart_param_config()
并传递给它一个
uart_config_t
结构。
uart_config_t
结构应该包含所有需要的参数。请看下面的例子。
const uart_port_t uart_num = UART_NUM_2;
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_CTS_RTS,
.rx_flow_ctrl_thresh = 122,
};
// 配置UART参数
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
多步配置
通过调用下面表格中的专用函数来单独配置特定参数。如果重新配置单个参数,这些函数也很有用。
配置参数 | 函数 |
---|---|
波特率 |
|
传输比特数 |
从
中选择 |
奇偶校验控制 |
从
中选择 |
停止位数 |
中
中选择 |
硬件流量控制模式 |
中
中选择 |
通信模式 |
中
中选择 |
所有上述功能都有
_get_
相同的功能来检索当前的设置,例如
uart_get_baudrate()
。
设置通信引脚
设置完通信参数后,配置另一个UART设备将要连接的物理GPIO引脚。为此,调用函数
uart_set_pin()
并指定GPIO引脚号码,驱动程序应将Tx、Rx、RTS和CTS信号路由到这些引脚。如果你想为一个特定的信号保留当前分配的引脚号码,请传递宏
UART_PIN_NO_CHANGE
。
对于不使用的引脚,应指定同样的宏。
// Set UART pins(TX: IO4, RX: IO5, RTS: IO18, CTS: IO19)
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_2, 4, 5, 18, 19));
驱动程序安装
一旦通信引脚设置完毕,通过调用
uart_driver_install()
并指定以下参数来安装驱动程序:
- Tx缓冲区的大小
- Rx缓冲区的大小
- 事件队列句柄和大小
- 用于分配中断的标志
该函数将为UART驱动器分配所需的内部资源。
// 设置带有事件队列的UART缓冲IO。
const int uart_buffer_size = (1024 * 2);
QueueHandle_t uart_queue;
// 在此使用事件队列安装UART驱动程序
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_2, uart_buffer_size, \
uart_buffer_size, 10, &uart_queue, 0));
这一步完成后,你可以连接外部UART设备并检查通信。
运行UART通信
串行通信由每个UART控制器的有限状态机(FSM)控制。
发送数据的过程包括以下步骤:
- 将数据写入Tx FIFO缓冲器中。
- FSM将数据序列化。
- FSM将数据发送出去。
接收数据的过程是类似的,但步骤是相反的:
- FSM处理一个输入的串行流并进行并行处理。
- FSM将数据写入Rx FIFO缓冲器中。
- 从Rx FIFO缓冲区读取数据。
因此,应用程序将被限制在使用
uart_write_bytes()
和
uart_read_bytes()
分别从各自的缓冲区写入和读取数据,FSM将完成其他工作。
发送
在准备好传输的数据后,调用函数
uart_write_bytes()
,并将数据缓冲区的地址和数据长度传给它。该函数将把数据复制到Tx环形缓冲区(立即或在有足够空间后),然后退出。当Tx FIFO缓冲区有空闲空间时,一个中断服务例程(ISR)在后台将数据从Tx环形缓冲区移到Tx FIFO缓冲区。下面的代码演示了这个函数的使用。
// 写数据到UART。
char* test_str = "This is a test string.\n";
uart_write_bytes(uart_num, (const char*)test_str, strlen(test_str));
uart_write_bytes_with_break()
函数与
uart_write_bytes()
类似,但在传输结束时增加了一个串行中断信号。串行中断信号 “意味着保持Tx线低电平的时间超过了一个数据帧。
//向UART写入数据,以中断信号结束。
uart_write_bytes_with_break(uart_num, "test break\n",strlen("test break\n"), 100);
另一个向Tx FIFO缓冲区写入数据的函数是
uart_tx_chars()
。与
uart_write_bytes()
不同,这个函数不会阻塞,直到有可用的空间。相反,它将写入所有可以立即装入硬件Tx FIFO的数据,然后返回被写入的字节数。
有一个 “同伴 “函数
uart_wait_tx_done()
可以监控Tx FIFO缓冲区的状态,一旦缓冲区为空就返回。
// 等待数据包被发送
const uart_port_t uart_num = UART_NUM_2;
ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // 等待超时是100个RTOS刻度(TickType_t)。
接收
一旦数据被UART接收并保存在Rx FIFO缓冲区,就需要用函数
uart_read_bytes()
来检索。在读取数据之前,可以通过调用
uart_get_buffered_data_len()
来检查Rx FIFO缓冲区中的可用字节数。下面给出一个使用这些函数的例子。
// 读取来自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);
如果不再需要Rx FIFO缓冲区中的数据,你可以通过调用
uart_flush()
来清除缓冲区。
软件流程控制
如果硬件流控制被禁用,你可以分别使用
uart_set_rts()
和
uart_set_dtr()
函数来手动设置RTS和DTR信号电平。
通信模式选择
UART控制器支持多种通信模式。可以使用函数
uart_set_mode()
选择一种模式。一旦选择了一个特定的模式,UART驱动就会相应地处理所连接的UART设备的行为。例如,它可以使用RTS线控制RS485驱动芯片,以允许半双工的RS485通信。
// 在rs485半双工模式下设置UART
ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
使用中断
有许多中断可以在特定的UART状态或检测到错误后产生。官方技术手册中提供了可用中断的完整列表。你可以通过调用
uart_enable_intr_mask()
或
uart_disable_intr_mask()
分别启用或禁用特定中断。所有中断的掩码都可以作为
UART_INTR_MASK
。
默认情况下,
uart_driver_install()
函数会安装驱动程序的内部中断处理程序来管理Tx和Rx环形缓冲区,并提供高级别的API函数,如事件(见下文)。也可以用
uart_isr_register()
函数注册一个较低级别的中断处理程序来代替,并使用
uart_isr_free()
函数再次释放它。一些使用Tx和Rx环形缓冲器、事件等的UART驱动函数在这种情况下不会自动工作 – 有必要在ISR中直接处理中断。在自定义处理程序的实现中,使用
uart_clear_intr_status()
清除中断状态位。
API提供了一种方便的方法来处理本文件中讨论的特定中断,将它们包装成专用函数:
-
事件检测
:
uart_event_type_t
中定义了几个事件,可以使用FreeRTOS队列功能报告给用户应用程序。你可以在调用
驱动程序安装
中描述的
uart_driver_install()
时启用这个功能。一个使用事件检测的例子可以在 peripherals/uart/uart_events 中找到。 -
达到FIFO空间阈值或传输超时
:当Tx和Rx FIFO缓冲区被特定数量的字符填满时,或在发送或接收数据超时时,可以触发一个中断。要使用这些中断,请执行以下步骤:-
通过在结构
uart_intr_config_t
中输入缓冲区长度和超时的各自阈值,并调用
uart_intr_config()
来配置这些值。 -
使用函数
uart_enable_tx_intr()
和
uart_enable_rx_intr()
启用中断。 -
使用相应的函数
uart_disable_tx_intr()
或
uart_disable_rx_intr()
停用这些中断。
-
通过在结构
-
模式检测
:当检测到同一字符被重复接收/发送若干次的 “模式 “时触发的中断。这个功能在peripherals/uart/uart_events的例子中得到了证明。例如,它可以用来检测一个命令串后面有特定数量的相同字符(“模式”)加在命令串的最后。以下是可用的功能:-
使用
uart_enable_pattern_det_intr()
配置并启用该中断。 -
使用
uart_disable_pattern_det_intr()
禁用该中断。
-
使用
宏定义
该API还定义了几个宏。例如,
UART_FIFO_LEN
定义了硬件FIFO缓冲区的长度;
UART_BITRATE_MAX
给出了UART控制器支持的最大波特率,等等。
删除驱动程序
如果不再需要用
uart_driver_install()
建立的通信,可以通过调用
uart_driver_delete()
来删除驱动程序以释放分配的资源。
例程参考
static const int RX_BUF_SIZE = 1024;
static char tx_buf[] = "Hello world!";
#define TXD_PIN (GPIO_NUM_4)
#define RXD_PIN (GPIO_NUM_5)
void uart_init(void)
{
const 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_APB,
};
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
}
static void tx_task(void *arg)
{
while (1)
{
int len = strlen(tx_buf);
int rxBytes =uart_write_bytes(UART_NUM_1, tx_buf, len);
printf("write %d bytes\n",rxBytes);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
static void rx_task(void *arg)
{
uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
while (1)
{
int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1000 / portTICK_RATE_MS);
if (rxBytes > 0)
{
data[rxBytes] = 0;
printf("read %d bytes '%s'\n",rxBytes,data);
}
}
// free(data);
}
void app_main(void)
{
uart_init();
printf("UART init ...........\n");
xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL);
xTaskCreate(tx_task, "uart_tx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL);
}