ESP32——UART笔记

ESP32——UART学习笔记

本篇目的

  1. 快速入门ESP32的UART串口。
  2. 简要介绍UART串口概念,以及ESP32如何通过UART串口与PC通信。
  3. 如何在ESP-IDF开发环境下对UART串口编程。

适用范围

ESP32全系列(S、C等)芯片和模组。

开发环境

IDF 版本:[ESP-IDF v4.4.3](Release ESP-IDF Release v4.4.3 · espressif/esp-idf (github.com))

例程路径esp-idf/examples/peripherals/uart at v4.4.3 · espressif/esp-idf (github.com)

操作系统:VMWare, Ubuntu20.04

编辑编译方式:VSCode + Espressif IDF v1.5.1

开发板:[ESP32-S3-DevKitC-1](ESP32-S3-DevKitC-1 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)) *1

开发板供电:带外部供电的 USB HUB

前置知识

  1. 你已经搭建完了ESP-IDF开发环境。

  2. 你会复制idf环境中的examples里的例程到你的工作目录,并且能通过命令行成功编译工程。

  3. (非必须)你配置好了VSCode的ESP-IDF插件,并且能通过插件编译工程。

    1. (非必须,但是能改善体验)通过ESP-IDF插件正确配置工程头文件:

      通过VSCode打开工程后,按ctrl+shitf+p打开VSCode命令,输入:

      ESP-IDF: Add vscode configuration folder

      然后插件会在工程目录下生成一个名称为.vscode的文件夹,打开.vscode/c_cpp_properties.json,如下图所示:

      在这里插入图片描述

      在24行添加:

      ,

      ​ “compileCommands”: “${workspaceFolder}/build/compile_commands.json”

      逗号不能少,像这样:

      在这里插入图片描述

      这样你就可以跳转到本工程的其他组件的头文件和源文件了,当然要在你设置了target或者编译了之后生成了build文件夹之后才会起作用。

      这样你的头文件就不会有波浪线了,函数也能正确跳转:

      在这里插入图片描述

      在这里插入图片描述

串口

感性认识

  • 串口是什么?

    感性认识:串行接口(Serial port)简称串口,是一种通信方式和通信接口,它可以让计算机或其他设备与其他设备通过串行数据线进行通信。 串口是一种常用的通信接口,可以在计算机和外部设备之间进行数据传输。 例如,串口可以用来连接打印机、键盘、游戏控制器等设备。

    通常,电脑与ESP32之间最常用的通信方式就是一种称为UART的串口,并且只要两根数据线(发送TxD、接收RxD)和一根地线即可双向通信。

    ESP32-S3 应用程序开发

    图片来源:ESP32-S3 应用程序开发

    串行接口 - 维基百科,自由的百科全书 (wikipedia.org)

  • 串口的作用?

    ESP32通过UART串口与电脑连接时,可以用来输出日志、输出调试信息、传输数据和调试,也可以通过串口控制台例程,像Linux命令行一样控制ESP32。

    使用串口作为通信接口的外设中,最常见的就是GPS模块,比如HT1818Z3G5L、Air551G等,ESP32可以通过串口来读取GPS数据或者配置GPS模块。下图是GPS模块HT1818Z3G5L。

在这里插入图片描述

定义

串口与并口(并行接口,Parallel Port)相对应,串口实际上并非特指某一种具体的接口(比如UART、SPI、USB等等),串行式逐位传输数据的接口都可以称为串口,但是习惯上串口一般指的是以通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)为控制器的串口。UART的参考资料如下:

UART - 维基百科,自由的百科全书 (wikipedia.org)

本文的串口如无特别说明均指的是UART。

物理接口

如果PC想通过串口与ESP32通信,则需要正确地连接串口线,所以首先需要了解一下串口的物理接口。

串口的物理接口通常指以下两种:

  1. 单片机上直接引出的,使用TTL电平作为逻辑电平标准的,复用作为串口的引脚。对于ESP32-S3-DevKitC-1来说如下所示,这个只是默认的UART0的串口引脚,对于ESP32-S3来说可以配置任意引脚为串口引脚。

在这里插入图片描述

  1. 由美国电子工业联盟(EIA)制定的串行数据通信的接口标准RS232,它与上一种物理接口最大的不同有两点:

    1. 一是接口不同,它使用专门的9针的RS232接口(还有25针的RS232接口,但极少使用),如下图所示:

