04. 串口通信

news/2025/3/10 21:03:24/文章来源:https://www.cnblogs.com/FlurryHeart/p/18763660

一、串口通信简介

  串口通信是一种设备间常用的串行通信方式,串口按位(bit)发送和接收字节。串口通信的数据包由发送设备的 TXD 接口传输到接收设备的 RXD 接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成如下图所示。

串口通信协议数据帧格式

  串口通信协议数据包组成可以分为 波特率数据帧格式 两部分。、

【1】、波特率

  波特率 表示 每秒钟传送的码元符号的个数,所以它决定了数据帧里面每一个位的时间长度。两个要通信的设备的波特率一定要设置相同。

【2】、数据帧

  串口通信的数据帧包括 起始位停止位有效数据位 以及 校验位

  串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的 起始位 是由 一个逻辑 0 的数据位 表示,而数据帧的 停止位 可以是 0.5、1、1.5 或 2 个逻辑 1 的数据位 表示,只要双方约定一致即可。

  数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据,有效数据位通常会被约定为 5、6、7 或者 8 个位长有效数据位是低位(LSB)在前,高位(MSB)在后

  校验位 可以认为是 一个特殊的数据位校验位一般用来判断接收的数据位有无错误,检验方法有:奇检验偶检验0 检验1 检验 以及 无检验

  • 奇校验 是指 有效数据位和校验位中 “1” 的个数为奇数,比如一个 8 位长的有效数据为:10101001,总共有 4 个 “1”,为达到奇校验效果,校验位设置为 “1”,最后传输的数据是 8 位的有效数据加上 1 位的校验位总共 9 位。
  • 偶校验 是指 有效数据位和校验位中 “1” 的个数为偶数,比如数据帧:11001010,此时数据帧 “1” 的个数为 4 个,所以偶校验位为 “0”。
  • 0 校验 是指不管有效数据中的内容是什么,校验位总为 “0”
  • 1 校验 是指不管有效数据中的内容是什么,校验位总为 “1”
  • 无校验 是指 数据帧中不包含校验位

  尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。

二、UART简介

  ESP32 S3 芯片中有三个 UART 控制器可供使用,并且兼容不同的 UART 设备。此外,UART 还可以用作红外数据交换(IrDA)或 RS485 调制解调器。三个 UART 控制器分别有一组功能相同的寄存器,分别为 UART0、UART1、UART2。

  UART 是一种以字符为导向的通用数据链,可以实现设备间的通信。异步通信不需要在发送数据的过程中添加时钟信息,但这也要求发送端和接收端的速率、停止位以及奇偶校验位等参数的配置要相同,唯有如此通信才能成功。

  UART 数据帧始于一个起始位,接着是有效数据,然后是奇偶校验位,最后才是停止位。ESP32 S3 芯片上的 UART 控制器支持多种字符长度和停止位。另外,控制器还支持软、硬件控制流和 GDMA,可以实现无缝高速的数据传输。

UART框架图

【1】、RAM

  ESP32 S3 芯片中三个 UART 控制器(UART0、UART1、UART2)共用 1024×8 bit 的 RAM 空间。通过配置 UART_TX_SIZE 可以对三个 UART 控制器中的其中一个的 Tx_FIFO 以 1 block 为单位进行扩展。同理,配置 UART_RX_SIZE 也是一样的。

【2】、Clock

  UART 作为异步通信的外设,它的寄存器配置模块与 TX/RX/FIFO 都工作在 APB_CLK 时钟域内,而控制 UART 接收与发送的 Core 模块工作在 UARTCore 时钟域。Clock 有三个时钟源,分别为:APB_CLK、RC_FAST_CLK 以及晶振时钟 XTAL_CLK,它们可以通过配置寄存器 UART_SCLK_SEL 来选择使用哪个时钟作为时钟源。选择后的时钟源通过预分频器(Divider)分频后进入 UART Core 模块。该分频器支持小数分频,支持的分频范围为:1~256,分频系数为:

\[UART\_SCLK\_DIV\_NUM + \frac{UART\_SCLK\_DIV\_B}{UART\_SCLK\_DIV\_A} \]

