Detailed serial communication (project-level receiving and sending mechanism, based on STM32F103ZET6)

Introduction
As in the things workers (one of the five top IT industry) sectors, serial communication is undoubtedly one of the key points can be a good grasp of serial communication, it is essential! In order to allow readers to better understand and learn the serial port, I transplanted the application serial port of the project to the STM32F103ZET6 that most scholars have learned, and passed the verification!

purpose

Realize the communication with the external MCU, complete the data receiving and processing, including multiple data buffer functions, and improve the performance of the serial port! After the data is successfully parsed, the data transmission to the external MCU is completed, and the communication process is completed

concept

In the project, what we must understand is the relationship between threads and processes: (briefly describe)

  1. The process is the basic unit of allocation, it is an instance of the execution of the program, it is established when the program is run.
  2. A thread is the smallest unit of executing a program, it is an execution flow of a process, and a process can be composed of multiple threads.
    It can be simply understood as creating an endless loop while(1) in the main function of our STM32 development board, that is, a thread, which can be simply understood as a thread corresponding to a while loop, and the loop may not be exited, depending on the content of the project;

initialization

Initialize the timer, determine the overflow time at 10ms, initialize the serial port, select the serial port you want to communicate

Program explanation

Header file call:
Look at the header file comments, string.h is very important. The copy function memcpy() and value function memset() used in subsequent programs will all be used. For specific parameters, you can see the library function

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

Macro definitions
explanation see watching

#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_;

Variable definitions

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

Focus

The editor also tried a lot of methods before, and finally chose this set of processing methods:
initialization of the serial port and receiving data.
Process combing:
every time a byte is received, an interrupt will be triggered (stop bit to trigger the interrupt). The received data is directly stored in the global bufff defined by us. At this time, we should pay attention to the length of the received data. Once the length exceeds the length, we choose to discard other data.

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)
	{
    
    
		;
	}
}

Timer interrupt initialization and data processing
The focus of initialization is the overflow time, which is 10ms. If the time interval between two data transmissions is less than 10ms, it is judged as one frame of data.
Calculation formula:
Tout=(reload value+1) *(Frequency value +1)/72MHz
process overview
Always start the timer, cycle counting, the initial value of the serial data buffer quantity and the number of data saved in the timer are all zero, when the timer expires, use the global variable num to compare first The number of data received in the serial port USART_RX_STA. If the serial port receives new data during this overflow period, it then enters the timer interrupt. At this time, the initial value is num=0, but USART_RX_STA=1, which is judged in the timer interrupt , If USART_RX_STA>num, it means that a frame of data has not been received completely, if it is equal and not zero, it means that no new data has been received in the last overflow time period (10ms), it is judged that the reception of a frame of data is finished, and then Cycle through the array of the structure that saves this data is empty, if it is empty, copy the data to this array, if the opened array space is occupied, ignore this data One
sentence summary:
Cycle to determine whether the array memory in the structure Is empty, once it is detected to be empty, the data received will be copied into this structure
algorithm code analysis

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 );
	}
	   
	
}

to sum up:

The serial port is only responsible for receiving data (within a certain number), the timer monitors the data transmission and reception, and at the same time is responsible for copying the data and saving it to the array. The while loop is responsible for always detecting and parsing out the data in the array, and responding accordingly! We can directly call the serial port sending function without data processing by ourselves.
I hope readers can communicate with each other and communicate a lot, thank you!

Guess you like

Origin blog.csdn.net/weixin_42271802/article/details/105273861