在这里插入图片描述

  1. 二是电平标准不同,TTL的逻辑电平标准用2.4V~5V表示逻辑1,用0V~0.5V表示逻辑0;RS232的逻辑电平标准用**-15V~-3V表示逻辑1**,用**+3V~+15V表示逻辑0**。为什么RS232使用绝对值更高电压作为逻辑标准呢?因为更高的电平可以保障信号在较远距离传输时,不易被其他信号干扰到无法区分逻辑0和逻辑1。

9针的RS232接口有以下引脚(并非按标准顺序排序):

  • Transmit Data (TXD):发送数据
  • Receive Data (RXD):接收数据
  • Data Terminal Ready (DTR):数据终端准备
  • Data Set Ready (DSR):数据准备好
  • Request To Send (RTS):请求发送
  • Clear To Send (CTS):清除发送(发送允许)
  • Ring Indicator (RI):响铃指示
  • Data Carrier Detect (DCD):数据载波检测
  • Ground (GND):地

RS232并非本篇的重点,因此只要记住TxD和RxD分别是用来发送数据和接收数据的就行了,其余引脚DCD、DSR、DTR、RTS、CTS都是用作控流(控制数据流)。其中DTR和RTS通常用作自动下载,RTS和CTS通常用作硬件流控。

TTL电平的串口与RS232电平的串口的关系如下图所示,通常RS232适用于15m以内的串口通信。

在这里插入图片描述

在短距离通信(通常<30cm)时,实际上可以不使用RS232的接口标准,直接使用TTL电平进行串口通信。如下所示:

在这里插入图片描述

下图为ESP32-S3-DevKitC-1板载UART转USB芯片CP2102。

在这里插入图片描述

如何连接

连接示意图如下,需要特别注意的是,在连接串口时,两个设备的TxD和RxD是要交叉连接的,并且TxD只作串行信号输出引脚,单向输出;RxD只作串行信号输入引脚,单向输入。TxD和RxD都是单向的。下图中,ESP32的TxD连接到了CP2102的RxD,ESP32的RxD连接到了CP2102的TxD。当然,完全可以将ESP32的TxD连接到ESP32的RxD自发自收。

在这里插入图片描述

由于TxD和RxD是独立的两根线(两个独立的数据通道),完全可以同时发送和接收,这种允许二台设备间同时进行双向数据传输的系统称为全双工full-duplex)通信系统。与全双工相对,半双工half-duplex)通信系统只允许二台设备间分时双向数据传输。由于ESP32不支持半双工串口,在此不介绍,如果想要了解半双工串口可以参考以下链接:

stm32 USART串口半双工功能测试_云端FFF的博客-CSDN博客_stm32半双工串口

如果想将全双工串口转为半双工串口,可以参考:如何把双线串口转单线串口? WhyCan Forum(哇酷开发者社区)

全双工串口转为半双工串口看似有点脱裤子放屁,但是将串口模块的全双工串口转为半双工串口时很有用,特别是在ESP32的IO资源很紧张时。

对于ESP32来说,大部分外设都可以自行指定GPIO,因此可以用一个IO分时复用为仅接收的UART RxD引脚或者仅发送的UART TxD引脚(即单工模式)就可以节省多一个IO。

比如,将GPS模块的串口转为半双工,那么与ESP32连接时可以只连接一个引脚,并且ESP32初始化UART为接收模式即可,需要配置GPS模块时再将那个引脚配置为发送模式。

在这里插入图片描述

下图为原理图中的USB转串口部分,在R7和R9附近可以看到交叉连接的串口引脚。

在这里插入图片描述

ESP32-S3-DevKitC-1的原理图:sch_esp32-s3-devkitc-1_v1_20210 (espressif.com)

需要注意的是,ESP32使用3.3V电源供电,3.3V在TTL的逻辑电平标准兼容范围之内的,但是ESP32不兼容5V电压,如果你使用的USB转UART是输出的TTL电平是5V的,你需要加上电平转换模块。

UART协议

UART协议对于用户来说并非必须知晓,开发者在使用UART通信时,更多情况下只会更改串口波特率,其余参数均默认,因此将UART协议放在之后的实验中,通过逻辑分析仪讲述。

UART - 维基百科,自由的百科全书 (wikipedia.org)

串口波特率可以不严谨地理解为每秒传输多少个比特(bit)(比特率),比如波特率115200表示每秒传输115200 bit。实际上波特率并不等同于比特率,但是习惯上称呼为波特率。