【3】、UART 控制器模块

  UART 控制器可以分为两个功能块,分别为:发送块(Transmitter)以及接收块(Receiver)。

  发送块包含一个发送 FIFO 用于缓存待发送的数据。软件可以通过 APB 总线向 Tx_FIFO 写数据,也可以通过 GDMA 将数据传入 Tx_FIFO。Tx_FIFO_Ctrl,用于控制 Tx_FIFO 的读写过程,当 Tx_FIFO 非空时,Tx_FSM 通过 Tx_FIFO_Ctrl 读取数据,并将数据按照配置的帧格式转化成比特流。比特流输出信号 txd_out 可以通过配置 UART_TXD_INV 寄存器实现取反功能。

  接收块包含一个接收 FIFO 用于缓存待处理的数据。输入比特流 rxd_in 可以输入到 UART 控制器。可以通过 UART_RXD_INV 寄存器实现取反。Baudrate_Detect 通过检测最小比特流输入信号的脉宽来测量输入信号的波特率。Start_Detect 用于检测数据的 START 位,当检测到 START 位之后,Rx_FSM 通过 Rx_FIFO_Ctrl 将帧解析后的数据存入 Rx_FIFO 中。软件可以通过 APB 总线读取 Rx_FIFO 中的数据也可以使用 GDMA 方式进行数据接收。

【4】、UART Core

  HW_Flow_Ctrl 通过标准 UART RTS 和 CTS(rtsn_out 和 ctsn_in)流控信号来控制 rxd_in 和txd_out 的数据流。SW_Flow_Ctrl 通过在发送数据流中插入特殊字符以及在接收数据流中检测特殊字符来进行数据流的控制。

三、串口常用函数

  ESP-IDF 提供了一套 API 来配置串口。要使用串口功能,需要导入必要的头文件 driver/uart.h

3.1、配置串口端口

  我们可以使用 uart_param_config() 函数 设置指定 UART 端口的通信参数,它的声明如下:

/*** @brief 配置串口端口* * @param uart_num UART外设端口号* @param uart_config UART的参数配置信息* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config);

  形参 uart_num 用来 指定 UART 外设端口号。UART 外设端口号在 uart.h 文件中有定义。

typedef enum 
{UART_NUM_0,                         /*!< UART port 0 */UART_NUM_1,                         /*!< UART port 1 */
#if SOC_UART_HP_NUM > 2UART_NUM_2,                         /*!< UART port 2 */
#endif
#if SOC_UART_HP_NUM > 3UART_NUM_3,                         /*!< UART port 3 */
#endif
#if SOC_UART_HP_NUM > 4UART_NUM_4,                         /*!< UART port 4 */
#endif
#if (SOC_UART_LP_NUM >= 1)LP_UART_NUM_0,                      /*!< LP UART port 0 */
#endifUART_NUM_MAX,                       /*!< UART port max */
} uart_port_t;

  形参 uart_config指向 uart_config_t 结构体的指针,它包含了 UART 的参数配置信息。该结构体的定义如下所示:

typedef struct 
{int baud_rate;                      // 波特率uart_word_length_t data_bits;       // 数据位uart_parity_t parity;               // 校验位uart_stop_bits_t stop_bits;         // 停止位uart_hw_flowcontrol_t flow_ctrl;    // 硬件流控制uint8_t rx_flow_ctrl_thresh;        // 硬件控制流阈值union {uart_sclk_t source_clk;         // 时钟源
#if (SOC_UART_LP_NUM >= 1)lp_uart_sclk_t lp_source_clk;
#endif};struct {uint32_t backup_before_sleep: 1;} flags;                            // 配置标志
} uart_config_t;

  成员 baud_rate 代表 串口的波特率,即数据传输速率,具体指的是每秒传输的位数。常见的波特率值包括 9600 和 115200 等。

  成员 data_bits数据位的数量,也就是每个字节中的位数。它的可选值如下:

typedef enum 
{UART_DATA_5_BITS   = 0x0,    /*!< word length: 5bits*/UART_DATA_6_BITS   = 0x1,    /*!< word length: 6bits*/UART_DATA_7_BITS   = 0x2,    /*!< word length: 7bits*/UART_DATA_8_BITS   = 0x3,    /*!< word length: 8bits*/UART_DATA_BITS_MAX = 0x4,
} uart_word_length_t;

  成员 parity奇偶校验位,它的可选值如下:

