串口接收不定長數據
- 應用場景
- 搬運串口外設中長度未知的數據
- 通常用於下位機串口發送一幀的場合
- 問題:中斷發送數據幀的速率很快,MCU來不及處理此次接收到的數據
- 在重新開啟接收DMA通道之前,將LumMod_Rx_Buf緩沖區里面的數據復制到另外一個數組中, 然后再開啟DMA,然后馬上處理復制出來的數據。
- 建立雙緩沖,在LumMod_Uart_DMA_Rx_Data函數中,重新配置DMA_MemoryBaseAddr 的緩沖區地址,那么下次接收到的數據就會保存到新的緩沖區中,不至於被覆蓋。
- 原理
- 當串口在一定的、很短的單位時間以后沒有接收到新的數據,就觸發中斷
- 方式1:DMA+串口空閑中斷
- 參考文檔
- https://blog.csdn.net/dddxxxx/article/details/79278967?tdsourcetag=s_pctim_aiomsg
- http://www.51hei.com/bbs/dpj-39885-1.html
- https://www.cnblogs.com/einstein-2014731/p/5768083.html
- 接收
- 串口空閑中斷+DMA
- 發送
- DMA發送+DMA發送中斷完成函數
- DMA中斷配置
- 發生時間
- IDLE就是串口收到一幀數據后,發生的中斷。什么是一幀數據呢?比如說給單片機一次發來1個字節,或者一次發來8個字節,這些一次發來的數據,就稱為一幀數據,也可以叫做一包數據。
- RXNE和IDLE區別
- 當接收到1個字節,就會產生RXNE中斷,當接收到一幀數據,就會產生IDLE中斷。比如給單片機一次性發送了8個字節,就會產生8次RXNE中斷,1次IDLE中斷。

/*****************************bsp_usart_dma.h**************************/ //bsp_usart_dma.h #ifndef __USARTDMA_H #define __USARTDMA_H #include "stm32f10x.h" #include <stdio.h> // 串口工作參數宏定義 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200 // USART GPIO 引腳宏定義 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 // 串口中斷宏定義 #define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler // DMA中斷宏定義 #define DMA_TX_FLG_TC DMA1_IT_TC4 #define DMA_TX_FLG_ALL DMA1_IT_GL4 #define DMA_RX_FLG_ALL DMA1_IT_GL5 // 串口對應的DMA請求通道 #define USART_TX_DMA_CHANNEL DMA1_Channel4 #define USART_RX_DMA_CHANNEL DMA1_Channel5 // 外設寄存器地址 #define USART_DR_ADDRESS (USART1_BASE+0x04) // 一次發送的數據量 #define SENDBUFF_SIZE 5000 void USART_Config(void); void USARTx_DMA_Config(void); #endif /* __USARTDMA_H */ /****************************bsp_usart_dma.c*************************/ //bsp_usart_dma.c #include "bsp_usart_dma.h" uint8_t SendBuff[SENDBUFF_SIZE]; /** * @brief USART GPIO 配置,工作參數配置 * @param 無 * @retval 無 */ void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打開時鍾 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 將USART Tx的GPIO配置為推挽復用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 將USART Rx的GPIO配置為浮空輸入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 配置串口的工作參數 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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(DEBUG_USARTx, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //開啟空閑中斷 USART_Cmd(DEBUG_USARTx, ENABLE); } /** * @brief USARTx TX DMA 配置,內存到外設(USART1->DR) * @param 無 * @retval 無 */ void USARTx_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 開啟DMA時鍾 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA發送 通道4 DMA_DeInit(USART_TX_DMA_CHANNEL); // 恢復缺省值 DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE); //關閉DMA DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // 設置DMA源地址:串口數據寄存器地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; // 內存地址(指針) DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向:從內存到外設 發送 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; // 傳輸大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外設地址不增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 內存地址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// 外設數據單位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 內存數據單位 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; // 模式,一次或者循環模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 優先級:中 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止內存到內存的傳輸 // 配置DMA發送 通道4 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); DMA_ClearFlag(DMA_TX_FLG_ALL); DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE); DMA_ITConfig(USART_TX_DMA_CHANNEL, DMA_IT_TC, ENABLE); //DMA接收 通道5 DMA_Cmd(USART_RX_DMA_CHANNEL,DISABLE); DMA_DeInit(USART_RX_DMA_CHANNEL); DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //配置DMA通道 DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure); DMA_ClearFlag(DMA_RX_FLG_ALL); DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 開啟串口DMA發送 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); // 開啟串口DMA接收 } /**************************bsp_nvic.c**************************************/ void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* 配置NVIC為優先級組1 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //DMA1通道4發送中斷 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } /*********************************主函數**********************/ #include "stm32f10x.h" #include "bsp_led.h" #include "bsp_nvic.h" #include "bsp_usart_dma.h" /* 緩沖區,簡單軟件標志位 */ extern uint8_t SendBuff[SENDBUFF_SIZE]; extern int Key1Flg,Key2Flg,DMA1Flg,Usart1_FLg; //標志位 /* 中斷事件 */ void Usart1_Event(void); void DMA1_Event(void); int main(void) { uint_8 a; NVIC_Configuration(); USART_Config(); USARTx_DMA_Config(); /*填充將要發送的數據*/ for(a=0;a<SENDBUFF_SIZE;a++) { SendBuff[a] = 'P'; } /* 等待中斷,由於使用中斷方式,CPU不用輪詢按鍵,標志位多的時候也可以換成狀態機使用swtch */ while(1) { if (DMA1Flg == 1) { DMA1_Event(); DMA1Flg = 0; } if(Usart1_FLg ==1) { Usart1_Event(); Usart1_FLg = 0; } } } /*********************************中斷事件******************************************/ /* 串口接收中斷事件 */ void Usart1_Event() { //串口接受完成產生空閑中斷 if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET) { //關閉接受,清除DMA通道5中斷,重啟DMA接受 DMA_Cmd(USART_RX_DMA_CHANNEL, DISABLE); DMA_ClearITPendingBit(DMA_RX_FLG_ALL); DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE); USART_ReceiveData(USART1);//Clear IDLE interrupt flag bit } } /* DMA發送中斷事件 */ void DMA1_Event() { int i; if(DMA_GetITStatus(DMA_TX_FLG_TC)) { //關閉DMA DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE); //測試是否進入通道4中斷 for(i = 0;i<100;i++) { USART_SendData(DEBUG_USARTx,i); } //清除標志位 DMA_ClearFlag(DMA_TX_FLG_TC); } }
- 方式2:環形隊列
- 環形隊列FIFO+結束標志(0x0a...)+溢出標志位
- 當一次接收的數據超出隊列結構長度即隊列滿時,觸發溢出標志位;當上一次的數據處理完畢后,回收內存空間,以用於下次隊列周期的數據存放;判斷一次接收完成的標志是結束標志字節,通常是0x0d或0x0a或0x0d+0x0a。
- 優勢是能最大限度合理利用內存空間,缺點是必須要加結束標志位,
- 使用場景
- 串口沒有DMA和空閑中斷的情況,
- 按鍵FIFO,
- 參考文章
- 代碼部分
- 待補充,參考安富萊代碼按鍵FIFO