波特率 - 维基百科,自由的百科全书 (wikipedia.org)

比特率 - 维基百科,自由的百科全书 (wikipedia.org)

ESP32的串口

数量

通常一个ESP32有多个UART,不同系列的ESP32(如S系列、C系列)拥有的串口数量也不尽相同,如何知道手上的开发板的串口数量?可以去ESP产品选型器中查看。

在这里插入图片描述

参数

通常来说,对于串口,我们最关注的参数差不多就是通信速率了,从ESP32-S3的技术规格书里看到它支持的最高速率可达到5Mbps(如果不算停止位等),这意味着最高速率时,ESP32每秒钟可以传输五百万位(5,000,000 bit/s)数据,也就是六十二万五千字节(625,000 Byte/s = 625 kByte/s),这个速率对于ESP32来说是对外通信的比较快的通信方式,当然在没有丢包重传等保障措施的情况下,为了保证通信质量不会用这么高的速度,最常用的通信速率是115200,下载速率常用460800、921600、1000000、2000000。

在这里插入图片描述

其余参数将在程序解读中介绍。

特殊串口

通常,UART0是作为日志输出的串口,UART0引脚对于ESP32-S3-DevKitC-1来说就是丝印标注TX和RX的引脚。如果没特殊需求不需要更改。

对于ESP32-S3来说,USB接口可以虚拟出一个串口,但是使用这个串口运行控制台例程时通常要改程序,而且这个串口会在ESP32开机之后一段时间才会被虚拟出来,因此开机信息可能会丢失开通一部分。

在menucinfig的ESP System Settings中,有这么两个选项:

在这里插入图片描述

第一个选项为控制台的输出通道,默认为UART0,从选项右侧的截图可以看到可以选择自定义串口等通道,帮助内容的翻译如下:

选择发送控制台输出的位置(通过stdout和stderr)。

  • 默认是在预定义的GPIO上使用UART0。
  • 如果选择 “Custom”,可以选择UART0或UART1,并且可以选择任何引脚。
  • 如果选择 “无”,除了ROM启动器的初始输出外,任何UART上都不会有控制台输出。这个ROM输出可以通过GPIO捆绑或EFUSE来抑制,详情请参考芯片数据手册。
  • 在带有USB OTG外设的芯片上,"USB CDC "选项将输出重定向到CDC端口。这个选项使用芯片ROM中的CDC驱动。这个选项与TinyUSB协议栈不兼容。
  • 在带有USB串行/JTAG调试控制器的芯片上,选择该选项可以将输出重定向到该设备的CDC/ACM(串行端口仿真)组件。

第二个选项为控制台次要输出通道,默认选择USB串口,也可以关闭次要控制台,帮助内容的翻译如下:

当UART0端口作为主端口被选中但没有连接时,这个二级选项支持通过其他特定端口输出,如USB_SERIAL_JTAG。这个二级输出目前只支持不使用REPL的非阻塞模式。如果你想用REPL在阻塞模式下输出或通过这个次级端口输入,请在 "控制台输出通道"菜单中把主配置改为这个端口。

这意味这ESP32通过ESP_LOGI输出的日志内容会向USB串口复制一份,通过USB串口也可以查看日志。ESP_LOGI是ESP-IDF中输出信息级别日志的函数,下文的实验中将会用到。

ESP32上的串口实验

从以上内容中,已经了解了串口的基本概念、串口的连接方式、ESP32-S3串口的信息、常用的参数和特殊的串口,接下来会通过具体程序来解释UART协议,以及如何对串口编程。

接下来主要讲解以下例程:

  1. esp-idf/examples/peripherals/uart/uart_echo 在 v4.4.3 ·乐鑫/ESP-IDF (github.com)
  2. esp-idf/examples/peripherals/uart/uart_async_rxtxtasks v4.4.3 ·乐鑫/ESP-IDF (github.com)
  3. esp-idf/examples/peripherals/uart/uart_events v4.4.3 ·乐鑫/ESP-IDF (github.com)
  4. esp-idf/examples/peripherals/uart/uart_repl v4.4.3 ·乐鑫/ESP-IDF (github.com)

如何使用例程?在ESP-IDF编程指南中的第五步:开始创建工程中有详细的步骤,在此不再赘述。

快速入门 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)

如何配置VSCode上的ESP-IDF插件:ESP32-IDF环境搭建之vscode环境

开始实验前的准备

