【STM32】STM32标准库学习笔记(一)——串口通信

【STM32】STM32标准库学习笔记(一)——串口通信

  • 前言
  • 串口通信
    • 串口通信思路
    • 软件部分代码设计
      • 串口发送部分
      • 串口接收部分
      • 串口接收数据解析部分
    • 硬件部分代码设计
  • 测试部分
    • 测试模板搭建
    • 测试
  • 完整源码
    • 硬件部分完整源码
    • 软件部分完整源码
    • 任务队列

前言

关于STM32的串口通信之前的文章里有介绍过,传送门如下。
串口通信(往期)传送门
【串口通信】K210与STM32串口通信、K210与OpenMV串口通信
但是我觉得之前这一份代码还不够好用,因此我重制了一份。
本篇博文主要介绍STM32(标准库)如何进行串口通信,使用的STM32型号为STM32F103ZET6,我将串口通信代码划分为了两部分,一部分为硬件部分,一部分为软件部分。 软件部分与无关,不管是HAL库还是标准库都能用,硬件部分则是兼容HAL库或者标准库,本篇主要介绍标准库的串口通信。 事实上,在其他平台,只要配置好串口,软件部分的代码也是能够通用的。

串口通信

串口通信思路

串口通信的思路就是发送端将数据打包发送,接收端从帧头开始接收数据,为了防止帧头和发送数据混在一起,所以设置两个帧头。当两个帧头都满足条件时才会继续接收,设置有效数据长度位是为了方便灵活接收不同长度的数据,比如有效数据长度位是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]

这部分的代码是不唯一的,根据个人情况定义即可。

硬件部分代码设计

由于是标准库,因此串口初始化代码需要自己写,这里举例USART1的初始化代码,USART2和USART3的初始化在末尾完整源码中,串口初始化代码都差不多,这里可带入三个参数,分别可设置串口的波特率,抢占优先级,子优先级。

/********************************************************************

串口初始化函数 Init_UART1

	 各参数作用
	 uint32_t baud: 	        				            设置串口波特率
	 uint8_t pp:											设置串口抢占优先级
	 uint8_t sp:											设置串口子优先级
	 
********************************************************************/
void Init_UART1(uint32_t baud, uint8_t pp, uint8_t sp)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	// 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);						            // USART1 在 APB2 总线上
  
	// USART1_TX PA9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  // USART1_RX PA10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure); 

  // USART1 初始化设置
	USART_InitStructure.USART_BaudRate = baud;												// 串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							    // 8数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								    // 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); 												// 初始化串口1

	// USART1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pp ;							    // 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = sp;									    // 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;									        // IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);															// 根据指定的参数初始化NVIC寄存器
  	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);									        // 使能接收中断
	
	USART_Cmd(USART1, ENABLE);                    										    // 使能串口1 
	
}

然后就是封装一下串口发送数据的函数,这部分调用了标准库的发送函数USART_SendData,带入参数为被发送结构体以及串口号,比如要发送的结构体为data,通过串口1来发送,就是
Data_Transmit(&data,USART1);

/********************************************************************

串口发送数据函数 Data_Transmit

	 各参数作用
	 DataTransmit *data: 	        		选择要发送的串口发送数据结构体
	 USART_TypeDef* USARTx:				    选择通过哪一个串口发送
	 
********************************************************************/
void Data_Transmit(DataTransmit *data, USART_TypeDef* USARTx)
{
	
	for(uint8_t i = 0; i < data -> cnt; i++)
	{
		USART_SendData(USARTx, data -> transmit_data[i]);
		while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
	}
	
}

/********************************************************************

串口打包发送数据函数 Data_Pack_Transmit

	 各参数作用
	 DataTransmit *data: 	        		选择要发送的串口发送数据结构体
	 USART_TypeDef* USARTx:				    选择通过哪一个串口发送
	 
********************************************************************/
void Data_Pack_Transmit(DataTransmit *data, USART_TypeDef* USARTx)
{
	
	Data_Pack(data);				
    Data_Transmit(data, USARTx);			
	
}