typedef enum 
{UART_PARITY_DISABLE  = 0x0,     // 不使用校验位UART_PARITY_EVEN     = 0x2,     // 偶校验UART_PARITY_ODD      = 0x3      // 奇校验
} uart_parity_t;

  成员 stop_bits停止位的数量,它的可选值如下:

typedef enum 
{UART_STOP_BITS_1   = 0x1,  /*!< stop bit: 1bit*/UART_STOP_BITS_1_5 = 0x2,  /*!< stop bit: 1.5bits*/UART_STOP_BITS_2   = 0x3,  /*!< stop bit: 2bits*/UART_STOP_BITS_MAX = 0x4,
} uart_stop_bits_t;

  成员 flow_ctrl硬件流控制的设置,它的可选值如下:

typedef enum 
{UART_HW_FLOWCTRL_DISABLE = 0x0,     // 不使用硬件流控制UART_HW_FLOWCTRL_RTS     = 0x1,     // 只启用 RTS 信号流控制UART_HW_FLOWCTRL_CTS     = 0x2,     // 只启用 CTS 信号流控制UART_HW_FLOWCTRL_CTS_RTS = 0x3,     // 同时启用 RTS 和 CTS 信号流控制UART_HW_FLOWCTRL_MAX     = 0x4,
} uart_hw_flowcontrol_t;

  成员 rx_flow_ctrl_thresh硬件控制流阈值

  成员 source_clk 用来 配置时钟源,它的可选值如下:

typedef enum 
{UART_SCLK_APB = SOC_MOD_CLK_APB,        // 选择 APB 作为时钟源UART_SCLK_RTC = SOC_MOD_CLK_RC_FAST,    // 选择 RTC 作为时钟源UART_SCLK_XTAL = SOC_MOD_CLK_XTAL,      // 选择 XTAL 作为时钟源UART_SCLK_DEFAULT = SOC_MOD_CLK_APB,    // 选择 APB 时钟源为默认选项
} soc_periph_uart_clk_src_legacy_t;

  该函数返回 ESP_OK 表示 配置成功,返回 ESP_FAIL 表示 配置失败

#define ESP_OK          0       /*!< esp_err_t value indicating success (no error) */
#define ESP_FAIL        -1      /*!< Generic esp_err_t code indicating failure */

3.2、配置UART引脚

  我们可以使用 uart_set_pin() 函数 设置某个管脚的中断服务函数,它的声明如下:

/*** @brief 配置UART引脚* * @param uart_num UART外设端口号* @param tx_io_num 发送引脚号,若不需要此功能,可将此参数设为-1* @param rx_io_num 接收引脚号,若不需要此功能,可将此参数设为-1* @param rts_io_num rts引脚号,若不需要此功能,可将此参数设为-1* @param cts_io_num cts引脚号,若不需要此功能,可将此参数设为-1* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num);

3.3、安装驱动程序

  我们可以使用 uart_driver_install() 函数 安装 UART 驱动程序,并 指定发送和接收缓冲区的大小,其函数原型如下所示:

/*** @brief 安装驱动程序* * @param uart_num UART外设端口号* @param rx_buffer_size UART接收环形缓冲区大小* @param tx_buffer_size UART发送环形缓冲区大小* @param event_queue_size UART驱动程序内部缓冲队列的大小* @param uart_queue 保存用户定义的用于接收数据的队列句柄* @param intr_alloc_flags UART中断分配标志* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int event_queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags);

3.4、获取数据长度

  我们可以使用 uart_get_buffered_data_len() 函数 获取接收环形缓冲区中缓存的数据长度,其函数原型如下所示:

/*** @brief 获取数据长度* * @param uart_num UART外设端口号* @param size 保存了接受缓存的数据长度* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t *size);

3.5、接收数据

  我们可以使用 uart_read_bytes() 函数 从UART 接收缓冲区中读取数据,其函数原型如下所示:

/*** @brief 接收数据* * @param uart_num UART外设端口号* @param buf 缓冲区的指针* @param length 数据长度* @param ticks_to_wait 超时等待的时间,单位是FreeRTOS节拍计数* @return int 成功读取的字节数,如果发生错误则返回-1*/
int uart_read_bytes(uart_port_t uart_num, void *buf, uint32_t length, TickType_t ticks_to_wait);

