串口通信详解(项目级接收、发送机制,基于STM32F103ZET6)

前言
作为从事物联网(五大IT热门行业之一)行业的工作者,串口通信无疑是key点之一,能很好的掌握串口通信,是必不可少的!为了让读者更好的理解和学习串口,我将项目的应用串口移植到了大多数学者学过的STM32F103ZET6,并且通过了验证!

目的

实现与外部mcu通信,完成对数据的接收处理,包括多条数据缓存功能,提高串口的性能!成功解析数据后完成对外部mcu的数据发送,完成通信流程

概念

在项目中,我们必须了解的是线程与进程的关系:(简单描述一下)

  1. 进程是分配的基本单位,它是执行程序的一个实例,在运行程序时建立。
  2. 线程是执行程序的最小单位,是进程的一个执行流,,一个进程可以是多个线程组成。
    可以简单理解为我们STM32开发板的main函数中创建一个死循环while(1),也就是一个线程,可以简单理解为一个线程对应一个while循环,可以不退出循环,主要看项目内容;

初始化

初始化定时器,溢出时间确定在10ms,串口初始化,选择自己想要的串口进行通信

程序讲解

头文件调用:
看头文件注释,string.h很重要,后续程序中用到的拷贝函数memcpy(),值位函数memset()都会用到,具体参数,可以看库函数

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "stdio.h"
#include "string.h"//字符串操作头文件

宏定义
解释看注视

#define len 10//根据自己情况调节每次接收数据的最大长度
#define ARRAY_NUM 4//缓存从外部mcu收到的数据条数
#define ture 1
#define fals 0
typedef struct _usart//每个数据的参数,数据,标志位信息
{
    
    
	uint16_t DATA_LEN;
	uint8_t DATA[250];
	uint8_t FLAG;
}usart_;

变量定义

uint8_t bufff[len];//保存长度为len的字节
uint16_t USART_RX_STA=0;
usart_ g_uart[ARRAY_NUM];//保存数据为ARRAY_NUM的数据

重点

小编以前也是进行了很多的方法尝试,最终选择了这套处理方式:
串口的初始化及接收数据
流程梳理:
每接收到一个字节,会触发(停止位来触发中断)一次中断,此时将收到的数据直接保存在我们定义的全局bufff中,此时我们应该注意到接收数据的长度,一旦超过长度选择抛弃掉其他的数据

void 中断函数入口(void)
{
    
    
	if(判断是否为接收中断触发)
	{
    
    
		uint8_t 临时变量;
		临时变量=(uint8_t)接收函数(串口);
		if(接收的数据长度>=预定的长度)
		{
    
    
			接收的数据长度=预定的长度; 
			返回;
		}
		串口数据赋值给定义的数组;
	}
}
void usart_init(u32 bound)
{
    
    
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate=bound;
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStructure.USART_Parity=USART_Parity_No;
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	USART_Cmd(USART1, ENABLE);
	
}

void USART1_IRQHandler(void)
{
    
    
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
    
    
		uint8_t res;
		res=(uint8_t)USART_ReceiveData(USART1);
		if(USART_RX_STA>=len)
		{
    
    
			USART_RX_STA=len; 
			return;
		}
		bufff[USART_RX_STA++]=res;
	}
	else if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
	{
    
    
		;
	}
}

定时器中断初始化及数据处理
初始化中的重点就是溢出的时间,为10ms即可,如果两个数据传输的时间间隔小于10ms,则判断为一帧数据
计算公式:
Tout=(重装载值+1)*(分频值+1)/72MHz
流程概述
一直开启定时器,循环计数,串口数据缓存数量与定时器中保存数据数量的初始值都为零,当定时器时间到,用全局变量num先比较串口中接收到的数据的数量USART_RX_STA,如果串口在此溢出时间段收到了新的数据,接着进入定时器中断,此时为初始值为num=0,但是USART_RX_STA=1,在定时器中断中判断,如果USART_RX_STA>num,则表明一帧数据未接受完全,如果相等且不为零,则表明在上一个溢出时间周期(10ms)中没有收到新的数据,判断为一帧数据接收结束,再循环扫描保存此数据的结构体的数组是否为空,如果为空,复制数据到此数组,如果开辟的数组空间都被占用,则忽略此数据
一句话概括:
循环判断结构体中的数组内存是否为空,一旦检测到为空,则将其收到的数据复制进此结构体
算法代码解析

