ESP32 之 ESP-IDF 教学(九)—— 串口通信(UART)

本文章 来自原创专栏《ESP32教学专栏 (基于ESP-IDF)》,讲解如何使用 ESP-IDF 构建 ESP32 程序,发布文章并会持续为已发布文章添加新内容! 每篇文章都经过了精打细磨!

↓↓↓通过下方对话框进入专栏目录页↓↓↓
CSDN 请求进入目录       _ O x

是否进入ESP32教学导航(基于ESP-IDF)?

       确定


一、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 Parameter error
参数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)在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

注意:当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_queuequeue_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_DATAUART数据事件
UART_BREAKUART中断事件
UART_BUFFER_FULLUART RX缓冲区完全事件
UART_FIFO_OVFUART FIFO溢出事件
UART_FRAME_ERRUART RX帧错误事件
UART_PARITY_ERRUART奇偶检查错误
UART_DATA_BREAKUART TX数据和中断事件
UART_PATTERN_DETUART 输入样式检测事件

上表中最后一个UART_PATTERN_DET是使用uart_enable_pattern_det_baud_intr()启用pattern detect功能后触发的事件。这个事件是UART接收到一种样式的数据后触发的事件。例如AT指令集,它的数据种有个加号标志 “+”。例如AT+CGMIAT+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()清除中断标志,等等。本文不再示例,供读者自行研究。

  • 41
    点赞
  • 137
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Augtons正(单片机)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值