(六)ESP32-S3之UART
SP32-S3 芯片中有三个 UART 控制器可供使用,并且兼容不同的 UART 设备。此外,UART 还可以用作红外数据交换(IrDA)或 RS485 调制解调器。三个 UART 控制器分别有一组功能相同的寄存器,分别为 UART0、UART1、UART2,在该实验中我们用到了 UART0。UART 是一种以字符为导向的通用数据链,可以实现设备间的通信。异步通信不需要在发送数据的过程中添加时钟信
ESP32-S3 的 UART 简介
SP32-S3 芯片中有三个 UART 控制器可供使用,并且兼容不同的 UART 设备。此外,
UART 还可以用作红外数据交换(IrDA)或 RS485 调制解调器。三个 UART 控制器分别有一组功能相同的寄存器,分别为 UART0、UART1、UART2,在该实验中我们用到了 UART0。
UART 是一种以字符为导向的通用数据链,可以实现设备间的通信。异步通信不需要在发
送数据的过程中添加时钟信息,但这也要求发送端和接收端的速率、停止位以及奇偶校验位等
参数的配置要相同,唯有如此通信才能成功。
UART 数据帧始于一个起始位,接着是有效数据,然后是奇偶校验位,最后才是停止位。
ESP32-S3 芯片上的 UART 控制器支持多种字符长度和停止位。另外,控制器还支持软、硬件控制流和 GDMA,可以实现无缝高速的数据传输。
ESP32-S3 的 UART 框架图
为了方便大家理解,我们把整个框图分成几个部分来介绍。
①:RAM
ESP32-S3 芯片中三个 UART 控制器(UART0、UART1、UART2)共用 1024×8-bit 的 RAM 空
间,图中①处仅列出了 UART0 的情况。通过配置 UART_TX_SIZE 可以对三个 UART 控制器中的其中一个的 Tx_FIFO 以 1block 为单位进行扩展。同理,配置 UART_RX_SIZE 也是一样的。具体的请参考《esp32-s3_technical_reference_manual_cn》。
②:Clock
UART 作为异步通信的外设,它的寄存器配置模块与 TX/RX/FIFO 都工作在 APB_CLK 时钟
域内,而控制 UART 接收与发送的 Core 模块工作在 UARTCore 时钟域。Clock 有三个时钟源,如图 中的②所示,分别为:APB_CLK、RC_FAST_CLK 以及晶振时钟 XTAL_CLK,他
们可以通过配置寄存器 UART_SCLK_SEL 来选择使用哪个时钟作为时钟源。选择后的时钟源通过预分频器(Divider)分频后进入 UART Core 模块。该分频器支持小数分频,分频系数为
支持的分频范围为:1~256。
③:UART 控制器模块
UART 控制器可以分为两个功能块,分别为:发送块(Transmitter)以及接收块(Receiver)。
发送块包含一个发送 FIFO 用于缓存待发送的数据。软件可以通过 APB 总线向 Tx_FIFO 写
数据,也可以通过 GDMA 将数据传入 Tx_FIFO。Tx_FIFO_Ctrl,用于控制 Tx_FIFO 的读写过程,当 Tx_FIFO 非空时,Tx_FSM 通过 Tx_FIFO_Ctrl 读取数据,并将数据按照配置的帧格式转化成比特流。比特流输出信号 txd_out 可以通过配置 UART_TXD_INV 寄存器实现取反功能。接收块包含一个接收 FIFO用于缓存待处理的数据。输入比特流 rxd_in可以输入到 UART控制器。可以通过 UART_RXD_INV 寄存器实现取反。Baudrate_Detect 通过检测最小比特流输入信号的脉宽来测量输入信号的波特率。Start_Detect用于检测数据的START位,当检测到START位之后,Rx_FSM通过 Rx_FIFO_Ctrl将帧解析后的数据存入 Rx_FIFO中。软件可以通过 APB总线读取 Rx_FIFO 中的数据也可以使用 GDMA 方式进行数据接收。
④:UART Core
HW_Flow_Ctrl 通过标准 UART RTS 和 CTS(rtsn_out 和 ctsn_in)流控信号来控制 rxd_in 和
txd_out 的数据流。SW_Flow_Ctrl 通过在发送数据流中插入特殊字符以及在接收数据流中检测特殊字符来进行数据流的控制。
ESP32S3 有三个串口,即 UART0、UART1、UART2。其中,开发板的串口 0 已经用
于自动下载与调试部分,故在实际应用中不建议使用串口 0 与其他设备通信。
UART 函数解析
ESP-IDF 提供了一套 API 来配置串口。要使用串口功能,需要导入必要的头文件:
#include "driver/uart.h"
配置 UART 端口
该函数用来设置指定 UART 端口的通信参数,该函数原型如下所示:
esp_err_t uart_param_config(uart_port_t uart_num,const uart_config_t *uart_config)
uart_num : UART 外设端口号,例如:UART_NUM_0、UART_NUM_1 等(在 uart.h 文件中有定义)
uart_config 指向 uart_config_t 结构体的指针,包含了 UART 的参数配置信息,需自行定义,并根据 UART 的参数配置填充结构体中的成员变量
返回值:ESP_OK 表示设置成功,ESP_FAIL 表示设置失败

完成上述结构体参数配置之后,可以将结构传递给 uart_param_config () 函数,用以实例化
串口并返回串口句柄。
配置 UART 引脚
该函数设置某个管脚的中断服务函数,该函数原型如下所示:
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 驱动程序,并指定发送和接收缓冲区的大小,其函数原型如下所示:
esp_err_t uart_driver_install(uart_port_t uart_num,
int rx_buffer_size,
int tx_buffer_size,
int event_queue_size,
QueueHandle_t *uart_queue,
int intr_alloc_flags)