你需要准备以下材料:

  1. ESP32开发板,这里是ESP32-S3-DevKitC-1 *1

  2. microUSB数据线(根据你的开发板选择) *2

  3. 如果你的ESP32开发板没有板载USB转串口,你需要准备一个USB转串口模块,并按以下方式连接:

在这里插入图片描述

  1. 如果你的ESP32开发板有板载USB转串口,用数据线连接标注的UART的接口并插入电脑即可。

uart_echo

复制该例程到你的路径,该例程的路径是:你的esp-idf路径/examples/peripherals/uart/uart_echo

通过VSCode打开该例程,如有需要,按前置知识的方法配置好VSCode的IntelliSense。

例程说明

如README.md所说,该例程是串口回声例程,其实就是复读机的意思,你通过UART向ESP32发送什么,ESP32就会原封不动地发回来。考虑到有些人可能没有多的USB转串口模块,因此接下来会将例程中默认的串口改为串口0,这样就可以不用别的USB转串口模块了,因此可以不完全按照README.md中的操作。

This example demonstrates how to utilize UART interfaces by echoing back to the sender any data received on configured UART.

这个例子演示了如何利用UART接口,将配置的UART上收到的任何数据回传给发送方。

运行它

  1. 打开路径,通过ls命令查看路径下的内容:

在这里插入图片描述

  1. 获取你的esp-idf环境变量,我设置为:get_idf4.4.3

在这里插入图片描述

  1. 设置目标,我使用的ESP32-S3开发板,因此设为esp32s3idf.py set-target esp32s3

在这里插入图片描述

  1. (可省略)打开menuconfig:idf.py menuconfig

在这里插入图片描述

打开倒数第4个选项Echo Example Configuration就可以查看工程中有什么选项可以设置:

在这里插入图片描述

在这里并不需要对这些配置进行什么更改,因为在menuconfig中的任何更改都会导致程序全部重新编译,编译一千多个文件还是挺麻烦的,所以在需要频繁更改选项时还是建议用.h设置选项。

Echo Example Configuration中选项的含义分别是:

  • UART port number:该例程的串口号,既待会运行时通过哪个串口来复读。在下面的步骤中将改成UART0。
  • UART communication speed:串口的波特率,默认115200即可。
  • UART RXD pin number:UART接收引脚。
  • UART TXD pin number:UART发送引脚。
  • UART echo example task stack size:该例程创建的任务的栈的大小,如果使用了ESP_LOGI,建议最少也要2048字节(ESP-IDF的freeRTOS创建任务时栈的单位是字节)。

了解示例的选项含义后即可退出,在menuconfig中下方有提示,按Q退出,按S保存。

在这里插入图片描述

如何查看例程有什么可以设置的选项呢?

一般查看项目的Kconfig文件就知道了,在这里是:main/Kconfig.projbuild.

第一行menu后面跟着的就是菜单名称。

在这里插入图片描述

  1. 打开main/uart_echo_example_main.c

    我们用以下内容替换29行到36行的宏定义部分:

    #define ECHO_TEST_TXD (UART_PIN_NO_CHANGE) /* CONFIG_EXAMPLE_UART_TXD */

    #define ECHO_TEST_RXD (UART_PIN_NO_CHANGE) /* CONFIG_EXAMPLE_UART_RXD */

    #define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)

    #define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)

    #define ECHO_UART_PORT_NUM (UART_NUM_0) /* CONFIG_EXAMPLE_UART_PORT_NUM */

    #define ECHO_UART_BAUD_RATE (115200) /* CONFIG_EXAMPLE_UART_BAUD_RATE */

    #define ECHO_TASK_STACK_SIZE (4096) /* CONFIG_EXAMPLE_TASK_STACK_SIZE */

在这里插入图片描述

这里其实就是将menuconfig的设置用自己的设置替换了而已,注意34行,我们使用默认的串口UART0,UART0的引脚默认就是原理图上的引脚,因此不需要改变。

UART_PIN_NO_CHANGE是该引脚不需要改变的意思,它在你的ESP-IDF路径/components/driver/include/driver/uart.h中有定义。

在这里插入图片描述

Universal Asynchronous Receiver/Transmitter (UART) - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)

  1. 编译idf.py build

在这里插入图片描述

正在编译中:

在这里插入图片描述

编译完成!

