
STM32串口通信 -- bsp_UART.c 文件的移植和函数使用
USART1、USART2、USART3、UART4、UART5、USART6,都写好了。1、打开工程文件夹,bsp文件夹下,有1个usart文件夹。复制此文件夹,到你的工程目录。USART的通信原理,本篇不述,网上资料泛滥着,请自行csdn.net搜索。两个设备,通信时如何接线,这里也特意不述、不贴图。至此,你的工程,就可以使用这个已写好的串口文件了。玩家,只管接线,调用函数,不用理会底层。初始
约定:
- 文中串口通信,均为UART异步通信。
- 函数命名,不使用USART,统一使用:UART1、UART2、UART3、UART4、UART5、UART6。
文章目录:
一、前言
STM32的UART串口通信,在使用CubeMX进行配置时,已非常地方便。
配置后,只需编写少量的代码,即可实现收发。
但是,为了更省工夫:
- 移植更方便 (省掉CubeMX的配置,不用编写回调函数, 复制一下文件就能直接使用)
- 使用更方便 (函数简单清晰,能收发不同类型的数据,能通信大部分的UART模块)
同时也为了照顾一些刚玩的兄弟:不想费时间学习UART理论、费时间调试收发机制。
为此,对UART的常用初始化、通信,重写了两个文件:bsp_UART.c 和 bsp_UART.h。
文章末处,粘贴了完整的代码,需要的自行复制去。
常用的F103、F407,UART1~UART6都已编写、封装好了需要的函数。
每个 UART的操作,共有7个函数可调用。以UART1为例 , 下面将详细描述使用方法:
// 初始化
void UART1_Init(uint32_t ulBaudrate); // 初始化串口1; GPIO引脚PA9+PA10、中断优先级、通信参数:波特率可设、8位数据、无校验、1个停止位
// 发送
void UART1_SendString(const char *pcString, ...); // 发送字符串; 参数:字符串地址; 使用方法如同printf
uint8_t UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs); // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
void UART1_SendData(uint8_t *puData, uint16_t usNum); // 发送指定数据; 参数:数据地址、字节数
// 接收
uint16_t UART1_GetRxNum(void); // 获取接收到的最新一帧字节数
uint8_t* UART1_GetRxData(void); // 获取接收到的数据 (缓存的地址)
void UART1_ClearRx(void); // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
二、文件移植
1、新建 bsp_UART.c 和 bsp_UART.h这两个文件
- 如果已有魔女开发板的示例网盘,打开随便一个示例的 BSP 文件夹,把其下UART文件夹,复制到你的目标工程中即可,不用另行新建文件。
- 如果没有,就到文章末处,复制 bsp_UART.c 和 bsp_UART.h 的代码,在目标工程的文件夹中,新建bsp_UART.c 和 bsp_UART.h 这两个文件,对应粘贴进去即可。
2、工程添加 bsp_UART.c 文件
- 双击keil左侧的目标文件夹,添加bsp_UART.c到工程中。
- 也可以点击工具栏的 组管理 按钮进行添加; 操作如下图:
3、添加头文件路径;
刚才添加了 c文件到工程,还需要添加头文件的路径,编译器才能知道 h 文件的存放位置 。
操作如下:
4、添加文件引用
在需要UART功能的文件中,添加:#include "bsp_UART.h"
如,main.c 中想使用UART,就在它的头部位置添加。
操作如下:
好了,移植就是这么简单,至此,你的工程中已经可以正常使用UART功能了。
三、初始化、引脚使用
以使用UART1为例,其它几个UART的初始化方法相同。
调用:UART1_Init (115200) ; // 参数:波特率
操作如下:
此函数将会执行:引脚PA9+PA10的初始化、UART1初始化、中断初始化、进入接收状态。
初始化仅需调用这个函数即可。不用修改代码,不用管其它的。
各个UART在初始化时,将默认使用以下引脚:
这些都是STM32不同系列通用的 UART引脚,不管F1还是F4,都可以使用以下引脚,无需修改。
串口端口 | 发送引脚 (TX) | 接收引脚 (RX) |
---|---|---|
UART1 | PA9 | PA10 |
UART2 | PA2 | PA3 |
UART3 | PB10 | PB11 |
UART4 | PC10 | PC11 |
UART5 | PC12 | PD2 |
UART6 | PC6 | PC7 |
四、发送
各个UART的发送,都提供了 3个函数。
灵活选择,能实现不同场景数据的发送, 如:float、int、数组、字符串、AT指令、结构体,等等。
发送机制:发送中断+环形队列。当然,这些底层不用管、不用修改,只管调用函数。
void UART1_SendString(const char *pcString, ...); // 发送字符串; 参数:字符串地址; 使用方法如同printf
void UART1_SendData(uint8_t *puData, uint16_t usNum); // 发送指定数据; 参数:数据地址、字节数
uint8_t UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs); // 本函数,针对ESP8266、蓝牙模块等AT固件,用于等待返回期待的信息; 参数:AT指令字符串、期待返回信息字符串、ms等待超时; 返回:0-执行失败、1-执行成功
函数解释:
① 发送字符串,用:UART1_SendString (char *string,...)
使用方法如同 printf,相当灵活,具体可以百度一下printf的格式化如何使用,操作是一样的。
使用示范:
UART1_SendString("\r 你好吗? \r");
UART1_SendString("字节数:%d 字节\r", rxNum);
UART1_SendString("数据 (ASCII): %s\r", (char *)rxData);
② 发送指定长度的数据,用:UART1_SendData (*data, num)
参数:缓存地址、字节数
可用于发送数组、结构体、字符串 等任意数据。
使用示范:
// 发送字符串
UART1_SendData((uint8_t*)"你好吗", 6);
// 发送数组、16进制数制
uint8_t myArray[50] = {0xAA, 0xBB, 0xCC, 0xDD};
UART1_SendData(myArray, 8);
// 发送浮点类型数据
float myFloat = 520.1314;
UART1_SendData(&myFloat, sizeof(float));
③ 发送AT指令:UART1_SendAT(char *pcAT, char *pcAckString, uint16_t usTimeOutMs);
参数:AT指令字符串、期待返回信息字符串、等待超时ms;
返回:0-执行失败、1-执行成功
本函数能很方便地与ESP8266、蓝牙模块等进行通过。
用于发送AT指令后,等待执行反馈;
使用示范:
UART1_SendAT("AT+RESTORE\r\n", "ready", 3000) ? printf("恢复出厂设置: 成功\r\n") : printf("恢复出厂设置: 失败\r\n"); // 恢复模块的出厂设置
UART1_SendAT("AT+CWMODE=1\r\n", "OK", 3000) ? printf("配置 STA模式: 成功\r\n") : printf("配置 STA模式: 失败\r\n"); // 工作模式:1_STA, 2_AP, 3_STA+AP
UART1_SendAT("AT+RST\r\n", "ready", 3000) ? printf("重启 ESP8266: 成功\r\n") : printf("重启 ESP8266: 失败\r\n"); // 重启模块: 设置工作模式后,需重启才生效
char strTemp[100] = {0};
sprintf(strTemp, "AT+CWJAP=\"%s\",\"%s\"\r\n", "wifiName", "wifiPassWord");
if(UART1_SendAT(strTemp, "OK\r\n", 6000) )
printf("连接WiFi网络 成功\r\n");
else
printf("连接WiFi网络 失败\r\n");
printf
很多人习惯在调试时使用printf。
在bsp_UART.c里,也做好了它的重定向输出。
printf已重定向输出到UART1,在初始化 UART1后,直接可用printf。
五、接收
接收处理,共3个函数:
uint16_t UART1_GetRxNum(void); // 获取接收到的最新一帧字节数
uint8_t* UART1_GetRxdData(void); // 获取接收到的数据(缓存的地址)
void UART1_ClearRx(void); // 清理接收到的数据(清理最后一帧字节数,因为它是判断接收的标志)
接收机制上,使用:接收中断 + 空闲中断 + 双缓冲区。这些底层工作都已封装处理好,不用管。使用上,只管调用上面这仨函数。
① 获取字节数:UART1_GetRxNum ( )
作用:获取最后一帧接收到的 字节数;
- 参数:无
- 返回:字节数 ( uint16_t)
- 串口在调用 初始化函数 后,再调用这个函数判断返回值,即可知道接收状态:
- 当返回值 (接收字节数 ) ==0 时:没有接收到数据;
- 当返回值 (接收字节数 ) > 0时:接收到新一帧数据 ;
② 获取数据:UART1_GetRxData ( )
作用:获取最后一帧接收到的 数据地址;
- 参数:无
- 返回:数据地址 (uint8_t* ,注意:是指针)
- 如果不会使用指针,可以把结果当数组使用,用符号 [ ] 对数据进行访问。
③ 清0本次接收:UART1_ClearRx ( ) ;
作用:在处理完最后一帧的数据后,清0本帧数据的缓存
- 参数:无
- 返回:无
- 其实就是清0 最后一帧的接收字节数。
- 重要:每次接收到一帧数据,处理完了,就调用这函数,清0,以方便下一轮的判断!
下面代码,示范如何接收、处理数据(以串口1为例,其它串口同理):
六、常见冲突、错误的解决
本文所提供的 bsp_UART.c 和 bsp_UART.h ,支持标准库、HAL库,复制过去即可。
1、添加到原子哥的标准库工程中可能的冲突
原子哥的示例,很多人喜欢使用。
这些示例中,一般已带有uart.c和printf的重定向处理。
如果想添加到这些示例中,建议先备份工程!删除工程中UART文件和代码,再移植此文件。
2、添加到CubeMX、CubeIDE工程中可能的冲突
明确地,无需在CubeMX、CubeIDE中做任何UART的设置。
如果工程中已配置UART,建议先备份工程!关闭CubeMX、CubeIDE中对UART使能,再移植。
如果,使用CubeMX、CubeIDEE配置工程时,想使用此代码,又想标记哪些串口和相关的UART引脚已使用,可以的,在CubeMX、CubeIDE上使能相关的UART即可,这样就能方便地标记哪些引脚被使用了。
但是,记得使用上述表格中的引脚,和,不能打勾串口配置选项的中断、DMA,否则冲突。
使用CubeMX时重点注意的冲突:
- 使用上述表格的所用的串口引脚
- 在CubeMX或CubeIDE中,可以使能UART,但不要打勾串口的中断、配置DMA,否则冲突。
当在CubeMX、CubeIDE中,如上使能串口后,也能使用HAL库的发送函数、接收函数,不冲突:
- HAL_UART_Transmit ( )
- HAL_UART_Receive( )
只是,不建议使用了。因为bsp_USART.c的函数,使用起来更方便。
3、不同串口,使用不同的方式
如果,某个串口,想使用CubeMX进行中断、DMA配置; 而某些串口,想使用文件中的函数:
可以的.
在bsp_UART.h文件中,修改下面的宏定义,默认是1,即开启,修改为0值关闭即可。
宏定义 值解释:
- 0:自己另行配置、编写,例如使用CubeMX的中断、DMA配置等
- 1:使用文件的配置、函数
4、发现通信中漏数据
这种情况,是缓存过小。
在bsp_UART.h里,把发送缓存或接收缓存改大一些即可
5、接收时,发现漏帧
不是漏了一帧的某些内容,是整帧漏了。
这种情况,一般是接收处理的不及时。
如对方每100ms发送一次,但我们的代码while里,每间隔500ms才执行一次接收处理。
把接收处理的间隔,调小一些即可。或者,while里的接收处理,完全不用时间间隔。
七、STM32F103 源代码
可复制以下代码到文件,也可以直接下载:bsp_UART.c + bsp_UART.h
1、bsp_UART.h
#ifndef __BSP__UART_H
#define __BSP__UART_H
/***********************************************************************************************************************************
** 【代码编写】 魔女开发板团队
** 【淘 宝】 魔女开发板 https://demoboard.taobao.com
***********************************************************************************************************************************
** 【文件名称】 bsp_UART.h
**
** 【 功 能 】 串口通信底层驱动(UART1、UART2、UART3、UART4、UART5、UART6)
** 波特率-None-8-1
** 调用全局声明中的串口函数,即可完成初始化、发送、接收.
**
** 【 约 定 】 本文件所用串口通信,均为异步通信。
** 2024年起更新的示例,串口通信函数命名时,统一使用"UART",而不使用旧文件中的"USART".
**
** 【适用平台】 STM32F103 + 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
**
** 【移植说明】 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-09-26 增加MD、HD的判断
** 2024-07-02 增加Modbus_CRC16校验的追加、判断函数
** 2024-06-30 优化UART初始化:增加奇偶校验3个寄存器位的代码配置行
** 2024-06-20 优化AT指令函数, 完善反馈等待机制; 取消:UARTx_WaitACK();
** 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 "stm32f10x.h" // 引用 标准库的底层支持文件
#endif
#ifdef USE_HAL_DRIVER
#include "stm32f1xx_hal.h" // 引用 HAL库的底层支持文件
#endif
/*****************************************************************************
** 移植配置修改区
** 备注:除非有特殊要求,否则,下面参数已通用如蓝牙通信、ESP8266通信、串口屏等
****************************************************************************/
// 串口开关
#define UART1_EN 1 // 串口1,0=关、1=启用; 倘若没用到USART1, 置0,就不会开辟USART1发送缓存、接收缓存,省一点资源;
#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 UART1_TX_BUF_SIZE 2048 // 配置每组USART发送环形缓冲区数组的大小,单位:字节数;
#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 UART1_RX_BUF_SIZE 2048 // 配置每组USART接收缓冲区的大小,单位:字节; 此值影响范围:中断里的接收缓存大小,接收后数据缓存的大小
#define UART2_RX_BUF_SIZE 512 // --- 当接收到的一帧数据字节数,小于此值时,数据正常;
#define UART3_RX_BUF_SIZE 512 // --- 当接收到的一帧数据字节数,超过此值时,超出部分,将在中断中直接弃舍,直到此帧接收结束(发生空闲中断);
#define UART4_RX_BUF_SIZE 512 //
#define UART5_RX_BUF_SIZE 512 //
// 结束-配置修改
/*****************************************************************************
** 声明全局函数
**
** 每个串口的函数:
** 初始化 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); // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
#ifdef STM32F10X_HD
// 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); // 清理接收到的数据 (清理最后一帧字节数,因为它是判断接收的标志)
#endif
// 辅助函数: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
// 文件结尾,需要保留至少1空行
2、bsp_UART.c
/***********************************************************************************************************************************
** 【代码编写】 魔女开发板团队
** 【代码更新】 2024-10-23-1
** 【淘 宝】 魔女开发板 https://demoboard.taobao.com
***********************************************************************************************************************************
** 【文件名称】 bsp_UART.c
**
** 【文件功能】 各USART的GPIO配置、通信协议配置、中断配置,及功能函数实现
**
** 【适用平台】 STM32F103 + keil5 + HAL库/标准库
**
** 【特别说明】 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;
#if 0
/******************************************************************************
* 函 数: delay_us
* 功 能: ms 延时函数
* 备 注: 1、系统时钟72MHz
* 2、打勾:Options/ c++ / One ELF Section per Function
3、编译优化级别:Level 3(-O3)
* 参 数: uint32_t us 微秒值
* 返回值: 无
******************************************************************************/
static volatile uint32_t ulTimesUS; // 使用volatile声明,防止变量被编译器优化
static void delay_us(uint16_t us)
{
ulTimesUS = us ;
while (ulTimesUS)
ulTimesUS--; // 操作外部变量,防止空循环被编译器优化掉
}
#endif
/******************************************************************************
* 函 数: delay_ms
* 功 能: ms 延时函数
* 备 注: 1、系统时钟72MHz
* 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 * 5500;
while (ulTimesMS)
ulTimesMS--; // 操作外部变量,防止空循环被编译器优化掉
}
// USART-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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能USART1
// 重置 UART
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; // TX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 引脚功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化引脚
// 配置 RX引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化引脚
// 配置 USART
USART_DeInit(USART1); // 复位串口
USART_InitTypeDef USART_InitStructure = {0}; // UART配置结构体
USART_InitStructure.USART_BaudRate = ulBaudrate; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 位长
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_ITConfig(USART1, USART_IT_TXE, DISABLE); // 发送中断 使能
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 = 0 ; // 抢占优先级
NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 初始化中断优先级
// 配置完成,打开串口
USART_Cmd(USART1, ENABLE); // 配置完成,打开串口, 开始工作
#endif
#ifdef USE_HAL_DRIVER
// 使能 时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA
__HAL_RCC_USART1_CLK_ENABLE(); // 使能USART1
// 重置 UART
__HAL_RCC_USART1_FORCE_RESET(); // 使能重置
__HAL_RCC_USART1_RELEASE_RESET(); // 取消重置
// 配置 TX引脚
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 声明初始化要用到的结构体
GPIO_InitStruct.Pin = GPIO_PIN_9 ; // 引脚 TX-PA9
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 工作模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化引脚工作模式
// 配置 RX引脚
GPIO_InitStruct.Pin = GPIO_PIN_10; // 引脚 RX-PA10
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 工作模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化引脚工作模式
// 计算波特率参数
float temp;
uint16_t mantissa, fraction;
SystemCoreClockUpdate(); // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
temp = (float)SystemCoreClock / (ulBaudrate * 16); // 波特率公式计算; USART1挂载在APB2, 时钟为系统时钟的1分频; 全局变量SystemCoreClock,在标准库、HAL库通用;
mantissa = temp; // 整数部分
fraction = (temp - mantissa) * 16; // 小数部分
USART1 -> BRR = mantissa << 4 | fraction; // 设置波特率
// 配置 UART
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, 0, 0); // 设置指定中断的优先级; 参数:中断请求编号、抢占级、子优先级
HAL_NVIC_EnableIRQ(USART1_IRQn); // 使能、启用指定的中断
// 配置完成,打开串口
USART1 -> CR1 |= 0x01 << 13; // 使能UART开始工作
#endif
// 关联缓冲区
xUART1.puRxData = uaUART1RxData; // 关联接收缓冲区的地址
xUART1.puTxFiFoData = uaUART1TxFiFoData; // 关联发送缓冲区的地址
// 输出提示
printf("\r\r\r=========== STM32F103 外设 初始化报告 ===========\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
// USART-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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能USART2
// 重置 UART
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 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 引脚功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化引脚
// 配置 RX引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // RX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化引脚
// 配置 USART
USART_DeInit(USART2); // 复位串口
USART_InitTypeDef USART_InitStructure = {0}; // UART配置结构体
USART_InitStructure.USART_BaudRate = ulBaudrate; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 位长
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_ITConfig(USART2, USART_IT_TXE, DISABLE); // 发送中断 使能
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 = 0 ; // 抢占优先级
NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 初始化中断优先级
// 配置完成,打开串口
USART_Cmd(USART2, ENABLE); // 配置完成,打开串口, 开始工作
#endif
#ifdef USE_HAL_DRIVER // HAL库 配置
// 使能 时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA
__HAL_RCC_USART2_CLK_ENABLE(); // 使能USART2
// 重置 UART
__HAL_RCC_USART2_FORCE_RESET(); // 使能重置
__HAL_RCC_USART2_RELEASE_RESET(); // 取消重置
// 配置 TX引脚
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 声明初始化要用到的结构体
GPIO_InitStruct.Pin = GPIO_PIN_2 ; // 引脚 TX-PA2
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 工作模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化引脚工作模式
// RX引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_3; // 引脚 RX-PA3
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 工作模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化引脚工作模式
// 计算波特率参数
float temp;
uint16_t mantissa, fraction;
SystemCoreClockUpdate(); // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16); // 波特率公式计算; USART2挂载在APB1, 时钟为系统时钟的2分频; 全局变量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, 0, 0); // 设置指定中断的优先级; 参数:中断请求编号、抢占级、子优先级
HAL_NVIC_EnableIRQ(USART2_IRQn); // 使能、启用指定的中断
// 开启USART2
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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // 使能UART3
// 重置 UART
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; // TX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 引脚功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化引脚
// 配置 RX引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化引脚
// 配置 USART
USART_DeInit(USART3); // 复位串口
USART_InitTypeDef USART_InitStructure = {0}; // UART配置结构体
USART_InitStructure.USART_BaudRate = ulBaudrate; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 位长
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_ITConfig(USART3, USART_IT_TXE, DISABLE); // 发送中断 使能
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 = 0 ; // 抢占优先级
NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 初始化中断优先级
// 配置完成,打开串口
USART_Cmd(USART3, ENABLE); // 配置完成,打开串口, 开始工作
#endif
#ifdef USE_HAL_DRIVER
// 使能 时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB
__HAL_RCC_USART3_CLK_ENABLE(); // 使能USART3
// 重置 UART
__HAL_RCC_USART3_FORCE_RESET(); // 使能重置
__HAL_RCC_USART3_RELEASE_RESET(); // 取消重置
// 配置 TX引脚
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 声明初始化要用到的结构体
GPIO_InitStruct.Pin = GPIO_PIN_10 ; // 引脚 TX-PB10
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 工作模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化引脚工作模式
// 配置 RX引脚
GPIO_InitStruct.Pin = GPIO_PIN_11; // 引脚 RX-PB11
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 工作模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化引脚工作模式
// 计算波特率参数
float temp;
uint16_t mantissa, fraction;
SystemCoreClockUpdate(); // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16); // 波特率公式计算; USART3挂载在APB1, 时钟为系统时钟的2分频; 全局变量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, 0, 0); // 设置指定中断的优先级; 参数:中断请求编号、抢占级、子优先级
HAL_NVIC_EnableIRQ(USART3_IRQn); // 使能、启用指定的中断
// 配置完成,打开串口
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
#ifdef STM32F10X_HD
// 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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能GPIOC
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); // 使能UART4
// 重置 UART
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; // TX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 引脚功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度
GPIO_Init(GPIOC, &GPIO_InitStructure); // 初始化引脚
// 配置 RX引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
GPIO_Init(GPIOC, &GPIO_InitStructure); // 初始化引脚
// 配置 USART
USART_DeInit(UART4); // 复位串口
USART_InitTypeDef USART_InitStructure = {0}; // UART配置结构体
USART_InitStructure.USART_BaudRate = ulBaudrate; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 位长
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_ITConfig(UART4, USART_IT_TXE, DISABLE); // 发送中断 使能
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 = 0 ; // 抢占优先级
NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 初始化中断优先级
// 配置完成,打开串口
USART_Cmd(UART4, ENABLE); // 配置完成,打开串口, 开始工作
#endif
#ifdef USE_HAL_DRIVER
// 使能 时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); // 使能GPIOC
__HAL_RCC_UART4_CLK_ENABLE(); // 使能UART4
// 重置 UART
__HAL_RCC_UART4_FORCE_RESET(); // 使能重置
__HAL_RCC_UART4_RELEASE_RESET(); // 取消重置
// 配置 TX引脚
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 声明初始化要用到的结构体
GPIO_InitStruct.Pin = GPIO_PIN_10 ; // 引脚 TX-PC10
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 工作模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化引脚工作模式
// 配置 RX引脚
GPIO_InitStruct.Pin = GPIO_PIN_11; // 引脚 RX-PC11
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 工作模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化引脚工作模式
// 计算波特率参数
float temp;
uint16_t mantissa, fraction;
SystemCoreClockUpdate(); // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16); // 波特率公式计算; UART4挂载在APB1, 时钟为系统时钟的2分频; 全局变量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, 0, 0); // 设置指定中断的优先级; 参数:中断请求编号、抢占级、子优先级
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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能GPIOC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); // 使能GPIOD
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE); // 使能UART5
// 重置 UART
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; // TX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 引脚功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度
GPIO_Init(GPIOC, &GPIO_InitStructure); // 初始化引脚
// 配置 RX引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // RX 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
GPIO_Init(GPIOD, &GPIO_InitStructure); // 初始化引脚
// 配置 USART
USART_DeInit(UART5); // 复位串口
USART_InitTypeDef USART_InitStructure = {0}; // UART配置结构体
USART_InitStructure.USART_BaudRate = ulBaudrate; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 位长
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_ITConfig(UART5, USART_IT_TXE, DISABLE); // 发送中断 使能
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 = 0 ; // 抢占优先级
NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 初始化中断优先级
// 配置完成,打开串口
USART_Cmd(UART5, ENABLE); // 配置完成,打开串口, 开始工作
#endif
#ifdef USE_HAL_DRIVER
// 使能 时钟
__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_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化引脚工作模式
// 配置 RX引脚
GPIO_InitStruct.Pin = GPIO_PIN_2; // 引脚 RX-PD2
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 工作模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚速率
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); // 初始化引脚工作模式
// 计算波特率参数
float temp;
uint16_t mantissa, fraction;
SystemCoreClockUpdate(); // 更新系统运行频率全局值; 函数SystemCoreClock( ),在标准库、HAL库通用
temp = (float)(SystemCoreClock / 2) / (ulBaudrate * 16); // 波特率公式计算; UART5挂载在APB1, 时钟为系统时钟的2分频; 全局变量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
#endif
/ 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
}
/ 辅助功能 /
///
/******************************************************************************
* 函 数: showData
* 功 能: 把数据,经串口1,发送到串口助手上,方便观察
* 参 数: uint8_t *rxData 数据地址
* uint16_t rxNum 字节数
* 返回值: 无
******************************************************************************/
void showData(uint8_t *rxData, uint16_t rxNum)
{
#if 0 // 写法1
printf("字节数: %d \r", rxNum); // 显示字节数
printf("ASCII 显示数据: %s\r", (char *)rxData); // 显示数据,以ASCII方式显示,即以字符串的方式显示
printf("16进制显示数据: "); // 显示数据,以16进制方式,显示每一个字节的值
while (rxNum--) // 逐个字节判断,只要不为'\0', 就继续
printf("0x%X ", *rxData++); // 格式化
printf("\r\r"); // 显示换行
#else // 写法2
UART1_SendString("字节数:%d \r", rxNum); // 显示字节数
UART1_SendString("ASCII : %s\r", (char *)rxData); // 显示数据,以ASCII方式显示,即以字符串的方式显示
UART1_SendString("16进制: "); // 显示数据,以16进制方式,显示每一个字节的值
while (rxNum--) // 逐个字节判断,只要不为'\0', 就继续
UART1_SendString("0x%X ", *rxData++); // 格式化
UART1_SendString("\r\r"); // 显示换行
#endif
}
// printf //
///
/******************************************************************************
* 功 能: printf函数支持代码
* 【特别注意】加入以下代码, 使用printf函数时, 不再需要选择use MicroLIB
* 参 数:
* 返回值:
* 备 注: 最后修改_2020年07月15日
******************************************************************************/
#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, 这里使用串口1(USART1)
{
#if 0 // 方式1-使用常用的方式发送数据,比较容易理解,但等待耗时大
while ((USART1->SR & 0X40) == 0); // 等待上一次串口数据发送完成
USART1->DR = (u8) ch; // 写DR,串口1将发送数据
#else
UART1_SendData((uint8_t *)&ch, 1); // 方式2-使用队列+中断方式发送数据; 无需像方式1那样等待耗时,但要借助已写好的函数、环形缓冲
#endif
return ch;
}
// 文件结尾,需要保留至少1空行
八、STM32F407 源代码
可复制以下代码到文件,也可以直接下载:
bsp_UART.c + bsp_UART.h
1、bsp_UART.h
#ifndef __BSP__UART_H
#define __BSP__UART_H
/***********************************************************************************************************************************
** 【代码编写】 魔女开发板团队
** 【淘 宝】 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、bsp_UART.c
/***********************************************************************************************************************************
** 【代码编写】 魔女开发板团队
** 【最后版本】 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
{
UART1_SendData((uint8_t *)&ch, 1); // 使用队列+中断方式发送数据; 无需像方式1那样等待耗时,但要借助已写好的函数、环形缓冲
return ch;
}
更多推荐
所有评论(0)