DMA和UART的深刻認識--串列埠接收的3種工作方式(附STM32F4程式碼)
可能會遇到的問題:
1.能實現接收但不傳送 注意是否是識別函數出錯
2.DMA單次傳輸模式要求再初始化,否者出現第二次中斷不執行。使用迴圈模式出現的問題是要結合配置公式:
3.DMA再次初始化不完全,會出現接收一次成功,再來一次不行。第三次能接收的問題
4.串列埠除錯連續點選的次數太快,會使的裡面的傳送程式出錯
一.串列埠uart中斷接收
遇到的問題:
1、串列埠除錯接收引腳壞掉
2.接收資料識別,使用的庫函數出錯
串列埠設定的一般步驟可以總結為如下幾個步驟:1) 串列埠時鐘使能, GPIO 時鐘使能。2) 設定引腳複用器對映:呼叫 GPIO_PinAFConfig 函式。3) GPIO 初始化設定:要設定模式為複用功能。
其中串列埠中斷服務程式的解析(正點原子):
void USART1_IRQHandler(void) //串列埠1中斷服務程式 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的資料必須是0x0d 0x0a結尾) { Res =USART_ReceiveData(USART1);//(USART1->DR); //讀取接收到的資料 if((USART_RX_STA&0x8000)==0)//接收未完成 相當於一個迴圈 一開始肯定進入這裡 因為賦初值為0 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始 else USART_RX_STA|=0x8000; //接收完成了 } else //還沒收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收 } } } }
上面的程式接收部分為正點原子編寫的一段程式,具體的思路是:
定義了接收狀態暫存器USART_RX_STA總共16位,由於串列埠接收到資料開始產生中斷,在中斷函式裡啟動接收指令,一次讀取一個位元組,根據標誌位收到倒數第二個位元組0x0d 和最後一個位元組0x0a代表資料接收完。因此在上面的中斷處理函式中它做的判斷是:依據讀取的資料判斷接收是否完成,或者接收到資料了若接收到14位了表明之前已經判斷是接收到0x0d,並將該位置位。若這次沒有收到資料0x0a說明接收出錯。所以兩個變數的判斷很關鍵:USART_RX_STA和Res(接收的單個位元組);
如果接收的位元組還沒有到14位 而且當前的res不是0x0d,則接收緩衝區陣列遞增,通過USART_RX_STA++的方式。若是累加的數目要大於已知的資料長度,則說明多接收了,肯定出問題,但為什麼是減一,原因是USART_RX_STA是從0開始的,也就是累加到2時已經代表3個數據了(C語言的知識)同時將USART_RX_STA=0,重新開始接收。
但是問題是:中斷接收字串,使用Res =USART_ReceiveData(USART1);只是讀取一個位元組?難道是每個位元組的接收都會啟動一次中斷?
答:這和我們啟動的中斷方式有關,下圖為串列埠中斷的方式:
本次的實驗使用的是:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);因此資料暫存器非空即來中斷。
因此每個位元組的接收都會來一次中斷。因此後面我們才會要使用DMA的方式。
在主函式內的操作:
1.中斷優先順序分組 NVIC_Configuration();
2.呼叫初始化函式 uart_init(9600);
串列埠中斷完成,接收資料正常。
為了方便檢視正確的狀態,啟用串列埠傳送模式
串列埠傳送,在上面我們已經啟動了收發模式
為此傳送時判斷上次傳輸完成以後:
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //等待上次傳輸完成。
啟動傳送命令:
USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]); //傳送資料到串列埠1
一次發一個位元組。
判斷髮送陣列的長度,並開啟迴圈傳送:如下所示:
void u3_printf(char* fmt,...)
{
u16 i,j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART1_TX__ins_BUF,fmt,ap);
va_end(ap);//以上的語句可以對輸入的字串按照一定的格式儲存
i=strlen((const char*)USART1_TX__ins_BUF);//此次傳送資料的長度
for(j=0;j<i;j++)//迴圈傳送資料
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //等待上次傳輸完成
USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]); //傳送資料到串列埠1
}
}
傳送部分完成,可以結合接收的資料傳送指定的字元;在這裡就會涉及到對接收字元的識別問題:1.可利用正則表示式 2.庫函式 3. 指定連續字元條件判斷
需要注意的是:strcmp庫函式實現的是兩個字串的比較,相等才為0,為此串列埠傳送的資料為帶有回車換行0x0d 和0x0a。
二、串列埠DMA中斷接收
配置成uart+DMA的方式,那麼uart的收模式不配置中斷,同時配置DMA中斷,固定位元組接收。(即是搬運資料滿了才DMA中斷)
1.uart的初始化去掉NVIC的配置
2.DMA配置
void MYDMA_Config_Rx(DMA_Stream_TypeDef*DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
(1)使能DMA的時鐘 並等待資料流可配置(這裡注意你要使用的DMA時鐘為哪個1或2)
(2).設定外設地址
(3).設定儲存器地址
(4).設定傳輸資料量
(5).設定DMA資料流的配置資訊
(6)使能DMA資料流,啟動傳輸
(7)中斷配置
注意的是根據是傳送和接收選擇:外設到記憶體 or記憶體到外設方向
正常傳輸模式和迴圈模式要注意,正常傳輸則傳輸完一次後DMA結束,下次要重啟DMA的配置資訊才能再次使用。可以選用迴圈模式,但是有一個問題是當接收的資料超過指定的接收長度時會出錯。
中斷的選擇為:
DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);//使能DMA2流2的傳輸完成中斷
3.DMA的中斷服務函式
void DMA2_Stream2_IRQHandler(void)
{
uint16_t pro=0;
Blue_receive_Flag = 1;
if(Blue_receive_Flag == 1) //
{
Receive_data_process();//接收的資料判斷 DMA接收 下一步通過串列埠
Blue_receive_Flag = 0;
//傳送應答 同上面的DMA 傳送
}
if(DMA_GetITStatus(DMA2_Stream2,DMA_IT_TCIF2)!= RESET) //DMA傳輸完成標誌
{
// DMA_Cmd(DMA2_Stream2, DISABLE); //關閉USART1 RX DMA2 所指示的通道
// pro = DMA_GetCurrDataCounter(DMA2_Stream2); //獲取DMA通道的DMA快取的大小
DMA_Cmd(DMA2_Stream2, ENABLE); //使能USART1 RX DMA2 所指示的通道
DMA_ClearITPendingBit(DMA2_Stream2,DMA_IT_TCIF2); //清除中斷標誌
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串列埠1的DMA接收
MYDMA_Enable(DMA2_Stream2,USART_REC_LEN); //開始一次DMA傳輸!
}
}
4.在主函式中需要初始化uart MYDMA_Config_Rx
uart(9600);
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)USART_RX_BUF,USART_REC_LEN);
5.啟動uart 和DMA
DMA_Cmd(DMA_Streamx, ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串列埠1的DMA接收
6.關閉DMA才可以設定
DMA_Cmd(DMA_Streamx, DISABLE); //關閉DMA傳輸
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //確保DMA可以被設定
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //資料傳輸量
//設定完成再次啟動即可
DMA_Cmd(DMA_Streamx, ENABLE); //開啟DMA傳輸直至完成了串列埠DMA接收的配置
而且在上面中指定DMA搬送的快取區是USART_RX_BUF,位元組長度USART_REC_LEN。因此在處理函式中可以直接操作此陣列USART_RX_BUF例如識別陣列中含有的字串,並作出相應的判斷比如傳送接收的內容或者其它指定的字元。
三、不定長度資料的DMA接收
採用UART中斷+DMA搬運
1.uart初始化
加上uart的中斷配置同時開啟空閒中斷
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//開啟空閒中斷 修改使用的是空閒中斷
Usart1NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串列埠1中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//搶佔優先順序3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02; //子優先順序3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的引數初始化VIC暫存器、
2.DMA配置去掉中斷
開啟DMA_Cmd(DMA2_Stream2, ENABLE); //正式驅動DMA傳輸
3.中斷服務函式void USART1_IRQHandler(void)
判斷當uart產生空閒的中斷時,從uart讀值以清除中斷標誌
(1)關鍵的一點是:
Usart1_Rec_Cnt = DMA_REC_LEN-DMA_GetCurrDataCounter(DMA2_Stream2); //算出接本幀資料長度
其中的DMA_GetCurrDataCounter獲得了當前剩餘緩衝區的大小
(2)並將接收的資料再次傳送出去:
len為Usart1_Rec_Cnt buf[t]為緩衝區DMA_Rece_Buf
for(t=0;t<len;t++) //迴圈傳送資料
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1,buf[t]);
}
(3)由於是單次傳輸,因此傳輸完成一次,要再次初始化DMA並清除中斷標誌
USART_ClearITPendingBit(USART1, USART_IT_IDLE); //清除中斷標誌
MYDMA_Enable(DMA2_Stream2,DMA_REC_LEN);//可以實現實時調節資料傳輸量
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)DMA_Rece_Buf, DMA_REC_LEN);
4.在主函式中初始化DMA和uart即可
完成
結果:其中可看到接收到22位元組 和29位元組的,原因是先使用串列埠除錯助手傳送給MCU,MCU依據接收的值再發送回串列埠。亂碼為本程式的其它操作可不比在意。