在这里插入图片描述

  1. 烧录idf.py -p /dev/ttyUSB0 flash

    其中的/dev/ttyUSB0需要根据你的串口修改。

    与 ESP32-S3 创建串口连接 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)

    在这里插入图片描述

    烧录完成!

    在这里插入图片描述

  2. 运行串口监视器,看看效果idf.py -p /dev/ttyUSB0 monitor

    其中的/dev/ttyUSB0需要根据你的串口修改。

    在这里插入图片描述

    在这里插入图片描述

    看起来没什么大不了的,试着输入一些字符:

    在这里插入图片描述

    可以看到,灰色的是“我输入的字符”,后面绿色的字符是ESP32接收到了我发送的字符,并通过ESP_LOGI函数发回来的信息Recv str: 后面则跟着“我输入的字符”。这个“我输入的字符”其实并不是我真正输入的,我输入的字符其实是看不见的,只是ESP32原封不动输出了一遍了而已。接下来解释程序时会解释这是什么意思。

  3. 退出串口监视器:直接按ctrl+l即可退出。

    在这里插入图片描述

    如果你对串口监视器不熟悉,请参考:IDF 监视器 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)

  4. 至此,你已经成功运行了这个例程!

它是怎么运行的?

接下来,我将带你了解他是如何运行的。同时,根据我的步骤,你也可以了解如何去学习esp-idf中的例程。

1. 先读README.md

一个好的README.md会交代例程的基本信息、作用、如何使用等信息,因此,阅读README.md是十分重要的。如果英语不太好也没关系,可以复制其中的内容到翻译软件,或者直接打开该例程的网页esp-idf/examples/peripherals/uart/uart_echo 在 v4.4.3 ·乐鑫/ESP-IDF (github.com),右键翻译该网页即可。

考虑到有些人只有一个开发板和一个数据线,因此没有完全按照例程中README的需求使用UART2和USB转串口模块,经过运行它中的步骤,只用板载的USB转串口和UART0即可。

2. 阅读源码

打开main/uart_echo_example_main.c:

在这里插入图片描述

esp-idf中,程序的入口是app_main,比较类似于STM32中的main。因此需要先看app_main

app_main中,可以看到他创建了一个任务,来运行复读机的程序,除此之外没有别的操作。如果你原意,完全可以将echo_task的内容全部放到app_main中。

在这里插入图片描述

如果你对freeRTOS创建任务不太熟悉,可以参考:xTaskCreate() - 追风*逐浪 - 博客园 (cnblogs.com)

echo_task在上文定义了,跳转到echo_task

在这里插入图片描述

先忽略uart_config_t uart_config,仅看while(1)之前的主要进行的操作,也就是这3行:

ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));

ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));

ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));

这三行做了什么呢?

首先是ESP_ERROR_CHECK,顾名思义,就是用来检查错误的,他定义在esp_err.h,如果保持默认设置,ESP_ERROR_CHECK中检测到了返回了代表错误的值ESP_FAIL,他会输出错误的信息,并重启ESP32,如下图所示。61行的ESP_ERROR_CHECK(ESP_FAIL);是用来测试效果的,正常情况不需要加上去。

在这里插入图片描述

uart_driver_install

还是顾名思义,uart_driver_install,也就是安装串口驱动,这种类似Linux开发的风格也是ESP-IDF的特色之一。如果你配置好了VSCode,你也可以直接参看uart_driver_install的声明:

在这里插入图片描述

声明中已经讲的很清楚了,不懂的地方可以复制到翻译器看一下,在此给出uart_driver_install的简要用法:

esp_err_t uart_driver_install(
    /* 你需要安装驱动的串口 */
    uart_port_t uart_num,
    /* Rx环形缓冲区的大小,一般设置为接收数据的缓冲区的两倍 */
    int rx_buffer_size,
    /* Tx环形缓冲区的大小,如果为0,那么发送数据时函数会被阻塞,直到发送完成 */
    int tx_buffer_size,
    /* UART事件队列的大小/深度,在例程中uart_events解释 */
    int queue_size,
    /* UART事件队列句柄,在例程中uart_events解释 */
    QueueHandle_t *uart_queue,
    /* 中断标志,通常设为0,使用默认的中断优先级即可 */
    int intr_alloc_flags);

在例程中,就是:

