概述

通用异步接收器/发射器 (UART) 是一种硬件功能,它使用广泛采用的异步串行通信接口(如 RS232、RS422、RS485)处理通信(即时序要求和数据成帧)。UART提供了一种广泛采用且廉价的方法来实现不同设备之间的全双工或半双工数据交换。

ESP32-C2 芯片具有两个 UART 控制器(也称为端口),每个控制器都具有一组相同的寄存器,以简化编程并提高灵活性。

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

(1)、概述反映了典型的编程工作流程,并分为以下部分:

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

步骤 1 到 3 包括配置阶段。第 4 步是 UART 开始运行的地方。步骤 5 和 6 是可选的。

UART 驱动程序的功能使用uart_port_t 标识每个 UART 控制器。以下所有函数调用都需要此标识

(2)、下表描述了目录外设/uart/ 中可用的代码示例。

代码示例

描述

外围设备/UART/uart_echo

配置 UART 设置、安装 UART 驱动程序以及通过 UART1 接口读取/写入。

外围设备/UART/uart_events

使用模式检测中断报告各种通信事件。

外围设备/UART/uart_async_rxtxtasks

通过同一 UART 在两个单独的 FreeRTOS 任务中发送和接收数据。

外围设备/UART/uart_select

对 UART 文件描述符使用同步 I/O 多路复用。

外围设备/UART/uart_echo_rs485

设置 UART 驱动程序以在半双工模式下通过 RS485 接口进行通信。此示例与外设/uart/uart_echo类似,但允许通过连接到 ESP32-C2 引脚的 RS485 接口芯片进行通信。

外围设备/UART/nmea0183_parser

通过解析通过UART外设从GPS接收的NMEA0183语句来获取GPS信息。

(3)、配置参数

调用函数uart_param_config() 并向其传递uart_config_t结构。uart_config_t结构应包含所有必需的参数。

const uart_port_t uart_num = UART_NUM_1;
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_CTS_RTS,
    .rx_flow_ctrl_thresh = 122,
};
// Configure UART parameters
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));

通过调用下表中的专用函数单独配置特定参数。如果重新配置单个参数,这些函数也很有用。

要配置的参数

功能

波特率

uart_set_baudrate()

传输位数

uart_set_word_length()uart_word_length_t中选出

奇偶校验控制

uart_set_parity()uart_parity_t中选出

停止位数

uart_set_stop_bits()uart_stop_bits_t中选中

硬件流控模式

uart_set_hw_flow_ctrl()uart_hw_flowcontrol_t中选出

通信模式

uart_set_mode()uart_mode_t中选出

上述每个函数都有对应的功能来检查当前设置的值。例如,要检查当前波特率值,请调用uart_get_baudrate()

(4)、设置通讯引脚

设置通信参数后,配置其他UART设备将连接到的物理GPIO引脚。为此,请调用函数uart_set_pin() 并指定驱动程序应将 Tx、Rx、RTS 和 CTS 信号路由到的 GPIO 引脚号。如果要保留当前为特定信号分配的引脚号,请传递宏UART_PIN_NO_CHANGE

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

// Set UART pins(TX: IO4, RX: IO5, RTS: IO18, CTS: IO19)
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, 4, 5, 18, 19));

(5)、驱动安装

设置通信引脚后,通过调用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_1, uart_buffer_size, \ uart_buffer_size, 10, &uart_queue, 0));

(6)、传输和接受

// 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_1, uart_buffer_size, \
                                        uart_buffer_size, 10, &uart_queue, 0));

函数uart_write_bytes_with_break() 类似于uart_write_bytes(),但在传输结束时增加了一个串行中断信号。“串行中断信号”意味着将Tx线保持低电平的时间超过一个数据帧

// 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));
// Write data to UART, end with a break signal.
uart_write_bytes_with_break(uart_num, "test break\n",strlen("test break\n"), 100);

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

(7)、示例代码

通过串口实现FCT测试功能,协议可以自定义解析回复,下面测试使用字符串分割方式(未做输入校验输入错误解析可能会崩溃)。

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/timer.h"
#include "esp_timer.h"
#include "driver/uart.h"

#define TXD1_PIN (GPIO_NUM_17) //串口1的发送数据引脚
#define RXD1_PIN (GPIO_NUM_16) //串口1的接收数据引脚
#define BUF_SIZE (1024) //接收数据缓存大小,该大小需要大于内部FIFO大小:UART_FIFO_LEN(128)

#define BUF_SEND_SIZE (1024) //发送数据缓存大小,该大小需要大于内部FIFO大小:UART_FIFO_LEN(128)


static QueueHandle_t QueueHandle_t_uart1;

