文章目录
一、DMA 简介
DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。
数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM 或者是FLASH。
DMA 控制器包含了DMA1 和DMA2,其中DMA1 有7 个通道,DMA2 有5 个通道,这里的通道可以理解为传输数据的一种管道。
注意:DMA2 只存在于大容量的单片机中
二、DMA 功能框图
DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握功能框图中的三部分内容即可;
DMA 请求
如果外设要想通过DMA 来传输数据,必须先给DMA 控制器发送DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA 控制器收到应答信号之后,就会启动DMA 的传输,直到传输完毕。
DMA 有DMA1 和DMA2 两个控制器,DMA1 有7 个通道,DMA2 有5 个通道,不同的DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置。
DMA1 各个通道的请求映像:
DMA2 各个通道的请求映像:
其中ADC3、SDIO 和TIM8 的DMA 请求只在大容量产品中存在。
通道
DMA 具有12 个独立可编程的通道,其中DMA1 有7 个通道,DMA2 有5 个通道,每个通道对应不同的外设的DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
仲裁器
当发生多个DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。
仲裁器管理DMA 通道请求分为两个阶段;
第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有4 个等级:非常高、高、中和低四个优先级;
第二阶段属于硬件阶段,如果两个或以上的DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0 高于通道1。
在大容量产品和互联型产品中,DMA1 控制器拥有高于DMA2 控制器的优先级。
三、DMA 数据配置
使用DMA,最核心就是配置要传输的数据,包括数据传输的方向,数据传输的单位,数据传输的数量,数据传输模式等等。
数据传输方向
DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向DMA_CCR 位4 DIR 配置:0 表示从外设到存储器,1 表示从存储器到外设。这里面涉及到的外设地址由DMA_CPAR 配置,存储器地址由DMA_CMAR 配置。
外设到存储器
当我们使用从外设到存储器传输时,以ADC 采集为例。DMA 外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储AD 采集的数据)的地址。方向我们设置外设为源地址。
存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
存储器到存储器
当我们使用从存储器到存储器传输时,以内部FLASH 向内部SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH 当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH 的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR 位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M 模式。
数据传输单位
以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR配置,这是一个32 位的寄存器,一次最多只能传输65535 个数据。
要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8 位的,所以我们定义的要发送的数据也必须是8 位。外设的数据宽度由DMA_CCR 的PSIZE[1:0] 配置,可以是8/16/32 位,存储器的数据宽度由DMA_CCR 的MSIZE[1:0] 配置,可以是8/16/32 位。
在DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由DMA_CCRx 的PINC 配置,存储器的地址指针由MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
数据传输结束标志
我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA 通道
在DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。
有关各个标志位的详细描述请参考DMA 中断状态寄存器DMA_ISR 的详细描述。
数据传输模式
传输完成分两种模式,一次传输和循环传输;
一次传输,即是传输一次之后就停止,要想再传输的话,必须关断DMA 使能后再重新配置后才能继续传输;
循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复;
具体的由DMA_CCR 寄存器的CIRC 循环模式位控制。
四、STM32CubeMX配置
基本STM32CubeMX配置不在这里赘述,可以参考STM32 CubeMx教程 – 基础知识及配置使用教程
基本包括,配置RCC为外部晶振模式;配置SYS debug为Seril Wire ;配置时钟树最大频率;配置工程的输出设置;
在这里讲解配置串口 、以及配置DMA
串口DMA设置
配置串口为异步通信模型(Asynchronous);
打开串口中断,把(5)的勾打上;
配置串口DMA
(2)点击DMA Setting;
(3)然后点击 Add,添加USART1_RX 和USART1_TX;
(4)然后配置Mode:
Normal:正常模式,只能启用一次DMA数据传输;
Circular:循环模式,传输一次以后还可以传输下一次,一直循环;
(5)配置DMA传输地址、传输类型,这里默认不做修改;
DMA基础设置
DMA1/DMA2: 在外设和存储器之间进行数据传输的配置。两个DMA控制器共包含12个通道(DMA1有7个,DMA2有5个)。
MemToMem: DMA通道的操作也可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。如果要使用DMA的存储器到存储器模式,在该菜单下进行配置。
注意: 如果你是在DMA设置界面添加DMA 而没有开启对应外设的话 ,默认为MENTOMEN
DMA Request : DMA传输的对应外设
Channel : DMA 传输通道设置
DMA1 : DMA1 Channel 0~DMA1 Channel 7
DMA2: DMA2 Channel 1~DMA1 Channel 5
Dirction : DMA传输方向
四种传输方向:
外设到内存 Peripheral To Memory
内存到外设 Memory To Peripheral
内存到内存 Memory To Memory
外设到外设 Peripheral To Peripheral
Priority: 传输速度
最高优先级 Very Hight
高优先级 Hight
中等优先级 Medium
低优先级 Low
DMA传输模式:
Normal:正常模式
当一次DMA数据传输完后,停止DMA传送,只能传输一次数据;
Circular: 循环模式
传输完成后又重新开始继续传输,不断循环永不停止,用于处理循环缓冲区和连续的数据传输
DMA指针地址设置:
Increment Address:地址指针递增
Peripheral:外设地址寄存器
功能:设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加 Data Width个字节,
Memory :内存地址寄存器
功能:设置传输数据时候内存地址是否递增。如果设置为递增,那么下一次传输的时候地址加 Data Width个字节,
五、代码讲解
串口DMA函数
串口DMA发送数据
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能: 串口通过DMA发送指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData: 需要发送的数据
Size : 发送的字节数
示例:HAL_UART_Transmit_DMA(&huart1, tx_buf, sizeof(tx_buf)); //串口发送tx_buf数组
串口DMA接收数据
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能: 串口通过DMA接受指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData: 需要存放接收数据的数组
Size: 接受的字节数
示例:HAL_UART_Transmit_DMA(&huart1, rx_buf, sizeof(rx_buf)); //串口发送rx_buf数组
代码实现
要求:使用串口中断DMA传输数据
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
uint8_t tx_buf[] = "hello lu shi jun \r\n ";
uint8_t rx_buf[4];
/* USER CODE END PD */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1,rx_buf,sizeof(rx_buf)); //打开DMA接收
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
/* 串口DMA收到数据回调函数 */
/* UART一旦开启DMA之后,DMA收发中断强制开启,DMA收发函数直接可以编写回调函数 */
/* 当串口接收到数据以后,发送一段特定的数据 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) //判断串口号
{
HAL_UART_Transmit_DMA(&huart1,tx_buf,sizeof(tx_buf)); //发送数据
//HAL_UART_Receive_DMA(&huart1,rx_buf,sizeof(rx_buf)); //打开DMA接收
}
}
/* USER CODE END 4 */
附录
本文涉及到的配置文件以及源代码:STM32 HAL库 STM32Cube MX配置 串口DMA(设置的0积分,不能下载留言我发给你)