文本颜色
背景颜色
xxxxxxxxxx
## ESP32——UART学习笔记
#### 文章目录
* [ESP32——UART学习笔记](#ESP32UART_2)
* [本篇目的](#_5)
* * [适用范围](#_11)
* [开发环境](#_15)
* [前置知识](#_29)
* [串口](#_69)
* * [感性认识](#_71)
* [定义](#_94)
* [物理接口](#_102)
* [如何连接](#_151)
* [UART协议](#UART_181)
* [ESP32的串口](#ESP32_193)
* * [数量](#_195)
* [参数](#_201)
* [特殊串口](#_210)
* [ESP32上的串口实验](#ESP32_237)
* * [开始实验前的准备](#_254)
* [uart_echo](#uart_echo_269)
* * [例程说明](#_275)
* [运行它](#_283)
* [它是怎么运行的?](#_419)
* * [1. 先读README.md](#1_READMEmd_423)
* [2. 阅读源码](#2__429)
* * [uart_driver_install](#uart_driver_install_465)
* [uart_param_config](#uart_param_config_539)
* [uart_set_pin](#uart_set_pin_551)
* [while循环](#while_584)
* [ESP_LOGI](#ESP_LOGI_652)
* [通过逻辑分析仪解释UART协议](#UART_680)
* [3. 试着修改参数](#3__789)
## 本篇目的
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)](https://github.com/espressif/esp-idf/releases/tag/v4.4.3))
**例程路径**: [esp-idf/examples/peripherals/uart at v4.4.3 · espressif/esp-idf (github.com)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart)
**操作系统**: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)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html)) *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`,如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8021774a9a32226a9a85e383846127ee.png#pic_center)
在24行添加:
> >
> ,
>
> • “compileCommands”: “${workspaceFolder}/build/compile_commands.json”
逗号不能少,像这样:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d6d8e8346924229f2fd2d7c2e9c27397.png#pic_center)
这样你就可以跳转到本工程的其他组件的头文件和源文件了,当然要在你设置了`target`或者编译了之后生成了`build`文件夹之后才会起作用。
这样你的头文件就不会有波浪线了,函数也能正确跳转:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e232f57107f6253c99f4a9b77a035b46.png#pic_center)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/874aab98c1e08b89003218145f29c1c1.png#pic_center)
## 串口
### 感性认识
* 串口是什么?
感性认识:串行接口(**Serial port**)简称串口,是一种**通信方式和通信接口**,它可以让计算机或其他设备与其他设备通过串行数据线进行通信。 串口是一种常用的通信接口,可以在计算机和外部设备之间进行数据传输。 例如,串口可以用来连接打印机、键盘、游戏控制器等设备。
通常,电脑与ESP32之间最常用的通信方式就是一种称为UART的串口,并且只要两根数据线(发送TxD、接收RxD)和一根地线即可双向通信。
![ESP32-S3 应用程序开发](https://i-blog.csdnimg.cn/blog_migrate/4ecb6523b3860a4a136c78ff497bddf2.png)
*图片来源:[ESP32-S3 应用程序开发](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/_images/what-you-need.png)*
> >
> [串行接口 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/%E4%B8%B2%E8%A1%8C%E7%AB%AF%E5%8F%A3)
* 串口的作用?
ESP32通过UART串口与电脑连接时,可以用来**输出日志、输出调试信息、传输数据和调试**,也可以通过[串口控制台例程](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/system/console),像Linux命令行一样控制ESP32。
使用串口作为通信接口的外设中,最常见的就是GPS模块,比如HT1818Z3G5L、Air551G等,ESP32可以通过串口来读取GPS数据或者配置GPS模块。下图是GPS模块HT1818Z3G5L。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/f5282841958e43296693ede9210d9756.png#pic_center)
### 定义
串口与并口(并行接口,Parallel Port)相对应,串口实际上并非特指某一种具体的接口(比如UART、SPI、USB等等),**串行式逐位传输数据的接口**都可以称为**串口**,但是习惯上串口一般指的是以**通用异步收发传输器**(Universal Asynchronous Receiver/Transmitter,**UART**)为控制器的串口。UART的参考资料如下:
> >
> [UART - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/zh-cn/UART)
本文的串口如无特别说明均指的是UART。
### 物理接口
如果PC想通过串口与ESP32通信,则需要正确地连接串口线,所以首先需要了解一下串口的物理接口。
串口的物理接口通常指以下两种:
1. 单片机上直接引出的,使用TTL电平作为逻辑电平标准的,复用作为串口的引脚。对于ESP32-S3-DevKitC-1来说如下所示,**这个只是默认的UART0的串口引脚**,对于ESP32-S3来说可以配置任意引脚为串口引脚。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/89a8c51e96d1edbd2b38821558221a00.jpeg#pic_center)
1. 由美国[电子工业联盟](https://zh.wikipedia.org/wiki/%E7%94%B5%E5%AD%90%E5%B7%A5%E4%B8%9A%E8%81%94%E7%9B%9F)(EIA)制定的串行数据通信的接口标准RS232,它与上一种物理接口最大的不同有两点:
1. 一是**接口不同**,它使用专门的9针的RS232接口(还有25针的RS232接口,但极少使用),如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e21d96ab2e12742ddcb3b6cab99f4527.jpeg#pic_center)
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以内的串口通信。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/decde18632f7bca93f8e3c26f6f89567.jpeg#pic_center)
在短距离通信(通常<30cm)时,实际上可以不使用RS232的接口标准,直接使用TTL电平进行串口通信。如下所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1f564f62d279f2896e988ba6498c1636.jpeg#pic_center)
下图为ESP32-S3-DevKitC-1板载UART转USB芯片CP2102。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ebf4b612eae048a69dc7b9fb742f1b4f.jpeg#pic_center)
### 如何连接
连接示意图如下,需要特别注意的是,在连接串口时,**两个设备的TxD和RxD是要交叉连接的**,并且TxD只作串行信号输出引脚,单向输出;RxD只作串行信号输入引脚,单向输入。TxD和RxD都是单向的。下图中,ESP32的TxD连接到了CP2102的RxD,ESP32的RxD连接到了CP2102的TxD。当然,完全可以将ESP32的TxD连接到ESP32的RxD自发自收。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/af3be9f31c797954c0e900897b3a705e.jpeg#pic_center)
由于TxD和RxD是独立的两根线(两个独立的数据通道),完全可以同时发送和接收,这种允许二台设备间**同时**进行**双向**数据传输的系统称为**全双工**(**full-duplex**)通信系统。与全双工相对,**半双工**(**half-duplex**)通信系统只允许二台设备间**分时双向**数据传输。由于ESP32不支持半双工串口,在此不介绍,如果想要了解半双工串口可以参考以下链接:
> >
> [stm32 USART串口半双工功能测试_云端FFF的博客-CSDN博客_stm32半双工串口](https://blog.csdn.net/wxc971231/article/details/102791734)
>
> 如果想将全双工串口转为半双工串口,可以参考:[如何把双线串口转单线串口? WhyCan Forum(哇酷开发者社区)](https://whycan.com/t_4839.html)。
>
> > > >
> > 全双工串口转为半双工串口看似有点脱裤子放屁,但是将串口模块的全双工串口转为半双工串口时很有用,特别是在ESP32的IO资源很紧张时。
> >
> > 对于ESP32来说,大部分外设都可以自行指定GPIO,因此可以用一个IO分时复用为*仅接收的UART RxD引脚*或者*仅发送的UART TxD引脚*(即**单工**模式)就可以节省多一个IO。
> >
> > 比如,将GPS模块的串口转为半双工,那么与ESP32连接时可以只连接一个引脚,并且ESP32初始化UART为接收模式即可,需要配置GPS模块时再将那个引脚配置为发送模式。
> >
> > ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/7a468b48ab2d36ec55bc9f352965bb62.jpeg#pic_center)
下图为原理图中的USB转串口部分,在R7和R9附近可以看到交叉连接的串口引脚。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4b02d7bcdc358ee507a2e86da5a36504.jpeg#pic_center)
> >
> ESP32-S3-DevKitC-1的原理图:[sch_esp32-s3-devkitc-1_v1_20210 (espressif.com)](https://dl.espressif.com/dl/SCH_ESP32-S3-DEVKITC-1_V1_20210312C.pdf)
需要注意的是,ESP32使用3.3V电源供电,3.3V在TTL的逻辑电平标准兼容范围之内的,但是ESP32**不兼容5V电压**,如果你使用的USB转UART是输出的TTL电平是5V的,你需要加上电平转换模块。
### UART协议
UART协议对于用户来说并非必须知晓,开发者在使用UART通信时,更多情况下只会更改**串口波特率**,其余参数均默认,因此将UART协议放在之后的实验中,通过逻辑分析仪讲述。
> >
> [UART - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/UART)
**串口波特率**可以不严谨地理解为每秒传输多少个比特(bit)(比特率),比如波特率115200表示每秒传输115200 bit。实际上波特率并不等同于比特率,但是习惯上称呼为波特率。
> >
> [波特率 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/%E6%B3%A2%E7%89%B9%E7%8E%87)
>
> [比特率 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/%E6%AF%94%E7%89%B9%E7%8E%87)
## ESP32的串口
### 数量
通常一个ESP32有多个UART,不同系列的ESP32(如S系列、C系列)拥有的串口数量也不尽相同,如何知道手上的开发板的串口数量?可以去[ESP产品选型器](https://products.espressif.com/#/product-selector?language=zh&names=)中查看。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9f8c5404c02fde0628a0656c9ebbc820.png#pic_center)
### 参数
通常来说,对于串口,我们最关注的参数差不多就是**通信速率**了,从ESP32-S3的[技术规格书](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_cn.pdf)里看到它支持的最高速率可达到**5Mbps**(如果不算停止位等),这意味着最高速率时,ESP32每秒钟可以传输五百万位(5,000,000 bit/s)数据,也就是六十二万五千字节(625,000 Byte/s = **625 kByte/s**),这个速率对于ESP32来说是对外通信的比较快的通信方式,当然在没有丢包重传等保障措施的情况下,为了保证通信质量不会用这么高的速度,**最常用的通信速率是115200**,下载速率常用460800、921600、1000000、2000000。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/bce1906cd5a74cab2bf9eab935a1a84b.jpeg#pic_center)
其余参数将在程序解读中介绍。
### 特殊串口
通常,UART0是作为日志输出的串口,UART0引脚对于ESP32-S3-DevKitC-1来说就是丝印标注TX和RX的引脚。如果没特殊需求不需要更改。
对于ESP32-S3来说,USB接口可以虚拟出一个串口,但是使用这个串口运行控制台例程时通常要改程序,而且这个串口会在ESP32开机之后一段时间才会被虚拟出来,因此开机信息可能会丢失开通一部分。
在menucinfig的ESP System Settings中,有这么两个选项:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/805c082bee1baf80e050918cfc5be6e7.png#pic_center)
第一个选项为`控制台的输出通道`,默认为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)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart/uart_echo)
2. [esp-idf/examples/peripherals/uart/uart_async_rxtxtasks v4.4.3 ·乐鑫/ESP-IDF (github.com)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart/uart_async_rxtxtasks)
3. [esp-idf/examples/peripherals/uart/uart_events v4.4.3 ·乐鑫/ESP-IDF (github.com)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart/uart_events)
4. [esp-idf/examples/peripherals/uart/uart_repl v4.4.3 ·乐鑫/ESP-IDF (github.com)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart/uart_repl)
如何使用例程?在ESP-IDF编程指南中的`第五步:开始创建工程`中有详细的步骤,在此不再赘述。
> >
> [快速入门 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/get-started/index.html#get-started-start-project)
>
> 如何配置VSCode上的ESP-IDF插件:[ESP32-IDF环境搭建之vscode环境](https://www.bilibili.com/video/BV15e41137p9)
### 开始实验前的准备
你需要准备以下材料:
1. ESP32开发板,这里是ESP32-S3-DevKitC-1 *1
2. microUSB数据线(根据你的开发板选择) *2
3. 如果你的ESP32开发板没有板载USB转串口,你需要准备一个USB转串口模块,并按以下方式连接:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d98cb6660761c3d37ff8ce801a266f80.png#pic_center)
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`命令查看路径下的内容:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d1615d4a6793236ec3764b319807f10e.jpeg#pic_center)
1. 获取你的`esp-idf`环境变量,我设置为:`get_idf4.4.3`
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1b0c1d93bf23355474d58cc6ad65ab93.jpeg#pic_center)
1. 设置目标,我使用的ESP32-S3开发板,因此设为`esp32s3`:`idf.py set-target esp32s3`
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6559af52ebd78ff438e2b5c0d9d5f798.png#pic_center)
1. (可省略)打开menuconfig:`idf.py menuconfig`
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/626809ee63d142d246c7dce19fdd61ad.png#pic_center)
打开倒数第4个选项`Echo Example Configuration`就可以查看工程中有什么选项可以设置:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c9fc9811a277325ea1e2b73a91080dc0.png#pic_center)
在这里并**不需要**对这些配置进行什么更改,因为在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`保存。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/56c40ce2233ee1dd4b15ac68ad04ea03.png#pic_center)
> >
> 如何查看例程有什么可以设置的选项呢?
>
> 一般查看项目的`Kconfig`文件就知道了,在这里是:`main/Kconfig.projbuild`.
>
> 第一行`menu`后面跟着的就是菜单名称。
>
> ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1dc909788d56feb89ba11586e7137582.png#pic_center)
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 */*
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1df07d26053d15f97de783a9de71f1d9.png#pic_center)
这里其实就是将menuconfig的设置用自己的设置替换了而已,注意34行,我们使用默认的串口UART0,UART0的引脚默认就是原理图上的引脚,因此不需要改变。
> >
> UART_PIN_NO_CHANGE是该引脚不需要改变的意思,它在`你的ESP-IDF路径/components/driver/include/driver/uart.h`中有定义。
>
> ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/bd49cbb8fd3d4056a2f0ed09ee2e1d98.png#pic_center)
> >
> > > >
> > [Universal Asynchronous Receiver/Transmitter (UART) - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/api-reference/peripherals/uart.html?#setting-communication-pins)
1. 编译`idf.py build`:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/24cde1254804816f011d289d6faf494a.png#pic_center)
正在编译中:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b2141f6eae85da6555475caebe78d86d.png#pic_center)
编译完成!
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9809fbbabb8757f059c53d9640dd66f6.png#pic_center)
1. 烧录`idf.py -p /dev/ttyUSB0 flash`:
其中的`/dev/ttyUSB0`需要根据你的串口修改。
> >
> [与 ESP32-S3 创建串口连接 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/get-started/establish-serial-connection.html)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0d6f515c2b48aa7a1b7581619d5fabb9.png#pic_center)
烧录完成!
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cbfbf3632eff94cd81aaaaf4ca2f8416.png#pic_center)
2. 运行串口监视器,看看效果`idf.py -p /dev/ttyUSB0 monitor`:
其中的`/dev/ttyUSB0`需要根据你的串口修改。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a00fa08b3115b77ac667f6043d9c3db1.png#pic_center)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/23ce0f336ba9fd34dd6f1df9544200ba.png#pic_center)
看起来没什么大不了的,试着输入一些字符:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/eae734f15a463938381df09c9cca7f69.png#pic_center)
可以看到,灰色的是“我输入的字符”,后面绿色的字符是*ESP32接收到了我发送的字符,并通过`ESP_LOGI`函数发回来的信息*,`Recv str:`后面则跟着“我输入的字符”。这个“我输入的字符”其实并不是我真正输入的,我输入的字符其实是看不见的,只是ESP32原封不动输出了一遍了而已。接下来解释程序时会解释这是什么意思。
3. 退出串口监视器:直接按`ctrl+l`即可退出。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/7dd3b68f0098db753536f0b3ecdbb195.png#pic_center)
> >
> 如果你对串口监视器不熟悉,请参考:[IDF 监视器 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/api-guides/tools/idf-monitor.html)
4. 至此,你已经成功运行了这个例程!
#### 它是怎么运行的?
接下来,我将带你了解他是如何运行的。同时,根据我的步骤,你也可以了解如何去学习esp-idf中的例程。
##### 1. 先读README.md
一个好的README.md会交代例程的基本信息、作用、如何使用等信息,因此,阅读README.md是十分重要的。如果英语不太好也没关系,可以复制其中的内容到翻译软件,或者直接打开该例程的网页[esp-idf/examples/peripherals/uart/uart_echo 在 v4.4.3 ·乐鑫/ESP-IDF (github.com)](https://github.com/espressif/esp-idf/tree/v4.4.3/examples/peripherals/uart/uart_echo),右键翻译该网页即可。
考虑到有些人只有一个开发板和一个数据线,因此没有完全按照例程中README的需求使用UART2和USB转串口模块,经过`运行它`中的步骤,只用板载的USB转串口和UART0即可。
##### 2. 阅读源码
打开`main/uart_echo_example_main.c`:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e185a9e20796bc8c3764a4108ba9b3a4.png#pic_center)
esp-idf中,程序的入口是`app_main`,比较类似于STM32中的`main`。因此需要先看`app_main`。
在`app_main`中,可以看到他创建了一个任务,来运行复读机的程序,除此之外没有别的操作。如果你原意,完全可以将`echo_task`的内容全部放到`app_main`中。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/76416dca4ce6a83e1a91a33a58929762.png#pic_center)
> >
> 如果你对freeRTOS创建任务不太熟悉,可以参考:[xTaskCreate() - 追风*逐浪 - 博客园 (cnblogs.com)](https://www.cnblogs.com/zhangkai163/p/5058780.html)
`echo_task`在上文定义了,跳转到`echo_task`:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ff23f54526d832c55b486f1bc74ffe5e.png#pic_center)
先忽略`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);`是用来测试效果的,正常情况不需要加上去。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6186cbf3c6dcd9024342d1087b0d1ef4.png#pic_center)
###### uart_driver_install
还是顾名思义,`uart_driver_install`,也就是安装串口驱动,这种类似Linux开发的风格也是ESP-IDF的特色之一。如果你配置好了VSCode,你也可以直接参看`uart_driver_install`的声明:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/07f3d15f4444148f98ba34bb06c3f1de.png#pic_center)
声明中已经讲的很清楚了,不懂的地方可以复制到翻译器看一下,在此给出`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)](https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.3/esp32s3/api-guides/memory-types.html#iram-ram)
如何查找这个选项呢?打开menuconfig,根据menuconfig下方的提示,按`/`即可查找符号:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e906525e600044ae2bb70c9081a07961.png#pic_center)
输入`UART_ISR_IN_IRAM`(注意不要输入`CONFIG_`):
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/494574c66746f3d6f4986834d410157b.png#pic_center)
回车即可查看这个选择的位置:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3631d4e3c1f18d3980722d24fdf9a636.png#pic_center)
在英文输入法下,按`shitf+/`,其实就是`?`,即可获得帮助信息:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/2e87f5ee0e8b08e21a4e2ebb19dd64a8.png#pic_center)
> >
> 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 功能`找到答案:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b87c72aa405b42d7baf4adf5a22cc623.png#pic_center)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9775e7b058f22216f398f632af370f8d.png#pic_center)
U0代表UART0,也就是:
串口引脚GPIO号U0TXD43U0RXD44U0RTS15U0CTS16
> >
> [esp32-s3_datasheet_cn.pdf (espressif.com)](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_cn.pdf)
###### while循环
在whlie循环之前还申请了一段空间用于存放数据:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d4f57a3c87a035e01749e118b7e9e3ff.png#pic_center)
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的信息。这个函数的参数的含义如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a7628e27596e0b618f1216b994bccc23.png#pic_center)
```
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`函数把刚刚读取到的数据写回去:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e6f93399da558997987b295a20ef6c40.png#pic_center)
`uart_write_bytes`的声明如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/738768d3abd7fa14e5f2969498178a7a.png#pic_center)
```
uart_write_bytes(
ECHO_UART_PORT_NUM, /* 串口端口号,这里是串口0 */
(const char *)data, /* 指向待发送的缓冲区的指针 */
len /* 数据长度 */
);
```
这里的`uart_write_bytes`的含义是**从数据缓冲区`data`向UART0写长度为`len`字节数据**,由于之前设置了Tx环形缓冲区的大小为0,所以它会一直阻塞到这里发送数据,直到数据发送完毕为止。
在实验中,也就是我们按下按键后,灰色的字节就是通过`uart_write_bytes`发送的数据,这也是“我输入的字符”加双引号的原因,因为这个是ESP32发回来的字符。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d31ba40fcb225e8a80ebfe32bd03c171.png#pic_center)
`if(len)`的作用是,如果接收到了数据(len不等于0),那么将数据的最后一个字符设为字符串结尾`data[len] = '\0';`:(因为没有清空字符串。如果不加,上一次发送了`hello`,这一次发送了`123`,那么这一次就会输出`123lo`,即包含上一次的字符,这不是我们想要的结果)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8dc7395bceb152d5261c0db069e240ce.png#pic_center)
###### ESP_LOGI
`ESP_LOGI`是一个非常重要也是非常常用的函数,它的作用是输出信息级别的日志,它在`esp_log.h`中声明,如何使用它呢?首先包含头文件`#include "esp_log.h"`,然后定义一个`TAG`,用于说明这个日志是在哪里输出的:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/452d76698e88a26567d9109ff10cc7cb.png#pic_center)
然后像`printf`一样使用就行了,在这里是:
```
ESP_LOGI(TAG, "Recv str: %s", (char *)data);
```
它输出的效果是这样的:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3323e4ca574676f66ad3f1e689636504.png#pic_center)
`I`表示这个是信息(*Information*)级别的日志,括号内是开机到输出这个信息的毫秒数,`UART TEST`就是我们刚刚定义的`TAG`,冒号后的内容就是像`printf`一样使用时应该输出的结果。绿色的原因是它增加了颜色的转义字符。
使用`ESP_LOGI`时不需要初始化,这是因为在启动阶段系统已经初始化过了。虽然默认是通过UART0输出,但是不代表你使用UART0时不用初始化UART0(手动狗头)。
在这里它的含义是,**将接收到的内容通过日志`ESP_LOGI`输出**。
> >
> 如果想要了解更多日志库的内容,可以参考:[ESP32学习笔记(6)——Log日志库使用 - 简书 (jianshu.com)](https://www.jianshu.com/p/62f57b7525ab)
至此,除了UART协议和`uart_config_t uart_config`没有说明,你已经大概了解了如何使用这个例程。
###### 通过逻辑分析仪解释UART协议
之所以要将UART协议放到最后再说,是因为**大部分情况下都不需要使用上UART协议的具体内容,就把UART当黑盒用,知道输入输出即可**。虽然UART协议用起来和学起来是比较简单和容易的,但是如果换稍微复杂一点的协议,比如SPI只看协议时序图就比较难理解了。而且结合具体波形更能理解使用的过程中发生了什么,怎么发生的。废话少说,上逻辑分析仪:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/f78f58cb1fd08d9da9bbabed15cad407.png#pic_center)
烧录程序后,打开逻辑分析仪,输入一个字母`Z`查看波形;
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/66702b3c77f40ab0b037fa7ff44c0046.png#pic_center)
波形长这样,接下来简要介绍一下各个部分的含义:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/31a246e4ad62f3609ce633dc4360a5cf.png#pic_center)
如何理解这个波形呢?我们需要从UART时序图入手,下图为UART数据帧的比较完整的时序图:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/fa7bd279909dce8d5c070be807c791a2.png#pic_center)
从UART数据帧中,可以看到它由以下部分组成:
1. 起始位:如果没传输数据,那么UART数据线会保持高电平状态,如果数据线从高电平被拉低到了低电平,这意味这需要传输数据了,低电平维持的时间为1个时钟周期(1 bit)。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a0b8adb108d437e6e9b7db35aebcece6.png#pic_center)
2. 数据位:也就是UART数据帧中实际的数据位,数据位的长度可以是5到8位,默认为8位。这里也是8位数据位。通常,数据是低位优先发送的,在这里,`Z`的ASCII码值是0x5A,换成二进制是0b01011010,由于发送时是低位在前发送,所以发送的顺序是`01011010`,好像没什么变化。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/438f3112520967652c686a42c07d0b7a.png#pic_center)
换`W`效果会更明显,`W`的ASCII码值是0x57,换成二进制是0b01010111,发送的顺序是`11101010`:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/f06d832ef70b18438fd077511f778555.png#pic_center)
1. 奇偶校验位:
默认的设置是不使用就校验位的,因此此处没有体现出来。
当UART控制器启用了奇偶校验,接收到数据位时会对数据帧中的`1`进行计数,如果接收到的是偶数的数据位,那么计数结果为0;如果是奇数,那么计数结果为1。
在数据没有出错的情况下,奇偶校验位结果等同与计数结果;反之,则说明数据位的数据发生了改变。
当然,奇偶校验作为一种十分简单的校验方法并不适用于所有情况,比如如果数据位中同时有两个相同电平的数据位发生了改变,奇偶校验是检测不出来的。
2. 停止位:表示数据位传输结束,通常会保持1到2个bit的时间。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5e0511bf0a5f6aa818da564d0a273689.png#pic_center)
了解了UART帧的结构,让我们看看UART帧和我们上文略过的串口配置结构体`uart_config_t uart_config`的关系,该结构体的声明如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e755b575ce31a13bb0b811639efe43f1.png#pic_center)
```
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;
```
下图显示了可以填进结构体参数的枚举。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e2bf9af28ee0597864a2c72a7f47ede5.png#pic_center)
在我们的例程中是这样配置的:
```
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,
};
```
与逻辑分析仪的波形对比:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/71ba07e7d8ac10859756714926c7ddda.png#pic_center)
可以看到他们之间的对应关系。
自此,uart_echo例程已经基本梳理了一遍了。
> >
> [UART:了解通用异步接收器/发送器的硬件通信协议 | 亚德诺半导体 (analog.com)](https://www.analog.com/cn/analog-dialogue/articles/uart-a-hardware-communication-protocol.html)
#### 3. 试着修改参数
你已经大概了解了UART协议,了解了uart_config_t中的参数和UART波形的关系,为了加深理解,你可以试着修改参数,比如:
1. 将8位数据位改为7位UART_DATA_7_BITS,再用其他串口上位机修改对应的参数看看能否正常通信?
2. 修改ESP32的程序的奇偶校验为奇校验,串口上位机设置为偶校验,看看能否接收到数据?
3. 修改波特率,看看最高能到多快还能正常通信?
4. 修改停止位,比如改成1.5位停止位,看看发送单个字符和连续发送两个字符时会发生什么?
5. 20ms的超时时间对人来说有点快,试着修改超时时间,设置多大才能连续输入字符?
6. 灰色字符如何换行,不与绿色的LOG信息在同一行?
7. ……
ESP32——UART学习笔记
文章目录
* [3. 试着修改参数](#3__789)
本篇目的
快速入门ESP32的UART串口。
简要介绍UART串口概念,以及ESP32如何通过UART串口与PC通信。
如何在ESP-IDF开发环境下对UART串口编程。
适用范围
开发环境
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
前置知识
你已经搭建完了ESP-IDF开发环境。
你会复制idf环境中的examples里的例程到你的工作目录,并且能通过命令行成功编译工程。
(非必须)你配置好了VSCode的ESP-IDF插件,并且能通过插件编译工程。
1. (非必须,但是能改善体验)通过ESP-IDF插件正确配置工程头文件:
通过VSCode打开工程后,按`ctrl+shitf+p`打开VSCode命令,输入:
> >
> ESP-IDF: Add vscode configuration folder
然后插件会在工程目录下生成一个名称为`.vscode`的文件夹,打开`.vscode/c_cpp_properties.json`,如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8021774a9a32226a9a85e383846127ee.png#pic_center)
在24行添加:
> >
> ,
>
> “compileCommands”: “${workspaceFolder}/build/compile_commands.json”
逗号不能少,像这样:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d6d8e8346924229f2fd2d7c2e9c27397.png#pic_center)
这样你就可以跳转到本工程的其他组件的头文件和源文件了,当然要在你设置了`target`或者编译了之后生成了`build`文件夹之后才会起作用。
这样你的头文件就不会有波浪线了,函数也能正确跳转:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e232f57107f6253c99f4a9b77a035b46.png#pic_center)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/874aab98c1e08b89003218145f29c1c1.png#pic_center)
串口
感性认识
串口是什么?
感性认识:串行接口(Serial port)简称串口,是一种通信方式和通信接口,它可以让计算机或其他设备与其他设备通过串行数据线进行通信。 串口是一种常用的通信接口,可以在计算机和外部设备之间进行数据传输。 例如,串口可以用来连接打印机、键盘、游戏控制器等设备。
通常,电脑与ESP32之间最常用的通信方式就是一种称为UART的串口,并且只要两根数据线(发送TxD、接收RxD)和一根地线即可双向通信。
图片来源: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通信,则需要正确地连接串口线,所以首先需要了解一下串口的物理接口。
串口的物理接口通常指以下两种:
单片机上直接引出的,使用TTL电平作为逻辑电平标准的,复用作为串口的引脚。对于ESP32-S3-DevKitC-1来说如下所示,这个只是默认的UART0的串口引脚,对于ESP32-S3来说可以配置任意引脚为串口引脚。
由美国电子工业联盟(EIA)制定的串行数据通信的接口标准RS232,它与上一种物理接口最大的不同有两点:
1. 一是**接口不同**,它使用专门的9针的RS232接口(还有25针的RS232接口,但极少使用),如下图所示:
二是电平标准不同,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协议,以及如何对串口编程。
接下来主要讲解以下例程:
esp-idf/examples/peripherals/uart/uart_echo 在 v4.4.3 ·乐鑫/ESP-IDF (github.com)
esp-idf/examples/peripherals/uart/uart_async_rxtxtasks v4.4.3 ·乐鑫/ESP-IDF (github.com)
esp-idf/examples/peripherals/uart/uart_events v4.4.3 ·乐鑫/ESP-IDF (github.com)
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环境
开始实验前的准备
你需要准备以下材料:
ESP32开发板,这里是ESP32-S3-DevKitC-1 *1
microUSB数据线(根据你的开发板选择) *2
如果你的ESP32开发板没有板载USB转串口,你需要准备一个USB转串口模块,并按以下方式连接:
如果你的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上收到的任何数据回传给发送方。
运行它
打开路径,通过
ls
命令查看路径下的内容:
获取你的
esp-idf
环境变量,我设置为:get_idf4.4.3
设置目标,我使用的ESP32-S3开发板,因此设为
esp32s3
:idf.py set-target esp32s3
(可省略)打开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
后面跟着的就是菜单名称。
打开
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)
编译
idf.py build
:
正在编译中:
编译完成!
烧录
idf.py -p /dev/ttyUSB0 flash
:
其中的/dev/ttyUSB0
需要根据你的串口修改。
与 ESP32-S3 创建串口连接 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)
烧录完成!
运行串口监视器,看看效果
idf.py -p /dev/ttyUSB0 monitor
:
其中的/dev/ttyUSB0
需要根据你的串口修改。
看起来没什么大不了的,试着输入一些字符:
可以看到,灰色的是“我输入的字符”,后面绿色的字符是ESP32接收到了我发送的字符,并通过ESP_LOGI
函数发回来的信息,Recv str:
后面则跟着“我输入的字符”。这个“我输入的字符”其实并不是我真正输入的,我输入的字符其实是看不见的,只是ESP32原封不动输出了一遍了而已。接下来解释程序时会解释这是什么意思。
退出串口监视器:直接按
ctrl+l
即可退出。
如果你对串口监视器不熟悉,请参考:IDF 监视器 - ESP32-S3 - — ESP-IDF 编程指南 v4.4.3 文档 (espressif.com)
至此,你已经成功运行了这个例程!
它是怎么运行的?
接下来,我将带你了解他是如何运行的。同时,根据我的步骤,你也可以了解如何去学习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号U0TXD43U0RXD44U0RTS15U0CTS16
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数据帧中,可以看到它由以下部分组成:
起始位:如果没传输数据,那么UART数据线会保持高电平状态,如果数据线从高电平被拉低到了低电平,这意味这需要传输数据了,低电平维持的时间为1个时钟周期(1 bit)。
数据位:也就是UART数据帧中实际的数据位,数据位的长度可以是5到8位,默认为8位。这里也是8位数据位。通常,数据是低位优先发送的,在这里,
Z
的ASCII码值是0x5A,换成二进制是0b01011010,由于发送时是低位在前发送,所以发送的顺序是01011010
,好像没什么变化。
换W
效果会更明显,W
的ASCII码值是0x57,换成二进制是0b01010111,发送的顺序是11101010
:
奇偶校验位:
默认的设置是不使用就校验位的,因此此处没有体现出来。
当UART控制器启用了奇偶校验,接收到数据位时会对数据帧中的1
进行计数,如果接收到的是偶数的数据位,那么计数结果为0;如果是奇数,那么计数结果为1。
在数据没有出错的情况下,奇偶校验位结果等同与计数结果;反之,则说明数据位的数据发生了改变。
当然,奇偶校验作为一种十分简单的校验方法并不适用于所有情况,比如如果数据位中同时有两个相同电平的数据位发生了改变,奇偶校验是检测不出来的。
停止位:表示数据位传输结束,通常会保持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波形的关系,为了加深理解,你可以试着修改参数,比如:
将8位数据位改为7位UART_DATA_7_BITS,再用其他串口上位机修改对应的参数看看能否正常通信?
修改ESP32的程序的奇偶校验为奇校验,串口上位机设置为偶校验,看看能否接收到数据?
修改波特率,看看最高能到多快还能正常通信?
修改停止位,比如改成1.5位停止位,看看发送单个字符和连续发送两个字符时会发生什么?
20ms的超时时间对人来说有点快,试着修改超时时间,设置多大才能连续输入字符?
灰色字符如何换行,不与绿色的LOG信息在同一行?
……