测试部分

测试模板搭建

先上传一下我的工程模板,链接如下所示,设置的是0积分免费下载。 如果你想要自己搭建工程模板,或者自己已经有的,也可以使用自己的。另外我这个压缩包里也有 uartconfig 和 uartprotocol 的源码,不想去文末复制的也可以下载。
STM32串口通信代码
下载完后,将FirmWare下的工程模板Template解压,注意不要进错目录,HardWare文件夹中是HAL库的ioc文件。
【STM32】STM32标准库学习笔记(一)——串口通信_第1张图片
然后在解压后的文件夹中创建一个名为MyLibrary的文件夹,这个名字可以自行定义。
【STM32】STM32标准库学习笔记(一)——串口通信_第2张图片
将Layer解压。
在这里插入图片描述
注意路径,将HardWareLayer -> FirmWare -> UART 下的文件复制或者移动到 MyLibrary
【STM32】STM32标准库学习笔记(一)——串口通信_第3张图片
注意路径,将HardWareLayer -> FirmWare -> TIMER 下的文件复制或者移动到 MyLibrary
【STM32】STM32标准库学习笔记(一)——串口通信_第4张图片
注意路径,将SoftWareLayer -> UART 下的文件复制或者移动到 MyLibrary
【STM32】STM32标准库学习笔记(一)——串口通信_第5张图片
然后打开工程即可。
【STM32】STM32标准库学习笔记(一)——串口通信_第6张图片
然后按照图中顺序依次点击,创建一个文件夹。
【STM32】STM32标准库学习笔记(一)——串口通信_第7张图片
然后在该文件夹下添加文件,定位到刚才的MyLibrary目录下,将.c的文件都添加进来。
【STM32】STM32标准库学习笔记(一)——串口通信_第8张图片
然后记得在魔术棒中添加 MyLibrary 文件夹的环境,不然编译器是找不到路径的。
【STM32】STM32标准库学习笔记(一)——串口通信_第9张图片
然后将 uartprotocol.c 文件中的 669 行开始到 888 行 复制到 main.c
【STM32】STM32标准库学习笔记(一)——串口通信_第10张图片
【STM32】STM32标准库学习笔记(一)——串口通信_第11张图片
然后直接编译即可。
【STM32】STM32标准库学习笔记(一)——串口通信_第12张图片

【STM32】STM32标准库学习笔记(一)——串口通信_第13张图片
需要提到的是,我这里串口发送和解析用的是任务队列进行延时,因为串口发送如果太快,将会导致程序一直卡在中断中,无法做其他任务,然后我用了一个LED来标识是否正常运行,任务队列需要定时器来计数,这也是之前为什么要复制timer文件的原因,当然这个文件在文章末尾也可以复制。
整体工作流程就是每一次触发定时器,都会进行任务切换,且将任务标志置1。
【STM32】STM32标准库学习笔记(一)——串口通信_第14张图片
而任务运行函数在 while 循环中运行,每次任务切换,且标志置为1的时候,将会执行一次对应任务,然后标志置为0,也就是说一次任务切换只会执行一次任务,这样避免串口一直发送数据导致程序一直卡在中断函数中。
【STM32】STM32标准库学习笔记(一)——串口通信_第15张图片

测试

使用ST-Link进行测试,接线方法如图所示,这里采用的测试方法是自发自收,所以各串口的RX和TX接一起即可。
然后DeBug即可。
在这里插入图片描述
右键将结构体添加进来。
【STM32】STM32标准库学习笔记(一)——串口通信_第16张图片
t1是被串口1发送的,target1是保存串口1接收的数据的。
t2是被串口1发送的,target2是保存串口2接收的数据的。
t3是被串口1发送的,target3是保存串口3接收的数据的。
也就是说在t1的数据改变的时候,target1也改变,就说明能成功通信了。
在这里插入图片描述
右键取消一下HEX显示。
在这里插入图片描述
然后点击按钮开始运行,更改t1中参数的值,会发现target1中也会相应更改。
【STM32】STM32标准库学习笔记(一)——串口通信_第17张图片
t2和target2也是如此。
在这里插入图片描述
t3和target3也是如此,三部分响应时间都很快,说明可以成功串口通信。
在这里插入图片描述