/*串口任务*/
static void uart_task(void *arg)
{
    /*配置串口参数*/
    uart_config_t uart_config = {
        .baud_rate = 115200,//波特率
        .data_bits = UART_DATA_8_BITS,//数据位8位
        .parity    = UART_PARITY_DISABLE,//无奇偶校验
        .stop_bits = UART_STOP_BITS_1,//停止位1位
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,//不使用硬件流控
        .source_clk = UART_SCLK_APB,//串口使用的时钟
    };
    /*初始化串口1*/
    uart_driver_install(UART_NUM_1, 
        BUF_SIZE, //串口1接收缓存大小
        BUF_SEND_SIZE, //串口1发送缓存大小
        10, //队列大小为10
        &QueueHandle_t_uart1, //缓存管理
        0 //设置串口中断优先级,设置为0意味着让系统从1-3级中自动选择一个
    );
    /*设置串口参数*/
    uart_param_config(UART_NUM_1, &uart_config);
    /*设置串口的TX,RX,RTS,DTR引脚*/             //不使用RTS,DTR
    uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    // 设置串口模式 485半双工通讯模式
    // ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
    // 设置UART TOUT功能的读取超时 
    // ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, ECHO_READ_TOUT));
    /*申请一块内存,用于临时存储接收的数据*/
    uint8_t *data = (uint8_t *) malloc(BUF_SIZE);
    uart_event_t event;
    while (1) {
        if(xQueueReceive(QueueHandle_t_uart1, (void * )&event, portMAX_DELAY))
        {
            switch(event.type) {
                case UART_DATA://接收到数据
                    //读取接收的数据
                    uart_read_bytes(UART_NUM_1, data, event.size, portMAX_DELAY);
                    Uart_DataAnalysis(dtmp,event.size);
                    //返回接收的数据
                    uart_write_bytes(UART_NUM_1, (const char*) data, event.size);
                    break;
                case UART_FIFO_OVF://FIFO溢出(建议加上数据流控制)
                    uart_flush_input(UART_NUM_1);
                    xQueueReset(QueueHandle_t_uart1);
                    break;
                case UART_BUFFER_FULL://接收缓存满(建议加大缓存 BUF_SIZE)
                    uart_flush_input(UART_NUM_1);
                    xQueueReset(QueueHandle_t_uart1);
                    break;
                case UART_BREAK://检测到接收数据中断
                    break;
                case UART_PARITY_ERR://数据校验错误
                    break;
                case UART_FRAME_ERR://数据帧错误
                    break;
                case UART_PATTERN_DET://接收到相匹配的字符(没用到)
                    break;
                default:
                    break;
            }
        }
    }
    free(data);
    data = NULL;
    vTaskDelete(NULL);
}
//串口事件信息分析处理
void Uart_DataAnalysis(uint8_t *str, int size)
{
    char* rest = NULL;
    int mark = 0;
    char* value = NULL;
    char scmd [2] = {0};
    char delim = ':';
    char* stemp = (char*)str;

    printf("read usrt:%s\n", stemp);

    rest  = strncpy(scmd,stemp, 1); //获取命令
    if(rest == NULL)
    {
        printf("uart input scmd format is error.\n");
        return;
    }
    mark  = strfindsub(stemp,delim);
    value = mysubstr(stemp,mark+1,(size - mark-1)); //获取命令携带的信息
    int cmd = atoi(scmd);
    switch (cmd)
    {
        case FCT_CMD_WRITESN_FCT:         // 写入SN
            printf("FCT write sn read value:%s\n", (char *)value);
            break;
        case FCT_CMD_REQUEST_FCT: // 请求进入FCT
            printf("FCT start read value:%s\n", (char *)value);
            break;
        case FCT_CMD_SET_NET_STAR: // 开始配网

            break;
        case FCT_CMD_SET_NET_STEP: // 断开网络

            break;
        case FCT_CMD_SET_WIFI_INFO: // 获取wifi信息

            break;
        default:
            break;
    }
}
//获取字符串中固定字符第一次出现的位置
int strfindsub(const char *haystack, char ch)
{
    int cnt = 0;
    while (haystack[cnt] != ch)
    {
            cnt++;
    }

    return cnt;
}
//拷贝字符串指定位置间的子字符串
char* mysubstr(char* srcstr, int offset, int length) 
{
    assert(length > 0);
    assert(srcstr != NULL);
 
    int total_length = strlen(srcstr);//首先获取srcstr的长度
    //判断srcstr的长度减去需要截取的substr开始位置之后,剩下的长度
    //是否大于指定的长度length,如果大于,就可以取长度为length的子串
    //否则就把从开始位置剩下的字符串全部返回。
    int real_length = ((total_length - offset) >= length ? length : (total_length - offset)) + 1;
    char *tmp;
    if (NULL == (tmp=(char*) malloc(real_length * sizeof(char)))) {
        printf("Memory overflow . \n");
        exit(0);
    }
    strncpy(tmp, srcstr+offset, real_length - 1);
    tmp[real_length - 1] = '\0';
 
    return tmp;
}
void app_main(void)
{
    xTaskCreate(uart_task, "uart_task", 2048, NULL, 10, NULL);
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