【STM32】DMA初步使用

DMA简介

DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能
是用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情。

以STM32F103单片机为例
F03有DMA1和DMA2两组DMA,其中DMA1有7个通道,DMA2有5个通道。

DMA 请求

外设需要使用DMA,则必须向DMA发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

DMA 有 DMA1 和 DMA2 两个控制器,DMA1 有 7 个通道,DMA2 有 5 个通道,不同的 DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见 DMA 请求映像表。

DMA1 各个通道的请求映像
DMA1 各个通道的请求映像

DMA2 各个通道的请求映像
在这里插入图片描述

通道

DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,每
个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一
时间只能接收一个,不能同时接收多个。

仲裁器,优先级

当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

DMA的几个要点

DMA 传输数据的方向有三个:从外设到存储器从存储器到外设从存储器到存储器,。具体的方向 DMA_CCR配置,这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR 配置。

传输方向

外设到存储器

当我们使用从外设到存储器传输时,以 ADC 采集为例。DMA 外设寄存器的地址对应的就是 ADC 数据寄存器DR的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。

存储器到外设

当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器DR的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

存储器到存储器

当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。
在这里插入图片描述

传输长度 单位 地址自加

传输长度

以串口向电脑发送数据为例,源地址为片上flash,目标地址为USART->DR,可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。

目标和源的传输长度保持一致

要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCRx 的PSIZE[1:0]配置,可以是8/16/32位,存储器的数据宽度由DMA_CCRx的MSIZE[1:0]配置,可以是 8/16/32 位。

地址自动增加

在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。

在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。

以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定

传输完成,循环传输

数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA 通道在 DMA 传输过半传输完成传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器DMA_ISR 的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCRx 寄存器的 CIRC 循环模式位控制。

DMA固件库

链接: DMA固件库函数

DMA配置流程

要点

1) 使能 DMA 时钟;

2) 配置 DMA 数据参数;

3) 使能 DMA,进行传输;

4) 等待传输完成,并对源数据和目标地址数据进行比较。

#DMA配置
void DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	// 开启 DMA 时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	// 源数据地址 aSRC_Const_Buffer是使用const定义在内部flash上的一块数组
	DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)aSRC_Const_Buffer;
	 // 目标地址,例:如果是通过串口发送数据那么目标地址可以设置为USART1->DR
	 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
	 
	 // 方向:外设到存储器(这里的外设是内部的 FLASH),方向都是相对的,要看目标和源怎么设置了
	 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	 
	 // 传输大小
	 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
	 
	 // 外设,前文设置的外设地址(内部的 FLASH)地址递增
	 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	 
	 // 内存地址递增
	 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	 
	 // 外设数据单位
	 DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;
	 
	 // 内存数据单位
	 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
	 
	 // DMA 模式,一次或者循环模式
	 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	 
	 // 优先级:高
	 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	 
	 // 使能内存到内存的传输
	 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
	 
	 // 配置 DMA 通道
	 DMA_Init(DMA1_Channel6, &DMA_InitStructure);
	 
	 // 使能 DMA
	 DMA_Cmd(DMA1_Channel6, ENABLE);
}
### 配置STM32CubeMX实现USART DMA接收不定长数据 #### USART与DMA简介 在嵌入式开发中,通过串口(USART)传输数据是一项基本需求。当涉及到大量或不定长度的数据流时,直接存储器访问(DMA)可以显著提高效率并减轻CPU负担。对于STM32微控制器而言,在STM32CubeMX工具下配置USART接口配合DMA功能来处理这种场景是非常实用的方法。 #### 使用STM32CubeMX进行初步设置 启动STM32CubeMX软件后加载目标板对应的芯片型号,进入Pinout&Configuration界面完成如下操作: - **使能时钟**:确保USART外设及时钟已经开启。 - **选择模式**:将USART设定为异步通信方式,并勾选`Asynchronous`下的`Transmit`和`Receive`选项以启用发送/接收功能[^1]。 ```c /* USER CODE BEGIN USARTx_Init 0 */ /* USER CODE END USARTx_Init 0 */ ``` - **激活DMA支持**:切换到NVIC标签页找到对应USART中断源将其优先级调整至合适位置;接着回到Clock Configuration页面确认DMA控制器已被使能;最后返回Peripheral Configuration部分针对USART组件展开高级参数Advanced Parameters,把Reception Mode改为DMA Circular或者Normal模式取决于具体应用场景需求[^3]。 #### 编写应用程序代码片段 基于上述硬件层面上的准备工作之后,则需编写相应的固件程序逻辑用于实际业务流程控制。下面给出一段简单的例子展示如何利用HAL库函数读取未知大小的消息体: ```c #include "main.h" UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_rx; uint8_t aRxBuffer[RXBUFFERSIZE]; // 定义缓冲区大小 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART1_UART_Init(void); int main(void){ HAL_Init(); SystemClock_Config(); /* 初始化所有已配置设备 */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); while (true){ if(HAL_OK != HAL_UART_Receive_DMA(&huart1, aRxBuffer, RXBUFFERSIZE)){ Error_Handler(); // 错误处理机制 } // 数据到达事件触发后的回调函数内执行后续动作... } } // 当接收到新字符时调用此函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ static uint8_t index = 0; char receivedChar = aRxBuffer[index++]; if(receivedChar == '\n' || index >= RXBUFFERSIZE){ // 假定消息结束标志符为换行符'\n' ProcessReceivedData(aRxBuffer); // 处理完整的输入字符串 memset(aRxBuffer, 0, sizeof(aRxBuffer)); // 清空缓存准备下次接收 index = 0; // 重置索引计数器 } // 继续监听更多传入字节直到遇到终止条件为止 __HAL_DMA_DISABLE(hdma_usart1_rx); HAL_UART_Receive_DMA(&huart1, &aRxBuffer[index], RXBUFFERSIZE-index); } ``` 这段示范性的C语言脚本展示了怎样借助于DMA技术高效地获取来自外部源的一系列ASCII编码形式的信息单元序列——即所谓的“不定长”特征明显的数据集。每当检测到回车键('\n')表示单条记录完结之时便立即停止当前批次抓取过程转而交由专门负责解析这些原始位图结构的服务例程去做进一步分析工作之前先清零内部暂存空间以便重新开始新一轮循环等待新的指令到来继续重复相同的操作步骤直至整个会话周期完全结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值