文章目录
- 一、ESP32 的 UART 概览
- 1、简介
- 2、UART 使用简介
- 二、使用 UART 收发数据
- 1、设置 UART 参数
- ① 单步法(结构体法)
- ② 分步法
- ③ 代码示例(结构体法)
- 2、分配引脚,安装驱动
- ① 分配引脚
- ② 安装驱动
- ③ 代码示例
- 3、使用 UART 收发数据
- 二、使用队列处理 UART 事件
- 1、简介
- 2、示例
- 三、自定义 UART 中断
一、ESP32 的 UART 概览
1、简介
ESP32芯片有三个UART控制器(UART0、UART1和UART2),具有一组功能相同的寄存器,便于编程和灵活性。
每个UART控制器可独立配置波特率、数据位长、位序、停止位数、奇偶校验位等参数。所有的控制器都兼容 UART 支持的设备从各种制造商,也可以支持红外数据关联协议(IrDA)。
2、UART 使用简介
一个典型的UART使用步骤是:
- 设置 UART 参数、分配引脚
- 安装 UART 驱动(可与上一步颠倒)
- 收发数据
- 使用中断
- 卸载 UART 中断驱动,释放资源(当 UART 不再使用时)
二、使用 UART 收发数据
注意:1、2、3 步可以交换顺率
1、设置 UART 参数
设置 UART 参数有两种方式:单步法(结构体法)、分步法。
① 单步法(结构体法)
调用函数uart_param_config()并将一个uart_config_t结构传(指针)递给它。uart_config_t结构应该包含所有必需的参数。
ESP-IDF定义的结构体类型uart_config_t简介:
typedef struct {
int baud_rate; //波特率
uart_word_length_t data_bits; /*!< UART byte size*/
uart_parity_t parity; //奇偶校验方式
uart_stop_bits_t stop_bits; //停止位数
uart_hw_flowcontrol_t flow_ctrl; //硬件流控方式
uint8_t rx_flow_ctrl_thresh; //硬件流控阈值
union {
uart_sclk_t source_clk; //时钟源
//deprecated vt. 强烈反对, 抨击; 对...表示不赞成;
//下面带有__attribute__((deprecated))的use_ref_tick意味着某代码已过时,不推荐再使用。
bool use_ref_tick __attribute__((deprecated)); //已过时,请改用source_clk选择时钟源。
};
} uart_config_t;
② 分步法
分布法使用见下表
欲配置的参数 | 函数 |
波特率 (Baud rate) |
|
传输数据位长 (Number of transmitted bits) |
|
奇偶校验 (parity control) |
|
停止位数 (Number of stop bits) |
|
硬件流控方式 (Hardware flow control mode) |
|
通信方式 (Communication mode) |
|
同时,上面的每个函数都有一个_get_对应对象来检查当前设置的值。例如,要检查当前波特率值,调用uart_get_baudrate()。
③ 代码示例(结构体法)
示例:
#include "driver/uart.h"
...
...
uart_config_t uartConfig = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.source_clk = UART_SCLK_APB,
};
uart_param_config(UART_NUM_2, &uartConfig);
...
...
2、分配引脚,安装驱动
① 分配引脚
分配引脚的方法是调用函数uart_set_pin()
函数 |
|
函数原型 | 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) |
返回值 | ESP_OK Success ESP_FAIL |
参数 | uart_numUART 编号 tx_io_numTXD GPIO 引脚编号 rx_io_numRXD GPIO 引脚编号 rts_io_numRTS GPIO 引脚编号 cts_io_numCTS GPIO引脚编号 |
注意: 对于后四个参数,可以提供一个宏UART_PIN_NO_CHANGE来保留已经分配的引脚
② 安装驱动
安装驱动通过调用uart_driver_install()安装驱动程序,并指定以下参数:
函数 |
|
函数原型 | 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驱动程序并将UART设置为默认配置。同时,UART ISR处理器将被附加到运行该函数的同一个CPU核心上。Rx_buffer_size应该大于UART_FIFO_LEN。Tx_buffer_size应该是0或者大于UART_FIFO_LEN。 |
返回值 | ESP_OK ——Success ESP_FAIL—— Parameter error |
参数 | uart_numUART 编号 rx_buffer_size接收(RX)缓冲区大小 tx_buffer_size发送(TX)缓冲区大小 (可为零) queue_size想要的 UART 事件队列大小 uart_queueUART 事件队列句柄(输出参数)。填入 intr_alloc_flags中断分配标志 —— 一个或多个ESP_INTR_FLAG_*值。更多信息请参见esp_intr_alloc.h。 不要在这里设置 ESP-IDF Release v4.0.3及更高版本已支持 IRAM 中断:如果想要设置 打开的方法见下图:(使用 |
注意:当TX 缓冲区参数填 0 或 NULL 的时候,不使用缓冲区,使用uart_write_bytes()发送操作时,在数据发送之前将会阻塞。同样也有不阻塞的函数uart_tx_chars(),它不会为等待TX FIFO有足够的空间而阻塞,而是将填充可用的TX FIFO。此函数返回时,FIFO是满的。
这个安装驱动uart_driver_install()函数安装驱动程序的内部 ISR (中断服务程序)来管理 Tx 和 Rx 循环缓冲区,并提供高级API函数,如events(见下文)。
当然,开发者也可以自定义一个低级的中断来直接管理 UART,具体内容见下文。
③ 代码示例
示例:(代码参数较多,所以换行写)
QueueHandle_t eventQueue;
uart_driver_install(
UART_NUM_2, //UART 编号
sizeof rxBuf, //Rx 缓冲区大小
sizeof rxBuf, //Tx 缓冲区大小
16, //事件队列长度(可以不要,此参数填 0,然后下一个参数填NULL)
&eventQueue, //(QueueHandle_t*)接受被创建的句柄的变量指针,类型为FreeRTOS的队列
0 //中断分配标志,这里写 0 表示不想分配中断
);
uart_set_pin(
UART_NUM_2, //UART 编号
19, //TX GPIO
18, //RX GPIO
5, //RTS GPIO
4 //CTS GPIO
);
3、使用 UART 收发数据
串行通信由每个UART控制器的有限状态机(FSM)控制。 发送数据的过程包括以下步骤:
- 写数据到Tx FIFO缓冲区
- FSM进行数据序列化
- FSM发送数据
接收数据的过程是类似的,但步骤相反: FSM处理传入的串行流并将其并行化 FSM将数据写入Rx FIFO缓冲区 从Rx FIFO缓冲区读取数据
使用函数uart_write_bytes() 和 uart_read_bytes() 分别进行发送数据以及接收数据。两个函数的参数均为(UART_NUM,发送内容首地址/接受缓冲区地址,长度)
示例:每 1 秒向串口发送一个字母,依次是 ABCD…XYZ 并循环进行。调试信息显示一句"Well, xxx byte(s) has been sent."
static char letter = 'A';
void taskBTSend(void *args){
while (1){
int count = uart_write_bytes(UART_NUM_2, &letter, 1);
ESP_LOGI("TAG", "Well, %d byte(s) has been sent.", count);
if (letter >= 'Z'){
letter = 'A';
}else{
letter ++;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
//正常不会执行到这里
vTaskDelete(NULL);
}
/****************************************/
void app_main(){
...
//省略了设置参数等步骤,只展示发送数据,本示例中这在一个任务中进行
//实测发现,任务堆栈(第三个参数)设为1024过小
xTaskCreate(taskBTSend, "taskBTSend", 2048, NULL, 4, NULL);
/* 问: 为什么这么大?
答: 因为ESP32的FreeRTOS与原生FreeRTOS中的任务创建函数不同。
在任务堆栈大小(深度)参数上不同。
ESP32 的 FreeRTOS 中这个参数指的是字节数
原生 FreeRTOS 中这个参数指的是变量数,而不是字节数
例如,1个uint32_t变量,前者占用任务堆栈深度为4,而后者只是1
*/
...
}
运行结果:
此外,还有很多函数如uart_flush()(清除UART缓冲区)、uart_get_buffered_data_len()(获取缓冲区中数据的长度)等有用的API,敬请读者参阅 官方文档
二、使用队列处理 UART 事件
1、简介
上文说到,当安装驱动的时候uart_driver_install()函数有一个参数*uart_queue和queue_size。该函数会利用这两个参数创建一个供开发者处理 UART 事件的队列。此队列即 FreeRTOS 的 Queue。需要用 QueueHandle_t 变量接收这个被创建的队列。根据 FreeRTOS 的使用逻辑我们知道这是为任务准备的,因此,我们要创建一个任务专门负责管理这个队列,即处理 UART 发生的各种事件。
注意:此功能以及其他由默认驱动程序内部 ISR 负责的功能只能在没有自定义 UART 中断的时候使用!!!
UART 默认的事件处理 没有使用esp_event.h中的事件循环(EventLoop)。而是使用队列传输事件对象(一个uart_event_t类型的结构体)这个结构体包含了事件类型和UART_DATA事件携带的数据。
整个结构体的结构如下:
typedef struct {
// 事件类型
uart_event_type_t type;
// 仅 UART_DATA 事件 —— UART 数据长度
size_t size;
// 仅 UART_DATA 事件 —— 超时标志
bool timeout_flag;
} uart_event_t;
其中,事件的类型type包括以下内容:
Type | 含义 |
UART_DATA | UART数据事件 |
UART_BREAK | UART中断事件 |
UART_BUFFER_FULL | UART RX缓冲区完全事件 |
UART_FIFO_OVF | UART FIFO溢出事件 |
UART_FRAME_ERR | UART RX帧错误事件 |
UART_PARITY_ERR | UART奇偶检查错误 |
UART_DATA_BREAK | UART TX数据和中断事件 |
UART_PATTERN_DET | UART 输入样式检测事件 |
上表中最后一个UART_PATTERN_DET是使用uart_enable_pattern_det_baud_intr()启用pattern detect功能后触发的事件。这个事件是UART接收到一种样式的数据后触发的事件。例如AT指令集,它的数据种有个加号标志 “+”。例如AT+CGMI、AT+BAUD1等。我们可以用以上函数监听这个“+”加号,一旦检测到,则触发这个事件,供任务去处理、解析。点击此处查看官方文档对函数uart_enable_pattern_det_baud_intr()的介绍。以及示例\examples\peripherals\uart\uart_events对事件UART_PATTERN_DET的介绍
2、示例
// 此函数taskReceive是个任务
static void taskReceive(void *args){
if (xQueueReceive(uart0_queue, &event, portMAX_DELAY)) {
switch (event.type){
case UART_DATA:{
ESP_LOGI("TAG", "接收到了%d长度的数据", event.size);
//todo 插入代码处理事件
break;
}case UART_BUFFER_FULL:{
ESP_LOGI("TAG", "RX缓冲区满了");
//todo 插入处理事件
break;
}case ....:{
....
break;
}default:{
....
break;
}
}
}
}
三、自定义 UART 中断
如果不想使用默认的 UART 中断(如上文的 UART 事件 Queue 等高级API)或者自己另有别的绝妙之用,可以注册自己的UART中断。
- 使用 uart_isr_register() 注册中断
- 使用 uart_isr_free() 释放注册的中断
写的中断 ISR 程序需要尽可能简短,不要忘了在处理中断前后调用uart_clear_intr_status()清除中断标志,等等。本文不再示例,供读者自行研究。