此工程的硬件環境爲尚學STM32F103ZET6核心板+正點原子3.5寸TFTLCD
工程下載鏈接:https://download.csdn.net/download/qq_40501580/11203377
一、什麼是串口空閒中斷,有啥子用?
CSDN上看到的教程大多是直接就編寫程序實現空閒中斷,但沒有對原理性部分闡述清楚,也沒有寫爲什麼要這樣子寫代碼,那我就自己來總結一下前人的經驗。
在實際做項目的時候,經常需要用串口接收數據,一般是使用串口中斷來接收數據。但是用這種方法的話,就要頻繁進入串口中斷,效率就比較低,裸機(區分系統跑代碼)會增加單片機的負荷。於是就想到用DMA來接收串口數據,但是關鍵的一點,當發送的數據量不定時,如OpenMV發送特徵物體中標座標、接收RM裁判系統回饋數據、Manifold妙算傳輸控制炮管的位置指令,就需要用到串口空閒中斷了。接收不定長度數據是串口空閒中斷的重要使用方法。
在STM32的串口控制器中,設置了有串口空閒中斷,即如果串口空閒,又開啓了串口空閒中斷的話,就觸發串口空閒中斷,然後程序就會跳到串口空閒中斷去執行。有了這個,是不是可以判斷什麼時候串口數據接收完畢了呢?因爲串口數據接收完畢後,串口總線肯定是會空閒的嘛,那這個中斷肯定是會觸發的了。
那麼單片機是怎麼判斷總線空閒的呢?比如一幀數據是18個字節,當在第19個字節的時間裏,串口沒有接受到數據,即過了一段時間,串口接收的數據幀沒有了,即判斷爲總線空閒。
二、串口空閒中斷的配置
void USART3_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//USART3_TX GPIOB.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.11
//USART3_RX GPIOB.11初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB11
//USART3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
//USART3 初始化設置
USART_InitStructure.USART_BaudRate = 115200;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長爲8位數據格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
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_IDLE, ENABLE);//開啓串口空閒中斷
USART_Cmd(USART3, ENABLE); //使能串口3
}
注意這裏的配置與配置接收中斷類似,但開啓的中斷要改爲USART_IT_IDLE標誌符(串口空閒中斷)
三、DMA配置
void DMA_config(DMA_Channel_TypeDef* DMA_CHx,s32 peripherals_addr,s32 memory_addr,u16 size)
{
DMA_InitTypeDef DMA_InitTypestruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA_CHx);
DMA_InitTypestruct.DMA_BufferSize=size; //通道傳輸數據量
DMA_InitTypestruct.DMA_DIR=DMA_DIR_PeripheralSRC; //數據傳輸方向爲 外設到存儲器
DMA_InitTypestruct.DMA_M2M=DMA_M2M_Disable;//不開啓存儲器到存儲器方式
DMA_InitTypestruct.DMA_MemoryBaseAddr=memory_addr;//存儲器基地址
DMA_InitTypestruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//存儲器數據寬度(一次傳輸的數據位數)
DMA_InitTypestruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//開啓存儲器增量模式(因爲存儲器被設置爲一個數組)
DMA_InitTypestruct.DMA_Mode=DMA_Mode_Normal;//正常模式,數據傳輸就一次
DMA_InitTypestruct.DMA_PeripheralBaseAddr=peripherals_addr;//外設基地址
DMA_InitTypestruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//外設數據寬度
DMA_InitTypestruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//不要外設增量模式
DMA_InitTypestruct.DMA_Priority=DMA_Priority_VeryHigh;//這裏設置爲最高優先級(一共4個優先級)
DMA_Init(DMA_CHx,&DMA_InitTypestruct);
DMA_Cmd(DMA_CHx,ENABLE);
}
注意,這裏配置的模式是DMA_Mode_Normal(正常模式),數據傳輸就一次。思路如下:初始化時開啓DMA,在總線空閒時,收到第一幀數據,之後關閉DMA,處理數據,再重新配置DMA的接收字節數後,開啓DMA,準備下一幀數據的接收。所以,這裏不需要設置DMA爲循環發送模式,正常模式即可。
四、中斷程序的編寫
這個就是關鍵的地方了。在這裏需要對DMA設置下。當進入這個中斷的時候,串口接收的數據,已經在內存的數組中了。通過減去DMA的剩餘計數值,就可以知道接收到了多少個數據。然後再把DMA給關掉,重新設置接收數據長度,再開啓DMA,接收下一次串口數據。爲什麼要這麼做了,因爲在STM32手冊中有如下說明:
另外還有一點,串口空閒中斷觸發後,硬件會自動將串口空閒中斷標誌位給置1,我們是需要將標誌位給置0的,不然又要進中斷了,這個在手冊中也有說明。
串口空閒中斷程序參考如下:
/*------------串口空閒中斷程序------------*/
/*------------串口空閒中斷程序------------*/
void USART3_IRQHandler(void)
{
u8 num=0;
static u8 last_num=0;
s8 i=0;
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
{
num=USART3->SR;
num=USART3->DR; //清除USART_IT_IDLE標誌位
DMA_Cmd(DMA1_Channel3,DISABLE);
num = BufferSize - DMA_GetCurrDataCounter(DMA1_Channel3); //得到接收的數據個數
receive_data[num] = '\0'; //爲字符串加上結束符
if(last_num>num)
{
for(i=last_num-num; i>0; i--)
receive_data[num+i]=0; //清除上一次傳輸的數據
}
DMA1_Channel3->CNDTR=BufferSize; //設置DMA下一次接收的字節數
DMA_Cmd(DMA1_Channel3,ENABLE);
Send_Counter++; //加滿溢出後爲1 0000 0000,而Send_Counter只能加載1個字節,所以溢出後自動爲0
receive_flag=1;
last_num = num;
}
}
這裏要注意的操作是:
1、根據芯片手冊,清除空閒中斷的標誌位是要先讀USARTx->SR,再讀USARTx->DR。
2、DMA1_Channel3->CNDTR是DMA剩餘字節寄存器
3、DMA_GetCurrDataCounter(DMA1_Channel3);返回當前剩餘數據單元的數量
五、工程應用演示
串口助手發送數據如下:
3.5寸TFTLCD顯示畫面如下: