6. 串口通信
6.1 串口通信介绍
串口是指外设和处理器之间通过数据信号线、地线和控制线等,按位进行传输数据的一种通讯方式。尽管传输速度比并行传输低。但串口可以在使用一根线发送数据的同时用另一根线接收数据。 这种通信方式使用的数据线少,在远距离通信中可以节约通信成本。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验位,这些参数在两个通信端口之间必须一致。
6.2 串口通信参数介绍
串口通信参数包括波特率(Baud Rate)、数据位(Data Bits)、校验位(Parity Bits)、停止位(Stop Bits)等。这些参数描述了传输数据的基本规格。例如,波特率定义了数据传输的速率,数据位确定每个数据字节中包含的位数,校验位用于数据的差错检测,停止位表示数据传输结束的标志等。
- 波特率:衡量通信速度的参数,它表示每秒钟传送的 bit 的个数。
- 数据位:衡量通信中实际数据位的参数,表示一个信息包里包含的数据位的个数。
- 停止位:用于表示单个信息包的最后位,典型值为 1、1.5 和 2 位。由于数据是在传输线上传输的,每个设备都有自己的时钟,很有可能在通信过程中出现不同步,停止位不仅仅表示传输的结束,还能提供校正时钟同步的机会。停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率也越慢。
- 奇偶检验位:表示一种简单的检查错误的方式。 关于更为详细的介绍请搜索百度。
6.3 串口工作模式
串口工作模式分为三种:单工、全双工模式和半双工模式。
- 单工:在通信的任意时刻,信息只能由 A 传到 B,或B传到A;
- 半双工:在通信的任意时刻,信息即可由 A 传到 B,又能由 B 传到 A,但同时只能有一个方向上的传输存在;
- 全双工:在通信的任意时刻,通信线路上存在 A 到 B 和 B 到 A 的双向信号传输;
6.4 串口通信协议
串口通信协议定义了在串口上进行数据交换的规则和格式。常见的串口通信协议包括ASCII协议、Modbus协议、RS-232协议等。协议规定了数据的帧结构、数据格式、校验方式等,确保发送和接收双方按照相同的规则进行数据交换,从而实现数据的正确传输和解析。
串口通信是一位一位地传输,每传输一个字符总是以起始位开始,以停止位结束,字符之间没有固定的时间间隔要求。每一个字符的前面都有一位起始位(低电平),后面由 7 位数据位组成,接着是一位校验位,最后是停止位。停止位后面是不定长的空闲位,停止位和空闲位都规定为高电平。
6.5 串口通信的作用及优势
串口是计算机和外部设备之间最常见的通信接口之一,具有重要的作用和广泛的应用。在计算机领域,串口的重要性体现在以下几个方面:
- 数据传输:串口是一种常用的数据传输接口。通过串口,计算机可以与各种外部设备进行数据交换和通信。无论是传感器、执行器、显示器、打印机还是其他外部设备,串口通信都可以实现数据的传输和控制。
- 远程控制和监控:串口通信被广泛应用于远程控制和监控领域。通过串口,计算机可以远程控制设备的动作,并实时监测设备的状态和数据信息。这在工业控制、自动化系统、远程监控等场景中具有重要的作用。
- 调试和故障排查:串口通信是调试和故障排查的重要工具。通过串口,计算机可以与嵌入式系统、单片机等进行通信,实时监控和调试程序,输出调试信息,进行错误定位和排查,并对系统进行状态监测和故障诊断。
- 硬件连接:串口可以作为计算机与各种外部设备之间的连接桥梁。通过串口,可以连接和控制各类外部设备,如传感器、执行器、外围设备等。串口能够提供稳定的数据传输和双向通信功能。
- 通信协议:串口通信协议是计算机与外部设备之间数据传输的规范和约定。通过定义不同的协议,可以实现不同设备之间的数据交互和通信。常见的串口通信协议有UART、RS-232、RS-485等。
总之,串口对于计算机和外部设备之间的通信具有重要的作用。它是数据传输、远程控制和监控、调试和故障排查的关键工具,是计算机与外部设备连接和通信的桥梁。通过串口通信,可以实现与各种外部设备的数据交互,提高系统的功能和性能。具体到串口通信的作用和优势,可以总结如下: - 数据传输:串口通信可以实现双向数据的可靠传输,包括发送和接收各种类型的数据。
- 远程控制和监控:通过串口通信,可以实现远程控制设备的动作,并实时监测设备的状态和数据信息。
- 调试和故障排查:串口通信是调试和故障排查的重要工具,可以实时监控和调试程序,输出调试信息,进行错误定位和排查。
- 灵活性和实时性:串口通信具有较高的灵活性和实时性,可以根据需求调整波特率和参数,并及时处理数据和响应外部事件。
- 成本效益:串口通信使用简单、成本低廉的硬件,并且广泛应用于各个领域,是一种经济实用的通信方式。
总之,串口通信在数据传输、远程控制与监控、调试和故障排查等方面具有重要的作用和诸多优势,是实现设备间数据交互和系统功能的重要手段。
6.6 串口通信原理图
ESP32S3有三个串口,即 UART0、UART1、UART2。其中,开发板的串口0已经用于自动下载与调试部分,故在实际应用中不建议使用串口0与其他设备通信。
我们可以使用 UART1(串口1)、串口2(UART2) 与外部串口设备进行通信。除串口0外,串口引脚与其他外设一样,可以使用任意的GPIO作为通信引脚。
6.7 串口通信驱动流程
6.7.1 导入头文件
include "driver/uart.h"
在源文件中添加以上头文件,以访问 ESP-IDF 提供的串口相关功能。这个头文件提供了相应的函数和数据类型,用于配置和控制串口通信。
6.7.2 初始化串口
使用 uart_config_t
结构体进行 UART 的配置。可以设置波特率、数据位数、停止位、奇偶校验等参数。注意选择正确的 UART 号(如 UART_NUM_0、UART_NUM_1 等)和 GPIO 引脚。
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_1, &uart_config); // 将以上参数配置到 UART
2
3
4
5
6
7
8
9
10
关于 uart_config_t
结构体中,各个参数的说明:
.baud_rate
:串口的波特率(数据传输速率),表示每秒传输的位数。常见的波特率有 9600、115200 等。注意,在使用高速波特率时,需要确保硬件的支持。.data_bits
:数据位的数量,也就是每个字节中的位数。可选值为:UART_DATA_5_BITS
、UART_DATA_6_BITS
、UART_DATA_7_BITS
、UART_DATA_8_BITS
。.parity
:奇偶校验位的设置。可选值为:UART_PARITY_DISABLE
(禁用校验位)、UART_PARITY_EVEN
(偶校验位)、UART_PARITY_ODD
(奇校验位)。.stop_bits
:停止位的数量。可选值为:UART_STOP_BITS_1
(1 个停止位)、UART_STOP_BITS_1_5
(1.5 个停止位,仅适用于 5 数据位)和UART_STOP_BITS_2
(2 个停止位)。.flow_ctrl
:硬件流控制的设置。可选值为:UART_HW_FLOWCTRL_DISABLE
(禁用流控制)、UART_HW_FLOWCTRL_RTS
(只启用 RTS 信号流控制)和UART_HW_FLOWCTRL_CTS
(只启用 CTS 信号流控制)。 参数设置完成之后,通过uart_param_config()
函数将参数设置到对应的串口中。示例:
uart_param_config(UART_NUM_1, &uart_config); // 将以上参数配置到 UART
uart_param_config()
是 ESP32 中关于 UART 参数配置的函数,用于设置指定 UART 端口的通信参数。 函数原型如下:
esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config)
参数说明:
uart_num:UART
端口号,例如UART_NUM_1
或UART_NUM_2
。uart_config
:一个指向uart_config_t
结构体的指针,包含了 UART 的参数配置信息。
6.7.3 绑定串口引脚
我们设置好了串口的参数,但是串口的通信引脚还要设置,以下是绑定串口引脚示例:
//使用到的引脚
int tx_pin = 9;
int rx_pin = 10;
//绑定引脚 TX=tx_pin RX=rx_pin RTS=不使用 CTS=不使用
uart_set_pin(UART_NUM_1, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
2
3
4
5
uart_set_pin()
函数是用于设置 UART 引脚连接的函数。它用于将指定的 UART 端口与指定的引脚进行连接。具体的使用方法和参数解释如下:
void uart_set_pin(uart_port_t uart_num, int8_t tx_io_num, int8_t rx_io_num, int8_t rts_io_num, int8_t cts_io_num
uart_num
:UART 端口号,可选值为UART_NUM_0
、UART_NUM_1
、UART_NUM_2
。tx_io_num
:UART 发送引脚的 GPIO 号。如果不需要发送功能,可以将此参数设置为-1
。rx_io_num
:UART 接收引脚的 GPIO 号。如果不需要接收功能,可以将此参数设置为-1
。rts_io_num
:USART 请求发送 (RTS) 引脚的 GPIO 号。如果不需要 RTS 功能,可以将此参数设置为-1
,示例中的UART_PIN_NO_CHANGE
=-1
。cts_io_num
:USART 清除发送 (CTS) 引脚的 GPIO 号。如果不需要 CTS 功能,可以将此参数设置为-1
,示例中的UART_PIN_NO_CHANGE = -1
。 使用该函数可以将 UART 的发送、接收、RTS 和 CTS 引脚与指定的 GPIO 引脚进行连接。
6.7.4 设置工作模式
根据需求,选择 UART 的工作模式。可以使用 uart_set_mode()
函数设置为 UART 模式或 RS485 模式:
uart_set_mode(UART_NUM_1, UART_MODE_RS485_HALF_DUPLEX);
uart_set_mode()
是 ESP-IDF 中的一个函数,用于设置 UART(串口)的工作模式。它可以设置串口为传输模式、波特率产生器模式或某些特殊模式。 函数原型如下:
esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
参数说明:
uart_num
:UART 端口号,可选值为UART_NUM_0
、UART_NUM_1
、UART_NUM_2
。mode
:UART 的工作模式,可选值为UART_MODE_UART
、UART_MODE_RS485_HALF_DUPLEX
、UART_MODE_RS485_FULL_DUPLEX
、UART_MODE_IRDA
。 不同的工作模式会影响 UART 的数据传输方式和硬件控制信号的使用。以下是各种工作模式的说明:UART_MODE_UART
:传输模式,用于基本的串口通信,支持全双工传输。UART_MODE_RS485_HALF_DUPLEX
:RS485 半双工模式,用于串口通信协议中的 RS485 半双工通信。在此模式下,需要单独配置 RS485 的相关参数。UART_MODE_RS485_FULL_DUPLEX
:RS485 全双工模式,用于串口通信协议中的 RS485 全双工通信。在此模式下,需要单独配置 RS485 的相关参数。UART_MODE_IRDA
:红外数据传输模式,用于红外数据通信。 注意,在使用 RS485 相关模式时,需要配置 RS485 的引脚和参数,以及使能 RS485 驱动器的控制。
6.7.5 安装驱动程序
使用 uart_driver_install()
函数安装 UART 驱动程序,并指定接收和发送缓冲区的大小:
uart_driver_install(UART_NUM_1, BUF_SIZE * 2, 0, 0, NULL, 0);
uart_driver_install
是 ESP-IDF 中的一个函数,用于安装 UART(串口)驱动程序,并初始化串口硬件。该函数会分配内存资源并配置串口的工作参数。
函数原型如下:
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags)
参数说明:
uart_num
:UART 端口号,可选值为UART_NUM_0
、UART_NUM_1
、UART_NUM_2
。rx_buffer_size
:接收缓冲区大小,用于存储接收到的数据。x_buffer_size
:发送缓冲区大小,用于存储待发送的数据。queue_size
:UART 驱动程序内部缓冲队列的大小,用于存储待处理的接收和发送数据。uart_queue
:指向用户定义的用于接收数据的队列句柄,在接收数据时,接收到的数据会存储在这个队列中。intr_alloc_flags
:UART 中断分配标志,用于配置中断分配策略。 使用uart_driver_install
函数可以方便地初始化 UART,并且指定相应的缓冲区和队列大小以及其他参数。
6.7.6 发送和接收数据
使用 uart_write_bytes()
函数发送数据,使用 uart_read_bytes()
函数接收数据。 uart_write_bytes
是 ESP-IDF 中的一个函数,用于向 UART(串口)发送数据。该函数将指定的数据写入到 UART 发送缓冲区,并触发数据的发送。
函数原型如下:
esp_err_t uart_write_bytes(uart_port_t uart_num, const char *src, size_t size)
参数说明:
uart_num
:UART 端口号,可选值为UART_NUM_0
、UART_NUM_1
、UART_NUM_2
。src
:指向源数据缓冲区的指针,包含要发送的数据。size
:要发送的数据的字节数。 以下是一个示例代码片段:
// 向 UART 发送数据
uart_write_bytes(UART_NUM_1, "WWW.LCKFB.COM", 14);
2
需要注意的是,使用 uart_write_bytes
函数发送数据时,数据会被复制到 UART 发送缓冲区,函数会立即返回,而不会等待数据完全发送完成。因此,如果需要确保数据完全发送成功,可以使用uart_wait_tx_done
函数进行等待。
在确保 UART 初始化成功,并配置了正确的波特率和其他参数之后,就可以使用 uart_write_bytes
函数将数据发送到 UART 设备上。
串口接收方式
创建 UART 读取任务:创建一个任务,持续读取串口数据并处理。
xTaskCreate(uart_task, "uart_task", 4096, NULL, configMAX_PRIORITIES-1, NULL);
实现 UART 读取任务函数:在任务函数中,使用 uart_read_bytes() 函数读取串口接收缓冲区中的数据。
//串口读取任务
void uart_task(void *arg)
{
uint8_t rx_data[200]={0};
while (1)
{
//接收串口数据收到的数据长度
int rx_bytes = uart_read_bytes(UART_NUM_2, rx_data, 200, 10 / portTICK_PERIOD_MS);
if( rx_bytes > 0 )//数据长度大于0,说明接收到数据
{
rx_data[rx_bytes] = 0;//将串口数据的最后一个设置为0,形成字符串
//通过串口2输出接受到的数据
uart_write_bytes(UART_NUM_2, (const char*)"uart2 received : ", strlen("uart2 received : "));
uart_write_bytes(UART_NUM_2, (const char*)rx_data, strlen((const char*)rx_data));
//UART环缓冲区刷新。丢弃UART RX缓冲区中的所有数据,准备下次接收
uart_flush(UART_NUM_2);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
以上代码就是一个用于串口读取任务的示例代码。通过任务的方式,检测串口接收缓冲器中是否有数据,当有数据时,代码通过 uart_read_bytes()
函数从 UART 端口(UART_NUM_2)中读取数据,并将读取到的数据存储在 rx_data数组中。任务使用了一个无限循环 while (1)
来持续地接收和处理 UART 数据portMAX_DELAY 参数用于指定如果队列没有可用的数据时,任务将一直等待直到接收到数据为止。uart_read_bytes()
是一个用于从 UART 端口读取数据的函数。它是 ESP-IDF SDK 中提供的 UART 库函数之一。该函数具有以下形式:
esp_err_t uart_read_bytes(uart_port_t uart_num, uint8_t *data, size_t length, TickType_t ticks_to_wait)
参数说明如下:
uart_num
:要读取数据的 UART 端口号,如UART_NUM_0
、UART_NUM_1
、UART_NUM_2
等。data
:一个指向接收数据缓冲区的指针,用于存储从 UART 接收到的数据。length
:要读取的数据字节数,即将接收的数据长度。ticks_to_wait
:读取数据的超时时间,以 FreeRTOS 中的节拍数表示。可以使用portMAX_DELAY
表示无限等待,或者使用一个具体的节拍数值。
该函数带有返回参数,当返回-1时,说明接收错误;当返回的值大于等于0时,返回的值是从UART缓冲区中读取的字节数。 需要注意的是,该函数将阻塞当前任务,直到满足以下任一条件:
- 接收到指定数量的数据字节。
- 发生了 UART 错误。
- 超过了指定的超时时间。
还需要注意的是,在调用uart_read_bytes
函数之前,必须先正确初始化 UART 端口,并设置相关的 UART 参数,以便能够正常读取数据。
6.8 硬件连接与准备
本案例使用常用的模块CH340,将开发板和电脑连接,测试串口通信功能。
CH340是一种USB转串口芯片,常用于微型控制器、单片机、Arduino的USB转串口功能,也可用于USB数据收发。使用CH340模块时,需要安装对应的驱动程序,来使得计算机能够识别CH340芯片的串口输出。对于Windows系统,驱动程序通常会自动安装;对于Mac和Linux系统,需要下载安装相应的驱动程序支持才能使用。
文件下载
在下载中心
的百度网盘链接中开发工具章节里面下载CH340驱动
文件-CH340-Windows驱动
CH340模块通常有CH340G和CH340E两种型号,其中CH340G在0°C到70°C的工作温度范围内工作,而CH340E则在-40°C到85°C的温度范围内工作。这两种型号都支持多种波特率和数据位、停止位的选项,以适应不同的串口通信需求。
CH340模块在使用上较为便捷,连接电脑和开发板后后,只需要通过串口发送指令即可实现与单片机的通信。其优点是接口简单、价格低廉、 驱动程序方便使用等,因此在开发过程以及需要与计算机通信的各种项目中被广泛使用。
实物连接如下图:
6.9 串口通信验证
新建两个文件, bsp_uart.c
和 bsp_uart.h
。保存到main/hardware/uart目录下,如果没有该目录请进行创建。
添加头文件及路径。
在 bsp_uart.c
文件中编写如下代码。
#include "bsp_uart.h"
/**
* @函数说明 uart_init_config
* @传入参数 uart_port = 串口号
* 可选参数:UART_NUM_0、UART_NUM_1、UART_NUM_2
* baud_rate = 串口波特率
* tx_pin = 发送引脚
* rx_pin = 接收引脚
* @函数返回 无
*/
void uart_init_config(uart_port_t uart_port, uint32_t baud_rate, int tx_pin, int rx_pin)
{
//定义 串口配置结构体,必须赋初值,否则无法实现
uart_config_t uart_config={0};
uart_config.baud_rate = baud_rate; //配置波特率
uart_config.data_bits = UART_DATA_8_BITS; //配置数据位为8位
uart_config.parity = UART_PARITY_DISABLE; //配置校验位为不需要校验
uart_config.stop_bits = UART_STOP_BITS_1; //配置停止位为 一位
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; //禁用硬件流控制
//将以上参数加载到串口的寄存器
uart_param_config(uart_port, &uart_config);
//绑定引脚 TX=tx_pin RX=rx_pin RTS=不使用 CTS=不使用
uart_set_pin(uart_port, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//安装 串口 驱动程序
uart_driver_install(uart_port, 200, 200, 0, NULL, 0);
}
/**
* @函数说明 串口2接收任务
* @传入参数 无
* @函数返回 无
* @函数说明 请通过xTaskCreate创建该任务
*/
void uart2_rx_task(void)
{
uint8_t rx_data[200]={0};
uint8_t temp[50]={0};
while(1)
{
//接收串口数据收到的数据长度
int rx_bytes = uart_read_bytes(UART_NUM_2, rx_data, 200, 10 / portTICK_PERIOD_MS);
if( rx_bytes > 0 )//数据长度大于0,说明接收到数据
{
rx_data[rx_bytes] = 0;//将串口数据的最后一个设置为0,形成字符串
//输出接收数据的长度
sprintf((const char*)temp,"uart2 string length : %d\r\n", rx_bytes);
uart_write_bytes(UART_NUM_2, (const char*)temp, strlen((const char*)temp));
//通过串口2输出接受到的数据
uart_write_bytes(UART_NUM_2, (const char*)"uart2 received : ", strlen("uart2 received : "));
uart_write_bytes(UART_NUM_2, (const char*)rx_data, strlen((const char*)rx_data));
//UART环缓冲区刷新。丢弃UART RX缓冲区中的所有数据,准备下次接收
uart_flush(UART_NUM_2);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
在 bsp_uart.h
文件中编写如下代码。
#ifndef _BSP_UART_H_
#define _BSP_UART_H_
//导入串口设备库
#include "driver/uart.h"
//导入GPIO设备库
#include "driver/gpio.h"
//导入 strlen 函数支持
#include <string.h>
/**
* @函数说明 uart_init_config
* @传入参数 uart_port = 串口号
* 可选参数:UART_NUM_0、UART_NUM_1、UART_NUM_2
* baud_rate = 串口波特率
* tx_pin = 发送引脚
* rx_pin = 接收引脚
* @函数返回 无
*/
void uart_init_config(uart_port_t uart_port, uint32_t baud_rate, int tx_pin, int rx_pin);
/**
* @函数说明 串口2接收任务
* @传入参数 无
* @函数返回 无
* @函数说明 请通过xTaskCreate创建该任务
*/
void uart2_rx_task(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在 main.c
文件中编写如下代码。
#include <stdio.h>
//导入ESP32的日志输出功能
#include "esp_log.h"
//导入FREERTOS的任务调度
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
//导入串口设备库
#include "driver/uart.h"
//导入GPIO设备库
#include "driver/gpio.h"
#include "bsp_uart.h"
void app_main(void)
{
//初始化串口2 TX=GPIO10 RX=GPIO9
uart_init_config(UART_NUM_2, 115200, 10, 9);
//创建串口2接收任务
xTaskCreate(uart2_rx_task, "uart2_rx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL);
//通过串口2发送字符串 start uart demo
uart_write_bytes(UART_NUM_2, (const char*)"start uart demo", strlen("start uart demo"));
while(1)
{
//串口2发送数据
uart_write_bytes(UART_NUM_2, (const char*)"Task running : main", strlen("Task running : main"));
//ESP32S3的日志输出数据
ESP_LOGI("main", "Task running : main\r\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
6.10 串口通信效果
将代码烧录开发板,安装硬件连接与准备章节的实物连接,得到以下效果。
开发板持续通过串口2发送字符串 "Task running : main",当开发板的串口2接收到数据时,又通过串口2将数据回显并且显示数据长度:uart2 string length : 7
uart2 received : afddbfg