前言
作爲從事物聯網(五大IT熱門行業之一)行業的工作者,串口通信無疑是key點之一,能很好的掌握串口通信,是必不可少的!爲了讓讀者更好的理解和學習串口,我將項目的應用串口移植到了大多數學者學過的STM32F103ZET6,並且通過了驗證!
目的
實現與外部mcu通信,完成對數據的接收處理,包括多條數據緩存功能,提高串口的性能!成功解析數據後完成對外部mcu的數據發送,完成通信流程
概念
在項目中,我們必須瞭解的是線程與進程的關係:(簡單描述一下)
- 進程是分配的基本單位,它是執行程序的一個實例,在運行程序時建立。
- 線程是執行程序的最小單位,是進程的一個執行流,,一個進程可以是多個線程組成。
可以簡單理解爲我們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循環負責一直檢測和解析出數組中的數據,並做相應迴應!串口發送函數我們可以直接調用,不需要自己進行數據處理
希望讀者能夠互相交流,多多交流,謝謝!