使用 uart_driver_install ()函数可以方便地初始化 UART,并且指定相应的缓冲区和队列大小
以及其他参数。
获取数据长度
该函数用于获取接收环形缓冲区中缓存的数据长度,其函数原型如下所示:
esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t* size);
uart_num UART 外设端口号例如:UART_NUM_0、UART_NUM_1 等
size 结构体 size_t 指针所接受缓存的数据长度
接收数据
该函数从 UART 接收缓冲区中读取数据,其函数原型如下所示:
int uart_read_bytes(uart_port_t uart_num,void *buf,uint32_t length,TickType_t ticks_to_wait)

发送数据
该函数将指定的数据写入到 UART 发送缓冲区,并触发数据的发送,其函数原型如下所示
int uart_write_bytes(uart_port_t uart_num, const void *src, size_t size);

返回值:ESP_OK 表示设置成功,ESP_FAIL 表示设置失败。
在使用 uart_write_bytes()函数发送数据时,重要的是要理解该函数的执行机制:数据首先被
复制到 UART 发送缓冲区,随后函数会返回,并不会等待数据完全发送完成。因此,若需确保
数据完整无误地发送成功,应当调用 uart_wait_tx_done()函数进行同步等待,直至发送过程完全结束。在确认 UART 已成功初始化,并且已经配置了正确的波特率及其他相关参数之后,即可调用 uart_write_bytes()函数,将数据准确无误地发送至 UART 设备。
UART 驱动解析
在 IDF 版的 04_uart 例程中,作者在 04_uart \components\BSP 路径下新增了一个 UART 文件夹,用于存放 uart.c和 uart.h这两个文件。其中,uart.h文件负责声明UART相关的函数和变量,而 uart.c 文件则实现了 UART 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
uart.h
/* 引脚和串口定义 */
#define USART_UX UART_NUM_0
#define USART_TX_GPIO_PIN GPIO_NUM_43
#define USART_RX_GPIO_PIN GPIO_NUM_44
/* 串口接收相关定义 */
#define RX_BUF_SIZE 1024 /* 环形缓冲区大小 */
uart.c
/**
* @brief 初始化串口
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
* @retval 无
*/
void usart_init(uint32_t baudrate)
{
uart_config_t uart_config; /* 串口配置句柄 */
uart_config.baud_rate = baudrate; /* 波特率 */
uart_config.data_bits = UART_DATA_8_BITS; /* 字长为 8 位数据格式 */
uart_config.parity = UART_PARITY_DISABLE; /* 无奇偶校验位 */
uart_config.stop_bits = UART_STOP_BITS_1; /* 一个停止位 */
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; /* 无硬件控制流 */
uart_config.source_clk = UART_SCLK_APB; /* 配置时钟源 */
uart_config.rx_flow_ctrl_thresh = 122; /* 硬件控制流阈值 */
uart_param_config(USART_UX, &uart_config); /* 配置 uart 端口 */
/* 配置 uart 引脚 */
uart_set_pin(USART_UX,
USART_TX_GPIO_PIN,
USART_RX_GPIO_PIN,
UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
/* 安装串口驱动 */
uart_driver_install(USART_UX,
RX_BUF_SIZE * 2,
RX_BUF_SIZE * 2,
20,
NULL,
0);
}
SP32-S3 的串口通讯驱动不需要为 UART0 编写中断回调函数,因为在 ESP32-S3 IDF 库中
已经封装了数据读写函数。UART0 通过函数获取 RX 环形缓冲区缓存的数据长度,并判断该数据长度非空后,将其逐一通过读写函数进行操作。
CMakeLists.txt
打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:
set(src_dirs
USART
LED)
set(include_dirs
USART
LED)
set(requires
driver)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main,该函数代码如下:
**
* @brief 程序入口
* @param 无
* * @retval 无
*/
void app_main(void)
{
esp_err_t ret;
uint8_t len = 0;
uint16_t times = 0;
unsigned char data[RX_BUF_SIZE] = {0};
ret = nvs_flash_init(); /* 初始化 NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化 LED */
usart_init(115200); /* 初始化串口 */
while(1)
{
/* 获取环形缓冲区数据长度 */
uart_get_buffered_data_len(USART_UX, (size_t*) &len);
if (len > 0) /* 判断数据长度 */
{
memset(data, 0, RX_BUF_SIZE); /* 对缓冲区清零 */
printf("\n 您发送的消息为:\n");
uart_read_bytes(USART_UX, data, len, 100); /* 读数据 */
uart_write_bytes(USART_UX,
(const char*)data,
strlen((const char*)data)); /* 写数据 */
}
else
{
times++;
if (times % 5000 == 0)
{
printf("\n 正点原子 ATK-DNESP32-S3 开发板 串口实验\n");
printf("正点原子@ALIENTEK\n\n\n");
}
if (times % 200 == 0)
{
printf("请输入数据,以回车键结束\n");
}
if (times % 30 == 0)
{
LED_TOGGLE();
}
vTaskDelay(10);
}
}
}
实验的实验代码很简单,在完成初始化后,就不断地通过串口通信驱动提供的数据接收
并判断数据长度大小,若还未完成数据接收,则每间隔一段时间就使用 printf 函数通过 UART0
打印一段提示信息,若数据接收完毕,则将数据原原本本地使用 printf 函数通过 UART0 打印出去,实现数据的回显功能。
更多推荐



所有评论(0)