【STM32 CubeMX + Keil】 RS485 通信 - 实践
目录
一、什么是 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网络 (另一端设备) |
---|---|---|
VCC | 5V | --- |
GND | GND (共地) | --- |
IN | UART_TX (PB10) | --- |
OUT | UART_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;
}