3.6、发送数据

  我们可以使用 uart_write_bytes() 函数 将指定的数据写入到 UART 发送缓冲区,并触发数据的发送,其函数原型如下所示:

/*** @brief 发送数据* * @param uart_num UART外设端口号* @param src 发送的数据的缓冲区* @param size 要发送的数据长度* @return int 成功发送的字节数,如果发生错误则返回-1*/
int uart_write_bytes(uart_port_t uart_num, const void *src, size_t size);

  在使用 uart_write_bytes() 函数发送数据时,数据首先被复制到 UART 发送缓冲区,随后函数会返回,并不会等待数据完全发送完成。因此,若需确保数据完整无误地发送成功,应当调用 uart_wait_tx_done() 函数进行同步等待,直至发送过程完全结束。在确认 UART 已成功初始化,并且已经配置了正确的波特率及其他相关参数之后,即可调用 uart_write_bytes() 函数,将数据准确无误地发送至 UART 设备。

四、实验例程

  这里,我们在【components】文件夹下的【peripheral】文件夹下的【inc】文件夹(用来存放头文件)新建一个 bsp_uarrt.h 文件,在【components】文件夹下的【peripheral】文件夹下的【src】文件夹(用来存放源文件)新建一个 bsp_uart.c 文件。

#ifndef __BSP_UART_H__
#define __BSP_UART_H__#include "driver/gpio.h"
#include "driver/uart.h"#define USART_TX_GPIO_PIN   GPIO_NUM_41
#define USART_RX_GPIO_PIN   GPIO_NUM_42#define TX_BUF_SIZE         1024                // 串口发送缓冲区大小
#define RX_BUF_SIZE         1024                // 串口接收缓冲区大小void bsp_uart_init(uart_port_t uart_port, uint32_t baud_rate);#endif // !__BSP_UART_H__
#include "bsp_uart.h"/*** @brief 串口初始化* * @param uart_port 串口号* @param baud_rate 波特率*/
void bsp_uart_init(uart_port_t uart_port, uint32_t baud_rate)
{uart_config_t uart_config = {0};uart_config.baud_rate = baud_rate;                      // 波特率uart_config.data_bits = UART_DATA_8_BITS;               // 数据位uart_config.parity = UART_PARITY_DISABLE;               // 校验位uart_config.stop_bits = UART_STOP_BITS_1;               // 停止位uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;       // 硬件流控uart_config.source_clk = UART_SCLK_APB;                 // 配置时钟源uart_config.rx_flow_ctrl_thresh = 122;                  // 硬件控制流阈值uart_param_config(uart_port, &uart_config);// 配置uart引脚uart_set_pin(uart_port, USART_TX_GPIO_PIN, USART_RX_GPIO_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);// 安装串口驱动uart_driver_install(uart_port, RX_BUF_SIZE, TX_BUF_SIZE, 20, NULL, 0);
}

  ESP32-S3 的串口通讯驱动不需要为串口编写中断回调函数,因为在 ESP32-S3 IDF 库中已经封装了数据读写函数。串口通过函数获取 RX 环形缓冲区缓存的数据长度,并判断该数据长度非空后,将其逐一通过读写函数进行操作。

  修改【main】文件夹下的 main.c 文件。

#include <string.h>#include "freertos/FreeRTOS.h"#include "bsp_uart.h"// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{uart_port_t uart_port = UART_NUM_1;size_t length = 0;uint8_t data[RX_BUF_SIZE] = {0};char *temp = "\n 发送的消息为:\n";bsp_uart_init(uart_port, 115200);while (1){uart_get_buffered_data_len(uart_port, &length);                     // 获取环形缓冲区数据长度if (length > 0){memset(data, 0, RX_BUF_SIZE);uart_write_bytes(uart_port, temp, strlen((char *)temp));        // 写数据uart_read_bytes(uart_port, data, length, 100);                  // 读取串口数据uart_write_bytes(uart_port, data, strlen((char *)data));        // 写数据}// 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数vTaskDelay(pdMS_TO_TICKS(10));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/896976.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享

