esp32学习笔记——UART


前言

提示:这里可以添加本文要记录的大概内容:
嵌入式应用通常要求一个简单的并且占用系统资源少的方法来传输数据。通用异步收发传输器 (UART) 即可以满足这些要求,它能够灵活地与外部设备进行全双工数据交换。
通用异步接收器/发送器 (UART) 是一种硬件功能,它使用广泛采用的异步串行通信接口(例如 RS232、RS422、RS485)来处理通信(即时序要求和数据帧)。
ESP32 芯片具有三个 UART 控制器(UART0、UART1 和 UART2),每个控制器都具有一组相同的寄存器,以简化编程并提高灵活性。每个 UART 控制器可独立配置波特率、数据位长度、位顺序、停止位数量、奇偶校验位等参数。


一、UART主要介绍

UART 是一种以字符为导向的通用数据链,可以实现设备间的通信。异步传输的意思是不需要在发送数据上添加时钟信息。这也要求发送端和接收端的速率、停止位、奇偶校验位等都要相同,通信才能成功。
一个典型的 UART 帧开始于一个起始位,紧接着是有效数据,然后是奇偶校验位(可有可无),最后是停止位。
ESP32 上的 UART 控制器支持多种字符长度和停止位。另外,控制器还支持软硬件流控和 DMA,可以实现无缝高速的数据传输。开发者可以使用多个 UART 端口,同时又能保证很少的软件开销。
主要特性:
• 可编程收发波特率
• 3 个 UART 的发送 FIFO 以及接收 FIFO 共享 1024 × 8-bit RAM
• 全双工异步通信
• 支持输入信号波特率自检功能
• 支持 5/6/7/8 位数据长度
• 支持 1/1.5/2/3 个停止位
• 支持奇偶校验位
• 支持 RS485 协议
• 支持 IrDA 协议
• 支持 DMA 高速数据通信
• 支持 UART 唤醒模式
• 支持软件流控和硬件流控
在这里插入图片描述
上图为 UART 基本架构图。UART 有两个时钟源:80-MHz APB_CLK 以及参考时钟 REF_TICK 。可以通过配置 UART_TICK_REF_ALWAYS_ON 来选择时钟源。时钟中的分频器用于对时钟源进行分频,然后产生时钟信号来驱动 UART 模块。UART_CLKDIV_REG 将分频系数分成两个部分:
UART_CLKDIV 用于配置整数部分,UART_CLKDIV_FRAG 用于配置小数部分。
UART 控制器可以分为两个功能块:发送块和接收块。
发送块包含一个发送 FIFO 用于缓存待发送的数据。软件可以通过 APB 总线写 Tx_FIFO,也可以通过 DMA 将数据搬入 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 中。
在这里插入图片描述
上图为串口的RAM
由上图可以看到三个串口共用一个1024*8的RAM
UART0 的 Tx_FIFO 和 Rx_FIFO 可以通过置位 UART_TXFIFO_RST 和 UART_RXFIFO_RST 来复位。
UART1 的Tx_FIFO 和 Rx_FIFO 可以通过置位 UART1_TXFIFO_RST 和 UART1_RXFIFO_RST 来复位。
UART2没有相应的寄存器来进行复位。
UART1的TX以及RX会受到UART2的影响,UART1只能在UART2没有数据传输的时候使用。
UART 控制器有两种数据流控方式:硬件流控和软件流控。硬件流控主要通过输出信号 rtsn_out 以及输入信号dsrn_in 进行数据流控制。软件流控主要通过在发送数据流中插入特殊字符以及在接收数据流中检测特殊字符来实现数据流控功能。

二、使用步骤及接口函数介绍

1、设置通讯参数- 设置波特率、数据位、停止位等。
2、设置通信引脚- 分配用于连接到设备的引脚。
3、驱动程序安装- 为 UART 驱动程序分配 ESP32 的资源。
4、运行 UART 通信- 发送/接收数据
5、使用中断- 触发特定通信事件的中断
6、删除驱动程序- 如果不再需要 UART 通信,则释放分配的资源
注意:UART 驱动程序的函数使用uart_port_t. 所有函数调用都需要此标识。

1.设置通讯参数- 设置波特率、数据位、停止位等

设置串口配置参数:

esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config);

参数1:串口数
参数2:串口配置参数uart_config_t
返回:ESP_OK 配置成功
ESP_FAIL 配置失败
在这里插入图片描述
uart_config_t这个结构体内就是对串口参数的配置,从上到下依次是:

串口波特率
串口字节长度
设置奇偶校验位
设置停止位
设置串口硬件流控制模式RTS/CTS
UART 硬件 RTS 阈值
选择时钟源
选择是否使用ref_tick是时钟源(不推荐)

2、设置通信引脚- 分配用于连接到设备的引脚。

设置引脚

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);

参数:
串口数
TX引脚
RX引脚
RTS引脚
CTS引脚
(如果不设置RTS以及CTS可以写为-1或者传入宏定义UART_PIN_NO_CHANGE)

3、驱动程序安装- 为 UART 驱动程序分配 ESP32 的资源。

