文章目录

  • 一、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)

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()。

③ 代码示例(结构体法)

示例:

#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()

函数

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()安装驱动程序,并指定以下参数:

函数

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 事件队列句柄(输出参数)。填入QueueHandle_t变量指针用于接受一个新的队列句柄来访问 UART 事件。如果设置为 NULL 则驱动程序将不会使用事件队列。

intr_alloc_flags中断分配标志 —— 一个或多个ESP_INTR_FLAG_*值。更多信息请参见esp_intr_alloc.h。

不要在这里设置ESP_INTR_FLAG_IRAM(驱动的ISR处理器不在IRAM中)

ESP-IDF Release v4.0.3及更高版本已支持 IRAM 中断:如果想要设置ESP_INTR_FLAG_IRAM,请在 menuconfig 中打开CONFIG_UART_ISR_IN_IRAM

打开的方法见下图:(使用idf.py menuconfig)

esp32 c3 idf设置usb串口 esp32串口2_uart

esp32 c3 idf设置usb串口 esp32串口2_esp32 c3 idf设置usb串口_02

esp32 c3 idf设置usb串口 esp32串口2_esp32 c3 idf设置usb串口_03

esp32 c3 idf设置usb串口 esp32串口2_uart_04

注意:当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
	*/
	...
}

运行结果:

esp32 c3 idf设置usb串口 esp32串口2_物联网_05


此外,还有很多函数如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()清除中断标志,等等。本文不再示例,供读者自行研究。