ESP32学习笔记(4)——UART串口使用

一、概述

通用异步收发送器(UART)是一种硬件特性,它使用广泛适应的异步串行通信接口(如RS 232、RS 422、RS 485)来处理通信(即时序要求和数据帧)。UART提供了一种广泛采用和廉价的方法来实现不同设备之间的全双工或半双工数据交换。

ESP32芯片有三个UART控制器(UART 0UART 1UART 2),它们具有一组相同的寄存器,以便于编程和灵活性。

每个UART控制器都是独立配置的,参数包括波特率、数据比特长度、位序、停止位数、奇偶校验位等。所有控制器都与不同厂商的UART支持设备兼容,还可以支持红外数据关联协议(IRDA)。

ESP-IDF 编程指南——UART

二、API说明

以下 UART 接口位于 driver/include/driver/uart.h

2.1 uart_param_config

2.2 uart_driver_install

2.3 uart_read_bytes

2.4 uart_write_bytes

2.5 uart_set_pin

ESP32的串口是支持引脚映射的,比如我的开发板串口一默认的是GPIO9和GPIO10,现在将TX、RX映射到GPIO4和GPIO5上。

三、编程流程

3.1 设置通信参数

如设置波特率、数据位、停止位等
在结构体中进行配置:

typedef struct {
    int baud_rate;                      /*!< UART baud rate*/
    uart_word_length_t data_bits;       /*!< UART byte size*/
    uart_parity_t parity;               /*!< UART parity mode*/
    uart_stop_bits_t stop_bits;         /*!< UART stop bits*/
    uart_hw_flowcontrol_t flow_ctrl;    /*!< UART HW flow control mode (cts/rts)*/
    uint8_t rx_flow_ctrl_thresh;        /*!< UART HW RTS threshold*/
    union {
        uart_sclk_t source_clk;         /*!< UART source clock selection */
        bool use_ref_tick  __attribute__((deprecated)); /*!< Deprecated method to select ref tick clock source, set source_clk field instead */
    };
} uart_config_t;

3.2 设置通信引脚

ESP32的串口是支持引脚映射的,比如我的开发板串口一默认的是GPIO9和GPIO10,现在将TX、RX映射到GPIO4和GPIO5上。

请调用函数uart_set_pin()并指定驱动程序应将Tx,Rx,RTS和CTS信号路由至的GPIO引脚号。

如果要为特定信号保留当前分配的管脚号,请传递宏UART_PIN_NO_CHANGE

应该为不使用的引脚指定相同的宏。

// Set UART pins(TX: IO17 (UART2 default), RX: IO16 (UART2 default), RTS: IO18, CTS: IO19)
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_2, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, 18, 19));

3.3 驱动程序安装

设置好通信引脚后,通过调用安装驱动程序uart_driver_install()并指定以下参数:

  • Tx环形缓冲区的大小
  • Rx环形缓冲区的大小
  • 事件队列句柄和大小
  • 分配中断的标志

该功能将为UART驱动程序分配所需的内部资源。

// Setup UART buffered IO with event queue
const int uart_buffer_size = (1024 * 2);
QueueHandle_t uart_queue;
// Install UART driver using an event queue here
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_2, uart_buffer_size, \
                                        uart_buffer_size, 10, &uart_queue, 0));

3.4 运行UART通信

串行通信由每个UART控制器的有限状态机(FSM)控制。

发送数据的过程涉及以下步骤:

  1. 将数据写入Tx FIFO缓冲区
  2. FSM序列化数据
  3. FSM将数据发送出去

接收数据的过程类似,但是步骤相反:

  1. FSM处理传入的串行流并将其并行化
  2. FSM将数据写入Rx FIFO缓冲区
  3. 从Rx FIFO缓冲区读取数据

因此,应用程序将被限制为分别使用uart_write_bytes()和从相应的缓冲区写入和读取数据uart_read_bytes(),而FSM将完成其余的工作。