安装驱动:

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);

参数:
串口数
TX环形缓冲区的大小
TX缓冲区的大小
事件队列大小
句柄
分配中断的标志
注意:Rx_buffer_size应该大于UART_FIFO_LEN。Tx_buffer_size应该为零或大于UART_FIFO_LEN。
代码如下(示例):

4、运行 UART 通信- 发送/接收数据。

读:
FSM 处理传入的串行流并将其并行化
FSM 将数据写入 Rx FIFO 缓冲区
从 Rx FIFO 缓冲区读取数据
在读取数据之前,可以通过调用来检查 Rx FIFO 缓冲区中可用的字节数uart_get_buffered_data_len()
如果不再需要 Rx FIFO 缓冲区中的数据,可以通过调用uart_flush()清除缓冲区。

int uart_read_bytes(uart_port_t uart_num, void* buf, uint32_t length, TickType_t ticks_to_wait);

参数:
串口数
指向缓冲区的指针
数据长度
超时时间
返回:-1错误
读到的字节数

写:

int uart_write_bytes(uart_port_t uart_num, const void* src, size_t size);

参数:
串口数
写入的地址
写的大小
返回:-1错误
写入的字节数

可以通过调用uart_wait_tx_done()来查看报文是否被发送成功

5、使用中断- 触发特定通信事件的中断。

在特定的 UART 状态或检测到的错误之后,可以生成许多中断,可以通过调用uart_enable_intr_mask()或uart_disable_intr_mask()分别启用或禁用特定的中断。所有中断的掩码都可以作为UART_INTR_MASK.
默认情况下,uart_driver_install()函数安装驱动程序的内部中断处理程序来管理 Tx 和 Rx 环形缓冲区。也可以使用注册一个较低级别的中断处理程序uart_isr_register(),并使用uart_isr_free()再次释放。在这种情况下,一些使用 Tx 和 Rx 环形缓冲区、事件等的 UART 驱动程序函数将不会自动工作 - 必须直接在 ISR 中处理中断。在自定义处理程序实现中,使用uart_clear_intr_status()清除中断状态位。

6、删除驱动程序- 如果不再需要 UART 通信,则释放分配的资源

调用uart_driver_install()之后如果不再需要与之建立的通信,可以通过调用uart_driver_delete()来删除驱动程序以释放分配的资源。

#include <stdio.h>
#include "my_usart.h"

static const int RX_BUF_SIZE = 1024;
static void my_usart_tx_task(void *arg);
static void my_usart_rx_task(void *arg);

void my_usart_init(void)
{
    const uart_config_t uart_config = {
        .baud_rate = my_uart_baud,//波特率
        .data_bits = my_uart_data_size,//数据大小
        .parity = my_uart_set_parity,//奇偶校验位
        .stop_bits = UART_STOP_BITS_1,//停止位
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,//流量控制模式
        .source_clk = UART_SCLK_APB,//串口时钟源选择
    };
    //安装串口驱动程序
    uart_driver_install(UART_NUM_1, RX_BUF_SIZE*2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM_1, &uart_config);//配置UART
    uart_set_pin(UART_NUM_1, my_Tx_PIN, my_Rx_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);//设置引脚
}
int my_usart_sendData(const char* logName, const char* data)
{
    const int len = strlen(data);//计算数据长度
    const int txBytes = uart_write_bytes(UART_NUM_1, data, len);//返回为串口写入的字节数
    // ESP_LOGI("Write %d bytes", txBytes);
    ESP_LOGI(logName, "Write %d bytes", txBytes);
    printf("%s\n",data);
    return txBytes;
}
static void my_usart_tx_task(void *arg)
{
    static const char *TX_TASK_TAG = "TX_TASK";
    esp_log_level_set(TX_TASK_TAG, ESP_LOG_INFO);
    while (1)
    {
        my_usart_sendData(TX_TASK_TAG,"Hello world111");
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}
static void my_usart_rx_task(void *arg)
{
    static const char *RX_TASK_TAG = "RX_TASK";
    esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
    uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1);//分配一个内存块
    while (1) {
        const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1000 / portTICK_RATE_MS);//读取内存块的数据,没数据返回-1,读到就返回数据的长度
        if (rxBytes > 0) {
            data[rxBytes] = 0;
            ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
        }
    }
    free(data);//释放内存块
}
void run_usart_task(void)
{
    my_usart_init();
    xTaskCreate(my_usart_rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL);
    xTaskCreate(my_usart_tx_task, "uart_tx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL);
}
#ifndef __MYUSART_H_
#define __MYUSART_H_
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"

#define my_Tx_PIN   (GPIO_NUM_4)
#define my_Rx_PIN     (GPIO_NUM_5)
#define my_uart_baud    (115200)
#define my_uart_data_size   (UART_DATA_8_BITS)
#define my_uart_set_parity    (UART_PARITY_DISABLE)
#define my_uart_set_stop_bit  (UART_STOP_BITS_1)

void my_usart_init(void);
int my_usart_sendData(const char* logName, const char* data);

void run_usart_task(void);

#endif

总结

示例代码:代码