【STM32 CubeMX + Keil】 RS485 通信 - 实践

目录

一、什么是 RS485

二、接线方式

三、UART通信代码

四、初始化

五、发送数据

六、接收数据

七、收发效果

八、UART驱动 源代码


一、什么是 RS485

RS-485(其官方标准号为 TIA/EIA-485,传统上常简称为RS-485)是由美国电子工业协会(EIA)及电信工业协会(TIA)共同制定的一种物理层电气通信标准。这与我们常说的“Wi-Fi”对应于官方标准“IEEE 802.11”是类似的道理。

RS485 核心特点包括:

  • 电气标准:规定了接口芯片的电平范围、负载能力等电气参数。
  • 差分传输:采用一对差分信号线(A和B)进行电压差传输数据,具备强大的抗共模干扰能力。
  • 长距离与多点通信:非常适合长距离传输(可达千米以上), 并支持一条总线上连接多个设备。

需要注意的是,RS-485本身并不定义数据格式或通信协议,它仅负责比特流的可靠传输。

因此,在实际应用中,它必须与高层协议配合使用。最经典的组合是作为UART(异步串行通信)的物理层:由控制器通过UART生成数据帧,再经由RS-485收发器芯片转换为差分信号在总线上传输。诸如Modbus等流行工业协议均基于此架构。

1、差分信号(抗干扰秘诀)

  • 使用A、B两条线传输一个信号。
  • 逻辑1: A线电压比B线低 >200mV。
  • 逻辑0: A线电压比B线高 >200mV。
  • 优势: 外界干扰会同时影响A、B线,但两者电压差基本不变,从而极大提升抗干扰能力。

2、半双工

  • 同一时间,设备只能发送接收,不能同时进行

3、多点总线

  • 所有设备都并联在同一对A/B总线上,轻松组建网络
  • 最多可连接32个设备(理论值)

4、距离与速度

  • 两者成反比。速度越快,距离越短
  • 理论:10Mbps时约10米,100kbps时可达1200米
  • 实际应用:9600bps + 50米范围内

5、终端电阻

  • 在长距离或高速通信时,信号会在总线末端反射,造成通信质量下降甚至失败
  • 总线的两端,在A、B线之间各并联一个匹配电阻(通常为120Ω)
  • 仅在总线两端的设备上启用,不在中间设备上使用

6、典型场景

工业控制(PLC、传感器)、安防系统(门禁、摄像头)、智能楼宇(电表、空调)等需要长距离、多设备、环境复杂的场合。


二、接线方式

在 RS485 通信中,典型的接线结构如下:

STM32 的 UART 引脚 ↔ RS485 收发器 ↔ A/B 两线构成的 485 网络 ↔ 另一端的 RS485 设备 ↔ 对端 UART 接口

1、RS485 与 UART的接线区别

  • 纯 UART 通信  (点对点)  两个 MCU 直接相连时,遵循“交叉互连”原则:一方的 TX 接另一方的 RX,并将双方的 GND 相连,以建立共地参考。

  • RS485 通信  (网络式):设备接入 RS485 网络时,需在 MCU 的 UART 接口后增加一个 RS485 收发器。所有设备通过 A、B 两条总线并联,构成多点网络。

2、方向控制

RS485 是半双工通信,收发器需在发送(TX)和接收(RX)模式之间切换。

硬件电路上, 有两种切换方向的方式:手动换向、自动换向。 淘宝买模块时,一定要注意区分。

特性手动换向自动换向 (主流)
原理通过 GPIO 控制 DE/RE 引脚,由软件控制方向切换使用专用芯片或硬件电路实现自动切换
优点硬件简单,成本低软件简单,可像普通 UART 直接收发
缺点软件复杂,需精确控制时序硬件成本稍高
应用老旧或成本极其敏感的设计现代主流设计,本教程采用此方案

注意:本教程以自动换向方案为例进行介绍。

3. 接线指南

典型的自动换向 RS485 模块(以 STM32 的 UART3 为例,TX-PB10,RX-PB11)接线如下:

    引脚       接微控制器 (STM32)   接入485网络  (另一端设备)
VCC5V ---
GNDGND (共地)---
IN UART_TX (PB10)---
OUTUART_RX (PB11)---
A---总线 A 线
B---总线 B 线
GND(屏蔽)---可不接,用于屏蔽层接地

4、接线图示

场景一:使用核心板(需外接 RS485 模块)

请参考下图,使用杜邦线将核心板与 RS485 模块相连。

场景二:使用集成 RS485 的开发板

若开发板已板载 RS485 电路,则操作更为简便:

  • 使用跳线帽将标有 “RS485” 的排针与对应的 UART 引脚短接。
  • 将外部设备的 A、B 线接入板载的 RS485 接线端子。

提示:具体连接方式请查阅您开发板的原理图。


三、UART通信代码

为演示RS485通信,原计划采用STM32CubeMX配置UART3底层驱动。

经过考虑,最终决定使用已编写完善的UART底层驱动文件(bsp_UART.c 与 bsp_UART.h)。

主要基于以下考虑:

  • 更容易移植:CubeMX生成的配置通常仍需手动补充功能代码。而独立的驱动文件已完成功能封装,接口清晰,复制即可使用,可显著减少移植工作量。
  • 避免重复开发:RS485通信所使用的UART底层配置较为固定,无需在不同项目中重复编写与调试。直接调用经过验证的稳定驱动,既能节省开发时间,也使开发者更专注于应用层逻辑的实现。

文末附有基于F407 标准库、HAL库 编写的 bsp_UART.c 与 bsp_UART.h 源文件,读者可直接复制使用。

将这两个文件添加到工程中并正确引用后,即可调用其中已实现的UART相关功能,包括:

  • UART初始化
  • 数据发送与接收
  • AT指令发送
  • Modbus校验等功能

这些封装好的函数便于快速搭建RS485通信测试环境,提升开发效率。

以UART3的函数为例 ,其函数如下:

// 初始化
void      UART3_Init(uint32_t ulBaudrate);                                      // 初始化串口3; GPIO引脚PB10+PB11、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
// 发送
void      UART3_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART3_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART3_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
// 接收
uint16_t  UART3_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART3_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART3_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)

四、初始化

如前所述,在使用自动换向RS485电路时,其程序操作与普通UART完全相同。

在本示例中,RS485转换电路连接至STM32的UART3。因此,我们仅需一行代码即可完成初始化:

    UART1_Init(115200);  // 初始化 UART1, PA9+PA10
    UART3_Init(115200);  // 初始化 UART3, PB10+PB11

UART_Init3() 该函数已封装好所有底层配置,包括引脚模式与通信协议 (波特率-None-8-1)。

相较于通过CubeMX修改配置再生成,这种直接调用预编写驱动的方式,更为简单高效。



五、发送数据

每个 UART 都提供了三个函数用于数据发送,以适应不同应用场景:

// 发送
void      UART3_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART3_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART3_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功

应用示例:

为方便测试,可搭建一个数据转发链路:使用串口助手通过UART1向STM32发送数据,STM32再将接收到的数据通过RS485(UART3)转发出去。

具体实现流程如下图所示:


六、接收数据

每个 UART 的接收处理同样由三个函数配合完成,采用查询“最后一帧数据字节数”的方式进行:

// 接收
uint16_t  UART3_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART3_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART3_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)

典型处理流程

  • 检查 UART3_GetRxNum() 的返回值,当返回值大于0时,表示接收到一帧数据。
  • 随后即可通过 UART3_GetRxData() 获取数据地址进行读取。
  • 处理完毕后调用 UART3_ClearRx() 清除标志

RS485(UART3)的 接收判断与数据获取流程,示例,如下图:


七、收发效果实测

为便于调试,本次测试采用如下配置:

  • 一方:使用集成 RS485 电路功能的开发板。
  • 另一方:使用 USB 转 RS485 模块(请注意不是 RS485 转 TTL 模块)连接至电脑。

接线示意图如下:

在电脑端打开两个串口助手窗口,分别用于数据的收发与显示:

  • 串口助手 1:打开与开发板连接的 USB 转 TTL 端口,波特率设置为 115200(用于 UART1 与电脑通信)。
  • 串口助手 2:打开 USB 转 RS485 模块对应的端口,波特率同样设置为 115200(用于 UART3 经 RS485 网络与电脑通信)。

它们 的数据流向,如下:

串口助手1<=>板载USB转TTL<=>UART1(STM32)<=>程序内部转发<=>UART3(STM32)<=>RS485收发电路<=>A/B总线<=>USB转RS485模块<=>串口助手2

收发测试正常,效果如下图:


八、UART驱动 源代码

1、bsp_USRT.h (STM32F407_同时通用 标准库 / HAL库)

