串口通信詳解(項目級接收、發送機制,基於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循環負責一直檢測和解析出數組中的數據,並做相應迴應!串口發送函數我們可以直接調用,不需要自己進行數據處理
希望讀者能夠互相交流,多多交流,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章