uart_driver_install(
    ECHO_UART_PORT_NUM, /* 安装串口0的驱动,UART_NUM_0 */
    BUF_SIZE * 2,       /* Rx环形缓冲区的大小,1024*2(字节) */
    0,                  /* 没有设置Tx环形缓冲区,这意味着我们发送数据时会阻塞到发送函数中 */
    0,                  /* 没有设置UART事件队列 */
    NULL,               /* 没有设置UART事件队列句柄,我们没有启用这个功能 */
    intr_alloc_flags    /* 在这里是0,即默认中断优先级 */
);

需要注意,CONFIG_UART_ISR_IN_IRAM是在menuconfig中的一个选项,如果启用这个选项,会将串口中断服务函数放到IRAM中。

#if CONFIG_UART_ISR_IN_IRAM
    intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif

什么是IRAM?存储器类型 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)

如何查找这个选项呢?打开menuconfig,根据menuconfig下方的提示,按/即可查找符号:

在这里插入图片描述

输入UART_ISR_IN_IRAM(注意不要输入CONFIG_):

在这里插入图片描述

回车即可查看这个选择的位置:

在这里插入图片描述

在英文输入法下,按shitf+/,其实就是?,即可获得帮助信息:

在这里插入图片描述

If this option is not selected, UART interrupt will be disabled for a long time and may cause data lost when doing spi flash operation.

如果不选择该选项,在进行spi flash操作时,UART中断将长时间被禁用,可能导致数据丢失。

通过uart_driver_install,已经初步设置了UART0的基本参数。

uart_param_config

uart_param_config是设置UART配置参数的意思,由于我们还没有了解UART协议,此处暂且略过UART参数结构体,即上文提到的uart_config_t uart_config

esp_err_t uart_param_config(
    /* 你需要配置的串口 */
    uart_port_t uart_num,
    /* 指向UART参数结构体的指针 */
    const uart_config_t *uart_config);
uart_set_pin

uart_set_pin用于设置引脚,对于ESP32-S3来说可以设置任意引脚,ESP32需要注意有些引脚只能设置为输入模式。

esp_err_t uart_set_pin(
    uart_port_t uart_num,   /* 串口号 */
    int         tx_io_num,  /* 发送引脚TxD */
    int         rx_io_num,  /* 接收引脚RxD */
    int         rts_io_num, /* 请求发送引脚RTS */
    int         cts_io_num  /* 清除发送引脚CTS */
);

在这里,每个引脚都是宏定义为了UART_PIN_NO_CHANGE,也就是未改变,那么怎么查看具体是哪个引脚呢?可以在ESP32-S3的数据手册中的2.5 GPIO 功能找到答案:

在这里插入图片描述

在这里插入图片描述

U0代表UART0,也就是:

串口引脚GPIO号
U0TXD43
U0RXD44
U0RTS15
U0CTS16

esp32-s3_datasheet_cn.pdf (espressif.com)

while循环

在whlie循环之前还申请了一段空间用于存放数据:

在这里插入图片描述

while循环的主体如下:

while (1) {
    // Read data from the UART
    int len = uart_read_bytes(ECHO_UART_PORT_NUM, data, (BUF_SIZE - 1), 20 / portTICK_RATE_MS);
    // Write data back to the UART
    uart_write_bytes(ECHO_UART_PORT_NUM, (const char *)data, len);
    if (len) {
        data[len] = '\0';
        ESP_LOGI(TAG, "Recv str: %s", (char *)data);
    }
}

首先通过uart_read_bytes函数读取串口ECHO_UART_PORT_NUM的信息,也就是读取UART0的信息。这个函数的参数的含义如下:

在这里插入图片描述

uart_read_bytes(
    ECHO_UART_PORT_NUM,   /* 串口端口号,这里是串口0 */
    data,                 /* 指向缓冲区的指针 */
    (BUF_SIZE - 1),       /* 最大的读取的数据长度,设置为(BUF_SIZE - 1)是因为需要\0进行结尾 */
    20 / portTICK_RATE_MS /* 超时时间,注意这里是RTOS ticks数 */
);

这里的uart_read_bytes的含义是读取UART0中不超过1023字节的数据到数据缓冲区data,如果超过20ms没有数据那么就返回读取到的数据的长度

然后通过uart_write_bytes函数把刚刚读取到的数据写回去:

在这里插入图片描述

uart_write_bytes的声明如下:

在这里插入图片描述

uart_write_bytes(
    ECHO_UART_PORT_NUM, /* 串口端口号,这里是串口0 */
    (const char *)data, /* 指向待发送的缓冲区的指针 */
    len                 /* 数据长度 */
);