#ifndef __BSP__UART_H
#define __BSP__UART_H
/***********************************************************************************************************************************
 ** 【代码编写】  魔女开发板团队
 ** 【最后版本】  2024-07-08-01
 ** 【淘    宝】  https://demoboard.taobao.com
 ***********************************************************************************************************************************
 ** 【文件名称】  bsp_UART.h
 **
 ** 【 功  能 】  串口通信底层驱动(UART1、UART2、UART3、UART4、UART5、UART6)
 **               波特率-None-8-1
 **               调用全局声明中的串口函数,即可完成初始化、发送、接收.
 **
 ** 【 约  定 】  本文件所用串口通信,均为异步通信。
 **               2024年起更新的示例,串口函数命名时统一使用"UART",而不使用旧文件中的"USART".
 **
 ** 【适用平台】  STM32F407 + keil5 + HAL库/标准库
 **
 ** 【串口引脚】  各个串口的初始化函数UARTx_Init(),将按以下默认引脚进行初始化:
 **               1- UART1  TX_PA9   RX_PA10       特别说明:魔女开发板系列,均板载USB转TTL,PCB已布线连接好UART1, 使用和烧录用的同一USB接口,即可通过UART1和电脑进行通信。具体查看资料文件夹中的说明文件。
 **               2- UART2  TX_PA2   RX_PA3
 **               3- UART3  TX_PB10  RX_PB11
 **               4- UART4  TX_PC10  RX_PC11
 **               5- UART5  TX_PC12  RX_PD2
 **               6- UART6  TX_PC6   RX_PC7
 **
 ** 【移植说明】  1- 如果使用CubeMX配置的工程,无须对UART进行任何配置。
 **               2- 适用于HAL库、标准库工程,通过下面操作,均可直接使用。
 **               3- 复制本工程的UART文件夹,到目标工程文件夹中;
 **               4- 添加头文件路径: Keil > Option > C/C++ > Include Paths;
 **               5- 添加C文件到工程: Keil > 左侧工程管理器中双击目标文件夹 > 选择 bsp_UART.c;
 **               6- 添加文件引用:    #include "bsp_uart.h",即哪个文件要用串口功能,就在其代码文件顶部添加引用;
 **
 ** 【代码使用】  每组串口,已封装好7个函数 (初始化1个、发送3个、接收3个)。下面以UART1作示范说明:
 **               1、初始化:             void      UART1_Init(uint32_t ulBaudrate);                                      // 初始化串口; 配置GPIO引脚PA9+PA10、配置通信参数:波特率-None-8-1、配置中断
 **               2、发送指定长度的数据  void      UART1_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定长度的数据; 参数:数据地址、字节数
 **               3、发送字符串          void      UART1_SendString(const char *pcString,...);                           // 发送字符串;   参数:字符串地址; 使用方法如同printf
 **               4、发送AT指令          uint8_t   UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
 **               5、获取接收的字节数:   uint16_t  UART1_GetRxNum(void);                                                 // 获取接收到的字节数; 如果返回值大于0,即为接收到新一帧数据
 **               6、获取接收的数据:    uint8_t*  UART1_GetRxData(void);                                                // 获取接收到的数据(缓存的地址)
 **               7、清零接收标志:      void      UART1_ClearRx(void);                                                  // 清0接收标志,即清0接收到的字节数; 每次处理完成数据,就要调用这个函数清0,方可进入下轮接收判断
 **
 ** 【更新记录】  2024-10-22  中断服务函数:使用宏定义代替位操作,以提高可读情; 优化注释;
 **               2024-07-02  增加Modbus_CRC16校验的追加、判断函数
 **               2024-06-30  优化UART初始化:增加奇偶校验3个寄存器位的代码配置行
 **               2024-06-20  优化AT指令函数:名称、机制;
 **               2024-06-07  增加AT指令等待反馈处理函数:UARTx_WaitACK();
 **               2024-06-07  优化函数:UARTx_SendString();
 **               2024-06-06  优化代码适配,使文件能适用于标准库、HAL库工程
 **               2024-04-04  UARTx_SendString(),取消临时缓存的static,以减少RAM占用,适配LVGL移植;
 **               2024-03-27  修改xUSATR_TypeDef结构体,优化缓冲的存储方式
 **               2024-02-04  简化接收函数命名,函数名中使用Rx字符,代替旧函数名称中的Reveived字符;
 **               2024-01-09  取消部分静态变量,并入结构体中,使用调用更直观
 **               2024-01-03  完善函数、注释
 **               2023-12-25  增加接收函数的封装,取消接收变量(全局)的使用
 **               2023-12-23  完成对HAL库的支持
 **               2023-01-27  增加宏定义、完善注释
 **               2021-12-16  完善接收机制:取消接收标志,判断接收字节数>0即为接收到新数据
 **               2021-11-03  完善接收函数返回值处理
 **               2021-08-14  增加宏定义:接收缓存区大小设定值,使空间占用更可控;
 **               2021-08-14  修改发送函数名称:UARTx_Printf(),修改为:UARTx_SendString();
 **               2020-09-02  文件建立、UART1接收中断、空闲中断、发送中断、DMA收发
 **               2021-06-04  UART1、2、3及UART4、5 的收发完善代码
 **               2021-06-09  完善文件格式、注释
 **               2021-06-22  完善注释说明
 **
************************************************************************************************************************************/
#include "stdio.h"        // 引用C语言标准库,它定义了标准输入输出函数; 如:printf、scanf、sprintf、fopen 等;
#include "stdarg.h"       // 引用C语言标准库,它定义了处理可变参数的宏; 如:va_start、va_arg、va_end、va_list 等;
#include "string.h"       // 引用C语言标准库,它定义了操作字符串的函数; 如:strcpy、strcmp、strlen、memset、memcpy 等;
#ifdef USE_STDPERIPH_DRIVER
    #include "stm32f4xx.h"                        // 引用 标准库的底层支持文件
#endif
#ifdef USE_HAL_DRIVER
    #include "stm32f4xx_hal.h"                    // 引用 HAL库的底层支持文件
#endif
/*****************************************************************************
 ** 移植配置修改区
 ** 备注:除非有特殊要求,否则,下面参数已通用:RS485、蓝牙模块、ESP8266模块、串口屏等
****************************************************************************/
// 串口开关
#define UART1_EN                       1          // 串口1,0=关、1=启用;  倘若没用到UART1, 置0,就不会开辟UART1发送缓存、接收缓存,省一点资源;
#define UART2_EN                       1          // 串口2,0=关、1=启用;  同上;
#define UART3_EN                       1          // 串口3,0=关、1=启用;  同上;
#define UART4_EN                       1          // 串口4,0=关、1=启用;  同上;
#define UART5_EN                       1          // 串口5,0=关、1=启用;  同上;
#define UART6_EN                       1          // 串口5,0=关、1=启用;  同上;
// 发送缓冲区大小
#define UART1_TX_BUF_SIZE           2048          // 配置每组UART发送环形缓冲区数组的大小,单位:字节数;
#define UART2_TX_BUF_SIZE            512          // -- 只有在前面串口开关在打开状态,才会定义具体的缓冲区数组
#define UART3_TX_BUF_SIZE            512          // -- 默认值:512,此值已能适配大部场景的通信; 如果与ESP8266之类的设备通信,可适当增大此值。
#define UART4_TX_BUF_SIZE            512          // -- 值范围:1~65535; 注意初始化后,不要超过芯片最大RAM值。
#define UART5_TX_BUF_SIZE            512          // -- 注意此值是一个环形缓冲区大小,决定每一帧或多帧数据进入发送前的总缓存字节数,先进先出。
#define UART6_TX_BUF_SIZE            512          //
// 接收缓冲区大小
#define UART1_RX_BUF_SIZE           2048          // 配置每组UART接收缓冲区的大小,单位:字节; 此值影响范围:中断里的接收缓存大小,接收后数据缓存的大小
#define UART2_RX_BUF_SIZE           1024          // --- 当接收到的一帧数据字节数,小于此值时,数据正常;
#define UART3_RX_BUF_SIZE           1024          // --- 当接收到的一帧数据字节数,超过此值时,超出部分,将在中断中直接弃舍,直到此帧接收结束(发生空闲中断);
#define UART4_RX_BUF_SIZE           1024          //
#define UART5_RX_BUF_SIZE           1024          //
#define UART6_RX_BUF_SIZE           1024          //
// 结束-配置修改
/*****************************************************************************
 ** 声明全局函数
 **
 ** 每个串口的函数:
 ** 初始化    1个   波特率-None-8-1
 ** 发送      3个   发送指定长度数据、字符串、AT指令
 ** 接收      3个   获取字节数、获取数据、清0
****************************************************************************/
// UART1
void      UART1_Init(uint32_t ulBaudrate);                                      // 初始化串口1; GPIO引脚PA9+PA10、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART1_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART1_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART1_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART1_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART1_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// UART2
void      UART2_Init(uint32_t ulBaudrate);                                      // 初始化串口2; GPIO引脚PA2+PA3、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART2_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART2_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART2_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART2_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART2_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART2_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// UART3
void      UART3_Init(uint32_t ulBaudrate);                                      // 初始化串口3; GPIO引脚PB10+PB11、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART3_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART3_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART3_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART3_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART3_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART3_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// UART4
void      UART4_Init(uint32_t ulBaudrate);                                      // 初始化串口4; GPIO引脚PC10+PC11、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART4_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART4_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART4_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART4_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART4_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART4_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// UART5
void      UART5_Init(uint32_t ulBaudrate);                                      // 初始化串口5; GPIO引脚PC12+PD2、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART5_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART5_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART5_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART5_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART5_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART5_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// UART6
void      UART6_Init(uint32_t ulBaudrate);                                      // 初始化串口6; GPIO引脚PC6+PC7、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
void      UART6_SendData(uint8_t *puData, uint16_t usNum);                      // 发送指定数据; 参数:数据地址、字节数
void      UART6_SendString(const char *pcString, ...);                          // 发送字符串;   参数:字符串地址; 使用方法如同printf
uint8_t   UART6_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);    // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
uint16_t  UART6_GetRxNum(void);                                                 // 获取接收到的最新一帧字节数
uint8_t * UART6_GetRxData(void);                                                // 获取接收到的数据 (缓存的地址)
void      UART6_ClearRx(void);                                                  // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
// 辅助函数:Modbus_CRC16校验
void      Modbus_AddCRC16(uint8_t *_pcData, uint16_t _usLen);                   // 对数据追加2字节的ModbusCRC16校验值到末尾; 参数:原始数据、原始数据字节数; 注意:调用函数后,原始数据会增加2字节
uint8_t   Modbus_CheckCRC16(uint8_t *_pcData, uint16_t _usLen);                 // 对带ModbusCRC16校验的数据段进行校验; 返回:0-错误、1-匹配正确;
#endif

2、bsu_UART.c (STM32F407_同时通用 标准库 / HAL库)