void 定时器中断函数入口(void) {
    
      	
if(判断是否为定时器中断) 	
{
    
    	   	static uint16_t num定义静态变量;
 		static uint8_t index定义静态变量;
 		unsigned char buff_busy定义局部变量; 		
 		if(串口中断接收数据数量>静态变量) 
 		{
    
     		
 		   静态变量=串口中断接收数据数量; 		
 		} 		
 		else if(静态变量==串口中断接收数据数量&&静态变量!=0) 		
 		{
    
    
			  while(g_uart[静态变量%定义的缓存数组数量].FLAG==ture)
				{
    
    
					静态变量=(静态变量+1)%定义的缓存数组数量;
					if(局部变量++>定义的缓存数组数量)
					{
    
    
						将收到的数据清空;
						USART_RX_STA=0;//清零
						num=0;
						break;
					}
					
				}
				一旦检测到空数组,就开始赋值;
	            g_uart[idex].DATA_LEN=num;//数组长度
				memset(g_uart[idex].DATA,0,g_uart[idex].DATA_LEN);	//将要赋值的结构体数组清零		memcpy(g_uart[idex].DATA,bufff,g_uart[idex].DATA_LEN);//将要赋值的数据存到清零的结构体
	g_uart[idex].FLAG=ture;//将此结构体的标志位标志位ture
memset(bufff,0,g_uart[idex].DATA_LEN);//将串口接收的数据bufff成功赋值给结构体后立即清零,方便下一次的接收
					USART_RX_STA=0;//方便下一次数据接收计数
					num=0; 	//清零	
					} 		
					else  		
					{
    
    
					 	; 		
					} 	
		  LED1=!LED1;
		  TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除中断标志位
		  	}
	      }
void timer_init(void)
{
    
    
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	LED_Init();
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period= 100;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	TIM_Cmd(TIM3, ENABLE);
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
	NVIC_Init(&NVIC_InitStructure);
	
}
void TIM3_IRQHandler(void)
{
    
     
	if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
	{
    
    	  
		static uint16_t num=0;
		static uint8_t idex=0;
		unsigned char buff_busy;
		if(USART_RX_STA>num)
		{
    
    
			num=USART_RX_STA;
		}
		else if(USART_RX_STA==num&&num!=0)
		{
    
    
			  while(g_uart[idex%ARRAY_NUM].FLAG==ture)
				{
    
    
					idex=(idex+1)%ARRAY_NUM;
					if(buff_busy++>ARRAY_NUM)
					{
    
    
						memset(bufff,0,g_uart[0].DATA_LEN);
						USART_RX_STA=0;
						num=0;
						break;
					}
					
				}
				  g_uart[idex].DATA_LEN=num;
					memset(g_uart[idex].DATA,0,g_uart[idex].DATA_LEN);
					memcpy(g_uart[idex].DATA,bufff,g_uart[idex].DATA_LEN);
					g_uart[idex].FLAG=ture;
					
					memset(bufff,0,g_uart[idex].DATA_LEN);
					USART_RX_STA=0;
					num=0;
		}
		else 
		{
    
    
			;
		}
		
		
		  LED1=!LED1;
		  TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
	}
	   
	
}

总结:

串口只负责接收数据(在一定数目内),定时器监听数据收发情况,同时负责复制数据保存到数组内,while循环负责一直检测和解析出数组中的数据,并做相应回应!串口发送函数我们可以直接调用,不需要自己进行数据处理
希望读者能够互相交流,多多交流,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_42271802/article/details/105273861