问题描述 当在AKS中遇见复杂的网络问题,想要进入到特定的POD中来抓取网络文件包进行分析。特分享抓取网络包的方法!操作步骤 第一步:使用kubectl get pods命令确认问题Pod所在的Node 第二步:使用node shell登录到相应node上 kubectl node-shell <node name> 第三步:…

App虚拟机环境搭建

App虚拟机环境搭建 在uniapp的开发环境中,我想要搭建一个平台完成对于app的开发,所以要配置app的基本环境和安装虚拟机 虚拟机安装 这里使用Mumu虚拟机.直接从官网下载然后傻瓜式安装https://mumu.163.com/搭建环境 主要是给虚拟机的接口和abd.exe文件的地址找到,配置到HBuilde…

nodejs的下载安装

首先进入官网https://nodejs.cn/download/ 左边是稳定版,右边是最新版,安装自己想要的版本 安装到自己想要的路径下 一直next,这步不要勾选 然后install 在安装路径下创建两个文件夹,分别是node_cache和node_global 配置环境变量 再在系统path中添加%NODE_HOME%、%NODE_HOM…

安卓虚拟机的创建

在android studio上创建一个安卓虚拟机 可以选择手机型号 可选的都是谷歌的手机 而且对应的ui都是原生安卓ui 随便选就好 记得安卓版本不要太老,没必要虚拟机运行起来之后就会在边上有个视图

android studio的下载

进入android studio官网下载即可,中间所有东西点同意和next就行

Vue3--ref - reactive

reactive可以深层次对比ref定义对象类型是借用reactive总结

征程 6 工具链 BEVPoolV2 算子使用教程 【2】-BEVPoolV2 QAT 链路实现示例

1.引言 在上一篇帖子中,我们已详尽阐述了 BEVPoolV2 相较于 BEVPoolV1 的改进之处,并对 BEVPoolV2 实现的代码进行了解析。想必大家对 BEVPoolV2 算子的功能及实现已有了一定程度的理解,此篇帖子将展示 征程 6 工具链 BEVPoolV2 单算子 QAT 链路的实现范例,以进一步增进用户…

包装类--java进阶day05

1.包装类比如要让s+100,输出223.如果直接相加,结果是123100,这时就可以将s转换为包装类,然后再用包装类进行相加 2.包装类类型3.手动拆/装箱 我们这里只介绍Integer,其他包装类都是一个模板 第一个方式已经过时,不建议使用.4.自动拆/装箱.5.包装类常用方法 可以直接类名调…

掌握这些 UI 交互设计原则,提升产品易用性

在当今数字化时代,用户对于产品的体验要求越来越高,UI 交互设计成为决定产品成败的关键因素之一。一个易用的产品能够让用户轻松、高效地完成各种操作,而实现这一目标的核心在于遵循一系列科学合理的 UI 交互设计原则。本文将详细阐述简洁性、一致性、反馈、可访问性以及用户…

C++ this

今天开始进入C++学习了,之前的这周末复习下 侧重与底层原理 当把函数放到结构体里面,编译器会帮我们传递一个参数:结构体的地址this指针本质就是对象地址

【由技及道】镜像星门开启:Harbor镜像推送的量子跃迁艺术【人工智障AI2077的开发日志010】

当构建产物需要穿越多维宇宙时,当Docker镜像要同时存在于72个平行世界——这就是镜像推送的量子艺术。本文记录一个未来AI如何通过Harbor建立镜像星门,让每个构建产物都能瞬间抵达所有维度。![量子镜像跃迁示意图]( 摘要:当构建产物需要穿越多维宇宙时,当Docker镜像要同时存…

构造矩形

构造矩形 题目描述 现有 \(n\) 条长度为 \(m\) 的线段,垂直于 x 轴分布,且互不重合。第 \(i\) 条线段的两个端点均为整数点,分别为 \((a_i, 0)\) 和 \((a_i, m)\)。每条线段上有 \(m+1\) 个整数点,纵坐标分别为 \(0, 1, 2, …, m\)。 现在,你需要选择两条不同的线段,并在…