stm32——直接内存访问(DMA)
直接内存访问(DMA)
简单解释 直接内存访问(DMA)是一种计算机系统功能,允许外设直接访问系统内存,而无需通过CPU进行数据传输。这种机制可以提高数据传输效率,减少CPU的负担,从而提升系统性能。
DMA基本结构
存储器影像
| 类型 | 起始地址 | 存储器 | 作用 |
|---|---|---|---|
| ROM | 0x0800 0000 | Flash | 程序存储(这个在之前进行配置linux下的stm32开发环境提及,烧录的起始地址) |
| ROM | 0x1FFF F000 | 系统存储器 | 存储bootLoader |
| ROM | 0x1FFF F800 | 选项字节 | 一些配置参数 |
| RAM | 0x2000 0000 | SRAM | 临时变量 |
| RAM | 0x4000 0000 | 外设寄存器 | 外设配置参数 |
| RAM | 0xE000 0000 | 内核外设寄存器 | 内核外设配置参数 |
简单来个例子
直接上代码
void DMA_Init (void) {
__HAL_RCC_DMA1_CLK_ENABLE ();
dma.Instance = DMA1_Channel1; // DMA通道实例
dma.Init.Direction = DMA_MEMORY_TO_MEMORY; // 数据传输方向:内存到内存
dma.Init.MemInc = DMA_MINC_ENABLE; // 存储器地址递增模式
dma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器数据宽度
dma.Init.PeriphInc = DMA_PINC_ENABLE; // 外设地址递增模式
dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据宽度
dma.Init.Mode = DMA_NORMAL; // 工作模式:正常模式
dma.Init.Priority = DMA_PRIORITY_HIGH; // 优先级:高优先级
HAL_DMA_Init (&dma);
}
简单看一下结构体参数列表
typedef struct __DMA_HandleTypeDef
{
DMA_Channel_TypeDef *Instance; /*!< Register base address */
DMA_InitTypeDef Init; /*!< DMA communication parameters */
HAL_LockTypeDef Lock; /*!< DMA locking object */
__IO HAL_DMA_StateTypeDef State; /*!< DMA transfer state */
void *Parent; /*!< Parent object state */
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete callback */
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback */
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer error callback */
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer abort callback */
__IO uint32_t ErrorCode; /*!< DMA Error code */
DMA_TypeDef *DmaBaseAddress; /*!< DMA Channel Base Address */
uint32_t ChannelIndex; /*!< DMA Channel Index */
} DMA_HandleTypeDef;
| 参数 | 说明 |
|---|---|
| Instance | DMA通道实例 |
| 下面为Init结构体参数 | |
| Direction | 数据传输方向 |
| MemInc | 存储器地址递增模式 |
| MemDataAlignment | 存储器数据宽度 |
| PeriphInc | 外设地址递增模式 |
| PeriphDataAlignment | 外设数据宽度 |
| Mode | 工作模式 |
| Priority | 优先级 |
下面依次解释:
Instance
DMA通道选择,对于stm32f1系列而言拥有2个DMA通道
查看源码,这里有一个巧妙的地址映射:
首先mcu编程无论是外设还一些其他功能本质是在操纵寄存器,这里采用一个结构体的强制类型转换来实现寄存器的访问,DMA1_Channel1_BASE等于DMA1_BASE+0x08,DMA1_Channel2_BASE等于DMA1_BASE+0x1C,以此类推,这样就可以通过结构体指针来访问对应的寄存器了。
#define DMA1 ((DMA_TypeDef *)DMA1_BASE)
#define DMA2 ((DMA_TypeDef *)DMA2_BASE)
#define DMA1_Channel1 ((DMA_Channel_TypeDef *)DMA1_Channel1_BASE)
#define DMA1_Channel2 ((DMA_Channel_TypeDef *)DMA1_Channel2_BASE)
#define DMA1_Channel3 ((DMA_Channel_TypeDef *)DMA1_Channel3_BASE)
#define DMA1_Channel4 ((DMA_Channel_TypeDef *)DMA1_Channel4_BASE)
#define DMA1_Channel5 ((DMA_Channel_TypeDef *)DMA1_Channel5_BASE)
#define DMA1_Channel6 ((DMA_Channel_TypeDef *)DMA1_Channel6_BASE)
#define DMA1_Channel7 ((DMA_Channel_TypeDef *)DMA1_Channel7_BASE)
#define DMA2_Channel1 ((DMA_Channel_TypeDef *)DMA2_Channel1_BASE)
#define DMA2_Channel2 ((DMA_Channel_TypeDef *)DMA2_Channel2_BASE)
#define DMA2_Channel3 ((DMA_Channel_TypeDef *)DMA2_Channel3_BASE)
#define DMA2_Channel4 ((DMA_Channel_TypeDef *)DMA2_Channel4_BASE)
#define DMA2_Channel5 ((DMA_Channel_TypeDef *)DMA2_Channel5_BASE)
// BASE地址映射
#define DMA1_BASE (AHBPERIPH_BASE + 0x00000000UL)
#define DMA1_Channel1_BASE (AHBPERIPH_BASE + 0x00000008UL)
#define DMA1_Channel2_BASE (AHBPERIPH_BASE + 0x0000001CUL)
#define DMA1_Channel3_BASE (AHBPERIPH_BASE + 0x00000030UL)
#define DMA1_Channel4_BASE (AHBPERIPH_BASE + 0x00000044UL)
#define DMA1_Channel5_BASE (AHBPERIPH_BASE + 0x00000058UL)
#define DMA1_Channel6_BASE (AHBPERIPH_BASE + 0x0000006CUL)
#define DMA1_Channel7_BASE (AHBPERIPH_BASE + 0x00000080UL)
Direction
数据传输方向,查看源码 其给出了三种传输方向:外设到内存,内存到外设,内存到内存
其中m to m模式需要注意一下,mcu本身并不存在硬件的电路,所以这种模式是采用软件实现。
#define DMA_PERIPH_TO_MEMORY 0x00000000U /*!< Peripheral to memory direction */
#define DMA_MEMORY_TO_PERIPH ((uint32_t)DMA_CCR_DIR) /*!< Memory to peripheral direction */
#define DMA_MEMORY_TO_MEMORY ((uint32_t)DMA_CCR_MEM2MEM) /*!< Memory to memory direction */
MemInc MemDataAlignment PeriphInc PeriphDataAlignment
上述是寄存器与外设的地址递增模式和数据宽度,地址递增模式有两种:递增和不递增,数据宽度有三种:字节,半字,字。
#define DMA_PINC_ENABLE ((uint32_t)DMA_CCR_PINC) /*!< Peripheral increment mode enable */
#define DMA_PINC_DISABLE 0x00000000U /*!< Peripheral increment mode disable */
#define DMA_MINC_ENABLE ((uint32_t)DMA_CCR_MINC) /*!< Memory increment mode enable */
#define DMA_MINC_DISABLE 0x00000000U /*!< Memory increment mode disable */
#define DMA_PDATAALIGN_BYTE 0x00000000U /*!< Peripheral data alignment: Byte */
#define DMA_PDATAALIGN_HALFWORD ((uint32_t)DMA_CCR_PSIZE_0) /*!< Peripheral data alignment: HalfWord */
#define DMA_PDATAALIGN_WORD ((uint32_t)DMA_CCR_PSIZE_1) /*!< Peripheral data alignment: Word */
#define DMA_MDATAALIGN_BYTE 0x00000000U /*!< Memory data alignment: Byte */
#define DMA_MDATAALIGN_HALFWORD ((uint32_t)DMA_CCR_MSIZE_0) /*!< Memory data alignment: HalfWord */
#define DMA_MDATAALIGN_WORD ((uint32_t)DMA_CCR_MSIZE_1) /*!< Memory data alignment: Word */
Mode
工作模式 分为正常模式与循环模式
注意一点:循环模式在 m to m 模式下不能使用 会跑飞
#define DMA_NORMAL 0x00000000U /*!< Normal mode */
#define DMA_CIRCULAR ((uint32_t)DMA_CCR_CIRC) /*!< Circular mode */
Priority
优先级 分为四个等级:低,中,高,超高
#define DMA_PRIORITY_LOW 0x00000000U /*!< Priority level: Low */
#define DMA_PRIORITY_MEDIUM ((uint32_t)DMA_CCR_PL_0) /*!< Priority level: Medium */
#define DMA_PRIORITY_HIGH ((uint32_t)DMA_CCR_PL_1) /*!< Priority level: High */
#define DMA_PRIORITY_VERY_HIGH ((uint32_t)DMA_CCR_PL) /*!< Priority level: Very High */
数据传输函数
这里需要注意 DMA传输完成后需要手动清除标志位并将状态恢复为READY,否则下次启动DMA传输会失败。
void DMA_Transfer (uint32_t * src, uint32_t * dst, uint32_t size) {
HAL_DMA_Start (&dma, (uint32_t)src, (uint32_t)dst, size);// 启动DMA传输
// 等待传输完成
while (HAL_DMA_GetState (&dma) != HAL_DMA_STATE_READY) {
// 可以在这里添加超时处理或其他逻辑
if (__HAL_DMA_GET_FLAG (&dma, __HAL_DMA_GET_TC_FLAG_INDEX (&dma))) {
// 1. 清除硬件标志位
__HAL_DMA_CLEAR_FLAG (&dma, __HAL_DMA_GET_TC_FLAG_INDEX (&dma));
// 2. 手动将状态恢复为 READY (否则下次无法启动)
dma.State = HAL_DMA_STATE_READY;
// 3. 解锁 (HAL_DMA_Start 会加锁,这里必须解)
__HAL_UNLOCK (&dma);
break; // 跳出循环
}
}
// 传输完成后可以在这里进行其他操作,例如处理传输结果等
}
另其实HAL库也给出了一个轮询传输完成的函数,内部也是通过检查标志位来实现的,使用起来更方便一些。
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout);
此外还可以采用中断的方式进一步提高性能。
最后结合ADC
有了之前adc 与dma 的铺垫 这里直接上代码
stm32—— 模数转换器(ADC)
- 首先在adc初始化中添加DMA句柄绑定
hadc.Init.NbrOfDiscConversion = 1; // 不连续转换子组大小为 1(每次触发转换一个通道)
hadc.Init.NbrOfConversion = 1; // 规则组转换总数 为 1
// HAL_DMA_Init (&dma); // 将 ADC 与 DMA 关联
HAL_ADC_Init (&hadc); // 初始化 ADC
hadc.DMA_Handle = &dma; // 将 DMA 句柄关联到 ADC 句柄
- 在DMA初始化中将模式设置为循环模式
dma.Init.Mode = DMA_CIRCULAR ; // 工作模式:循环模式
- 最后在主函数初始化DMA ADC 启动传输
DMA_Init ();
ADC_Init ();
HAL_ADC_Start_DMA (&hadc, &ad_value, 1);
更多推荐

所有评论(0)