天天看點

stm32 usart 單線半雙工序列槽 DMA發送 最後一個位元組發不出來問題

最近遇到一個小問題,感覺很有意思便記下來分享一下 ,順便也為日後類似的問題提供點思路:

使用stm32序列槽發送資料 ,序列槽是單線半雙工模式,要求資料發送前序列槽變成發送模式,發送完後立即變回接收模式,發送操作使用DMA來發送,程式開了序列槽中斷和DMA中斷,序列槽中斷主要是用于接收與解析資料,DMA中斷是想用于操作序列槽收發模式切換,發現DMA發送時最後一個資料老是發不出來。

經過分析定位發現是因為DMA将傳輸完成後,序列槽其實還沒有将所有的問題發送出去(至少還有一個位元組沒發出去),然後修改操序列槽收發模式切換時間點将該問題解決,具體為:序列槽在DMA傳輸前由接收模式變成發送模式,在DMA傳輸完成中斷中開啟序列槽發送完成中斷,在序列槽發送完成中斷中将序列槽工作模式從發送模式變回接收模式,問題完解決,相關配置代碼如下:

序列槽配置:

void Usart_Init( void )
{
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef  GPIO_InitStructure;

    //GPIO clock enable
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);     // USART1時鐘使能

    //USART1_TX
    GPIO_InitStructure.GPIO_Pin = USART1_TX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	           // 複用推挽輸出
    GPIO_Init(USART1_TXRX_PORT, &GPIO_InitStructure);

    //USART1_RX
    GPIO_InitStructure.GPIO_Pin = USART1_RX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;      // 浮空輸入
    GPIO_Init(USART1_TXRX_PORT, &GPIO_InitStructure);


    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(USART1, &USART_InitStructure);                       // 初始化序列槽1
		USART_HalfDuplexCmd(USART1, ENABLE);                            // 使能變雙工
 
    DMA_Usart_Init();

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;         // USART1中斷
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;// 搶占優先級2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		    // 子優先級1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			      // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	                          // 根據指定的參數初始化NVIC寄存器

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);            // 開啟序列槽1接收中斷
    USART_Cmd(USART1, ENABLE);                                // 使能序列槽1
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);            // 開啟序列槽USART1的DMA發送

}
           

DMA配置:

void DMA_Usart_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //開啟DMA1時鐘

    DMA_Cmd(DMA1_Channel4, DISABLE);//關閉DMA1的通道4  Usart1_TX對應通道4
    DMA_DeInit(DMA1_Channel4);//恢複預設值

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);//設定序列槽發送資料寄存器
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMATXBUFF1;//設定發送緩沖區首位址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//設定外設位目标,記憶體緩沖區->外設寄存器
    DMA_InitStructure.DMA_BufferSize = 10;//需要發送的位元組數,這裡可以設定為0,因為在實際要發送時還會重新設定
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設位址不做增加調整,調整不調整是DMA自動實作的
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//記憶體緩沖區位址增加調整
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設資料寬度8位,1個位元組
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//記憶體資料寬度8位,1個位元組
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//單次傳輸模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;//優先級配置
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//關閉記憶體到記憶體的DMA模式
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);//寫入配置

    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;//DMA1中斷
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ;//搶占優先級2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//子優先級1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根據指定的參數初始化NVIC寄存器

    DMA_ClearFlag(DMA1_FLAG_GL4);//清除DMA所有标志 GL4:通道4全局标志 TC4:傳輸完成 HT4:傳輸過半 TE4:傳輸錯誤
    DMA_Cmd(DMA1_Channel4, DISABLE);//關閉DMA1的通道4
    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);//開啟發送DMA通道中斷

}
           

序列槽中斷:

void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  // 接收資料寄存器非空标志
    {
        if(RxNum1 > (RXBUFFSIZE1 - 1))
        {
            RxNum1 = 0;
        }
        RXBUFF1[RxNum1++] = USART_ReceiveData(USART1);
    }
    else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)// 發送完成标志
    {
        USART_ITConfig(USART1, USART_IT_TC, DISABLE);
        USART_ClearFlag(USART1,USART_FLAG_TC);
        USART1_RX_EN_TX_DIS();
    }
    else
    {
			
    }

}
           

DMA中斷:

void DMA1_Channel4_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_FLAG_TC4) != RESET)     // DMA1通道4發送完成标志
    {
        Usart1TxBusy = 0;
        DMA_Cmd(DMA1_Channel4, DISABLE);            // 關閉DMA1的通道4
        DMA_ClearFlag(DMA1_FLAG_GL4);               // 清除DMA1的通道4的所有中斷标志,因為隻有發送完成标志,是以直接全部清除
        USART_ClearFlag(USART1,USART_FLAG_TC);	    // 在關中斷前先清中斷标志,防止是序列槽上一個資料發送時的中斷标志
        USART_ITConfig(USART1, USART_IT_TC, ENABLE);// DMA1傳輸完成後使能Usart1發送完成中斷
    }
}
           

繼續閱讀