关于STM32的串口通信之前的文章里有介绍过,传送门如下。
串口通信(往期)传送门
【串口通信】K210与STM32串口通信、K210与OpenMV串口通信
但是我觉得之前这一份代码还不够好用,因此我重制了一份。
本篇博文主要介绍STM32(HAL库)如何进行串口通信,使用的STM32型号为STM32F103ZET6,工程模板使用STM32CubeMX生成, 我将串口通信代码划分为了两部分,一部分为硬件部分,一部分为软件部分。 软件部分与库无关,不管是HAL库还是标准库都能用,硬件部分则是兼容HAL库或者标准库,本篇主要介绍HAL库的串口通信。 事实上,在其他平台,只要配置好串口,软件部分的代码也是能够通用的。
如果你需要标准库的串口通信,也可以看
【STM32】STM32标准库学习笔记(一)——串口通信
串口通信的思路就是发送端将数据打包发送,接收端从帧头开始接收数据,为了防止帧头和发送数据混在一起,所以设置两个帧头。当两个帧头都满足条件时才会继续接收,设置有效数据长度位是为了方便灵活接收不同长度的数据,比如有效数据长度位是2,则有data[0]和data[1]两个数据,最后再接收一位校验位,若校验通过则解析并保存数据。
帧头1 | 帧头2 | 有效数据长度位 | data[0] | …… | data[n] | 校验位 |
---|
依据上面的串口通信思路,同时为了方便定义变量,定义结构体来保存各类变量, 在需要定义新变量的时候使用结构体定义即可。这部分保存在uartprotocol.h中,完整源码在文章末尾。
// 串口发送相关结构体
typedef struct
{
uint8_t head1; // 帧头1
uint8_t head2; // 帧头2
uint8_t length; // 有效数据长度
uint8_t cnt; // 总数据长度
uint8_t data[40]; // 有效数据数组
uint8_t transmit_data[50]; // 实际发送的数组 附带上帧头1 帧头2 有效数据长度位 校验位
}DataTransmit;
// 串口接收相关结构体
typedef struct
{
uint8_t head1; // 帧头1
uint8_t head2; // 帧头2
uint8_t length; // 有效数据长度
uint8_t cnt; // 总数据长度
uint8_t state; // 接收状态
uint8_t i; // 有效数据下标
uint8_t data; // 接收数据缓冲位
uint8_t receive_data[50]; // 实际接收的数组 附带上帧头1 帧头2 有效数据长度位 校验位
}DataReceive;
// 接收数据解析相关结构体
typedef struct
{
uint16_t x; // 目标x轴坐标
uint16_t y; // 目标y轴坐标
uint8_t color; // 目标颜色标志位
uint8_t shape; // 目标形状标志位
uint8_t flag; // 目标标志位
}TargetProperty;
定义串口发送数据结构体初始化函数 Data_Transmit_Init
此函数带入参数为结构体,帧头,有效数据长度,在 while 循环前初始化对应的结构体即可设置结构体对应发送数据的帧头以及有效数据长度位。
/********************************************************************
串口发送数据结构体初始化函数 Data_Transmit_Init
各参数作用
DataTransmit *data: 选择要初始化的串口发送数据结构体
head1: 帧头1
head2: 帧头2
length: 有效数据长度
********************************************************************/
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = length;
data -> cnt = length + 4;
for(uint8_t i = 0; i < length; i++)
{
data -> data[i] = 0;
}
for(uint8_t j = 0; j < length + 4; j++)
{
data -> transmit_data[j] = 0;
}
}
定义串口发送数据打包函数 Data_Pack
此函数带入参数为结构体,在串口发送函数之前(串口发送函数在硬件部分),将要发送的结构体打包即可。
/********************************************************************
串口发送数据打包函数 Data_Pack
各参数作用
DataTransmit *data: 选择要打包的串口发送数据结构体
********************************************************************/
void Data_Pack(DataTransmit *data)
{
data -> transmit_data[0] = data -> head1;
data -> transmit_data[1] = data -> head2;
data -> transmit_data[2] = data -> length;
for(uint8_t i = 0; i < data -> length; i++)
{
data -> transmit_data[3+i] = data -> data[i];
}
uint8_t sum = 0;
for(uint8_t j = 0; j < data -> length + 3; j++)
{
sum = sum + data -> transmit_data[j];
}
data -> transmit_data[data -> length + 3] = sum;
}
定义串口接收数据结构体初始化函数 Data_Receive_Init
此函数带入参数为结构体,在 while 循环前初始化对应的结构体即可设置结构体对应接收数据的帧头,在接收函数中,只有满足帧头条件才会进行接收。
/********************************************************************
串口接收数据结构体初始化函数 Data_Receive_Init
各参数作用
DataReceive *data: 选择要初始化的串口接收数据结构体
head1: 帧头1
head2: 帧头2
********************************************************************/
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = 0;
data -> cnt = 0;
data -> state = 0;
data -> i = 0;
data -> data = 0;
for(uint8_t j = 0; j < 50; j++)
{
data -> receive_data[j] = 0;
}
}
定义串口接收数据函数 Data_Receive
此函数带入参数为结构体以及被接收的数据,在串口中断函数中(串口中断函数在硬件部分),使用该函数接收数据即可,其工作过程如下。
每次根据不同的状态 state 进入不同的 if
进入状态0后,若传入的数据等于帧头1,则保存该数据,然后进入状态1。
进入状态1后,若传入的数据等于帧头2,则保存该数据,然后进入状态2。
进入状态2后,判断需要接收的有效数据长度是否小于40,若小于,则保存该数据以及总数据长度,然后进入状态3。
进入状态3后,开始接收有效数据长度个数据,接收完毕会进入状态4。
进入状态4后,保存最后一位校验位,然后状态置0,等待下一次接收。
不满足以上条件均会将状态置0重新开始接收。
/********************************************************************
串口接收数据函数 Data_Receive
各参数作用
DataReceive *data: 选择要接收的串口接收数据结构体
buf: 接收数据
********************************************************************/
void Data_Receive(DataReceive *data, uint8_t buf)
{
if(data -> state == 0 && buf == data -> head1)
{
data -> state = 1;
data -> receive_data[0] = buf;
}
else if(data -> state == 1 && buf == data -> head2)
{
data -> state = 2;
data -> receive_data[1] = buf;
}
else if(data -> state == 2 && buf < 40)
{
data -> state = 3;
data -> length = buf;
data -> cnt = buf+4;
data -> receive_data[2] = buf;
}
else if(data -> state == 3 && data -> length > 0)
{
data -> length = data -> length - 1;
data -> receive_data[3 + data -> i] = buf;
data -> i = data -> i + 1;
if(data -> length == 0)
{
data -> state = 4;
}
}
else if(data -> state == 4)
{
data -> receive_data[3 + data -> i] = buf;
data -> state = 0;
data -> i = 0;
}
else
{
data -> state = 0;
data -> i = 0;
}
}
此部分根据个人需求自行定义,这里举例我的一个定义,提供一些思路。
首先是初始化函数,定义的结构体中的变量都需要初始化为0
/********************************************************************
串口接收数据结构体初始化函数 Target_Init
各参数作用
TargetAttribute *target: 选择要初始化的结构体
********************************************************************/
void Target_Init(TargetProperty *target)
{
target -> x = 0;
target -> y = 0;
target -> color = 0;
target -> shape = 0;
target -> flag = 0;
}
然后是数据解析部分,带入参数为接收数据的结构体,以及解析结果保存的结构体,进入该函数后首先会对接收数据进行和校验,和校验通过则保存数据。
/********************************************************************
串口接收数据解析函数 Target_Parse
各参数作用
DataReceive *data: 选择被解析的结构体
TargetProperty *target: 选择解析完成保存的结构体
********************************************************************/
void Target_Parse(DataReceive *data, TargetProperty *target)
{
uint8_t sum = 0;
uint8_t i = 0;
while(i < data -> cnt - 1)
{
sum = sum + data -> receive_data[i];
i = i + 1;
}
if(sum == data -> receive_data[data -> cnt - 1])
{
target -> x = data -> receive_data[3]*256 + data -> receive_data[4];
target -> y = data -> receive_data[5]*256 + data -> receive_data[6];
target -> color = data -> receive_data[7];
target -> shape = data -> receive_data[8];
target -> flag = data -> receive_data[9];
}
}
讲下保存数据中的数组下标问题,假设有效数据长度是7,也就是有7个位来保存有效数据,算上帧头,有效数据长度位,校验位,总长度就是11,在总数组中对应的下标如下所示,显而易见,需要从下标3开始保存数据。
帧头1 | 帧头2 | 有效数据长度位 | data[0] | …… | data[n] | 校验位 |
---|---|---|---|---|---|---|
接收数据[0] | 接收数据[1] | 接收数据[2] | 接收数据[3] | …… | 接收数据[9] | 接收数据[10] |
这部分的代码是不唯一的,根据个人情况定义即可。
由于是HAL库,因此串口初始化代码不需要自己写,使用STM32CubeMX生成即可,这里主要是将HAL库中的串口发送与接收函数封装一下,方便使用。
这部分调用了HAL库的发送函数HAL_UART_Transmit,带入参数为被发送结构体以及串口号,比如要发送的结构体为data,通过串口1来发送,就是
Data_Transmit(&data,&huart1);
如果有看我标准库的串口通信教程博文的人会发现,其实两份博文中,区别也就是串口硬件部分配置的区别,其他地方是没有区别的,这也是为了方便使用,如果你要用到多个32,使用标准库的用标准库的代码配置串口通信,使用HAL库的用HAL库的代码配置串口通信,就算两部32用的库不一样,我也能保证两边可以通信,肥肠好用。
/********************************************************************
串口发送数据函数 Data_Transmit
各参数作用
DataTransmit *data: 选择要发送的串口发送数据结构体
UART_HandleTypeDef *huart, : 选择通过哪一个串口发送
********************************************************************/
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(huart, data -> transmit_data, data -> cnt , 0xFFFF);
}
/********************************************************************
串口打包发送数据函数 Data_Pack_Transmit
各参数作用
DataTransmit *data: 选择要发送的串口发送数据结构体
UART_HandleTypeDef *huart, : 选择通过哪一个串口发送
********************************************************************/
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
Data_Pack(data);
Data_Transmit(data,huart);
}
/********************************************************************
串口接收1个数据函数 Buffer_Receive
各参数作用
DataReceive *data: 选择通过哪一个串口接收数据结构体接收
UART_HandleTypeDef *huart, : 选择通过哪一个串口接收
********************************************************************/
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &data -> data, 1);
return data -> data;
}
先上传一下我的工程模板,链接如下所示,设置的是0积分免费下载。 如果你想要自己搭建工程模板,或者自己已经有的,也可以使用自己的。另外我这个压缩包里也有 uartconfig 和 uartprotocol 的源码,不想去文末复制的也可以下载。
STM32串口通信代码
下载完后,将HardWare下的ioc文件打开,直接生成代码即可。
当然,如果你想自己配置,教程如下,如果你不想自己配,用上面我给的ioc文件打开直接生成代码即可。
首先打开STM32CubeMX,点击图中红色箭头指向的按钮。
在搜索框中搜索103zet,这个根据个人型号不同可以设置不同的型号。
然后是在SYS中将DeBug设置为SW,这样就可以用ST—Link调试了。
然后在RCC中设置高速时钟为外部晶振。
在时钟树里面设置主频72M,APB1总线需要二分频。
然后就是串口设置,点击USART1,然后设置为Asynchronous即可。
你需要用到几个串口就设置几个,这里我设置了三个,你如果用不到这么多也可以只设置一个,波特率默认都是115200的,你要换其他波特率也可以改。
然后就是设置一下普通定时器TIM6,这个主要是用于控制一下串口收发的频率,太频繁发送会导致一直触发串口中断。分频系数选71,重装载值9999,自动装载使能即可。
然后就是记得在NVIC中使能中断,这一步一定要做。
然后就可以配置路径以及工程名字了,记得IDE选择MDK-ARM,版本V5。
然后就是个人喜好设置,我这里设置是只复制需要用到的库,并且生成独立的c和h文件。
然后就可以生成代码了。
生成代码完打开文件夹即可。
然后在解压后的文件夹中创建一个名为MyLibrary的文件夹,这个名字可以自行定义。
将Layer解压。
注意路径,将HardWareLayer -> HardWare -> UART 下的文件复制或者移动到 MyLibrary
注意路径,将SoftWareLayer -> UART 下的文件复制或者移动到 MyLibrary
然后打开工程,创建一个文件夹,名字随意,这里取名MyLibrary。
然后在该文件夹下添加文件,定位到刚才的MyLibrary目录下,将.c的文件都添加进来。
然后记得在魔术棒中添加 MyLibrary 文件夹的环境,不然编译器是找不到路径的。
然后在main.c的对应位置复制如下代码即可。
/* USER CODE BEGIN Includes */
#include "uartconfig.h"
#include "uartprotocol.h"
/* USER CODE BEGIN 0 */
// 串口发送
DataTransmit data_transmit_uart1; // 声明全局结构体 data_transmit_uart1 这个要放在 main 函数外面
DataTransmit data_transmit_uart2; // 声明全局结构体 data_transmit_uart2 这个要放在 main 函数外面
DataTransmit data_transmit_uart3; // 声明全局结构体 data_transmit_uart3 这个要放在 main 函数外面
// 串口接收
DataReceive data_receive_uart1; // 声明全局结构体 data_receive_uart1 这个要放在 main 函数外面
DataReceive data_receive_uart2; // 声明全局结构体 data_receive_uart2 这个要放在 main 函数外面
DataReceive data_receive_uart3; // 声明全局结构体 data_receive_uart3 这个要放在 main 函数外面
// 被发送数据
TargetProperty t1; // 声明全局结构体 t1 这个要放在 main 函数外面
TargetProperty t2; // 声明全局结构体 t2 这个要放在 main 函数外面
TargetProperty t3; // 声明全局结构体 t3 这个要放在 main 函数外面
// 接收数据解析
TargetProperty target1; // 声明全局结构体 target1 这个要放在 main 函数外面
TargetProperty target2; // 声明全局结构体 target2 这个要放在 main 函数外面
TargetProperty target3; // 声明全局结构体 target3 这个要放在 main 函数外面
// 定时器任务队列参数
uint8_t tim_task = 0; // 任务序号
uint8_t task_flag = 0; // 任务完成标志 0完成 1未完成
// 运行任务函数声明
void Run_Task(void);
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6); // 使能定时器6
Data_Transmit_Init(&data_transmit_uart1,0xAA,0xAA,7); // main函数中在 while 前 对结构体 data_transmit_uart1 进行初始化 帧头都为 0xAA 有效数据长度为 7
Data_Transmit_Init(&data_transmit_uart2,0xBB,0xBB,7); // main函数中在 while 前 对结构体 data_transmit_uart2 进行初始化 帧头都为 0xBB 有效数据长度为 7
Data_Transmit_Init(&data_transmit_uart3,0xCC,0xCC,7); // main函数中在 while 前 对结构体 data_transmit_uart3 进行初始化 帧头都为 0xCC 有效数据长度为 7
// 串口接收初始化
Data_Receive_Init(&data_receive_uart1,0xAA,0xAA); // main函数中在 while 前 对结构体 data_receive_uart1 进行初始化 设置帧头为 0xAA
Data_Receive_Init(&data_receive_uart2,0xBB,0xBB); // main函数中在 while 前 对结构体 data_receive_uart2 进行初始化 设置帧头为 0xBB
Data_Receive_Init(&data_receive_uart3,0xCC,0xCC); // main函数中在 while 前 对结构体 data_receive_uart3 进行初始化 设置帧头为 0xCC
// 被发送数据初始化
Target_Init(&t1); // main函数中在 while 前 对结构体 t1 进行初始化
Target_Init(&t2); // main函数中在 while 前 对结构体 t2 进行初始化
Target_Init(&t3); // main函数中在 while 前 对结构体 t3 进行初始化
// 接收数据解析初始化
Target_Init(&target1); // main函数中在 while 前 对结构体 target1 进行初始化
Target_Init(&target2); // main函数中在 while 前 对结构体 target2 进行初始化
Target_Init(&target3); // main函数中在 while 前 对结构体 target3 进行初始化
Buffer_Receive(&data_receive_uart1,&huart1); // main的函数在 while 前 HAL库需要接收一次 使之可以进入中断回调
Buffer_Receive(&data_receive_uart2,&huart2); // main的函数在 while 前 HAL库需要接收一次 使之可以进入中断回调
Buffer_Receive(&data_receive_uart3,&huart3); // main的函数在 while 前 HAL库需要接收一次 使之可以进入中断回调
/* USER CODE BEGIN 3 */
Run_Task(); // 运行任务
/* USER CODE BEGIN 4 */
// 运行任务函数
void Run_Task(void)
{
if(tim_task == 0 && task_flag == 1)
{
data_transmit_uart1.data[0] = t1.x/256;
data_transmit_uart1.data[1] = t1.x%256;
data_transmit_uart1.data[2] = t1.y/256;
data_transmit_uart1.data[3] = t1.y%256;
data_transmit_uart1.data[4] = t1.color;
data_transmit_uart1.data[5] = t1.shape;
data_transmit_uart1.data[6] = t1.flag;
Data_Pack_Transmit(&data_transmit_uart1, &huart1); // 对数据进行打包 并通过USART1 发送
task_flag = 0;
}
else if(tim_task == 1 && task_flag == 1)
{
data_transmit_uart2.data[0] = t2.x/256;
data_transmit_uart2.data[1] = t2.x%256;
data_transmit_uart2.data[2] = t2.y/256;
data_transmit_uart2.data[3] = t2.y%256;
data_transmit_uart2.data[4] = t2.color;
data_transmit_uart2.data[5] = t2.shape;
data_transmit_uart2.data[6] = t2.flag;
Data_Pack_Transmit(&data_transmit_uart2, &huart2); // 对数据进行打包 并通过USART2 发送
task_flag = 0;
}
else if(tim_task == 2 && task_flag == 1)
{
data_transmit_uart3.data[0] = t3.x/256;
data_transmit_uart3.data[1] = t3.x%256;
data_transmit_uart3.data[2] = t3.y/256;
data_transmit_uart3.data[3] = t3.y%256;
data_transmit_uart3.data[4] = t3.color;
data_transmit_uart3.data[5] = t3.shape;
data_transmit_uart3.data[6] = t3.flag;
Data_Pack_Transmit(&data_transmit_uart3, &huart3); // 对数据进行打包 并通过USART3 发送
task_flag = 0;
}
else if(tim_task == 3 && task_flag == 1)
{
Target_Parse(&data_receive_uart1,&target1); // 解析 data_receive_uart1 接收数据 给 target1
Target_Parse(&data_receive_uart2,&target2); // 解析 data_receive_uart2 接收数据 给 target2
Target_Parse(&data_receive_uart3,&target3); // 解析 data_receive_uart3 接收数据 给 target3
task_flag = 0;
}
}
// 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if( htim == (&htim6) )
{
if(tim_task < 3)
{
tim_task = tim_task + 1; // 任务切换
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
else
{
tim_task = 0; // 重头开始执行任务
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
}
}
// 串口中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // 中断回调函数中调用即可 函数 HAL_UART_RxCpltCallback 是一个弱定义 可以在 main.c 的 USER CODE BEGIN 4 中进行重写
{
if( huart == &huart1 ) // 检测到串口中断1
{
Data_Receive(&data_receive_uart1,Buffer_Receive(&data_receive_uart1,&huart1));
}
else if( huart == &huart2 ) // 检测到串口中断2
{
Data_Receive(&data_receive_uart2,Buffer_Receive(&data_receive_uart2,&huart2));
}
else if( huart == &huart3 ) // 检测到串口中断3
{
Data_Receive(&data_receive_uart3,Buffer_Receive(&data_receive_uart3,&huart3));
}
}
然后直接编译即可。
需要提到的是,我这里串口发送和解析用的是任务队列进行延时,因为串口发送如果太快,将会导致程序一直卡在中断中,无法做其他任务,任务队列需要定时器来计数,这也是之前为什么要配置TIM6的原因。
整体工作流程就是每一次触发定时器,都会进行任务切换,且将任务标志置1。
而任务运行函数在 while 循环中运行,每次任务切换,且标志置为1的时候,将会执行一次对应任务,然后标志置为0,也就是说一次任务切换只会执行一次任务,这样避免串口一直发送数据导致程序一直卡在中断函数中。
使用ST-Link进行测试,接线方法如图所示,这里采用的测试方法是自发自收,所以各串口的RX和TX接一起即可。
然后DeBug即可。
右键将结构体添加进来。
t1是被串口1发送的,target1是保存串口1接收的数据的。
t2是被串口1发送的,target2是保存串口2接收的数据的。
t3是被串口1发送的,target3是保存串口3接收的数据的。
也就是说在t1的数据改变的时候,target1也改变,就说明能成功通信了。
右键取消一下HEX显示。
然后点击按钮开始运行,更改t1中参数的值,会发现target1中也会相应更改。
t2和target2也是如此。
t3和target3也是如此,三部分响应时间都很快,说明可以成功串口通信。
由于当前手上没第二部32,因此两部32之间的通信就等我回学校再进行测试了,基本上是没问题的。
uartconfig.c
/********************************************************************/
// 作用: STM32HAL库串口配置
// 作者: FITQY
// 时间: 2022.08.29
/********************************************************************/
#include "uartconfig.h"
/********************************************************************
串口发送数据函数 Data_Transmit
各参数作用
DataTransmit *data: 选择要发送的串口发送数据结构体
UART_HandleTypeDef *huart, : 选择通过哪一个串口发送
********************************************************************/
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(huart, data -> transmit_data, data -> cnt , 0xFFFF);
}
/********************************************************************
串口打包发送数据函数 Data_Pack_Transmit
各参数作用
DataTransmit *data: 选择要发送的串口发送数据结构体
UART_HandleTypeDef *huart, : 选择通过哪一个串口发送
********************************************************************/
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
Data_Pack(data);
Data_Transmit(data,huart);
}
/********************************************************************
串口接收1个数据函数 Buffer_Receive
各参数作用
DataReceive *data: 选择通过哪一个串口接收数据结构体接收
UART_HandleTypeDef *huart, : 选择通过哪一个串口接收
********************************************************************/
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &data -> data, 1);
return data -> data;
}
uartconfig.h
#ifndef __UARTCONFIG_H
#define __UARTCONFIG_H
#include "stm32f1xx_hal.h"
#include "uartprotocol.h"
// 重命名方便定义
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
// 串口发送相关函数
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);
// 串口接收相关函数
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart);
#endif
uartprotocol.c
/********************************************************************/
// 作用: 串口发送与接收
// 作者: FITQY
// 时间: 2022.08.29
/********************************************************************/
#include "uartprotocol.h"
/********************************************************************
串口发送数据结构体初始化函数 Data_Transmit_Init
各参数作用
DataTransmit *data: 选择要初始化的串口发送数据结构体
head1: 帧头1
head2: 帧头2
length: 有效数据长度
********************************************************************/
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = length;
data -> cnt = length + 4;
for(uint8_t i = 0; i < length; i++)
{
data -> data[i] = 0;
}
for(uint8_t j = 0; j < length + 4; j++)
{
data -> transmit_data[j] = 0;
}
}
/********************************************************************
串口发送数据打包函数 Data_Pack
各参数作用
DataTransmit *data: 选择要打包的串口发送数据结构体
********************************************************************/
void Data_Pack(DataTransmit *data)
{
data -> transmit_data[0] = data -> head1;
data -> transmit_data[1] = data -> head2;
data -> transmit_data[2] = data -> length;
for(uint8_t i = 0; i < data -> length; i++)
{
data -> transmit_data[3+i] = data -> data[i];
}
uint8_t sum = 0;
for(uint8_t j = 0; j < data -> length + 3; j++)
{
sum = sum + data -> transmit_data[j];
}
data -> transmit_data[data -> length + 3] = sum;
}
// 至此串口数据发送函数定义结束 使用例如下
/*
DataTransmit data_transmit_uart; // 声明全局结构体 data_transmit_uart 这个要放在 main 函数外面
Data_Transmit_Init(&data_transmit_uart,0xAA,0xAA,1); // main函数中在 while 前 对结构体 data_transmit_uart 进行初始化 帧头都为 0xAA 有效数据长度为 1
data_transmit_uart.data[0] = 1; // 将发送的第一个数据赋值为 1 位置不限
// HAL库 使用方法
// 打包与发送分开
Data_Pack(&data_transmit_uart); // 对数据进行打包 每次要发送的数据改变的时候 都要重新打包 这个可放在 while 中
Data_Transmit(&data_transmit_uart, USART1); // 将数据通过USART1 发送
// 打包与发送合并
Data_Pack_Transmit(&data_transmit_uart, USART1); // 对数据进行打包 并通过USART1 发送
// 固件库 使用方法
// 打包与发送分开
Data_Pack(&data_transmit_uart); // 对数据进行打包 每次要发送的数据改变的时候 都要重新打包 这个可放在 while 中
Data_Transmit(&data_transmit_uart, USART1); // 将数据通过USART1 发送
// 打包与发送合并
Data_Pack_Transmit(&data_transmit_uart, USART1); // 对数据进行打包 并通过USART1 发送
*/
// 至此串口数据发送函数使用例结束
/********************************************************************
串口接收数据结构体初始化函数 Data_Receive_Init
各参数作用
DataReceive *data: 选择要初始化的串口接收数据结构体
head1: 帧头1
head2: 帧头2
********************************************************************/
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = 0;
data -> cnt = 0;
data -> state = 0;
data -> i = 0;
data -> data = 0;
for(uint8_t j = 0; j < 50; j++)
{
data -> receive_data[j] = 0;
}
}
/********************************************************************
串口接收数据函数 Data_Receive
各参数作用
DataReceive *data: 选择要接收的串口接收数据结构体
buf: 接收数据
********************************************************************/
void Data_Receive(DataReceive *data, uint8_t buf)
{
if(data -> state == 0 && buf == data -> head1)
{
data -> state = 1;
data -> receive_data[0] = buf;
}
else if(data -> state == 1 && buf == data -> head2)
{
data -> state = 2;
data -> receive_data[1] = buf;
}
else if(data -> state == 2 && buf < 40)
{
data -> state = 3;
data -> length = buf;
data -> cnt = buf+4;
data -> receive_data[2] = buf;
}
else if(data -> state == 3 && data -> length > 0)
{
data -> length = data -> length - 1;
data -> receive_data[3 + data -> i] = buf;
data -> i = data -> i + 1;
if(data -> length == 0)
{
data -> state = 4;
}
}
else if(data -> state == 4)
{
data -> receive_data[3 + data -> i] = buf;
data -> state = 0;
data -> i = 0;
}
else
{
data -> state = 0;
data -> i = 0;
}
}
// 至此串口数据接收函数定义结束 使用例如下
/*
DataReceive data_receive_uart; // 声明全局结构体 data_receive_uart 这个要放在 main 函数外面
Data_Receive_Init(&data_receive_uart,0xAA,0xAA); // main函数中在 while 前 对结构体 data_receive_uart 进行初始化 设置帧头为 0xAA
// HAL库 使用方法
Buffer_Receive(&huart1,&data_receive_uart); // main的函数在 while 前 HAL库需要接收一次 使之可以进入中断回调
// 串口中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // 中断回调函数中调用即可 函数 HAL_UART_RxCpltCallback 是一个弱定义 可以在 main.c 的 USER CODE BEGIN 4 中进行重写
{
if( huart == &huart1 ) // 检测到串口中断1
{
Data_Receive(&data_receive_uart,Buffer_Receive(&huart1,&data_receive_uart));
}
}
// 固件库 使用方法 直接将中断函数复制到 main.c 下面即可
// 串口1 中断函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 判断接收数据寄存器是否有数据
{
Data_Receive(&data_receive_uart, USART_ReceiveData(USART1)); // 从 串口1 接收1个数据
USART_ClearFlag(USART1,USART_IT_RXNE); // 清空中断标志 准备下一次接收
}
}
*/
// 至此串口数据接收函数使用例结束
// 以下为串口接收数据解析函数 此类函数可自行定义 增删改变量 方法不唯一
/********************************************************************
串口接收数据结构体初始化函数 Target_Init
各参数作用
TargetAttribute *target: 选择要初始化的结构体
********************************************************************/
void Target_Init(TargetProperty *target)
{
target -> x = 0;
target -> y = 0;
target -> color = 0;
target -> shape = 0;
target -> flag = 0;
}
/********************************************************************
串口接收数据解析函数 Target_Parse
各参数作用
DataReceive *data: 选择被解析的结构体
TargetProperty *target: 选择解析完成保存的结构体
********************************************************************/
void Target_Parse(DataReceive *data, TargetProperty *target)
{
uint8_t sum = 0;
uint8_t i = 0;
while(i < data -> cnt - 1)
{
sum = sum + data -> receive_data[i];
i = i + 1;
}
if(sum == data -> receive_data[data -> cnt - 1])
{
target -> x = data -> receive_data[3]*256 + data -> receive_data[4];
target -> y = data -> receive_data[5]*256 + data -> receive_data[6];
target -> color = data -> receive_data[7];
target -> shape = data -> receive_data[8];
target -> flag = data -> receive_data[9];
}
}
// 至此数据解析函数定义结束 使用例如下
/*
TargetProperty target; // 声明全局结构体 target 这个要放在 main 函数外面
Target_Init(&target); // main函数中在 while 前 对结构体 target 进行初始化
Target_Parse(&data_receive_uart,&target); // 解析接收数据 可以放在 while 中 也可放在其他地方 不唯一
*/
// 解析函数可根据自身需求自由定义 方法不唯一
// 库函数版本 测试范例 第一个使用UART1 第二个使用UART1、UART2、UART3
// 测试 main.c 如下 初始化 一个UART 并接收数据 可复制该代码 进行测试
/*
#include "uartconfig.h"
#include "uartprotocol.h"
#include "timer.h"
// 串口发送
DataTransmit data_transmit_uart1; // 声明全局结构体 data_transmit_uart1 这个要放在 main 函数外面
// 串口接收
DataReceive data_receive_uart1; // 声明全局结构体 data_receive_uart1 这个要放在 main 函数外面
// 被发送数据
TargetProperty t1; // 声明全局结构体 t1 这个要放在 main 函数外面
// 接收数据解析
TargetProperty target1; // 声明全局结构体 target1 这个要放在 main 函数外面
// 定时器任务队列参数
uint8_t tim_task = 0; // 任务序号
uint8_t task_flag = 0; // 任务完成标志 0完成 1未完成
// 运行任务函数声明
void Run_Task(void);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置NVIC中断分组2 2位抢占优先级 2位响应优先级 即可设置 0-3 抢占优先级 0-3 子优先级 2位就是2的2次方
LED_Init(); // LED 初始化 用于观察程序是否正常运行
// 定时器3 初始化
Init_TIM3(9999,71,0,1); // 定时器3 初始化 计数值 9999 分频系数 71 抢占优先级 0 子优先级 1 定时时间 (72M/(71+1))/(9999+1)=100 Hz 即 0.01s 产生一次中断
// 串口发送初始化
Init_UART1(115200,1,1); // 串口1 初始化 波特率115200 抢占优先级1 子优先级1
Data_Transmit_Init(&data_transmit_uart1,0xAA,0xAA,7); // main函数中在 while 前 对结构体 data_transmit_uart1 进行初始化 帧头都为 0xAA 有效数据长度为 7
// 串口接收初始化
Data_Receive_Init(&data_receive_uart1,0xAA,0xAA); // main函数中在 while 前 对结构体 data_receive_uart1 进行初始化 设置帧头为 0xAA
// 被发送数据初始化
Target_Init(&t1); // main函数中在 while 前 对结构体 t1 进行初始化
// 接收数据解析初始化
Target_Init(&target1); // main函数中在 while 前 对结构体 target1 进行初始化
while(1)
{
Run_Task(); // 运行任务
}
}
// 运行任务函数
void Run_Task(void)
{
if(tim_task == 0 && task_flag == 1)
{
data_transmit_uart1.data[0] = t1.x/256;
data_transmit_uart1.data[1] = t1.x%256;
data_transmit_uart1.data[2] = t1.y/256;
data_transmit_uart1.data[3] = t1.y%256;
data_transmit_uart1.data[4] = t1.color;
data_transmit_uart1.data[5] = t1.shape;
data_transmit_uart1.data[6] = t1.flag;
Data_Pack_Transmit(&data_transmit_uart1, USART1); // 对数据进行打包 并通过USART1 发送
GPIO_SetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
else if(tim_task == 1 && task_flag == 1)
{
Target_Parse(&data_receive_uart1,&target1); // 解析 data_receive_uart1 接收数据 给 target1
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
}
// 定时器3 中断函数
void TIM3_IRQHandler(void) // TIM3中断服务函数
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) // 检查TIM3更新中断发生与否
{
if(tim_task < 1)
{
tim_task = tim_task + 1; // 任务切换
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
else
{
tim_task = 0; // 重头开始执行任务
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除TIM3更新中断标志
}
}
// 串口1 中断函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 判断接收数据寄存器是否有数据
{
Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1)); // 从 串口1 接收1个数据
USART_ClearFlag(USART1,USART_IT_RXNE); // 清空中断标志 准备下一次接收
}
}
*/
// 测试 main.c 如下 初始化 三个UART 并接收数据 可复制该代码 进行测试
/*
#include "uartconfig.h"
#include "uartprotocol.h"
#include "timer.h"
// 串口发送
DataTransmit data_transmit_uart1; // 声明全局结构体 data_transmit_uart1 这个要放在 main 函数外面
DataTransmit data_transmit_uart2; // 声明全局结构体 data_transmit_uart2 这个要放在 main 函数外面
DataTransmit data_transmit_uart3; // 声明全局结构体 data_transmit_uart3 这个要放在 main 函数外面
// 串口接收
DataReceive data_receive_uart1; // 声明全局结构体 data_receive_uart1 这个要放在 main 函数外面
DataReceive data_receive_uart2; // 声明全局结构体 data_receive_uart2 这个要放在 main 函数外面
DataReceive data_receive_uart3; // 声明全局结构体 data_receive_uart3 这个要放在 main 函数外面
// 被发送数据
TargetProperty t1; // 声明全局结构体 t1 这个要放在 main 函数外面
TargetProperty t2; // 声明全局结构体 t2 这个要放在 main 函数外面
TargetProperty t3; // 声明全局结构体 t3 这个要放在 main 函数外面
// 接收数据解析
TargetProperty target1; // 声明全局结构体 target1 这个要放在 main 函数外面
TargetProperty target2; // 声明全局结构体 target2 这个要放在 main 函数外面
TargetProperty target3; // 声明全局结构体 target3 这个要放在 main 函数外面
// 定时器任务队列参数
uint8_t tim_task = 0; // 任务序号
uint8_t task_flag = 0; // 任务完成标志 0完成 1未完成
// 运行任务函数声明
void Run_Task(void);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置NVIC中断分组2 2位抢占优先级 2位响应优先级 即可设置 0-3 抢占优先级 0-3 子优先级 2位就是2的2次方
LED_Init(); // LED 初始化 用于观察程序是否正常运行
// 定时器3 初始化
Init_TIM3(9999,71,0,1); // 定时器3 初始化 计数值 9999 分频系数 71 抢占优先级 0 子优先级 1 定时时间 (72M/(71+1))/(9999+1)=100 Hz 即 0.01s 产生一次中断
// 串口发送初始化
Init_UART1(115200,1,1); // 串口1 初始化 波特率115200 抢占优先级1 子优先级1
Init_UART2(115200,1,1); // 串口2 初始化 波特率115200 抢占优先级1 子优先级1
Init_UART3(115200,1,1); // 串口3 初始化 波特率115200 抢占优先级1 子优先级1
Data_Transmit_Init(&data_transmit_uart1,0xAA,0xAA,7); // main函数中在 while 前 对结构体 data_transmit_uart1 进行初始化 帧头都为 0xAA 有效数据长度为 7
Data_Transmit_Init(&data_transmit_uart2,0xBB,0xBB,7); // main函数中在 while 前 对结构体 data_transmit_uart2 进行初始化 帧头都为 0xBB 有效数据长度为 7
Data_Transmit_Init(&data_transmit_uart3,0xCC,0xCC,7); // main函数中在 while 前 对结构体 data_transmit_uart3 进行初始化 帧头都为 0xCC 有效数据长度为 7
// 串口接收初始化
Data_Receive_Init(&data_receive_uart1,0xAA,0xAA); // main函数中在 while 前 对结构体 data_receive_uart1 进行初始化 设置帧头为 0xAA
Data_Receive_Init(&data_receive_uart2,0xBB,0xBB); // main函数中在 while 前 对结构体 data_receive_uart2 进行初始化 设置帧头为 0xBB
Data_Receive_Init(&data_receive_uart3,0xCC,0xCC); // main函数中在 while 前 对结构体 data_receive_uart3 进行初始化 设置帧头为 0xCC
// 被发送数据初始化
Target_Init(&t1); // main函数中在 while 前 对结构体 t1 进行初始化
Target_Init(&t2); // main函数中在 while 前 对结构体 t2 进行初始化
Target_Init(&t3); // main函数中在 while 前 对结构体 t3 进行初始化
// 接收数据解析初始化
Target_Init(&target1); // main函数中在 while 前 对结构体 target1 进行初始化
Target_Init(&target2); // main函数中在 while 前 对结构体 target2 进行初始化
Target_Init(&target3); // main函数中在 while 前 对结构体 target3 进行初始化
while(1)
{
Run_Task(); // 运行任务
}
}
// 运行任务函数
void Run_Task(void)
{
if(tim_task == 0 && task_flag == 1)
{
data_transmit_uart1.data[0] = t1.x/256;
data_transmit_uart1.data[1] = t1.x%256;
data_transmit_uart1.data[2] = t1.y/256;
data_transmit_uart1.data[3] = t1.y%256;
data_transmit_uart1.data[4] = t1.color;
data_transmit_uart1.data[5] = t1.shape;
data_transmit_uart1.data[6] = t1.flag;
Data_Pack_Transmit(&data_transmit_uart1, USART1); // 对数据进行打包 并通过USART1 发送
GPIO_SetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
else if(tim_task == 1 && task_flag == 1)
{
data_transmit_uart2.data[0] = t2.x/256;
data_transmit_uart2.data[1] = t2.x%256;
data_transmit_uart2.data[2] = t2.y/256;
data_transmit_uart2.data[3] = t2.y%256;
data_transmit_uart2.data[4] = t2.color;
data_transmit_uart2.data[5] = t2.shape;
data_transmit_uart2.data[6] = t2.flag;
Data_Pack_Transmit(&data_transmit_uart2, USART2); // 对数据进行打包 并通过USART2 发送
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
else if(tim_task == 2 && task_flag == 1)
{
data_transmit_uart3.data[0] = t3.x/256;
data_transmit_uart3.data[1] = t3.x%256;
data_transmit_uart3.data[2] = t3.y/256;
data_transmit_uart3.data[3] = t3.y%256;
data_transmit_uart3.data[4] = t3.color;
data_transmit_uart3.data[5] = t3.shape;
data_transmit_uart3.data[6] = t3.flag;
Data_Pack_Transmit(&data_transmit_uart3, USART3); // 对数据进行打包 并通过USART3 发送
GPIO_SetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
else if(tim_task == 3 && task_flag == 1)
{
Target_Parse(&data_receive_uart1,&target1); // 解析 data_receive_uart1 接收数据 给 target1
Target_Parse(&data_receive_uart2,&target2); // 解析 data_receive_uart2 接收数据 给 target2
Target_Parse(&data_receive_uart3,&target3); // 解析 data_receive_uart3 接收数据 给 target3
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
task_flag = 0;
}
}
// 定时器3 中断函数
void TIM3_IRQHandler(void) // TIM3中断服务函数
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) // 检查TIM3更新中断发生与否
{
if(tim_task < 3)
{
tim_task = tim_task + 1; // 任务切换
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
else
{
tim_task = 0; // 重头开始执行任务
task_flag = 1; // 任务完成标志 0完成 1未完成 每次只做一次任务
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除TIM3更新中断标志
}
}
// 串口1 中断函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 判断接收数据寄存器是否有数据
{
Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1)); // 从 串口1 接收1个数据
USART_ClearFlag(USART1,USART_IT_RXNE); // 清空中断标志 准备下一次接收
}
}
// 串口2 中断函数
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) // 判断接收数据寄存器是否有数据
{
Data_Receive(&data_receive_uart2, USART_ReceiveData(USART2)); // 从 串口2 接收1个数据
USART_ClearFlag(USART2,USART_IT_RXNE); // 清空中断标志 准备下一次接收
}
}
// 串口3 中断函数
void USART3_IRQHandler(void)
{
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) // 判断接收数据寄存器是否有数据
{
Data_Receive(&data_receive_uart3, USART_ReceiveData(USART3)); // 从 串口3 接收1个数据
USART_ClearFlag(USART3,USART_IT_RXNE); // 清空中断标志 准备下一次接收
}
}
*/
uartprotocol.h
#ifndef __UARTPROTOCOL_H
#define __UARTPROTOCOL_H
// 重命名方便定义
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
// 串口发送相关结构体
typedef struct
{
uint8_t head1; // 帧头1
uint8_t head2; // 帧头2
uint8_t length; // 有效数据长度
uint8_t cnt; // 总数据长度
uint8_t data[40]; // 有效数据数组
uint8_t transmit_data[50]; // 实际发送的数组 附带上帧头1 帧头2 有效数据长度位 校验位
}DataTransmit;
// 串口发送相关函数
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length);
void Data_Pack(DataTransmit *data);
// 串口接收相关结构体
typedef struct
{
uint8_t head1; // 帧头1
uint8_t head2; // 帧头2
uint8_t length; // 有效数据长度
uint8_t cnt; // 总数据长度
uint8_t state; // 接收状态
uint8_t i; // 有效数据下标
uint8_t data; // 接收数据缓冲位
uint8_t receive_data[50]; // 实际接收的数组 附带上帧头1 帧头2 有效数据长度位 校验位
}DataReceive;
// 串口接收相关函数
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2);
void Data_Receive(DataReceive *data, uint8_t buf);
// 接收数据解析相关结构体
typedef struct
{
uint16_t x; // 目标x轴坐标
uint16_t y; // 目标y轴坐标
uint8_t color; // 目标颜色标志位
uint8_t shape; // 目标形状标志位
uint8_t flag; // 目标标志位
}TargetProperty;
// 接收数据解析相关函数
void Target_Init(TargetProperty *target);
void Target_Parse(DataReceive *data, TargetProperty *target);
#endif