STM32 DMA

DMA简介

  Data Memory Access,直接储存器访问。可以把数据从一个地方搬移到另一个地方,同时不占用CPU。
其方向有

  1. Memory -> Memory 存储器到存储器
  2. Memory -> Periph 存储器到外设
  3. Periph -> Memory 外设到存储器

DMA功能框图

DMA请求

  在参考手册中可以找到DMA请求映像,里面规定了不同设备间传输数据应用哪条通道。

仲裁器

  当多个DMA请求同时发出时,仲裁器会仲裁顺序。这里就需要配置优先级了。可以通过软件编辑优先级,当软件优先级相同时,判断硬件优先级。这里数字越小优先级越高,如通道1优先级高于通道2。同时,DMA1的优先级高于DMA2。

寄存器讲解

  寄存器这里在参考手册,通道配置过程中写的很详细,其中解释了不同寄存器对应设置的功能。这里把整个过程分析一下。
  首先,在需要调用数据时,外设发送请求,仲裁后使能DMA,开始调取数据。需要指定数据所在位置和传输地。
  传输数据时,需要指定每次传输多少数据,一共传输多少次。另外传输时指针是否按照地址递增也是可以配置的,这就是增量模式是否开启。
  最后,传输完成后,可以配置是否循环发送。

固件库编程

结构体

  在 “stm32f10x_dma.h” 这个文件中有初始化函数,其中初始化结构体表明了一系列的配置。

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr;  //外设地址
  uint32_t DMA_MemoryBaseAddr;     //存储器地址
  uint32_t DMA_DIR;                //传输方向
                                       
  uint32_t DMA_BufferSize;           //传输数目                                  
  uint32_t DMA_PeripheralInc;       //外设地址增量模式 
  uint32_t DMA_MemoryInc;           //存储器地址增量模式
  uint32_t DMA_PeripheralDataSize; //外设数据宽度
  uint32_t DMA_MemoryDataSize;     //存储器数据宽度

  uint32_t DMA_Mode;              //模式选择
  uint32_t DMA_Priority;          

  uint32_t DMA_M2M;               
}DMA_InitTypeDef;

开始编程(一)

  这里编程完成两个任务。第一个,把FLASH中的值用DMA调到SRAM中。

1.在FLASH中写入数据

  使用 const 关键字可以定义常量,这种量只可读不可修改,会被存储到FLASH中。这里写入的数据是无意义的,只是为了实验。

#define BUFFER_SIZE     32
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
      0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
      0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
      0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
      0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
      0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
      0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
      0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
      0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};

2.建立一个数组用来接收数据

uint32_t aDST_Buffer[BUFFER_SIZE];

3.初始化DMA

void DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
	DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)aDST_Buffer;
	DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralSRC; 
	
	DMA_InitStruct.DMA_BufferSize         = BUFFER_SIZE;
	DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Enable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
	
	DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Word;
	
	DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority           = DMA_Priority_High;
	DMA_InitStruct.DMA_M2M                = DMA_M2M_Enable;
	
	DMA_Init(DMA1_Channel6,&DMA_InitStruct);
	DMA_Cmd(DMA1_Channel6,ENABLE);
}

  开启时钟,DMA是挂载在 AHB 时钟下的,使用 DMA1
  这里把FLASH视为外设,把SRAM视为存储器。要把 aSRC_Const_Buffer数据移动到 aDST_Buffer,这里两个参数需要传入地址的值,因此用 (uint32_t) 把指针强制转化为值。
  我们想要从外设读取数据,因此配置为 DMA_DIR_PeripheralSRC;若从存储器读取则用 DMA_DIR_PeripheraDIR
  数组中一共有BUFFER_SIZE个数据,所以传输数目为BUFFER_SIZE ;每个数据有4个字节;因此使用 DMA_PeripheralDataSize_WordDMA_MemoryDataSize_Word
  数据需要自动移位,开启 Inc
  模式为正常 DMA_Mode_Normal ,发送完后停止;优先级随意设置;因这里是M2M模式,所以开启M2M。
  使用初始化函数初始化。使用Cmd使能。因为是 M2M模式,选择的通道没有特殊要求。

4.检验是否传输成功

编写一个函数,比较两个地方的数据是否相同。

uint8_t Buffercmp(const uint32_t* pBuffer,
				uint32_t* pBuffer1,uint16_t BufferLength)
{
	while(BufferLength--)
	{
		if(*pBuffer != *pBuffer1)
		{
			return 0;
		}
		pBuffer++;
		pBuffer1++;
	}
	return 1;
}

5.主函数中操作

int main(void)
{
	LED_GPIO_Config();
	DMA_Config();	
	LED_G(Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE));
}

  主函数中,配置好LED,配置DMA。如果传输成功,两端数据相同,Buffercmp 返回1,绿色LED被点亮。

6.增强严谨性

  传输过程中会耗费时间,虽然在我们调用下一个函数时,数据已经传输完毕,但为了严谨,我们还是要在传输后检查是否传输完成。DMA传输完成后相应寄存器会置位,使用固件库函数可以检测。修改 main 函数如下。