由于当前手上没第二部32,因此两部32之间的通信就等我回学校再进行测试了,基本上是没问题的。

完整源码

硬件部分完整源码

uartconfig.c

/********************************************************************/
//	作用:				STM32固件库串口配置
//	作者:				FITQY
//	时间:				2022.08.29
/********************************************************************/
#include "uartconfig.h"

/********************************************************************

串口发送数据函数 Data_Transmit

	 各参数作用
	 DataTransmit *data: 	        		选择要发送的串口发送数据结构体
	 USART_TypeDef* USARTx:				    选择通过哪一个串口发送
	 
********************************************************************/
void Data_Transmit(DataTransmit *data, USART_TypeDef* USARTx)
{
	
	for(uint8_t i = 0; i < data -> cnt; i++)
	{
		USART_SendData(USARTx, data -> transmit_data[i]);
		while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
	}
	
}

/********************************************************************

串口打包发送数据函数 Data_Pack_Transmit

	 各参数作用
	 DataTransmit *data: 	        		选择要发送的串口发送数据结构体
	 USART_TypeDef* USARTx:				    选择通过哪一个串口发送
	 
********************************************************************/
void Data_Pack_Transmit(DataTransmit *data, USART_TypeDef* USARTx)
{
	
	Data_Pack(data);				
  Data_Transmit(data, USARTx);			
	
}

/********************************************************************

串口接收1个数据函数 Buffer_Receive

	 各参数作用
	 DataReceive *data: 							选择通过哪一个串口接收数据结构体接收
	 USART_TypeDef* USARTx:				    选择通过哪一个串口接收 
	 
********************************************************************/
uint8_t Buffer_Receive(DataReceive *data, USART_TypeDef* USARTx)
{
	
	data -> data = USART_ReceiveData(USARTx);
	return data -> data;
	
}

/********************************************************************

串口初始化函数 Init_UART1

	 各参数作用
	 uint32_t baud: 	        				设置串口波特率
	 uint8_t pp:											设置串口抢占优先级
	 uint8_t sp:											设置串口子优先级
	 
********************************************************************/
void Init_UART1(uint32_t baud, uint8_t pp, uint8_t sp)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	// 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);														// USART1 在 APB2 总线上
  
	// USART1_TX PA9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  // USART1_RX PA10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure); 

  // USART1 初始化设置
	USART_InitStructure.USART_BaudRate = baud;																				// 串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;												// 8数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;														// 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); 																				// 初始化串口1

	// USART1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pp ;												// 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = sp;																// 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;																		// IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);																										// 根据指定的参数初始化NVIC寄存器
  	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);																		// 使能接收中断
	
	USART_Cmd(USART1, ENABLE);                    																		// 使能串口1 
	
}

// 串口1 中断函数 复制到 main.c 中即可使用
/*

// 串口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);																				// 清空中断标志 准备下一次接收
			
    }
	
}

*/

/********************************************************************

串口初始化函数 Init_UART2

	 各参数作用
	 uint32_t baud: 	        				设置串口波特率
	 uint8_t pp:											设置串口抢占优先级
	 uint8_t sp:											设置串口子优先级
	 
********************************************************************/
void Init_UART2(uint32_t baud, uint8_t pp, uint8_t sp)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	// 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);														// USART2 在 APB1 总线上
  
	// USART2_TX PA2
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  // USART2_RX PA3
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure); 

  // USART2 初始化设置
	USART_InitStructure.USART_BaudRate = baud;																				// 串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;												// 8数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;														// 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); 																				// 初始化串口2

	// USART2 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pp ;												// 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = sp;																// 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;																		// IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);																										// 根据指定的参数初始化NVIC寄存器
  	
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);																		// 使能接收中断
	
	USART_Cmd(USART2, ENABLE);                    																		// 使能串口2
	
}

