前期准备:
- STM32CubeMX
- STM32RCT6核心板
- IDE Keil(MDK-ARM)
关于DMA
1. 什么是DMA?
DMA(Direct Memory Access,直接存储器访问) 提供在外设与存储器、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。
这里的外设指的是spi、usart、iic、adc等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。
2. DMA的意义
传感器A获取到了数据,我需要把获取到的数据交给传感器B使用,正常来说,需要CPU控制,把A获取到的数据赋给B。但如果使用DMA,我可以将A与B之间建立一个专门传输数据的通道,CPU无需介入,对于非常大的数据需要转移时,DMA无疑大大节省了CPU的资源
优点: 控制简单,它适用于数据传输率很高的设备进行成组传送。
缺点: 在DMA控制阶段,CPU无法访问内存,内存的效能没有充分发挥,相当一部分内存工作周期是空闲的。这是因为,外围设备传送两个数据之间的间隔一般总是大于内存存储周期,因此许多空闲的存储周期不能被CPU利用。
3. DMA的结构器
2个DMA控制器,DMA1有7个通道
DMA2有5个通道
1个通道同一时间只能进行一个,例如通道 1 的几个 DMA1 请求(ADC1、TIM2_CH3、TIM4_CH1),这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个。其他通道也是类似的
3. DMA的传输方式
- DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次 - DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
4. DMA及通道的优先级
优先级管理采用软件+硬件:
-
软件:每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级
最高级>高级>中级>低级 -
硬件:如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4
在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
5. DMA中断
DMA的每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求
可以通过设置寄存器的不同位来打开这些中断
6.指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值
STM32CubeMX部分
1.配置时钟
选择STM32F103RCTx系列芯片,配置时钟的同时会自动配置IO口引脚
将HCLK设置为最大频率72MHz
2.配置USART和DMA
配置USART,【STM32】HAL库 CubeMX例程三—串口中断通信(2)(附工程源码)文章有讲解过,这里就不再赘述
DMA Settings ——> Add
然后选择USART1_RX,接着同样步骤再把USART1_TX添加进来
- DMA Request(DMA请求): USART1_RX、USART1_TX
- Channel(通道): 通道5、通道4
- Dirction (DMA传输方向): Peripheral To Memory(外设到内存)、Memory To Peripheral(内存到外设 )
- Priority(优先级): 低、低
- Mode(模式): 正常、正常
- Increment Address(地址指针递增): 1.这里因我们是一直往固定外设地址&USART1发送数据,所以地址不递增 2.这里我们的场景是将内存中连续存储单元的数据发送到串口,毫无疑问内存地址是需要递增的
- Data Width(数据宽度): 字节、字节
USART1使能中断
3.工程生成
工程管理依旧是这几个选项,然后GENERATE CODE,STM32CubeMX部分完成
MDK 5部分
//DMA函数
· HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);串口DMA模式发送
· HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);串口DMA模式接收
· HAL_UART_DMAResume(&huart1); 恢复串口DMA
· HAL_UART_DMAPause(&huart1) 暂停串口DMA
· HAL_UART_DMAStop(&huart1); 结束串口DMA
1. DMA串口发送
在main.c中添加
/* USER CODE BEGIN Init */
uint8_t Senbuff[] = "Q大帅のUART DMA Test Success ! \r\n";
/* USER CODE END Init */
在while里写入
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
}
编译下载时需要选择相对应的下载器,勾选以下
效果如图
2. IDLE接收(串口空闲中断接收)
当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到内存,那该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断
思路流程:
- 开启串口DMA接收
- 串口收到数据,DMA不断传输数据到内存
- 一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
- 在中断服务函数中,可以计算刚才收到了多少个字节的数据
- 存储接收到的数据,清除标志位,开始下一帧接收
在原有代码上更改
//在main.c定义3个全局变量
/* USER CODE BEGIN Includes */
uint8_t rx_buffer[100]; //接收数据的数组
volatile uint8_t rx_len = 0; //接收数据的长度
volatile uint8_t recv_end_flag = 0; //接收结束标志位
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //开启空闲中断
HAL_UART_Receive_DMA(&huart1,rx_buffer,100); //开启DMA接收中断
/* USER CODE END 2 */
在stm32f1xx_it.c文件中
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 */
uint8_t tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE状态
if((tmp_flag != RESET))//判断接收是否结束
{
// recv_end_flag = 1; //接收结束
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清楚标志位
HAL_UART_DMAStop(&huart1);
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rx_len =100-temp; //计算数据长度
HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len);//发送数据
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);//开启DMA
}
/* USER CODE END USART1_IRQn 1 */
}
效果如图