3.4.1 发送

准备好要传输的数据后,调用该函数uart_write_bytes()并将数据缓冲区的地址和数据长度传递给该函数。该函数将数据复制到Tx环形缓冲区(立即或在有足够空间可用之后),然后退出。当Tx FIFO缓冲区中有可用空间时,中断服务程序(ISR)将数据从Tx环形缓冲区移至后台的Tx FIFO缓冲区。下面的代码演示了此功能的用法。

// Write data to UART.
char* test_str = "This is a test string.\n";
uart_write_bytes(uart_num, (const char*)test_str, strlen(test_str));

该功能uart_write_bytes_with_break()类似于uart_write_bytes()但在传输结束时添加了一个串行中断信号。意味着它会在发送完数据之后,设置TX低电平一段时间(RTOS任务节拍为单位)。

// Write data to UART, end with a break signal.
uart_write_bytes_with_break(uart_num, "test break\n",strlen("test break\n"), 100);

将数据写入Tx FIFO缓冲区的另一个功能是uart_tx_chars()。不像uart_write_bytes(),此功能在可用空间之前不会阻塞。相反,它将写入可立即放入硬件Tx FIFO中的所有数据,然后返回已写入的字节数。

有一个“陪伴”功能uart_wait_tx_done(),可监视Tx FIFO缓冲区的状态并在其为空时返回。

// Wait for packet to be sent
const int uart_num = UART_NUM_2;
ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // wait timeout is 100 RTOS ticks (TickType_t)

3.4.2 接收

UART接收到数据并将其保存在Rx FIFO缓冲区后,需要使用函数进行读出uart_read_bytes()。,这个函数会阻塞待在那里,直到读满需要的字节,或是超时。

在读取数据之前,您可以调用来检查Rx FIFO缓冲区中可用的字节数uart_get_buffered_data_len(),然后再读取相应的内容,这样就不会造成不必要的阻塞。下面给出了使用这些功能的示例。

// Read data from UART.
const int uart_num = UART_NUM_2;
uint8_t data[128];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length));
length = uart_read_bytes(uart_num, data, length, 100);

如果不再需要Rx FIFO缓冲区中的数据,则可以通过调用清除缓冲区uart_flush()

四、串口回环输出

这里我将GPIO4、GPIO5改成了GPIO23、GPIO18

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"

/**
 * This is an example which echos any data it receives on UART1 back to the sender,
 * with hardware flow control turned off. It does not use UART driver event queue.
 *
 * - Port: UART1
 * - Receive (Rx) buffer: on
 * - Transmit (Tx) buffer: off
 * - Flow control: off
 * - Event queue: off
 * - Pin assignment: see defines below
 */

#define ECHO_TEST_TXD  (GPIO_NUM_23)
#define ECHO_TEST_RXD  (GPIO_NUM_18)
#define ECHO_TEST_RTS  (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS  (UART_PIN_NO_CHANGE)

#define BUF_SIZE (1024)

static void echo_task(void *arg)
{
    /* Configure parameters of an UART driver,
     * communication pins and install the driver */
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
    };
    uart_driver_install(UART_NUM_1, BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM_1, &uart_config);
    uart_set_pin(UART_NUM_1, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS);

    // Configure a temporary buffer for the incoming data
    uint8_t *data = (uint8_t *) malloc(BUF_SIZE);

    while (1) {
        // Read data from the UART
        int len = uart_read_bytes(UART_NUM_1, data, BUF_SIZE, 20 / portTICK_RATE_MS);
        // Write data back to the UART
        uart_write_bytes(UART_NUM_1, (const char *) data, len);
    }
}

void app_main(void)
{
    xTaskCreate(echo_task, "uart_echo_task", 1024, NULL, 10, NULL);
}

五、串口队列接收