这里的uart_write_bytes的含义是从数据缓冲区data向UART0写长度为len字节数据,由于之前设置了Tx环形缓冲区的大小为0,所以它会一直阻塞到这里发送数据,直到数据发送完毕为止。

在实验中,也就是我们按下按键后,灰色的字节就是通过uart_write_bytes发送的数据,这也是“我输入的字符”加双引号的原因,因为这个是ESP32发回来的字符。

在这里插入图片描述

if(len)的作用是,如果接收到了数据(len不等于0),那么将数据的最后一个字符设为字符串结尾data[len] = '\0';:(因为没有清空字符串。如果不加,上一次发送了hello,这一次发送了123,那么这一次就会输出123lo,即包含上一次的字符,这不是我们想要的结果)

在这里插入图片描述

ESP_LOGI

ESP_LOGI是一个非常重要也是非常常用的函数,它的作用是输出信息级别的日志,它在esp_log.h中声明,如何使用它呢?首先包含头文件#include "esp_log.h",然后定义一个TAG,用于说明这个日志是在哪里输出的:

在这里插入图片描述

然后像printf一样使用就行了,在这里是:

ESP_LOGI(TAG, "Recv str: %s", (char *)data);

它输出的效果是这样的:

在这里插入图片描述

I表示这个是信息(Information)级别的日志,括号内是开机到输出这个信息的毫秒数,UART TEST就是我们刚刚定义的TAG,冒号后的内容就是像printf一样使用时应该输出的结果。绿色的原因是它增加了颜色的转义字符。

使用ESP_LOGI时不需要初始化,这是因为在启动阶段系统已经初始化过了。虽然默认是通过UART0输出,但是不代表你使用UART0时不用初始化UART0(手动狗头)。

在这里它的含义是,将接收到的内容通过日志ESP_LOGI输出

如果想要了解更多日志库的内容,可以参考:ESP32学习笔记(6)——Log日志库使用 - 简书 (jianshu.com)

至此,除了UART协议和uart_config_t uart_config没有说明,你已经大概了解了如何使用这个例程。

通过逻辑分析仪解释UART协议

之所以要将UART协议放到最后再说,是因为大部分情况下都不需要使用上UART协议的具体内容,就把UART当黑盒用,知道输入输出即可。虽然UART协议用起来和学起来是比较简单和容易的,但是如果换稍微复杂一点的协议,比如SPI只看协议时序图就比较难理解了。而且结合具体波形更能理解使用的过程中发生了什么,怎么发生的。废话少说,上逻辑分析仪:

在这里插入图片描述

烧录程序后,打开逻辑分析仪,输入一个字母Z查看波形;

在这里插入图片描述

波形长这样,接下来简要介绍一下各个部分的含义:

在这里插入图片描述

如何理解这个波形呢?我们需要从UART时序图入手,下图为UART数据帧的比较完整的时序图:

在这里插入图片描述

从UART数据帧中,可以看到它由以下部分组成:

  1. 起始位:如果没传输数据,那么UART数据线会保持高电平状态,如果数据线从高电平被拉低到了低电平,这意味这需要传输数据了,低电平维持的时间为1个时钟周期(1 bit)。

    在这里插入图片描述

  2. 数据位:也就是UART数据帧中实际的数据位,数据位的长度可以是5到8位,默认为8位。这里也是8位数据位。通常,数据是低位优先发送的,在这里,Z的ASCII码值是0x5A,换成二进制是0b01011010,由于发送时是低位在前发送,所以发送的顺序是01011010,好像没什么变化。

在这里插入图片描述

W效果会更明显,W的ASCII码值是0x57,换成二进制是0b01010111,发送的顺序是11101010

在这里插入图片描述

  1. 奇偶校验位:

    默认的设置是不使用就校验位的,因此此处没有体现出来。

    当UART控制器启用了奇偶校验,接收到数据位时会对数据帧中的1进行计数,如果接收到的是偶数的数据位,那么计数结果为0;如果是奇数,那么计数结果为1。

    在数据没有出错的情况下,奇偶校验位结果等同与计数结果;反之,则说明数据位的数据发生了改变。

    当然,奇偶校验作为一种十分简单的校验方法并不适用于所有情况,比如如果数据位中同时有两个相同电平的数据位发生了改变,奇偶校验是检测不出来的。

  2. 停止位:表示数据位传输结束,通常会保持1到2个bit的时间。

在这里插入图片描述

