前言:
(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);}}