/*********************************************************************
 * INCLUDES
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"

#define BUF_SIZE (1024)
#define UART_MAX_NUM_RX_BYTES (1024)

static void uartEventTask(void *pvParameters);

/*********************************************************************
 * LOCAL VARIABLES
 */
static QueueHandle_t s_uart0Queue;

static const char *TAG = "board_uart";

/*********************************************************************
 * PUBLIC FUNCTIONS
 */
/**
 @brief 串口驱动初始化
 @param 无
 @return 无
*/
void UART_Init(void)
{
    // Configure parameters of an UART driver,
    // communication pins and install the driver
    uart_config_t uart_config = 
    {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };

    uart_param_config(UART_NUM_0, &uart_config);                                            // 配置串口0参数
    uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);// 配置串口0引脚
    // Install UART driver, and get the queue.
    uart_driver_install(UART_NUM_0, BUF_SIZE * 2, BUF_SIZE * 2, 100, &s_uart0Queue, 0);     // 安装UART驱动程序

    // Create a task to handler UART event from ISR
    xTaskCreate(uartEventTask, "uartEventTask", 2048, NULL, 12, NULL);  
}


/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void uartEventTask(void *pvParameters)
{
    uart_event_t event;
    uint8_t *pTempBuf = (uint8_t *)malloc(UART_MAX_NUM_RX_BYTES);

    for(;;)
    {
        // Waiting for UART event.
        if(xQueueReceive(s_uart0Queue, (void *)&event, (portTickType)portMAX_DELAY))
        {
            bzero(pTempBuf, UART_MAX_NUM_RX_BYTES);

            switch(event.type)
            {
                // Event of UART receving data
                // We'd better handler data event fast, there would be much more data events than
                // other types of events. If we take too much time on data event, the queue might be full.
                case UART_DATA:
                    // ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
                    uart_read_bytes(UART_NUM_0, pTempBuf, event.size, portMAX_DELAY);
                    uart_write_bytes(UART_NUM_0, (const char *)pTempBuf, event.size);
                    break;

                // Event of HW FIFO overflow detected
                case UART_FIFO_OVF:
                    ESP_LOGI(TAG, "hw fifo overflow");
                    // If fifo overflow happened, you should consider adding flow control for your application.
                    // The ISR has already reset the rx FIFO,
                    // As an example, we directly flush the rx buffer here in order to read more data.
                    uart_flush_input(UART_NUM_0);
                    xQueueReset(s_uart0Queue);
                    break;

                // Event of UART ring buffer full
                case UART_BUFFER_FULL:
                    ESP_LOGI(TAG, "ring buffer full");
                    // If buffer full happened, you should consider encreasing your buffer size
                    // As an example, we directly flush the rx buffer here in order to read more data.
                    uart_flush_input(UART_NUM_0);
                    xQueueReset(s_uart0Queue);
                    break;

                case UART_PARITY_ERR:
                    ESP_LOGI(TAG, "uart parity error");
                    break;

                // Event of UART frame error
                case UART_FRAME_ERR:
                    ESP_LOGI(TAG, "uart frame error");
                    break;

                // Others
                default:
                    ESP_LOGI(TAG, "uart event type: %d", event.type);
                    break;
            }
        }
    }

    free(pTempBuf);
    pTempBuf = NULL;
    vTaskDelete(NULL);
}

void app_main(void)
{
    UART_Init();
}

六、UART1打印日志

在 ESP-IDF 中任何例程中输入idf.py menuconfig
选择 Component config


选择 Common ESP-related

选择 UART for console output

从默认串口0改为 Custom

选择 UART peripheral to use for console output 改为 UART1 输出

UART TX on GPIOUART RX on GPIO 改为你想要的引脚

修改完后,变成UART1打印日志


• 由 Leung 写于 2021 年 4 月 16 日

• 参考:[ESP32]UART串口使用
    ESP32 ESP-IDF UART使用模式检测中断报告事件实现 收发数据

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容