前言:
(1)DMA是單片機集成在芯片內部的一個數據搬運工,它可以代替單片機對數據進行傳輸、存儲,節約CPU資源。一般應用場景,ADC多通道采集,串口收發(頻繁進入接收中斷),SPI和IIC通信等
(2)STM32F2系列的DMA控制器最多有2個,每個控制器有8個數據流,每個數據流可以映射到不同的通道。例如,DMA2的數據流7可能用于某個特定外設,比如USART1的TX。(每個數據流同一時間只能服務一個外設。例如,若USART1_TX占用了DMA2_Stream7,則該流不可用于其他外設)
(3)DMA配置參數:
DMA請求源與通道選擇:DMA1通道1
數據傳輸方向:外設 ? 存儲器,存儲器 ? 存儲器
地址遞增配置:外設地址遞增和存儲器地址遞增(如果外設是ADC數據寄存器,關閉遞增,如果存儲器地址是接收數組,則開啟遞增)
數據寬度與對齊:外設/存儲器數據寬度(串口一般是8位,ADC一般是16位)
傳輸模式:單次模式(串口接收)和循環模式(ADC采集數據)
正文:
1、配置時鐘,燒入方式,配置RS485的接收使能腳
2、配置串口,DMA
3、串口串口收發緩存結構體
#define USART_TX_MAX_LEN 100
#define USART_RX_MAX_LEN 100
typedef struct
{unsigned char tx_buf[USART_TX_MAX_LEN]; unsigned char rx_buf[USART_RX_MAX_LEN];unsigned char rx_flag;unsigned char rx_len;
}usart_data_t;extern usart_data_t stUsart1Data;
?4、 串口初始化代碼加入??開啟IDLE中斷、開始DMA接收
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷
HAL_UART_Receive_DMA(&huart1,stUsart1Data.rx_buf,USART_RX_MAX_LEN);
5、串口中斷函數中加入 接收數據代碼
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 *//* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 */if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)) {__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標志位HAL_UART_AbortReceive(&huart1); //已經接收完一幀數據,所以這里要停止接收,然后再重新接收 uint32_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸的數據個數 ,stUsart1Data.rx_len = USART_RX_MAX_LEN - temp; //總計數減去未傳輸的數據個數,得到已經接收的數據個數stUsart1Data.rx_flag = 1; // 接受完成標志位置1 HAL_UART_Receive_DMA(&huart1,stUsart1Data.rx_buf,USART_RX_MAX_LEN); //開始DMA接收}/* USER CODE END USART1_IRQn 1 */
}
6、配置串口printf打印函數
void Usart1Printf(const char *format,...)
{uint16_t len;va_list args; va_start(args,format);len = vsnprintf((char*)stUsart1Data.tx_buf,sizeof(stUsart1Data.tx_buf)+1,(char*)format,args);va_end(args);if(HAL_UART_Transmit_DMA(&huart1, stUsart1Data.tx_buf, len)!= HAL_OK) //判斷是否發送正常,如果出現異常則進入異常中斷函數{Error_Handler();}}
7、RS485通信加入使能發送和接收
/* USER CODE BEGIN 1 */
void USART1_RS485_Send_Enable(void)
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
}void USART1_RS485_Receive_Enable(void)
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
}
8、測試代碼
USART1_RS485_Receive_Enable(); //默認為接收使能while (1){/* USER CODE END WHILE */if(stUsart1Data.rx_flag == 1) //接收完成標志{USART1_RS485_Send_Enable();Usart1Printf("%s\r\n",stUsart1Data.rx_buf);HAL_Delay(100);//等待串口DMA發送完成,如果不加入此行代碼,會出現發送失敗,因為還沒有發送完就切換成接收模式,導致不能發送數據 USART1_RS485_Receive_Enable();stUsart1Data.rx_len = 0;//清除計數stUsart1Data.rx_flag = 0;//清除接收結束標志位memset(stUsart1Data.rx_buf,0,USART_RX_MAX_LEN);}}
9、測試結果
10、優化RS485發送
當發送數據后,我們會等待一定時間,等待發送完成后在開啟串口接收,但此時資源遭到了一定的浪費
優化思路:等待觸發串口發送完成中斷后,使能串口接收中斷
void USART1_IRQHandler(void)
{HAL_UART_IRQHandler(&huart1);if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)) {__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標志位HAL_UART_AbortReceive(&huart1); //已經接收完一幀數據,所以這里要停止接收,然后再重新接收 uint32_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸的數據個數 ,stUsart1Data.rx_len = USART_RX_MAX_LEN - temp; //總計數減去未傳輸的數據個數,得到已經接收的數據個數stUsart1Data.rx_flag = 1; // 接受完成標志位置1 HAL_UART_Receive_DMA(&huart1,stUsart1Data.rx_buf,USART_RX_MAX_LEN); //開始DMA接收}if (HAL_UART_GetState(&huart1) == HAL_UART_STATE_READY) {// 發送完成 USART1_RS485_Receive_Enable();}
}USART1_RS485_Receive_Enable();while (1){/* USER CODE END WHILE */if(stUsart1Data.rx_flag == 1) //接收完成標志{USART1_RS485_Send_Enable();Usart1Printf("%s\r\n",stUsart1Data.rx_buf);
// HAL_Delay(100);//等待串口DMA發送完成
// USART1_RS485_Receive_Enable();stUsart1Data.rx_len = 0;//清除計數stUsart1Data.rx_flag = 0;//清除接收結束標志位memset(stUsart1Data.rx_buf,0,USART_RX_MAX_LEN);}}