1 typedef struct 2 { 3 u16 Head; 4 u16 Tail; 5 u16 Lenght; 6 u8 Ring_Buff[RINGBUFF_LEN]; 7 } RingBuff_t; 8 9 RingBuff_t ringBuff;//創建一個ringBuff的緩沖區 10 11 void RingBuff_Init(void) 12 { 13 //初始化相關信息 14 ringBuff.Head = 0; 15 ringBuff.Tail = 0; 16 ringBuff.Lenght = 0; 17 } 18 19 /** 20 * @brief Write_RingBuff 21 * @param u8 data 22 * @return FLASE:環形緩沖區已滿,寫入失敗; TRUE:寫入成功 23 * @note 往環形緩沖區寫入u8類型的數據 24 */ 25 u8 Write_RingBuff(u8 data) 26 { 27 if(ringBuff.Lenght >= RINGBUFF_LEN) //判斷緩沖區是否已滿 28 { 29 return FLASE; 30 } 31 ringBuff.Ring_Buff[ringBuff.Tail]=data; //寫入數據 32 33 ringBuff.Tail = ( ringBuff.Tail + 1 ) % RINGBUFF_LEN;//防止越界非法訪問,實現環形隊列.尾指針后移 34 ringBuff.Lenght++; 35 return TRUE; 36 } 37 38 39 40 /** 41 * @brief Read_RingBuff 42 * @param u8 *rData,用於保存讀取的數據 43 * @return FLASE:環形緩沖區沒有數據,讀取失敗;TRUE:讀取成功 44 * @note 從環形緩沖區讀取一個u8類型的數據 45 */ 46 u8 Read_RingBuff(u8 *rData) 47 { 48 if(ringBuff.Lenght == 0)//判斷非空 49 { 50 return FLASE; 51 } 52 53 *rData = ringBuff.Ring_Buff[ringBuff.Head];//先進先出FIFO,從緩沖區頭出 54 55 ringBuff.Head = (ringBuff.Head + 1) % RINGBUFF_LEN;//防止越界非法訪問,實現環形隊列.首指針后移 56 ringBuff.Lenght--; 57 58 return TRUE; 59 }
裸機--RS485通訊
- 物理層
- 協議標准對比
串口協議部分
RS232協議
- 串口通信最遠距離是50英尺,
- 可做到雙向傳輸,全雙工通訊,最高傳輸速率20kbps
- 邏輯1:-3 ~-15V
邏輯0:+3~+15V
- RS485
- RS-485 只有2 根信號線,所以只能工作在半雙工模式,常用於總線網.
- 電氣特性:邏輯“1”以兩線間的電壓差為+(2~6)V表示;邏輯“0”以兩線間的電壓差為-(2~6)V表示。接口信號電平比RS-232-C降低了,就不易損壞接口電路的芯片, 且該電平與TTL電平兼容,可方便與TTL電路連接。
- 數據最高傳輸速率為10Mbps。
- 接口是采用平衡驅動器和差分接收器的組合,抗共模干擾能力增強,即抗噪聲干擾性好。
- 最大的通信距離約為1219m,最大傳輸速率為10Mb/S,傳輸速率與傳輸距離成反比,在100Kb/S的傳輸速率下,才可以達到最大的通信距離,RS-485總線一般最大支持32個節點
- RS422
- RS-422 有4 根信號線:兩根發送、兩根接收。由於RS-422 的收與發是分開的所以可以同時收和發(全雙工),也正因為全雙工要求收發要有單獨的信道,所以RS-422適用於兩個站之間通信,星型網、環網,不可用於總線網;
文章參考較多網絡資源,如有侵權等等,請聯系或留言.