// 串口2 中断函数 复制到 main.c 中即可使用
/*

// 串口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);																				// 清空中断标志 准备下一次接收
			
    }
	
}

*/

/********************************************************************

串口初始化函数 Init_UART3

	 各参数作用
	 uint32_t baud: 	        				设置串口波特率
	 uint8_t pp:											设置串口抢占优先级
	 uint8_t sp:											设置串口子优先级
	 
********************************************************************/
void Init_UART3(uint32_t baud, uint8_t pp, uint8_t sp)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	// 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);														// USART3 在 APB1 总线上
  
	// USART3_TX PB10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
   
  // USART3_RX PB11
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &GPIO_InitStructure); 

  // USART3 初始化设置
	USART_InitStructure.USART_BaudRate = baud;																				// 串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;												// 8数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;														// 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); 																				// 初始化串口3

	// USART3 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pp ;												// 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = sp;																// 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;																		// IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);																										// 根据指定的参数初始化NVIC寄存器
  	
	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);																		// 使能接收中断
	
	USART_Cmd(USART3, ENABLE);                    																		// 使能串口3
	
}

// 串口3 中断函数 复制到 main.c 中即可使用
/*

// 串口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);																				// 清空中断标志 准备下一次接收
			
    }
	
}

*/

uartconfig.h

#ifndef __UARTCONFIG_H
#define __UARTCONFIG_H

#include "stm32f10x.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, USART_TypeDef* USARTx);
void Data_Pack_Transmit(DataTransmit *data, USART_TypeDef* USARTx);

// 串口接收相关函数
uint8_t Buffer_Receive(DataReceive *data, USART_TypeDef* USARTx);

// 串口初始化函数
void Init_UART1(uint32_t baud, uint8_t pp, uint8_t sp);
void Init_UART2(uint32_t baud, uint8_t pp, uint8_t sp);
void Init_UART3(uint32_t baud, uint8_t pp, uint8_t sp);

#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

任务队列

timer.c

/********************************************************************/
//	作用:				定时器中断服务任务队列运行
//	作者:				FITQY
//	时间:				2022.09.01
/********************************************************************/
#include "timer.h"

/********************************************************************

定时器初始化函数 Init_TIM3

	 各参数作用
	 uint16_t arr: 	        				设置自动装载值
	 uint16_t psc:									设置预分频系数
	 uint8_t pp:										设置定时器抢占优先级
	 uint8_t sp:										设置定时器子优先级
	 
********************************************************************/	 
void Init_TIM3(uint16_t arr,uint16_t psc,uint8_t pp, uint8_t sp)
{
	
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 						// 时钟使能
	
	// 定时器 TIM3 初始化
	TIM_TimeBaseStructure.TIM_Period = arr; 												// 设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler = psc; 											// 设置用来作为TIM3时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 				// 设置时钟分割 TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  		// TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 								// 根据指定的参数初始化TIM3的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); 							  				// 使能指定的TIM3中断 允许更新中断

	// 中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  								// TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pp;  		// 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = sp;  						// 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 								// IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);  																// 初始化NVIC寄存器

	TIM_Cmd(TIM3, ENABLE);  																				// 使能TIM3	
	
}

/*

// 定时器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更新中断标志 
				
	}
	
}

*/

// LED 初始化函数
void LED_Init(void)
{    	
	
 	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_5);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOE,&GPIO_InitStruct);
	
	GPIO_SetBits(GPIOE,GPIO_Pin_5);
	
}

timer.h

#ifndef __TIMER_H
#define __TIMER_H

#include "stm32f10x.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;

// 定时器3 初始化函数
void Init_TIM3(uint16_t arr,uint16_t psc,uint8_t pp, uint8_t sp);

// LED 初始化函数
void LED_Init(void);
 
#endif

你可能感兴趣的:(学习,单片机,嵌入式硬件,stm32,mcu)