串口空閒中斷+DMA接收不定長度的數據

  此工程的硬件環境爲尚學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顯示畫面如下:
在這裏插入圖片描述

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