/***********************************************************************************************************************************
 ** 【代码编写】  魔女开发板团队
 ** 【最后版本】  2024-07-08-01
 ** 【淘    宝】  https://demoboard.taobao.com
 ***********************************************************************************************************************************
 ** 【文件名称】  bsp_UART.c
 **
 ** 【文件功能】  各UART的GPIO配置、通信协议配置、中断配置,及功能函数实现
 **
 ** 【适用平台】  STM32F407 + keil5 + HAL库/标准库
 **
 ** 【最后更新]   20240708-1
 **
 ** 【特别说明】  1、为什么这个文件中,同时有标准库、HAL库的代码?
 **                  是为了方便移植!已做好预编译处理,可适用于标准库、HAL库的工程移植。
 **               2、为什么UART初始化、中断,用寄存器操作,而不是用更有可读性的HAL库?
 **                  因为CubeMX配置工程时,如果不对UART进行配置,且打勾只生成需要的文件,工程中将没有UART的HAL支持文件的。
 **                  另外, 中断里用寄存器操作,与HAL库的重重封装相比,明显地更高效!
************************************************************************************************************************************/
#include "bsp_UART.h"             // 头文件
/*****************************************************************************
 ** 声明本地变量
****************************************************************************/
typedef struct
{
    uint16_t  usRxNum;            // 新一帧数据,接收到多少个字节数据
    uint8_t  *puRxData;           // 新一帧数据,数据缓存; 存放的是空闲中断后,从临时接收缓存复制过来的完整数据,并非接收过程中的不完整数据;
    uint8_t  *puTxFiFoData;       // 发送缓冲区,环形队列; 为了方便理解阅读,没有封装成队列函数
    uint16_t  usTxFiFoData ;      // 环形缓冲区的队头
    uint16_t  usTxFiFoTail ;      // 环形缓冲区的队尾
} xUSATR_TypeDef;
/******************************************************************************
 * 函  数: delay_ms
 * 功  能: ms 延时函数
 * 备  注: 1、系统时钟168MHz
 *          2、打勾:Options/ c++ / One ELF Section per Function
            3、编译优化级别:Level 3(-O3)
 * 参  数: uint32_t  ms  毫秒值
 * 返回值: 无
 ******************************************************************************/
static volatile uint32_t ulTimesMS;    // 使用volatile声明,防止变量被编译器优化
static void delay_ms(uint16_t ms)
{
    ulTimesMS = ms * 16500;
    while (ulTimesMS)
        ulTimesMS--;                   // 操作外部变量,防止空循环被编译器优化掉
}
//////////////////////////////////////////////////////////////   UART-1   ///////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART1_EN
static xUSATR_TypeDef  xUART1 = { 0 };                      // 定义 UART1 的收发结构体
static uint8_t uaUART1RxData[UART1_RX_BUF_SIZE];            // 定义 UART1 的接收缓存
static uint8_t uaUART1TxFiFoData[UART1_TX_BUF_SIZE];        // 定义 UART1 的发送缓存
/******************************************************************************
 * 函  数: UART1_Init
 * 功  能: 初始化USART1的通信引脚、协议参数、中断优先级
 *          引脚:TX-PA10、RX-PA11
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t  ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART1_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);           // 使能 GPIOA  时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);          // 使能 USART1 时钟
    // 配置 引脚的复用功能
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);       // 配置PA9复用功能: USART1
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);      // 配置PA10复用功能:USART1
    // 重置 USART
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1, ENABLE);          // 使能重置
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1, DISABLE);         // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;                     // 引脚编号
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置R X引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;                    // 引脚编号
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(USART1, &USART_InitStructure);                       // 初始化USART
    // 配置 中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 开启 接收中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);                  // 开启 空闲中断
    // 配置中断优先级
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = USART1_IRQn;              // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(USART1, ENABLE);                                      // 使能USART1
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();                                   // 使能GPIOA
    __HAL_RCC_USART1_CLK_ENABLE();                                  // 使能USART1
    // 重置 USART
    __HAL_RCC_USART1_FORCE_RESET();                                 // 使能重置
    __HAL_RCC_USART1_RELEASE_RESET();                               // 取消重置
    // 配置 引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_9 | GPIO_PIN_10;               // 引脚 TX-PA9、RX-PA10
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_PULLUP;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;                    // 引脚复用功能
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率参数
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16);        // 波特率公式计算; USART1挂载在APB2, 时钟为系统时钟的2分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    USART1 -> BRR  = mantissa << 4 | fraction;                      // 设置波特率
    // 配置 USART
    USART1 -> CR1  =   0;                                           // 清0
    USART1 -> CR1 |=   0x01 << 2;                                   // 接收使能[02]: 0=失能、1=使能
    USART1 -> CR1 |=   0x01 << 3;                                   // 发送使能[03]:0=失能、1=使能
    USART1 -> CR1 |=   0x00 << 9;                                   // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    USART1 -> CR1 |=   0x00 << 10;                                  // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    USART1 -> CR1 |=   0x00 << 12;                                  // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    USART1 -> CR2  =   0;                                           // 数据清0
    USART1 -> CR2 &= ~(0x03 << 12);                                 // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    USART1 -> CR3  =   0;                                           // 数据清0
    USART1 -> CR3 &= ~(0x01 << 6);                                  // DMA接收[6]: 0=禁止、1=使能
    USART1 -> CR3 &= ~(0x01 << 7);                                  // DMA发送[7]: 0=禁止、1=使能
    // 配置 中断
    USART1 -> CR1 &= ~(0x01 << 7);                                  // 关闭发送中断
    USART1 -> CR1 |=   0x01 << 5;                                   // 使能接收中断: 接收缓冲区非空
    USART1 -> CR1 |=   0x01 << 4;                                   // 使能空闲中断:超过1字节时间没收到新数据
    USART1 -> SR   = ~(0x00F0);                                     // 清理中断
    // 配置 中断优先级
    HAL_NVIC_SetPriority(USART1_IRQn, 1, 1);                        // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(USART1_IRQn);                                // 使能、启用指定的中断
    // 配置完成,开启USART
    USART1 -> CR1 |=   0x01 << 13;                                  // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART1.puRxData = uaUART1RxData;                                // 关联接收缓冲区的地址
    xUART1.puTxFiFoData = uaUART1TxFiFoData;                        // 关联发送缓冲区的地址
    // 输出提示
    printf("\r\r\r===========  STM32F407VE 外设 初始化报告 ===========\r");                   // 输出到串口助手
    SystemCoreClockUpdate();                                                                  // 更新一下系统运行频率变量
    printf("系统时钟频率             %d MHz\r", SystemCoreClock / 1000000);                   // 输出到串口助手
    printf("UART1 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate); // 输出到串口助手
}
/******************************************************************************
 * 函  数: USART1_IRQHandler
 * 功  能: USART1的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
******************************************************************************/
void USART1_IRQHandler(void)
{
    static uint16_t cnt = 0;                                             // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART1_RX_BUF_SIZE];                           // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到外部缓存:xUARTx.puRxData[ ]
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((USART1->SR & USART_SR_TXE) && (USART1->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART1->DR = xUART1.puTxFiFoData[xUART1.usTxFiFoTail++];         // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART1.usTxFiFoTail == UART1_TX_BUF_SIZE)                    // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART1.usTxFiFoTail = 0;                                     // 将尾指针重置为0,实现环形队列的功能
        if (xUART1.usTxFiFoTail == xUART1.usTxFiFoData)                  // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            USART1->CR1 &= ~USART_CR1_TXEIE;                             // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (USART1->SR & USART_SR_RXNE)                                      // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt == UART1_RX_BUF_SIZE)                                    // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:UART1单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART1_RX_BUF_SIZE值,可解决此问题!\r");
            USART1->DR;                                                  // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = USART1->DR ;                                     // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (USART1->SR & USART_SR_IDLE)                                      // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART1.puRxData, rxTemp, UART1_RX_BUF_SIZE);              // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART1.usRxNum  = cnt;                                           // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                         // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART1_RX_BUF_SIZE);                            // 接收数据缓存数组,清零; 准备下一次的接收
        USART1 ->SR;
        USART1 ->DR;                                                     // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART1_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t*  puData   需发送数据的地址
 *          uint16_t  usNum    发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART1_SendData(uint8_t *puData, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                         // 把数据放入环形缓冲区
    {
        xUART1.puTxFiFoData[xUART1.usTxFiFoData++] = puData[i];  // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART1.usTxFiFoData == UART1_TX_BUF_SIZE)            // 如果指针位置到达缓冲区的最大值,则归0
            xUART1.usTxFiFoData = 0;
    }                                                            // 为了方便阅读理解,这里没有把此部分封装成队列函数,可以自行封装
    if ((USART1->CR1 & USART_CR1_TXEIE) == 0)                    // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        USART1->CR1 |= USART_CR1_TXEIE;                          // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART1_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART1_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                               // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                             // 新建一个可变参数列表
    va_start(ap, pcString);                                 // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                  // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                             // 清空可变参数列表
    UART1_SendData((uint8_t *)mBuffer, strlen(mBuffer));    // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART1_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART1_ClearRx();                                              // 清0
    UART1_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART1_GetRxNum())                                     // 判断是否接收到数据
        {
            UART1_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART1_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART1_SendStringForDMA
 * 功  能: UART通过DMA发送数据,省了占用中断的时间
 *         【适合场景】字符串,字节数非常多,
 *         【不 适 合】1:只适合发送字符串,不适合发送可能含0的数值类数据; 2-时间间隔要足够
 * 参  数: char strintTemp  要发送的字符串首地址
 * 返回值: 无
 * 备  注:  本函数为保留函数,留作用户参考。为了方便移植,本文件对外不再使用本函数。
 ******************************************************************************/
#if 0
void UART1_SendStringForDMA(char *stringTemp)
{
    static uint8_t Flag_DmaTxInit = 0;                // 用于标记是否已配置DMA发送
    uint32_t   num = 0;                               // 发送的数量,注意发送的单位不是必须8位的
    char *t = stringTemp ;                            // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                         // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    while (DMA1_Channel4->CNDTR > 0);                 // 重要:如果DMA还在进行上次发送,就等待; 得进完成中断清标志,F4不用这么麻烦,发送完后EN自动清零
    if (Flag_DmaTxInit == 0)                          // 是否已进行过USAART_TX的DMA传输配置
    {
        Flag_DmaTxInit  = 1;                          // 设置标记,下次调用本函数就不再进行配置了
        USART1 ->CR3   |= 1 << 7;                     // 使能DMA发送
        RCC->AHBENR    |= 1 << 0;                     // 开启DMA1时钟  [0]DMA1   [1]DMA2
        DMA1_Channel4->CCR   = 0;                     // 失能, 清0整个寄存器, DMA必须失能才能配置
        DMA1_Channel4->CNDTR = num;                   // 传输数据量
        DMA1_Channel4->CMAR  = (uint32_t)stringTemp;  // 存储器地址
        DMA1_Channel4->CPAR  = (uint32_t)&USART1->DR; // 外设地址
        DMA1_Channel4->CCR |= 1 << 4;                 // 数据传输方向   0:从外设读   1:从存储器读
        DMA1_Channel4->CCR |= 0 << 5;                 // 循环模式       0:不循环     1:循环
        DMA1_Channel4->CCR |= 0 << 6;                 // 外设地址非增量模式
        DMA1_Channel4->CCR |= 1 << 7;                 // 存储器增量模式
        DMA1_Channel4->CCR |= 0 << 8;                 // 外设数据宽度为8位
        DMA1_Channel4->CCR |= 0 << 10;                // 存储器数据宽度8位
        DMA1_Channel4->CCR |= 0 << 12;                // 中等优先级
        DMA1_Channel4->CCR |= 0 << 14;                // 非存储器到存储器模式
    }
    DMA1_Channel4->CCR  &= ~((uint32_t)(1 << 0));     // 失能,DMA必须失能才能配置
    DMA1_Channel4->CNDTR = num;                       // 传输数据量
    DMA1_Channel4->CMAR  = (uint32_t)stringTemp;      // 存储器地址
    DMA1_Channel4->CCR  |= 1 << 0;                    // 开启DMA传输
}
#endif
/******************************************************************************
 * 函  数: UART1_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART1_GetRxNum(void)
{
    return xUART1.usRxNum ;
}
/******************************************************************************
 * 函  数: UART1_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 缓存地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART1_GetRxData(void)
{
    return xUART1.puRxData ;
}
/******************************************************************************
 * 函  数: UART1_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART1_ClearRx(void)
{
    xUART1.usRxNum = 0 ;
}
#endif  // endif UART1_EN
//////////////////////////////////////////////////////////////   UART-2   ///////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART2_EN
static xUSATR_TypeDef  xUART2 = { 0 };                      // 定义 UART2 的收发结构体
static uint8_t uaUART2RxData[UART2_RX_BUF_SIZE];            // 定义 UART2 的接收缓存
static uint8_t uaUART2TxFiFoData[UART2_TX_BUF_SIZE];        // 定义 UART2 的发送缓存
/******************************************************************************
 * 函  数: UART2_Init
 * 功  能: 初始化USART2的通信引脚、协议参数、中断优先级
 *          引脚:TX-PA2、RX-PA3
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t  ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART2_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);           // 使能 GPIOA  时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);          // 使能 USART2 时钟
    // 配置 引脚的复用功能
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);       // 配置PA2复用功能:USART2
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);       // 配置PA3复用功能:USART2
    // 重置 USART
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2, ENABLE);          // 使能重置
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2, DISABLE);         // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;                     // 引脚编号:TX_PA2
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 RX引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;                     // 引脚编号:RX_PA3
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(USART2, &USART_InitStructure);                       // 初始化USART
    // 配置 中断
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);                  // 开启 接收中断
    USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                  // 开启 空闲中断
    // 配置 中断优先级
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = USART2_IRQn;              // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(USART2, ENABLE);                                      // 使能USART
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();                                   // 使能GPIOA
    __HAL_RCC_USART2_CLK_ENABLE();                                  // 使能USART2
    // 重置 USART
    __HAL_RCC_USART2_FORCE_RESET();                                 // 使能重置
    __HAL_RCC_USART2_RELEASE_RESET();                               // 取消重置
    // 配置 GPIO引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_2 | GPIO_PIN_3;                // 引脚 TX-PA2、RX-PA3
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_PULLUP;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;                    // 引脚复用功能
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率参数
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 4) / (ulBaudrate * 16);        // 波特率公式计算; USART2挂载在APB1, 时钟为系统时钟的4分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    USART2 -> BRR  = mantissa << 4 | fraction;                      // 设置波特率
    // 配置 UART
    USART2 -> CR1  =   0;                                           // 清0
    USART2 -> CR1 |=   0x01 << 2;                                   // 接收使能[02]: 0=失能、1=使能
    USART2 -> CR1 |=   0x01 << 3;                                   // 发送使能[03]:0=失能、1=使能
    USART2 -> CR1 |=   0x00 << 9;                                   // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    USART2 -> CR1 |=   0x00 << 10;                                  // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    USART2 -> CR1 |=   0x00 << 12;                                  // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    USART2 -> CR2  =   0;                                           // 数据清0
    USART2 -> CR2 &= ~(0x03 << 12);                                 // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    USART2 -> CR3  =   0;                                           // 数据清0
    USART2 -> CR3 &= ~(0x01 << 6);                                  // DMA接收[6]: 0=禁止、1=使能
    USART2 -> CR3 &= ~(0x01 << 7);                                  // DMA发送[7]: 0=禁止、1=使能
    // 配置 中断
    USART2 -> CR1 &= ~(0x01 << 7);                                  // 关闭发送中断
    USART2 -> CR1 |=   0x01 << 5;                                   // 使能接收中断: 接收缓冲区非空
    USART2 -> CR1 |=   0x01 << 4;                                   // 使能空闲中断:超过1字节时间没收到新数据
    USART2 -> SR   = ~(0x00F0);                                     // 清理中断
    // 配置 中断优先级
    HAL_NVIC_SetPriority(USART2_IRQn, 1, 1);                        // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(USART2_IRQn);                                // 使能、启用指定的中断
    // 配置完成,开启USART
    USART2 -> CR1 |=   0x01 << 13;                                  // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART2.puRxData = uaUART2RxData;                                // 获取接收缓冲区的地址
    xUART2.puTxFiFoData = uaUART2TxFiFoData;                        // 获取发送缓冲区的地址
    // 输出提示
    printf("UART2 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate);
}
/******************************************************************************
 * 函  数: USART2_IRQHandler
 * 功  能: USART2的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
 ******************************************************************************/
void USART2_IRQHandler(void)
{
    static uint16_t cnt = 0;                                             // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART2_RX_BUF_SIZE];                           // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到外部缓存:xUARTx.puRxData[ ]
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((USART2->SR & USART_SR_TXE) && (USART2->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART2->DR = xUART2.puTxFiFoData[xUART2.usTxFiFoTail++];         // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART2.usTxFiFoTail == UART2_TX_BUF_SIZE)                    // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART2.usTxFiFoTail = 0;                                     // 将尾指针重置为0,实现环形队列的功能
        if (xUART2.usTxFiFoTail == xUART2.usTxFiFoData)                  // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            USART2->CR1 &= ~USART_CR1_TXEIE;                             // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (USART2->SR & USART_SR_RXNE)                                      // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt == UART2_RX_BUF_SIZE)                                    // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:UART2单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART2_RX_BUF_SIZE值,可解决此问题!\r");
            USART2->DR;                                                  // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = USART2->DR ;                                     // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (USART2->SR & USART_SR_IDLE)                                      // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART2.puRxData, rxTemp, UART2_RX_BUF_SIZE);              // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART2.usRxNum  = cnt;                                           // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                         // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART2_RX_BUF_SIZE);                            // 接收数据缓存数组,清零; 准备下一次的接收
        USART2 ->SR;
        USART2 ->DR;                                                     // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART2_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t* puData     需发送数据的地址
 *          uint8_t  usNum      发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART2_SendData(uint8_t *puData, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                         // 把数据放入环形缓冲区
    {
        xUART2.puTxFiFoData[xUART2.usTxFiFoData++] = puData[i];  // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART2.usTxFiFoData == UART2_TX_BUF_SIZE)            // 如果指针位置到达缓冲区的最大值,则归0
            xUART2.usTxFiFoData = 0;
    }
    if ((USART2->CR1 & USART_CR1_TXEIE) == 0)                    // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        USART2->CR1 |= USART_CR1_TXEIE;                          // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART2_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART2_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                               // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                             // 新建一个可变参数列表
    va_start(ap, pcString);                                 // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                  // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                             // 清空可变参数列表
    UART2_SendData((uint8_t *)mBuffer, strlen(mBuffer));    // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART2_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART2_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART2_ClearRx();                                              // 清0
    UART2_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART2_GetRxNum())                                     // 判断是否接收到数据
        {
            UART2_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART2_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART2_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART2_GetRxNum(void)
{
    return xUART2.usRxNum ;
}
/******************************************************************************
 * 函  数: UART2_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 数据的地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART2_GetRxData(void)
{
    return xUART2.puRxData ;
}
/******************************************************************************
 * 函  数: UART2_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART2_ClearRx(void)
{
    xUART2.usRxNum = 0 ;
}
#endif  // endif UART2_EN
//////////////////////////////////////////////////////////////   USART-3   //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART3_EN
static xUSATR_TypeDef  xUART3 = { 0 };                      // 定义 UART3 的收发结构体
static uint8_t uaUart3RxData[UART3_RX_BUF_SIZE];            // 定义 UART3 的接收缓存
static uint8_t uaUart3TxFiFoData[UART3_TX_BUF_SIZE];        // 定义 UART3 的发送缓存
/******************************************************************************
 * 函  数: UART3_Init
 * 功  能: 初始化USART3的通信引脚、协议参数、中断优先级
 *          引脚:TX-PB10、RX-PB11
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART3_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);           // 使能 GPIOB  时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);          // 使能 USART3 时钟
    // 配置 引脚的复用功能
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);      // 配置PB10复用功能:USART3
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);      // 配置PB11复用功能:USART3
    // 重置 USART
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART3, ENABLE);          // 使能重置
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART3, DISABLE);         // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;                    // 引脚编号
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOB, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 RX引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;                    // 引脚编号
    GPIO_Init(GPIOB, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(USART3, &USART_InitStructure);                       // 初始化USART
    // 配置 中断
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);                  // 开启 接收中断
    USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);                  // 开启 空闲中断
    // 配置 中断优先级
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = USART3_IRQn;              // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(USART3, ENABLE);                                      // 使能USART
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();                                   // 使能GPIOB
    __HAL_RCC_USART3_CLK_ENABLE();                                  // 使能USART3
    // 重置 USART
    __HAL_RCC_USART3_FORCE_RESET();                                 // 使能重置
    __HAL_RCC_USART3_RELEASE_RESET();                               // 取消重置
    // 配置 GPIO引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_10 | GPIO_PIN_11;              // 引脚 TX-PB10、RX-PB11
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_PULLUP;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF7_USART3;                    // 引脚复用功能
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率参数
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 4) / (ulBaudrate * 16);        // 波特率公式计算; USART3挂载在APB1, 时钟为系统时钟的4分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    USART3 -> BRR  = mantissa << 4 | fraction;                      // 设置波特率
    // 配置 UART
    USART3 -> CR1  =   0;                                           // 清0
    USART3 -> CR1 |=   0x01 << 2;                                   // 接收使能[02]: 0=失能、1=使能
    USART3 -> CR1 |=   0x01 << 3;                                   // 发送使能[03]:0=失能、1=使能
    USART3 -> CR1 |=   0x00 << 9;                                   // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    USART3 -> CR1 |=   0x00 << 10;                                  // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    USART3 -> CR1 |=   0x00 << 12;                                  // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    USART3 -> CR2  =   0;                                           // 数据清0
    USART3 -> CR2 &= ~(0x03 << 12);                                 // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    USART3 -> CR3  =   0;                                           // 数据清0
    USART3 -> CR3 &= ~(0x01 << 6);                                  // DMA接收[6]: 0=禁止、1=使能
    USART3 -> CR3 &= ~(0x01 << 7);                                  // DMA发送[7]: 0=禁止、1=使能
    // 配置中断
    USART3 -> CR1 &= ~(0x01 << 7);                                  // 关闭发送中断
    USART3 -> CR1 |=   0x01 << 5;                                   // 使能接收中断: 接收缓冲区非空
    USART3 -> CR1 |=   0x01 << 4;                                   // 使能空闲中断:超过1字节时间没收到新数据
    USART3 -> SR   = ~(0x00F0);                                     // 清理一次中断标志
    // 配置 中断优先级
    HAL_NVIC_SetPriority(USART3_IRQn, 1, 1);                        // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(USART3_IRQn);                                // 使能、启用指定的中断
    // 配置完成,开启USART
    USART3 -> CR1 |=   0x01 << 13;                                  // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART3.puRxData = uaUart3RxData;                                // 获取接收缓冲区的地址
    xUART3.puTxFiFoData = uaUart3TxFiFoData;                        // 获取发送缓冲区的地址
    // 输出提示
    printf("UART3 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate);
}
/******************************************************************************
 * 函  数: USART3_IRQHandler
 * 功  能: USART3的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
 ******************************************************************************/
void USART3_IRQHandler(void)
{
    static uint16_t cnt = 0;                                             // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART3_RX_BUF_SIZE];                           // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到外部缓存:xUARTx.puRxData[ ]
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((USART3->SR & USART_SR_TXE) && (USART3->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART3->DR = xUART3.puTxFiFoData[xUART3.usTxFiFoTail++];         // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART3.usTxFiFoTail == UART3_TX_BUF_SIZE)                    // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART3.usTxFiFoTail = 0;                                     // 将尾指针重置为0,实现环形队列的功能
        if (xUART3.usTxFiFoTail == xUART3.usTxFiFoData)                  // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            USART3->CR1 &= ~USART_CR1_TXEIE;                             // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (USART3->SR & USART_SR_RXNE)                                      // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt == UART3_RX_BUF_SIZE)                                    // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:UART3单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART3_RX_BUF_SIZE值,可解决此问题!\r");
            USART3->DR;                                                  // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = USART3->DR ;                                     // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (USART3->SR & USART_SR_IDLE)                                      // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART3.puRxData, rxTemp, UART3_RX_BUF_SIZE);              // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART3.usRxNum  = cnt;                                           // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                         // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART3_RX_BUF_SIZE);                            // 接收数据缓存数组,清零; 准备下一次的接收
        USART3 ->SR;
        USART3 ->DR;                                                     // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART3_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t* puData   需发送数据的地址
 *          uint8_t  usNum      发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART3_SendData(uint8_t *puData, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                           // 把数据放入环形缓冲区
    {
        xUART3.puTxFiFoData[xUART3.usTxFiFoData++] = puData[i];    // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART3.usTxFiFoData == UART3_TX_BUF_SIZE)              // 如果指针位置到达缓冲区的最大值,则归0
            xUART3.usTxFiFoData = 0;
    }
    if ((USART3->CR1 & USART_CR1_TXEIE) == 0)                      // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        USART3->CR1 |= USART_CR1_TXEIE;                            // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART3_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART3_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                                // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                              // 新建一个可变参数列表
    va_start(ap, pcString);                                  // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                   // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                              // 清空可变参数列表
    UART3_SendData((uint8_t *)mBuffer, strlen(mBuffer));     // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART3_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART3_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART3_ClearRx();                                              // 清0
    UART3_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART3_GetRxNum())                                     // 判断是否接收到数据
        {
            UART3_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART3_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART3_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART3_GetRxNum(void)
{
    return xUART3.usRxNum ;
}
/******************************************************************************
 * 函  数: UART3_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 数据的地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART3_GetRxData(void)
{
    return xUART3.puRxData ;
}
/******************************************************************************
 * 函  数: UART3_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART3_ClearRx(void)
{
    xUART3.usRxNum = 0 ;
}
#endif  // endif UART3_EN
//////////////////////////////////////////////////////////////   UART-4   //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART4_EN
static xUSATR_TypeDef  xUART4 = { 0 };                      // 定义 UART4 的收发结构体
static uint8_t uaUart4RxData[UART4_RX_BUF_SIZE];            // 定义 UART4 的接收缓存
static uint8_t uaUart4TxFiFoData[UART4_TX_BUF_SIZE];        // 定义 UART4 的发送缓存
/******************************************************************************
 * 函  数: UART4_Init
 * 功  能: 初始化UART4的通信引脚、协议参数、中断优先级
 *          引脚:TX-PC10、RX-PC11
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART4_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);           // 使能 GPIOC 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);           // 使能 UART4 时钟
    // 配置 引脚复用功能
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_UART4);       // 配置PC10复用功能:UART4
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_UART4);       // 配置PC11复用功能:UART4
    // 重置 USART
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART4, ENABLE);           // 使能重置
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART4, DISABLE);          // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;                    // 引脚编号
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOC, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 RX引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;                    // 引脚编号
    GPIO_Init(GPIOC, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(UART4, &USART_InitStructure);                        // 初始化USART
    // 配置 中断
    USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);                   // 开启 接收中断
    USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);                   // 开启 空闲中断
    // 配置 中断优先级
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = UART4_IRQn;               // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(UART4, ENABLE);                                       // 使能USART
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();                                   // 使能GPIOC
    __HAL_RCC_UART4_CLK_ENABLE();                                   // 使能UART4
    // 重置 USART
    __HAL_RCC_UART4_FORCE_RESET();                                  // 使能重置
    __HAL_RCC_UART4_RELEASE_RESET();                                // 取消重置
    // 配置 GPIO引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_10 | GPIO_PIN_11;              // 引脚 TX-PC10、RX-PC11
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_PULLUP;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF8_UART4;                     // 引脚复用功能
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率参数
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 4) / (ulBaudrate * 16);        // 波特率公式计算; UART4挂载在APB1, 时钟为系统时钟的4分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    UART4 -> BRR  = mantissa << 4 | fraction;                       // 设置波特率
    // 配置 UART
    UART4 -> CR1  =   0;                                            // 清0
    UART4 -> CR1 |=   0x01 << 2;                                    // 接收使能[02]: 0=失能、1=使能
    UART4 -> CR1 |=   0x01 << 3;                                    // 发送使能[03]:0=失能、1=使能
    UART4 -> CR1 |=   0x00 << 9;                                    // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    UART4 -> CR1 |=   0x00 << 10;                                   // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    UART4 -> CR1 |=   0x00 << 12;                                   // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    UART4 -> CR2  =   0;                                            // 数据清0
    UART4 -> CR2 &= ~(0x03 << 12);                                  // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    UART4 -> CR3  =   0;                                            // 数据清0
    UART4 -> CR3 &= ~(0x01 << 6);                                   // DMA接收[6]: 0=禁止、1=使能
    UART4 -> CR3 &= ~(0x01 << 7);                                   // DMA发送[7]: 0=禁止、1=使能
    // 配置 中断
    UART4 -> CR1 &= ~(0x01 << 7);                                   // 关闭发送中断
    UART4 -> CR1 |=   0x01 << 5;                                    // 使能接收中断: 接收缓冲区非空
    UART4 -> CR1 |=   0x01 << 4;                                    // 使能空闲中断:超过1字节时间没收到新数据
    UART4 -> SR   = ~(0x00F0);                                      // 清理中断
    // 配置 中断优选级
    HAL_NVIC_SetPriority(UART4_IRQn, 1, 1);                         // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(UART4_IRQn);                                 // 使能、启用指定的中断
    // 配置完成,打开串口
    UART4 -> CR1 |=   0x01 << 13;                                   // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART4.puRxData = uaUart4RxData;                                // 获取接收缓冲区的地址
    xUART4.puTxFiFoData = uaUart4TxFiFoData;                        // 获取发送缓冲区的地址
    // 输出提示
    printf("UART4 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate);
}
/******************************************************************************
 * 函  数: UART4_IRQHandler
 * 功  能: UART4的中断处理函数
 *          接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
 ******************************************************************************/
void UART4_IRQHandler(void)
{
    static uint16_t cnt = 0;                                           // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART4_RX_BUF_SIZE];                         // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到外部缓存:xUARTx.puRxData[ ]
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((UART4->SR & USART_SR_TXE) && (UART4->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        UART4->DR = xUART4.puTxFiFoData[xUART4.usTxFiFoTail++];        // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART4.usTxFiFoTail == UART4_TX_BUF_SIZE)                  // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART4.usTxFiFoTail = 0;                                   // 将尾指针重置为0,实现环形队列的功能
        if (xUART4.usTxFiFoTail == xUART4.usTxFiFoData)                // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            UART4->CR1 &= ~USART_CR1_TXEIE;                            // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (UART4->SR & USART_SR_RXNE)                                     // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt == UART4_RX_BUF_SIZE)                                  // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:UART4单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART4_RX_BUF_SIZE值,可解决此问题!\r");
            UART4->DR;                                                 // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = UART4->DR ;                                    // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (UART4->SR & USART_SR_IDLE)                                     // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART4.puRxData, rxTemp, UART4_RX_BUF_SIZE);            // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART4.usRxNum  = cnt;                                         // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                       // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART4_RX_BUF_SIZE);                          // 接收数据缓存数组,清零; 准备下一次的接收
        UART4 ->SR;
        UART4 ->DR;                                                    // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART4_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t* puData   需发送数据的地址
 *          uint8_t  usNum    发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART4_SendData(uint8_t *puData, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                         // 把数据放入环形缓冲区
    {
        xUART4.puTxFiFoData[xUART4.usTxFiFoData++] = puData[i];  // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART4.usTxFiFoData == UART4_TX_BUF_SIZE)            // 如果指针位置到达缓冲区的最大值,则归0
            xUART4.usTxFiFoData = 0;
    }
    if ((UART4->CR1 & USART_CR1_TXEIE) == 0)                     // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        UART4->CR1 |= USART_CR1_TXEIE;                           // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART4_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART4_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                                // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                              // 新建一个可变参数列表
    va_start(ap, pcString);                                  // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                   // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                              // 清空可变参数列表
    UART4_SendData((uint8_t *)mBuffer, strlen(mBuffer));     // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART4_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART4_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART4_ClearRx();                                              // 清0
    UART4_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART4_GetRxNum())                                     // 判断是否接收到数据
        {
            UART4_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART4_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART4_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART4_GetRxNum(void)
{
    return xUART4.usRxNum ;
}
/******************************************************************************
 * 函  数: UART4_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 数据的地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART4_GetRxData(void)
{
    return xUART4.puRxData ;
}
/******************************************************************************
 * 函  数: UART4_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART4_ClearRx(void)
{
    xUART4.usRxNum = 0 ;
}
#endif  // endif UART4_EN
//////////////////////////////////////////////////////////////   UART-5   //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART5_EN
static xUSATR_TypeDef  xUART5 = { 0 };                      // 定义 UART5 的收发结构体
static uint8_t uaUart5RxData[UART5_RX_BUF_SIZE];            // 定义 UART5 的接收缓存
static uint8_t uaUart5TxFiFoData[UART5_TX_BUF_SIZE];        // 定义 UART5 的发送缓存
/******************************************************************************
 * 函  数: UART5_Init
 * 功  能: 初始化UART5的通信引脚、协议参数、中断优先级
 *          引脚:TX-PC12、RX-PD2
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART5_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);           // 使能 GPIOC 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);           // 使能 GPIOD 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE);           // 使能 UART5 时钟
    // 配置 引脚复用功能
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_UART5);       // 配置PC12复用功能:UART5
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_UART5);        // 配置PD2复用功能 :UART5
    // 重置 USART
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART5, ENABLE);           // 使能重置
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART5, DISABLE);          // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;                    // 引脚编号
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOC, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 RX引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;                     // 引脚编号
    GPIO_Init(GPIOD, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(UART5, &USART_InitStructure);                        // 初始化USART
    // 配置 中断
    USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);                   // 开启 接收中断
    USART_ITConfig(UART5, USART_IT_IDLE, ENABLE);                   // 开启 空闲中断
    // 中断优先级配置
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = UART5_IRQn;               // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(UART5, ENABLE);                                       // 使能USART
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();                                   // 使能GPIOC
    __HAL_RCC_GPIOD_CLK_ENABLE();                                   // 使能GPIOD
    __HAL_RCC_UART5_CLK_ENABLE();                                   // 使能UART5
    // 重置 UART
    __HAL_RCC_UART5_FORCE_RESET();                                  // 使能重置
    __HAL_RCC_UART5_RELEASE_RESET();                                // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_12 ;                           // 引脚 TX-PC12
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_NOPULL;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF8_UART5;                     // 引脚复用功能
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);                         // 初始化引脚
    // 配置 RX引脚
    GPIO_InitStruct.Pin   = GPIO_PIN_2 ;                            // 引脚 RX-PD2
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率参数
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 4) / (ulBaudrate * 16);        // 波特率公式计算; UART5挂载在APB1, 时钟为系统时钟的4分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    UART5 -> BRR  = mantissa << 4 | fraction;                       // 设置波特率
    // 配置 UART
    UART5 -> CR1  =   0;                                            // 清0
    UART5 -> CR1 |=   0x01 << 2;                                    // 接收使能[02]: 0=失能、1=使能
    UART5 -> CR1 |=   0x01 << 3;                                    // 发送使能[03]:0=失能、1=使能
    UART5 -> CR1 |=   0x00 << 9;                                    // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    UART5 -> CR1 |=   0x00 << 10;                                   // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    UART5 -> CR1 |=   0x00 << 12;                                   // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    UART5 -> CR2  =   0;                                            // 数据清0
    UART5 -> CR2 &= ~(0x03 << 12);                                  // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    UART5 -> CR3  =   0;                                            // 数据清0
    UART5 -> CR3 &= ~(0x01 << 6);                                   // DMA接收[6]: 0=禁止、1=使能
    UART5 -> CR3 &= ~(0x01 << 7);                                   // DMA发送[7]: 0=禁止、1=使能
    // 配置 中断
    UART5 -> CR1 &= ~(0x01 << 7);                                   // 关闭发送中断
    UART5 -> CR1 |=   0x01 << 5;                                    // 使能接收中断: 接收缓冲区非空
    UART5 -> CR1 |=   0x01 << 4;                                    // 使能空闲中断:超过1字节时间没收到新数据
    UART5 -> SR   = ~(0x00F0);                                      // 清理中断
    // 配置 中断优先级
    HAL_NVIC_SetPriority(UART5_IRQn, 0, 0);                         // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(UART5_IRQn);                                 // 使能、启用指定的中断
    // 配置完成,打开串口
    UART5 -> CR1 |=   0x01 << 13;                                   // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART5.puRxData = uaUart5RxData;                                // 获取接收缓冲区的地址
    xUART5.puTxFiFoData = uaUart5TxFiFoData;                        // 获取发送缓冲区的地址
    // 输出提示
    printf("UART5 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate);
}
/******************************************************************************
 * 函  数: UART5_IRQHandler
 * 功  能: UART5的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
 ******************************************************************************/
void UART5_IRQHandler(void)
{
    static uint16_t cnt = 0;                                           // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART5_RX_BUF_SIZE];                         // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到外部缓存:xUARTx.puRxData[ ]
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((UART5->SR & USART_SR_TXE) && (UART5->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        UART5->DR = xUART5.puTxFiFoData[xUART5.usTxFiFoTail++];        // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART5.usTxFiFoTail == UART5_TX_BUF_SIZE)                  // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART5.usTxFiFoTail = 0;                                   // 将尾指针重置为0,实现环形队列的功能
        if (xUART5.usTxFiFoTail == xUART5.usTxFiFoData)                // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            UART5->CR1 &= ~USART_CR1_TXEIE;                            // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (UART5->SR & USART_SR_RXNE)                                     // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt == UART5_RX_BUF_SIZE)                                  // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:UART5单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART5_RX_BUF_SIZE值,可解决此问题!\r");
            UART5->DR;                                                 // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = UART5->DR ;                                    // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (UART5->SR & USART_SR_IDLE)                                     // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART5.puRxData, rxTemp, UART5_RX_BUF_SIZE);            // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART5.usRxNum  = cnt;                                         // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                       // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART5_RX_BUF_SIZE);                          // 接收数据缓存数组,清零; 准备下一次的接收
        UART5 -> SR;
        UART5 -> DR;                                                   // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART5_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t* pudata     需发送数据的地址
 *          uint8_t  usNum      发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART5_SendData(uint8_t *pudata, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                         // 把数据放入环形缓冲区
    {
        xUART5.puTxFiFoData[xUART5.usTxFiFoData++] = pudata[i];  // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART5.usTxFiFoData == UART5_TX_BUF_SIZE)            // 如果指针位置到达缓冲区的最大值,则归0
            xUART5.usTxFiFoData = 0;
    }
    if ((UART5->CR1 & USART_CR1_TXEIE) == 0)                     // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        UART5->CR1 |= USART_CR1_TXEIE;                           // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART5_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART5_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                               // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                             // 新建一个可变参数列表
    va_start(ap, pcString);                                 // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                  // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                             // 清空可变参数列表
    UART5_SendData((uint8_t *)mBuffer, strlen(mBuffer));    // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART5_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART5_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART5_ClearRx();                                              // 清0
    UART5_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART5_GetRxNum())                                     // 判断是否接收到数据
        {
            UART5_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART5_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART5_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART5_GetRxNum(void)
{
    return xUART5.usRxNum ;
}
/******************************************************************************
 * 函  数: UART5_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 数据的地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART5_GetRxData(void)
{
    return xUART5.puRxData ;
}
/******************************************************************************
 * 函  数: UART5_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART5_ClearRx(void)
{
    xUART5.usRxNum = 0 ;
}
#endif  // endif UART5_EN
//////////////////////////////////////////////////////////////   USART-6   //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if UART6_EN
static xUSATR_TypeDef  xUART6 = { 0 };                      // 定义 UART6 的收发结构体
static uint8_t uaUart6RxData[UART6_RX_BUF_SIZE];            // 定义 UART6 的接收缓存
static uint8_t uaUart6TxFiFoData[UART6_TX_BUF_SIZE];        // 定义 UART6 的发送缓存
/******************************************************************************
 * 函  数: UART6_Init
 * 功  能: 初始化USART6的通信引脚、协议参数、中断优先级
 *          引脚:TX-PC6、RX-PC7
 *          协议:波特率-None-8-1
 *          发送:发送中断
 *          接收:接收+空闲中断
 *
 * 参  数: uint32_t ulBaudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART6_Init(uint32_t ulBaudrate)
{
#ifdef USE_STDPERIPH_DRIVER
    // 使能 时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);           // 使能 GPIOC  时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6, ENABLE);          // 使能 USART6 时钟
    // 配置 引脚复用功能
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_USART6);       // 配置PC6复用功能:USART6
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_USART6);       // 配置PC7复用功能:USART6
    // 重置 USART
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART6, ENABLE);          // 使能重置
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART6, DISABLE);         // 取消重置
    // 配置 TX引脚
    GPIO_InitTypeDef  GPIO_InitStructure = {0};                     // GPIO 初始化结构体
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;                     // 引脚编号
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;                   // 引脚方向: 复用功能
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                  // 输出模式:推挽
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;                   // 上下拉:上拉
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;               // 输出速度:50MHz
    GPIO_Init(GPIOC, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置 RX引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7;                     // 引脚编号
    GPIO_Init(GPIOC, &GPIO_InitStructure);                          // 初始化:把上述参数,更新到芯片寄存器
    // 配置UART
    USART_InitTypeDef USART_InitStructure;                          // 声明USART初始化结构体
    USART_InitStructure.USART_BaudRate = ulBaudrate;                // 设置波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 设置字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 设置一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
    USART_Init(USART6, &USART_InitStructure);                       // 初始化USART
    // 配置 中断
    USART_ITConfig(USART6, USART_IT_RXNE, ENABLE);                  // 开启 接收中断
    USART_ITConfig(USART6, USART_IT_IDLE, ENABLE);                  // 开启 空闲中断
    // 中断优先级配置
    NVIC_InitTypeDef  NVIC_InitStructure = {0};                     // 中断优先级配置结构体
    NVIC_InitStructure .NVIC_IRQChannel = USART6_IRQn;              // 指定中断通道
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 1;      // 设置抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 1;             // 设置响应优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);                                 // 初始化NVIC
    // 配置完成,开启USART
    USART_Cmd(USART6, ENABLE);                                      // 使能USART
#endif
#ifdef USE_HAL_DRIVER                                               // HAL库 配置
    // 使能 时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();                                   // 使能GPIOC
    __HAL_RCC_USART6_CLK_ENABLE();                                  // 使能UART6
    // 重置 UART5
    __HAL_RCC_USART6_FORCE_RESET();                                 // 使能重置
    __HAL_RCC_USART6_RELEASE_RESET();                               // 取消重置
    // 配置 GPIO引脚
    GPIO_InitTypeDef    GPIO_InitStruct = {0};                      // 声明初始化要用到的结构体
    GPIO_InitStruct.Pin   = GPIO_PIN_6 | GPIO_PIN_7;                // 引脚 TX-PC6、RX-PC7
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;                        // 工作模式
    GPIO_InitStruct.Pull  = GPIO_PULLUP;                            // 上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;              // 引脚速率
    GPIO_InitStruct.Alternate = GPIO_AF8_USART6;                    // 引脚复用功能
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);                         // 初始化引脚工作模式
    // 计算波特率
    float    temp;
    uint16_t mantissa, fraction;
    SystemCoreClockUpdate();                                        // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
    temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16);        // 波特率公式计算; USART6挂载在APB2, 时钟为系统时钟的2分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
    mantissa = temp;				                                // 整数部分
    fraction = (temp - mantissa) * 16;                              // 小数部分
    USART6 -> BRR  = mantissa << 4 | fraction;                      // 设置波特率
    // 配置 UART
    USART6 -> CR1  =   0;                                           // 清0
    USART6 -> CR1 |=   0x01 << 2;                                   // 接收使能[02]: 0=失能、1=使能
    USART6 -> CR1 |=   0x01 << 3;                                   // 发送使能[03]:0=失能、1=使能
    USART6 -> CR1 |=   0x00 << 9;                                   // 奇偶校验[09]:0=偶Even、1=奇Odd;  注意:使用奇偶校验,下面两项要置1
    USART6 -> CR1 |=   0x00 << 10;                                  // 校验位  [10]:0=禁用、1=使能;     注意,使用奇偶校验,此位要置1 (否则无效、数据错乱)
    USART6 -> CR1 |=   0x00 << 12;                                  // 数据位  [12]:0=8位、 1=9位;      注意:使用奇偶校验,此位要置1 (否则数据会发生错乱)
    USART6 -> CR2  =   0;                                           // 数据清0
    USART6 -> CR2 &= ~(0x03 << 12);                                 // 停止位[13:12]:00b=1个停止位、01b=0.5个停止位、10b=2个停止位、11b=1.5个停止位
    USART6 -> CR3  =   0;                                           // 数据清0
    USART6 -> CR3 &= ~(0x01 << 6);                                  // DMA接收[6]: 0=禁止、1=使能
    USART6 -> CR3 &= ~(0x01 << 7);                                  // DMA发送[7]: 0=禁止、1=使能
    // 配置 中断
    USART6 -> CR1 &= ~(0x01 << 7);                                  // 关闭发送中断
    USART6 -> CR1 |=   0x01 << 5;                                   // 使能接收中断: 接收缓冲区非空
    USART6 -> CR1 |=   0x01 << 4;                                   // 使能空闲中断:超过1字节时间没收到新数据
    USART6 -> SR   = ~(0x00F0);                                     // 清理中断
    // 配置 中断优先级
    HAL_NVIC_SetPriority(USART6_IRQn, 1, 1);                        // 设置指定中断的响应优先级;  参数:中断请求编号、抢占级、子优先级
    HAL_NVIC_EnableIRQ(USART6_IRQn);                                // 使能、启用指定的中断
    // 配置完成,打开串口
    USART6 -> CR1 |=   0x01 << 13;                                  // 使能UART开始工作
#endif
    // 关联缓冲区
    xUART6.puRxData = uaUart6RxData;                                // 获取接收缓冲区的地址
    xUART6.puTxFiFoData = uaUart6TxFiFoData;                        // 获取发送缓冲区的地址
    // 输出提示
    printf("UART6 初始化配置         %d-None-8-1; 已完成初始化配置、收发配置\r", ulBaudrate);
}
/******************************************************************************
 * 函  数: USART6_IRQHandler
 * 功  能: USART6的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 * 备  注: 本函数,当产生中断事件时,由硬件调用。
 *          如果使用本文件代码,在工程文件的其它地方,要注释同名函数,否则冲突。
 ******************************************************************************/
void USART6_IRQHandler(void)
{
    static uint16_t cnt = 0;                                             // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  rxTemp[UART6_RX_BUF_SIZE];                           // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUARTx.puRxData[xx]中;
    // 发送中断:用于把环形缓冲的数据,逐字节发出
    if ((USART6->SR & USART_SR_TXE) && (USART6->CR1 & USART_CR1_TXEIE))  // 检查发送寄存器空中断使能,且发送寄存器为空; TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART6->DR = xUART6.puTxFiFoData[xUART6.usTxFiFoTail++];         // 从FIFO队列中取出一个数据,放入USART的发送寄存器(硬件会自动发出),然后将FIFO的尾指针递增,指向下一个要发送的数据
        if (xUART6.usTxFiFoTail == UART6_TX_BUF_SIZE)                    // 检查FIFO尾指针是否到达了FIFO队列的末尾
            xUART6.usTxFiFoTail = 0;                                     // 将尾指针重置为0,实现环形队列的功能
        if (xUART6.usTxFiFoTail == xUART6.usTxFiFoData)                  // 检查FIFO尾指针是否追上了头指针,即所有数据是否都已发送完毕
            USART6->CR1 &= ~USART_CR1_TXEIE;                             // 关闭发送寄存器空中断,防止中断服务程序被不必要地调用
        return;
    }
    // 接收中断:用于逐个字节接收,存放到临时缓存
    if (USART6->SR & USART_SR_RXNE)                                      // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if (cnt >= UART6_RX_BUF_SIZE)                                    // 当前帧已接收的字节量,已满缓存区的大小; 为避免溢出,本包后面接收到的数据直接舍弃;
        {
            printf("警告:USART6单帧接收量,已超出接收缓存大小!\r修改文件bsp_UART.h的UART6_RX_BUF_SIZE值,可解决此问题!\r");
            USART6->DR;                                                  // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        rxTemp[cnt++] = USART6->DR ;                                     // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位
        return;
    }
    // 空闲中断:用于判断一帧数据结束,整理数据到外部备读
    if (USART6->SR & USART_SR_IDLE)                                      // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        memcpy(xUART6.puRxData, rxTemp, UART6_RX_BUF_SIZE);              // 把本帧接收到的数据,存入到结构体的数组成员xUARTx.puRxData中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串输出时尾部以0作字符串结束符
        xUART6.usRxNum  = cnt;                                           // 把接收到的字节数,存入到结构体变量xUARTx.usRxNum中;
        cnt = 0;                                                         // 接收字节数累计器,清零; 准备下一次的接收
        memset(rxTemp, 0, UART6_RX_BUF_SIZE);                            // 接收数据缓存数组,清零; 准备下一次的接收
        USART6 ->SR;
        USART6 ->DR;                                                     // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
        return;
    }
    return;
}
/******************************************************************************
 * 函  数: UART6_SendData
 * 功  能: UART通过中断发送数据
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意h文件中所定义的发缓冲区大小、注意数据压入缓冲区的速度与串口发送速度的冲突
 * 参  数: uint8_t  *puData   需发送数据的地址
 *          uint8_t   usNum    发送的字节数 ,数量受限于h文件中设置的发送缓存区大小宏定义
 * 返回值: 无
 ******************************************************************************/
void UART6_SendData(uint8_t *puData, uint16_t usNum)
{
    for (uint16_t i = 0; i < usNum; i++)                           // 把数据放入环形缓冲区
    {
        xUART6.puTxFiFoData[xUART6.usTxFiFoData++] = puData[i];    // 把字节放到缓冲区最后的位置,然后指针后移
        if (xUART6.usTxFiFoData == UART6_TX_BUF_SIZE)              // 如果指针位置到达缓冲区的最大值,则归0
            xUART6.usTxFiFoData = 0;
    }
    if ((USART6->CR1 & USART_CR1_TXEIE) == 0)                      // 检查USART寄存器的发送缓冲区空置中断(TXEIE)是否已打开
        USART6->CR1 |= USART_CR1_TXEIE;                            // 打开TXEIE中断
}
/******************************************************************************
 * 函  数: UART6_SendString
 * 功  能: 发送字符串
 *          用法请参考printf,及示例中的展示
 *          注意,最大发送字节数为512-1个字符,可在函数中修改上限
 * 参  数: const char *pcString, ...   (如同printf的用法)
 * 返回值: 无
 ******************************************************************************/
void UART6_SendString(const char *pcString, ...)
{
    char mBuffer[512] = {0};;                               // 开辟一个缓存, 并把里面的数据置0
    va_list ap;                                             // 新建一个可变参数列表
    va_start(ap, pcString);                                 // 列表指向第一个可变参数
    vsnprintf(mBuffer, 512, pcString, ap);                  // 把所有参数,按格式,输出到缓存; 参数2用于限制发送的最大字节数,如果达到上限,则只发送上限值-1; 最后1字节自动置'\0'
    va_end(ap);                                             // 清空可变参数列表
    UART6_SendData((uint8_t *)mBuffer, strlen(mBuffer));    // 把字节存放环形缓冲,排队准备发送
}
/******************************************************************************
 * 函    数: UART6_SendAT
 * 功    能: 发送AT命令, 并等待返回信息
 * 参    数: char     *pcString      AT指令字符串
 *            char     *pcAckString   期待的指令返回信息字符串
 *            uint16_t  usTimeOut     发送命令后等待的时间,毫秒
 *
 * 返 回 值: 0-执行失败、1-执行正常
 ******************************************************************************/
uint8_t UART6_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs)
{
    UART6_ClearRx();                                              // 清0
    UART6_SendString(pcAT);                                       // 发送AT指令字符串
    while (usTimeOutMs--)                                         // 判断是否起时(这里只作简单的循环判断次数处理)
    {
        if (UART6_GetRxNum())                                     // 判断是否接收到数据
        {
            UART6_ClearRx();                                      // 清0接收字节数; 注意:接收到的数据内容 ,是没有被清0的
            if (strstr((char *)UART6_GetRxData(), pcAckString))   // 判断返回数据中是否有期待的字符
                return 1;                                         // 返回:0-超时没有返回、1-正常返回期待值
        }
        delay_ms(1);                                              // 延时; 用于超时退出处理,避免死等
    }
    return 0;                                                     // 返回:0-超时、返回异常,1-正常返回期待值
}
/******************************************************************************
 * 函  数: UART6_GetRxNum
 * 功  能: 获取最新一帧数据的字节数
 * 参  数: 无
 * 返回值: 0=没有接收到数据,非0=新一帧数据的字节数
 ******************************************************************************/
uint16_t UART6_GetRxNum(void)
{
    return xUART6.usRxNum ;
}
/******************************************************************************
 * 函  数: UART6_GetRxData
 * 功  能: 获取最新一帧数据 (数据的地址)
 * 参  数: 无
 * 返回值: 数据的地址(uint8_t*)
 ******************************************************************************/
uint8_t *UART6_GetRxData(void)
{
    return xUART6.puRxData ;
}
/******************************************************************************
 * 函  数: UART6_ClearRx
 * 功  能: 清理最后一帧数据的缓存
 *          主要是清0字节数,因为它是用来判断接收的标准
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
void UART6_ClearRx(void)
{
    xUART6.usRxNum = 0 ;
}
#endif  // endif UART6_EN
/////////////////////////////////////////////////////////  ModbusCRC16 校验   /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CRC16查表法值表数组
// 查表法相比直接计算CRC值,大约可减少一半运算时间
static const uint8_t aModbusCRC16[] =
{
    // 高位
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    // 低位
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
    0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
    0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
    0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
    0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
    0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
    0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
    0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
    0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
/**********************************************************************************************************
 * 函 数:  Modbus_AddCRC16
 * 功 能:  在数据末尾追加两字节的ModbusCRC16校验值
 * 备 注: 1、函数将根据传入的原数据,计算CRC16值,在数据末尾追加两字节的CRC16校验值;
 *         2、注意传入的数组地址,要至少预留2字节的空间,用于追加CRC16的校验值;
 *
 * 参 数:  uint8_t*  _pcData:参与计算校验值的数据
 *         uint16_t  _usLen :数据字节数; 长度为数据的原字节数即可,不用加2;
 *
 * 返 回:  无
**********************************************************************************************************/
void  Modbus_AddCRC16(uint8_t *_pcData, uint16_t _usLen)
{
    uint8_t  ucCRCHi = 0xFF;                        // CRC值高位
    uint8_t  ucCRCLo = 0xFF;                        // CRC值低位
    uint16_t usIndex;                               // 索引
    while (_usLen--)                                // 开始逐字节查表
    {
        usIndex = ucCRCHi ^ *_pcData++;             // 注意,这里指针地址加1了
        ucCRCHi = ucCRCLo ^ aModbusCRC16[usIndex];
        ucCRCLo = aModbusCRC16[usIndex + 256];
    }
    _pcData[0] = ucCRCHi;                           // 末尾第1字节,追加CRC16的高位
    _pcData[1] = ucCRCLo;                           // 末尾第2字节,追加CRC16的低位
}
/**********************************************************************************************************
 * 函 数:  Modbus_CheckCRC16
 * 功 能:  对带ModbusCRC16校验的数据段进行校验
 *
 * 参 数:  uint8_t*  _pcData:数据段地址
 *         uint16_t  _usLen :数据段的长度(字节数); 长度是整包数据的字节数,即包含ModbusCRC16值的长度,不用减2;
 *
 * 返 回:  0-错误、1-正确
*******************************************************************************************/
uint8_t  Modbus_CheckCRC16(uint8_t *_pcData, uint16_t _usLen)
{
    uint8_t  ucCRCHi = 0xFF;                               // CRC值高位
    uint8_t  ucCRCLo = 0xFF;                               // CRC值低位
    uint16_t usIndex;                                      // 数组的索引
    _usLen -= 2;                                           // 字节数-2,不计算原数据末尾两字节(ModbusCRC16值)
    while (_usLen--)                                       // 开始逐字节查表获得ModbusCRC16值
    {
        usIndex = ucCRCHi ^ *_pcData++;                    // 注意,这里指针地址递增加1
        ucCRCHi = ucCRCLo ^ aModbusCRC16[usIndex];
        ucCRCLo = aModbusCRC16[usIndex + 256];
    }
    if ((ucCRCHi == *_pcData++) && (ucCRCLo == *_pcData))  // 把数据段的CRC16校验值,与原数据末尾的CRC16值相比较
        return 1;                                          // 成功匹配; 返回: 1
    return 0;                                              // 错误; 返回:0
}
/////////////////////////////////////////////////////////////  辅助功能   /////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/******************************************************************************
 * 函  数: debug_showData
 * 功  能: 经printf,发送到串口助手上,方便观察
 * 参  数: uint8_t  *rxData   数据地址
 *          uint16_t  rxNum    字节数
 * 返回值: 无
 ******************************************************************************/
#if 0
void debug_showData(uint8_t *puRxData, uint16_t usRxNum)
{
    printf("字节数: %d \r", usRxNum);                   // 显示字节数
    printf("ASCII 显示数据: %s\r", (char *)puRxData);    // 显示数据,以ASCII方式显示,即以字符串的方式显示
    printf("16进制显示数据: ");                          // 显示数据,以16进制方式,显示每一个字节的值
    while (usRxNum--)                                    // 逐个字节判断,只要不为'\0', 就继续
        printf("0x%X ", *puRxData++);                    // 格式化
    printf("\r\r");                                      // 显示换行
}
#endif
//////////////////////////////////////////////////////////////  printf   //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/******************************************************************************
 * 功能说明: printf函数支持代码
 *           【特别注意】加入以下代码, 使用printf函数时, 不再需要打勾use MicroLIB
 * 备    注:
 * 最后更新: 2024年06月07日
 ******************************************************************************/
#pragma import(__use_no_semihosting)
struct __FILE
{
    int handle;
};                                         // 标准库需要的支持函数
FILE __stdout;                             // FILE 在stdio.h文件
void _sys_exit(int x)
{
    x = x;                                 // 定义_sys_exit()以避免使用半主机模式
}
int fputc(int ch, FILE *f)                 // 重定向fputc函数,使printf的输出,由fputc输出到UART
{
#if 1                                      // 方式1-使用常用的方式发送数据,比较容易理解,但等待耗时大
    while ((USART1->SR & 0X40) == 0);      // 等待上一次串口数据发送完成
    USART1->DR = ch;                       // 写DR,串口1将发送数据
#else
    UART1_SendData((uint8_t *)&ch, 1);     // 方式2-使用队列+中断方式发送数据; 无需像方式1那样等待耗时,但要借助已写好的函数、环形缓冲
#endif
    return ch;
}

posted @ 2025-09-30 17:41  wzzkaifa  阅读(14)  评论(0)    收藏  举报