ESP-IDF+vscode开发ESP32第三讲——UART

目录

前言

一、ESP32中UART的配置

rs485

IrDA

二、代码编写

[2.1 uart.c](#2.1 uart.c)

[2.2 uart.h](#2.2 uart.h)

[2.3 mian.c](#2.3 mian.c)

[2.4 重点提示](#2.4 重点提示)

三、结果展示


前言

本文基于第一章创建好的工程,来写一个基于UART的通信工程,本章不对UART这个通讯技术进行讲解,在STM32篇已经涉及到了。本章只是梳理在ESP32使用UART的步骤。

开发板芯片是ESP32-P4、ESP-IDF版本是5.5.3。


一、 ESP32中UART的配置

ESP32-P4芯片中共有六个 UART 控制器,包含五个正常功能的 UART0~UART4 和一个满足低功耗需求的 LP UART。另外,UART 还可以用于红外数据交换 (IrDA) 或 RS485 调制解调器。一些基本特性如下:

注意,ESP32和STM32不一样,它的UART不是事先规定复用到哪些特殊引脚上,而是 可配置使用任意 GPIO 管脚,也就是说你可以选择任何一个GPIOx引脚成为RXD或TXD引脚,当然有些特殊引脚除外。

当然,官方也事先给一些引脚配置了默认复用功能。以ESP32-P4为例,如下:

|------------|-------------------------------|-----------------------------------------|-----------------------------|
| 接口 | 功能 | 功能IO | 默认IO |
| JTAG接口 | 用于调试、烧录代码 | MTCK MTDI MTMS MTDO | GPIO2 GPIO3 GPIO4 GPIO5 |
| USB1P1_N/P | 当不使用上述JTAG接口,可用该接口代替JTAG接口的功能 | USB1P1_N0 USB1P1_P0 USB1P1_N1 USB1P1_P1 | GPIO24 GPIO25 GPIO26 GPIO27 |
| UART0 | 异步通信串口 | UART0_TXD UART0_RXD | GPIO37 GPIO38 |
| LP_UART | 低功耗异步通信串口 | LP_UART_TXD LP_UART_RXD | LP_GPIO14 LP_GPIO15 |
| USB | 专用USB 2.0 接口 | USB D- USB D+ | USB_DM USB_DP |

以上是常用的,实际中还有更多,这些是我们比较关注的,平时优先保证这些引脚的默认复用功能。

rs485

ESP32的UART的五个 UART 控制器支持 RS485 通讯模式,这是stm32所不具备的。RS485 有两线半双工及四线全双工两种选择,UART 模块采用两线半双工模式,也就是只要两根差分信号线,只能收或者发。控制结构图如下:

当DE被使能为1时,就使能驱动器D了,此时使用了RS485 发送模式,使用D+、D-差分信号发送数据,数据内容由TXD决定;当DE被使能为0时,就关闭驱动器D了,此时使用原本UART通讯模式,直接使用TXD发送数据。

当RE被使能为0时,就使能接收器R了,此时使用了RS485 接收模式,使用D+、D-差分信号接收数据,数据内容转化为RXD;当DE被使能为1时,就关闭接收器R了,此时使用原本UART通讯模式,直接使用RXD接收数据。

在其他的MCU中,如STM32,没有内置RS485控制器,也就是图中右半部分,此时想要使用RS485功能需要外置RS485控制器。当然ESP32也可以不使用内置的RS485控制器,改为外置的。

IrDA

IrDA 是 UART 的红外无线版,它的核心作用是让普通串口通过红外光无线通信,且上层软件完全不用改,只改硬件物理层。家里的电视 / 空调遥控器用的就是IrDA协议。

对比维度 普通 UART(有线串口) IrDA UART(红外串口)
传输介质 金属导线(TX/RX/GND) 红外光(940nm 红外 LED)
信号形式 直接电平信号(高 / 低电平) 脉冲编码信号(硬件自动转脉冲)
通信模式 全双工(可同时收发) 半双工(同一时间只能发 OR 收)
硬件结构 仅 UART 控制器,直连导线 UART + 红外收发模块(发射管 + 接收头)
电气隔离 无隔离,需要共地 完全电气隔离,无需共地
传输距离 有线 1~5 米 红外 0~3 米(必须对准)

ESP32的UART 实现了其物理层协议。在 IrDA 编码模式下,支持最大信号速率到 115.2 Kbit/s,即 SIR 模式。来看一下这个模式下两种串口的时序对应,需要使用时再去深入研究

ESP32的UART的五个 UART 控制器也支持 IrDA 通讯模式,IrDA 是半双工传输协议,结构图如下

置位 UART_IRDA_EN 使能 IrDA 功能。置位 UART_IRDA_TX_EN(置 1)使能 IrDA 发送数据,这时不允许 IrDA 接收数据;复位 UART_IRDA_TX_EN(清 0) 使能 IrDA 接收数据,这时不允许 IrDA 发送数据。

二、代码编写

这里只使用uart的基本模式。基于工程模板创建组件uart。添加依赖:

  • REQUIRES esp_driver_uart
  • REQUIRES esp_driver_gpio

驱动代码如下,我这里始终使用GPIO37和GPIO38作为uart串口,是因为我的开发板只有这个引脚接入了空闲的usb接口,当然可以使用其他引脚,不过不好接线读取。可以在0-4中任意改变uaer端口。

2.1 uart.c

cpp 复制代码
#include <stdio.h>
#include "uart.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG = "UART";
QueueHandle_t uart_intr_handle= NULL;
void uart_intr_callback(void *arg);

int uart_band_aotuset(void)
{
    uart_bitrate_detect_config_t uart_bitrate_config = {
        .rx_io_num = uart_rx_pin,
        .source_clk = UART_SCLK_XTAL,
    };
    ESP_ERROR_CHECK(uart_detect_bitrate_start(uart_port, &uart_bitrate_config));
    vTaskDelay(pdMS_TO_TICKS(20));
    uart_bitrate_res_t res;
    ESP_ERROR_CHECK(uart_detect_bitrate_stop(uart_port, true, &res));
    if(res.low_period == 0 || res.high_period == 0){
        ESP_LOGE(TAG, "检测失败:无有效边沿");
        return ESP_FAIL;
    }
    else{
        ESP_LOGI(TAG, "检测成功,波特率为:%d", res.clk_freq_hz / (res.low_period + res.high_period));
        return res.clk_freq_hz / (res.low_period + res.high_period);
    }
}
void uart_init(void)
{
    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,
    };
    ESP_ERROR_CHECK(uart_driver_install(uart_port, uaer_rx_buffer, uart_tx_buffer, 5, &uart_intr_handle, 0));
    ESP_ERROR_CHECK(uart_param_config(uart_port, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(uart_port, uart_tx_pin, uart_rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_set_mode(uart_port, UART_MODE_UART));

    //中断配置
    uart_intr_config_t intr_config = {
        .intr_enable_mask = uart_intr,
        .rx_timeout_thresh = 20,
        .rxfifo_full_thresh = 50,
    };
    ESP_ERROR_CHECK(uart_intr_config(uart_port, &intr_config));
    uart_enable_intr_mask(uart_port, uart_intr);
    ESP_LOGI(TAG, "UART初始化成功,波特率默认为115200");

    xTaskCreate(uart_intr_callback, "uart_intr", 4096, NULL, 5, NULL);
}
void uart_intr_callback(void *arg)
{
    uart_event_t uart_event;
    int wait_read_size, len;
    char read_data[100];
    while(1)
    {
        xQueueReceive(uart_intr_handle, (void*)&uart_event, portMAX_DELAY);
        switch (uart_event.type)
        {
            case UART_DATA:
                if (uart_event.timeout_flag == false){
                    ESP_LOGI(TAG, "触发接收FIFO阈值中断, uart rx data size: %d", uart_event.size);
                }
                else{
                    ESP_LOGI(TAG, "触发接收超时中断, uart rx data size: %d", uart_event.size);
                }
                break;
            default:
                ESP_LOGI(TAG, "其他未知事件触发");
                break;
        }
        uart_get_buffered_data_len(uart_port, (size_t *)&wait_read_size);
        len = uart_read_bytes(uart_port, read_data, wait_read_size, 1000);
        read_data[len] = '\0';
        ESP_LOGI(TAG, "uart rx data: %s", read_data);
    }
}

2.2 uart.h

cpp 复制代码
#ifndef __UART_H_
#define __UART_H_
#include "driver/gpio.h"
#include "driver/uart.h"
#include "hal/uart_ll.h" // 取得uart的寄存器地址

#define uaer_rx_buffer 512
#define uart_tx_buffer 512
#define uart_port UART_NUM_4
#define uart_tx_pin GPIO_NUM_37
#define uart_rx_pin GPIO_NUM_38
#define uart_intr UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL

void uart_init(void); // 初始化uart
int uart_band_aotuset(void); // 检测波特率
#endif

2.3 mian.c

cpp 复制代码
#include <stdio.h>
#include "user.h"
#include "uart.h"
#include "esp_log.h"

static char* TAG = "MAIN";
void app_main(void)
{
    CONSOLE_REPL_INIT(); // 初始化控制台REPL环境
    uart_init();
    while (1)
    {
        char *test_str = "This is a test string.\n";
        uart_write_bytes(uart_port, test_str, strlen(test_str));
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

代码流程如上,下面会对一些重点进行提示。具体函数功能可以参考官方的编程手册和我整理的《ESP32 实用API指南

2.4 重点提示

UART 驱动本身没有像其他组件那样有注册回调函数API,所有的中断事件都会存入队列,函数uart_driver_installqueue_size 就是用来设置这个中断队列的长度,就是最大能容下几个中断源。uart_queue返回的是中断队列的句柄。

uart_intr_configuart_enable_intr_mask需要配套使用,一个用于配置中断源,一个用于使能中断源。

需要创建一个任务专门用于处理uart数据接收和发送,任务栈建议4096起步,否则容易溢出。

声明 #include "hal/uart_ll.h" 能够使用中断源的宏,否则会报错。具体中断源见ESP32-P4 技术参考手册 > UART 控制器 (UART) > UART 中断 [PDF]。

比较常用的中断源是UART_INTR_RXFIFO_TOUT 与**UART_INTR_RXFIFO_FULL,**TOUL是空闲中断,一个数据包接收后产生中断;FULL是接收阈值中断,当一个包的字节数大于设定的阈值便产生中断。

在中断任务中使用队列接收函数xQueueReceive ,阻塞等待中断源的到来,将接收的缓冲区指针用结构体uart_event_t 接收,结构体中uart_event_type_t用来判断中断类型。其中上面说的TOUL和FULL两个中断都会归于类型UART_DATA,其他则是一个类型对应一个中断。

结构体uart_event_t 中的timeout_flag 用来判断类型UART_DATA具体是哪一个中断源触发的,当timeout_flag = false 代表是FULL中断,timeout_flag = ture代表是TOUL中断。另外size用来判断类型UART_DATA接收到的数据大小。

函数uart_band_aotuset用来实现硬件自动波特率检测,我这里并未使用。

三、结果展示

相关推荐
F1FJJ1 小时前
Shield CLI PostgreSQL 插件现已上架 VS Code 扩展市场
网络·vscode·网络协议·postgresql·开源软件
golang学习记3 小时前
VS Code 1.113 发布:Agent 与 Chat 体验全面升级!
vscode·microsoft
Sarapines Programmer5 小时前
【VSCode插件】VSCode 插件 Roo Code 简明指南
ide·vscode·编辑器
monsion5 小时前
Code Agent 不是编程工具:它是今天最接近通用 Agent 的现成形态
人工智能·vscode·个人开发
千里马学框架7 小时前
aospc/c++的native 模块VScode和Clion
android·开发语言·c++·vscode·安卓framework开发·clion·车载开发
原来是猿7 小时前
VSCode常见快捷键大全
ide·vscode·编辑器
承渊政道8 小时前
【优选算法】(实战掌握分治思想的使用方法)
数据结构·c++·笔记·vscode·学习·算法·leetcode
shughui8 小时前
Cursor下载安装以及和VSCode的区别(附安装包)
ide·vscode·ai·编辑器·cursor
FateRing20 小时前
vscode插件fail to fecth
ide·vscode·编辑器
@haihi1 天前
ESP32 MQTT示例解析
开发语言·网络·mqtt·github·esp32