利用定時中斷接收不定長度的串列埠資料
阿新 • • 發佈:2018-12-05
在使用串列埠時,通常會遇到一些功能,如在TFT螢幕上顯示串列埠收到的字串,這些字串直接是對方printf過來的,沒有任何協議,此時為了保證顯示內容是一整個句子(通常句子傳送會有間隔),這是我們可以用定時器進行判斷是否接收完成。
以stm32f4為例,程式碼基於正點原子的例程,為了閱讀方便,刪除了部分註釋。
我們需要用到定時器和串列埠兩部分:
timer.h進行定時器初始化函式的宣告。
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
void TIM7_Int_Init(u16 arr,u16 psc);
#endif
timer.c進行定時器的定義及中斷函式說明,其中中斷函式內容的意思大致就是,當定時器溢位(計時到了),強制標記接收完成,然後清除中斷標誌位,禁用定時器,等待使用者處理串列埠接收buffer。
#include "timer.h"
#include "led.h"
extern vu16 USART3_RX_STA;
//定時器7中斷服務程式
void TIM7_IRQHandler(void)
{
if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中斷
{
USART3_RX_STA| =1<<15; //標記接收完成
TIM_ClearITPendingBit(TIM7, TIM_IT_Update ); //清除TIM7更新中斷標誌
TIM_Cmd(TIM7, DISABLE); //關閉TIM7
}
}
//通用定時器中斷初始化
//這裡始終選擇為APB1的2倍,而APB1為36M
//arr:自動重灌值。
//psc:時鐘預分頻數
void TIM7_Int_Init(u16 arr,u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);//TIM7時鐘使能
//定時器TIM7初始化
TIM_TimeBaseStructure.TIM_Period = arr; //設定在下一個更新事件裝入活動的自動重灌載暫存器週期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //設定用來作為TIMx時鐘頻率除數的預分頻值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設定時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根據指定的引數初始化TIMx的時間基數單位
TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE ); //使能指定的TIM7中斷,允許更新中斷
TIM_Cmd(TIM7,ENABLE);//使能定時器7
NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//搶佔優先順序0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子優先順序1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的引數初始化VIC暫存器
}
usart3.h中是對buffer長度的定義,及相關函式的宣告。
#ifndef __USART3_H
#define __USART3_H
#include "sys.h"
#include "timer.h"
#define USART3_MAX_RECV_LEN 400 //最大接收快取位元組數
#define USART3_MAX_SEND_LEN 400 //最大發送快取位元組數
extern u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收緩衝,最大USART3_MAX_RECV_LEN位元組
extern u8 USART3_TX_BUF[USART3_MAX_SEND_LEN]; //傳送緩衝,最大USART3_MAX_SEND_LEN位元組
extern u16 USART3_RX_STA; //接收資料狀態
void usart3_init(u32 bound); //串列埠3初始化
void u3_printf(char* fmt, ...);
void u3_putchar(u8 data);
#endif
usart3.c中是中斷服務函式和配置函式。
其中__align關鍵字用於記憶體對齊,具體可以參考原子的解釋:
FATFS一個小BUG搞了我2天才解決.特此發帖,希望大家不要重蹈我的覆轍.
#include "delay.h"
#include "usart3.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"
#include "timer.h"
//串列埠傳送快取區
__align(8) u8 USART3_TX_BUF[USART3_MAX_SEND_LEN]; //傳送緩衝,最大USART3_MAX_SEND_LEN位元組
//串列埠接收快取區
__align(8) u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收緩衝,最大USART3_MAX_RECV_LEN個位元組.
//通過判斷接收連續2個字元之間的時間差不大於100ms來決定是不是一次連續的資料.
//如果2個字元接收間隔超過100ms,則認為不是1次連續資料.也就是超過100ms沒有接收到
//任何資料,則表示此次接收完畢.
//接收到的資料狀態
//[15]:0,沒有接收到資料;1,接收到了一批資料.
//[14:0]:接收到的資料長度
u16 USART3_RX_STA=0;
void USART3_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到資料
{
res =USART_ReceiveData(USART3);
if((USART3_RX_STA&(1<<15))==0)//接收完的一批資料,還沒有被處理,則不再接收其他資料
{
if(USART3_RX_STA<USART3_MAX_RECV_LEN) //還可以接收資料
{
TIM_SetCounter(TIM7,0);//計數器清空
if(USART3_RX_STA==0)
TIM_Cmd(TIM7, ENABLE); //使能定時器7
USART3_RX_BUF[USART3_RX_STA++]=res; //記錄接收到的值
} else
{
USART3_RX_STA|=1<<15; //強制標記接收完成
}
}
}
}
//初始化IO 串列埠3
//bound:波特率
void usart3_init(u32 bound)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
USART_DeInit(USART3); //復位串列埠3
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能GPIOB時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//使能USART3時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_10; //GPIOB11和GPIOB10初始化
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//複用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推輓複用輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB11,和GPIOB10
GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3); //GPIOB11複用為USART3
GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3); //GPIOB10複用為USART3
USART_InitStructure.USART_BaudRate = bound;//波特率一般設定為9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位資料格式
USART_InitStructure.USART_StopBits = USART_StopBits_2;//一個停止位
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
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//開啟中斷
USART_Cmd(USART3, ENABLE); //使能串列埠
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//搶佔優先順序2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先順序3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的引數初始化VIC暫存器
TIM7_Int_Init(100-1,8400-1); //10ms中斷一次
TIM_Cmd(TIM7, DISABLE); //關閉定時器7
USART3_RX_STA=0; //清零
}
//串列埠3,printf 函式
//確保一次傳送資料不超過USART3_MAX_SEND_LEN位元組
void u3_printf(char* fmt,...)
{
u16 i,j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART3_TX_BUF,fmt,ap);
va_end(ap);
i=strlen((const char*)USART3_TX_BUF);//此次傳送資料的長度
for(j=0; j<i; j++) //迴圈傳送資料
{
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //等待上次傳輸完成
USART_SendData(USART3,(uint8_t)USART3_TX_BUF[j]); //傳送資料到串列埠3
}
}
void u3_putchar(u8 data)
{
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //等待上次傳輸完成
USART_SendData(USART3,data); //傳送資料到串列埠3
}
在中斷服務函式中有這麼一個過程,在正常的串列埠資料儲存過程中,多了個計數器清零和啟動定時器,因為進中斷的時候是當前收到的最後一個字元,那具體有沒有接收完呢?這個時候我們開始計時了(10ms),如果10ms內有資料接收(一般以9600的波特率是1ms發一個數據),那麼這裡會把計數值清空,繼續接收,如果沒有資料來了,定時器溢位會進定時器中斷,強制標記結束標誌。
if(USART3_RX_STA<USART3_MAX_RECV_LEN) //還可以接收資料
{
TIM_SetCounter(TIM7,0);//計數器清空
if(USART3_RX_STA==0)
TIM_Cmd(TIM7, ENABLE); //使能定時器7
USART3_RX_BUF[USART3_RX_STA++]=res; //記錄接收到的值
}
這種方法就不需要以\r\n
作為字串的結束符了。