了解了UART帧的结构,让我们看看UART帧和我们上文略过的串口配置结构体uart_config_t uart_config的关系,该结构体的声明如下:

在这里插入图片描述

typedef struct {
    int baud_rate;                      /*!< UART 波特率*/
    uart_word_length_t data_bits;       /*!< UART 数据位长度,5~8位,通常8位 */
    uart_parity_t parity;               /*!< UART 校验模式,通常不校验 */
    uart_stop_bits_t stop_bits;         /*!< UART 停止位长度,1~2位,通常1位 */
    uart_hw_flowcontrol_t flow_ctrl;    /*!< UART 硬件控流模式,通常不启用 */
    uint8_t rx_flow_ctrl_thresh;        /*!< UART HW RTS阈值,通常不使用 */
    union {
        uart_sclk_t source_clk;         /*!< UART 时钟源 */
        bool use_ref_tick  __attribute__((deprecated)); /*!< 忽略即可 */
    };
} uart_config_t;

下图显示了可以填进结构体参数的枚举。

在这里插入图片描述

在我们的例程中是这样配置的:

uart_config_t uart_config = {
    /* 波特率设置为115200 */
    .baud_rate  = ECHO_UART_BAUD_RATE,
    /* 8位数据位 */
    .data_bits  = UART_DATA_8_BITS,
    /* 不使用奇偶校验 */
    .parity     = UART_PARITY_DISABLE,
    /* 1位停止位 */
    .stop_bits  = UART_STOP_BITS_1,
    /* 不使用硬件流控 */
    .flow_ctrl  = UART_HW_FLOWCTRL_DISABLE,
    /* 如无特别需求默认使用UART_SCLK_APB即可 */
    .source_clk = UART_SCLK_APB,
};

与逻辑分析仪的波形对比:

在这里插入图片描述

可以看到他们之间的对应关系。

自此,uart_echo例程已经基本梳理了一遍了。

UART:了解通用异步接收器/发送器的硬件通信协议 | 亚德诺半导体 (analog.com)

3. 试着修改参数

你已经大概了解了UART协议,了解了uart_config_t中的参数和UART波形的关系,为了加深理解,你可以试着修改参数,比如:

  1. 将8位数据位改为7位UART_DATA_7_BITS,再用其他串口上位机修改对应的参数看看能否正常通信?
  2. 修改ESP32的程序的奇偶校验为奇校验,串口上位机设置为偶校验,看看能否接收到数据?
  3. 修改波特率,看看最高能到多快还能正常通信?
  4. 修改停止位,比如改成1.5位停止位,看看发送单个字符和连续发送两个字符时会发生什么?
  5. 20ms的超时时间对人来说有点快,试着修改超时时间,设置多大才能连续输入字符?
  6. 灰色字符如何换行,不与绿色的LOG信息在同一行?
  7. ……
  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
树莓派(Raspberry Pi)和ESP32都是流行的微型计算机平台,它们各自有独特的用途,但都支持串行通信,如UART(Universal Asynchronous Receiver/Transmitter),用于设备间的数据传输。 树莓派是一个基于Linux的单板电脑,广泛用于教育、物联网开发和原型设计。它的GPIO(General Purpose Input/Output)接口中就包含UART端口,可以用来连接其他硬件设备,如传感器或简单的控制模块,通过串口进行通信。 ESP32是Espressif Systems开发的一款嵌入式微控制器,它内置了Wi-Fi和蓝牙功能,特别适合物联网项目。ESP32同样具有多个UART端口,如TX/RX接口,用于与其他设备,如Arduino、PC或其他ESP32模块进行串口通信,或者作为微控制器之间的通信媒介。 在使用树莓派和ESP32UART通信时,通常的步骤包括配置端口参数(波特率、数据位数等)、编写发送和接收代码,以及可能的软件驱动设置。比如,如果你想要让树莓派和ESP32通过UART交换信息,你可能会: 1. 配置树莓派的GPIO UART设置,并启用相应的服务(如python的RPi.GPIO或picotcp)。 2. 使用Python的`serial`库在树莓派上创建串口连接。 3. 在ESP32上,通过SDK(如Micropython)配置串口并编写发送和接收函数。 4. 确保数据格式匹配,例如,是否需要添加校验和或者帧头。 相关问题: 1. ESP32UART接口如何配置? 2. 如何在树莓派上使用Python的`serial`库连接ESP32? 3. 什么是UART通信中的波特率和数据位数?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值