int main(void)
{
	LED_GPIO_Config();
	DMA_Config();
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC6) == RESET);
	
	LED_G(Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE));
	
}

  在初始化时,要保证这个标志位复位。由此,在初始化函数中加入下面的语句。

DMA_ClearFlag(DMA1_FLAG_TC6);

  至此编程工作已经完成。但要注意,因为 main 中使用了别的 .c 文件的全局变量,这需要在 main 中使用 extern 关键字再次声明。

开始编程(二)

这里完成外设和DMA之间的传输。这里把SRAM的数据通过DMA交由串口发出去。因为是外设请求DMA,所以要看好外设通道请求表,可以看见,USART1在DMA1的通道4。下面开始初始化DMA。

1.设置SRAM数据

设置一个 uint8_t 类型的数组,32个成员。32个成员是随意的。但为什么是uint8_t类型?下面会解释。

#define  BUFFSIZE            32
uint8_t SentBuff[BUFFSIZE];

2.初始化DMA

已知,由串口发送的数据都是暂存在 DR 寄存器中,因此第一个参数外设基地址就是 USART1 的DR寄存器的地址;在手册中可以查到,DR 的地址偏移为0x04。而 USART1 的基地址可以在 stm32f10x.h 头文件中查到

由此得到 USART1DR 寄存器的地址。

#define  USART1_DR_ADDESS    (USART1_BASE+0X04)

同时,可以看出 DR 寄存器只有8位的储存,在设置传输数据大小 DataSize 时要注意。

void DMA_M2P_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitStruct.DMA_PeripheralBaseAddr = USART1_DR_ADDESS;
	DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t) SentBuff;
	DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralDST;
	
	DMA_InitStruct.DMA_BufferSize         = BUFFSIZE;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
	
	DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
	
	DMA_InitStruct.DMA_Priority           = DMA_Priority_High;
	DMA_InitStruct.DMA_M2M                = DMA_M2M_Disable;
	DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;
	
	DMA_Init(DMA1_Channel4,&DMA_InitStruct);
	
	DMA_ClearFlag(DMA1_FLAG_TC4);
	DMA_Cmd(DMA1_Channel4,ENABLE);
}

要多次读取储存器中的数据,而外设的地址是固定的。因此Memory的 Inc 开启,Peripheral关闭。
初始化时注意通道,USART1在DMA1的通道4。

3.从外设发送请求

外设发出请求,DMA才会工作,在USART库中有函数 USART_DMACmd 用于配置请求。

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);

4.整合代码

extern uint8_t SentBuff[BUFFSIZE];

int main(void)
{
	uint16_t i=0;
	//向数组中填入数据
	for(i=0;i<BUFFSIZE;i++)
	{
		SentBuff[i] = 'P';
	}
	
	USART_Config();//开启串口
	
	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//开启串口请求
	DMA_M2P_Config();                           //配置串口
}
### STM32 DMA配置及使用教程 #### 1. 环境准备 为了成功配置使用STM32DMA功能,需确保已安装STM32CubeMX工具以及对应的目标芯片软件包。这些工具能够帮助开发者快速完成硬件初始化并生成基础代码框架[^1]。 #### 2. 基础概念理解 DMA (Direct Memory Access),即直接存储器存取技术,在嵌入式系统中用于减少CPU参与外设数据传输的工作量。对于STM32系列微控制器而言,其内部集成了多个独立的DMA通道来支持不同类型的外设操作,比如UART通信、ADC采样等场景下的高速数据搬运任务[^2]。 #### 3. 使用STM32CubeMX进行基本设置 利用STM32CubeMX可以非常方便地完成大部分关于DMA的基础设定工作: - 打开项目后找到目标外设(例如串口或者模拟数字转换模块),激活它; - 进入高级参数选项卡调整具体属性值,如方向(In to Mem/Mem To Out)、优先级等等; - 同时也要记得关联合适的中断服务程序以便后续处理可能发生的错误情况或者是结束标志通知机制[^1]。 #### 4. 编写自定义函数实现特定需求 当初步架构搭建完毕之后,则需要依据实际应用场景编写相应的驱动逻辑代码片段。下面给出了一段基于低层接口(LL Library)实现简单字符串通过指定串口号经由DMA方式发出的例子: ```c void UART_DMA_Send(USART_TypeDef *USARTx, uint8_t *str, uint16_t size){ // 设置内存地址给定的数据缓冲区首址作为源位置 LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_7, (uint32_t)str); // 定义本次要传送字节数目 LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, size); // 开启所选流号开始执行真正的物理拷贝过程 LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7); } ``` 以上示例展示了如何构建一个通用型发送子程序,其中涉及到几个关键动作点均已被封装成单独API调用形式呈现出来便于理解维护扩展[^2]。 #### 5. 测试验证效果 最后一步就是把前面几步产生的成果部署到真实的硬件平台上加以检验确认预期行为是否达成。这通常包括编译链接整个工程项目得到最终固件镜像文件下载烧录至目标板卡之上运行起来观察现象变化趋势并与理论分析对比找出差异原因所在直至完全匹配为止。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值