stm32-串列埠使用IDLE中斷接受不定長資料方法
方法1:串列埠接受資料,定時器來判斷超時是否接受資料完成。
方法2:DMA接受+IDLE中斷
實現思路:採用STM32F103的串列埠1,並配置成空閒中斷IDLE模式且使能DMA接收,並同時設定接收緩衝區和初始化DMA。那麼初始化完成之後,當外部給單片機發送資料的時候,假設這幀資料長度是200個位元組,那麼在微控制器接收到一個位元組的時候並不會產生串列埠中斷,而是DMA在後臺把資料默默地搬運到你指定的緩衝區裡面。當整幀資料傳送完畢之後串口才會產生一次中斷,此時可以利用DMA_GetCurrDataCounter();函式計算出本次的資料接受長度,從而進行資料處理。
應用物件:適用於各種串列埠相關的通訊協議,如:MODBUS,PPI ;還有類似於GPS資料接收解析,串列埠WIFI的資料接收等,都是很好的應用物件。
關鍵程式碼分析:
- 1
- 2
- 3
- 4
- 5
- 6
void uart_init(u32 bound);
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);
#endif
usart.C
//初始化IO 串列埠1
//bound:波特率
void uart_init(u32 bound)
{
//GPIO埠設定
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE); //使能USART1,GPIOA時鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA傳輸
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2時鐘
USART_DeInit(USART1); //復位串列埠1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶佔優先順序3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先順序3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的引數初始化VIC暫存器
//USART 初始化設定
USART_InitStructure.USART_BaudRate = bound;
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); //初始化串列埠
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//開啟空閒中斷
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串列埠1 DMA接收
USART_Cmd(USART1, ENABLE); //使能串列埠
//相應的DMA配置
DMA_DeInit(DMA1_Channel5); //將DMA的通道5暫存器重設為預設值 串列埠1對應的是DMA通道5
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外設usart基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DMA_Rece_Buf; //DMA記憶體基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //資料傳輸方向,從外設讀取傳送到記憶體
DMA_InitStructure.DMA_BufferSize = DMA_Rec_Len; //DMA通道的DMA快取的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址暫存器不變
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //記憶體地址暫存器遞增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //資料寬度為8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //資料寬度為8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常快取模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x擁有中優先順序
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設定為記憶體到記憶體傳輸
DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根據DMA_InitStruct中指定的引數初始化DMA的通道
DMA_Cmd(DMA1_Channel5, ENABLE); //正式驅動DMA傳輸
}
//重新恢復DMA指標
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //關閉USART1 TX DMA1所指示的通道
DMA_SetCurrDataCounter(DMA_CHx,DMA_Rec_Len);//DMA通道的DMA快取的大小
DMA_Cmd(DMA_CHx, ENABLE); //開啟USART1 TX DMA1所指示的通道
}
//傳送len個位元組
//buf:傳送區首地址
//len:傳送的位元組數
void Usart1_Send(u8 *buf,u8 len)
{
u8 t;
for(t=0;t<len;t++) //迴圈傳送資料
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1,buf[t]);
}
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
//串列埠中斷函式
void USART1_IRQHandler(void) //串列埠1中斷服務程式
{
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //接收中斷(接收到的資料必須是0x0d 0x0a結尾)
{
USART_ReceiveData(USART1);//讀取資料注意:這句必須要,否則不能夠清除中斷標誌位。
Usart1_Rec_Cnt =DMA_Rec_Len-DMA_GetCurrDataCounter(DMA1_Channel5); //算出接本幀資料長度
//***********幀資料處理函式************//
printf ("Thelenght:%d\r\n",Usart1_Rec_Cnt);
printf ("The data:\r\n");
Usart1_Send(DMA_Rece_Buf,Usart1_Rec_Cnt);
printf ("\r\nOver! \r\n");
//*************************************//
USART_ClearITPendingBit(USART1,USART_IT_IDLE); //清除中斷標誌
MYDMA_Enable(DMA1_Channel5); //恢復DMA指標,等待下一次的接收
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 11
方法3:實現思路:直接利用stm32的RXNE和IDLE中斷進行接收不定位元組資料。
基本知識:
IDLE中斷什麼時候發生?
IDLE就是串列埠收到一幀資料後,發生的中斷。什麼是一幀資料呢?比如說給微控制器一次發來1個位元組,或者一次發來8個位元組,這些一次發來的資料,就稱為一幀資料,也可以叫做一包資料。
如何判斷一幀資料結束,就是我們今天討論的問題。因為很多專案中都要用到這個,因為只有接收到一幀資料以後,你才可以判斷這次收了幾個位元組和每個位元組的內容是否符合協議要求。
看了前面IDLE中斷的定義,你就會明白了,一幀資料結束後,就會產生IDLE中斷。
如何配置好IDLE中斷?
下面我們就配置好串列埠IDLE中斷吧。
這是串列埠CR1暫存器,其中,對bit4寫1開啟IDLE中斷,對bit5寫1開啟接收資料中斷。(注意:不同系列的STM32,對應的暫存器位可能不同)
RXNE中斷和IDLE中斷的區別?
當接收到1個位元組,就會產生RXNE中斷,當接收到一幀資料,就會產生IDLE中斷。比如給微控制器一次性發送了8個位元組,就會產生8次RXNE中斷,1次IDLE中斷。
這是狀態暫存器,當串列埠接收到資料時,bit5就會自動變成1,當接收完一幀資料後,bit4就會變成1.
需要注意的是,在中斷函式裡面,需要把對應的位清0,否則會影響下一次資料的接收。比如RXNE接收資料中斷,只要把接收到的一個位元組讀出來,就會清除這個中斷。IDLE中斷,如何是F0系列的微控制器,需要用ICR暫存器來清除,如果是F1系列的微控制器,清除方法是“先讀SR暫存器,再讀DR暫存器”。(我怎麼知道?手冊上寫的)
下面以STM32F103為例給出源程式。
我們先來看程式中的主要部分。
串列埠初始化函式片段
串列埠中斷函式
串列埠中斷函式裡面,最重要的兩條語句,就是上圖中圈出來的兩條語句。第一條語句用來判斷是否接收到1個位元組,第二條語句用來判斷是否接收到1幀資料。(是不是感覺超級方便?媽媽再也不用擔心我如何判斷是否接收完1幀資料了。)
主函式
這個主函式,是用來驗證接收的正確性的。RxCounter表示的是這一幀資料有幾個位元組,接收完一幀資料,會在中斷函式裡面把ReceiveState置1,然後,通過串列埠把接收到的資料傳送回串列埠。這樣,既驗證了接收了多少位元組的正確性,又驗證了接收到的資料是否正確。
兩個程式程式碼均採用stm32f103zet6測試過,完全沒問題。