STM32 DMA直接存储器访问(寄存器与HAL库实现)

一、DMA介绍

直接存储器存取(direct memory access,DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

简单描述,DMA充当了一个数据搬运工的角色。

DMA核心作用

  • 解放 CPU:传统数据传输需要 CPU 逐条指令搬运(如读取外设数据→写入内存),而 DMA 可独立完成传输,CPU 可同时执行其他任务。
  • 提高效率:DMA 传输速度快,尤其适合大量数据(如音频、图像)的连续传输,避免 CPU 因等待传输而阻塞。
  • 支持多种场景:可实现外设→内存、内存→外设、内存→内存的数据传输。

DMA搬运什么数据

存储器、外设

这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括

自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。

三种搬运方式:

  • 存储器→存储器(例如:复制某些特别大的数据buf)
  • 存储器→外设 (例如:将数据buf写入串口TDR寄存器)
  • 外设→存储器 (例如:将串口RDR寄存器写入数据buf)

1. DMA框图

2. stm32中DMA

STM32F103中有2个DMA控制器,有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

一个通道每次只能搬运一个外设的数据,如果同时有多个外设的 DMA 请求,则按照优先级进行响应。

要注意的是DMA2只存在于大容量产品和互联型产品中。

3. DMA优先级管理

优先级管理采用软件+硬件:

软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级

最高级>高级>中级>低级

硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。

比如:如果软件优先级相同,通道2优先于通道4

二、实验:ROM到RAM

把ROM中的数据通过DMA传输到RAM,然后把数据通过printf发送到串口。

DMA传输不涉及外设,所以通道随便选。我们选DMA1的1通道。

注意DMA没有办法把数据从RAM传输到ROM(flash)。

1. ROM和RAM

STM32 的 ROM 主要用于存储程序代码、常量数据和固化的配置信息,其内容在断电后不会丢失。

  • Flash 存储器 :STM32 中最核心的 ROM 类型,用于存储用户编写的程序代码(如main函数、中断服务程序等)、常量(如const修饰的变量)以及程序运行所需的固定数据。
    特点:可多次擦写(但擦写次数有限,通常数万次以上),掉电数据不丢失,访问速度较快。
  • 系统存储器(System Memory) :一块特殊的 Flash 区域,由 ST 公司固化了Bootloader 程序(如 ISP 程序),用于通过串口、USB 等方式给单片机烧录程序。用户无法修改此区域内容。

STM32 的 RAM 用于存储程序运行时的临时数据,其内容在断电后会丢失,是程序运行的 "临时 workspace"。

  • SRAM(静态随机存取存储器):STM32 的 RAM 均为 SRAM,无需刷新电路,访问速度快,适合高速数据读写。

    特点:掉电数据丢失,读写速度远快于 Flash,容量通常小于 Flash

2. 寄存器实现

c 复制代码
#include "dma.h"

void DMA1_init()
{
    // 1. 开启时钟
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    // 2. DMA相关配置
    // 2.1 DMA方向 存储器到存储器 ROM===>RAM
    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
    // 2.2 数据宽度 8位
    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;
    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;
    // 2.3 地址自增
    DMA1_Channel1->CCR |= DMA_CCR1_MINC;
    DMA1_Channel1->CCR |= DMA_CCR1_PINC;
    // 2.4 传输完成中断
    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
    // 2.5 NVIC配置
    NVIC_SetPriorityGrouping(4);
    NVIC_SetPriority(DMA1_Channel1_IRQn, 1);
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

void DMA1_transmitData(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen)
{
    // 外设地址
    DMA1_Channel1->CPAR = srcAddr;
    // 存储器地址
    DMA1_Channel1->CMAR = destAddr;
    // 传输长度
    DMA1_Channel1->CNDTR = dataLen;
    // 开启通道
    DMA1_Channel1->CCR |= DMA_CCR1_EN;
}

void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & DMA_ISR_TCIF1)
    {
        // 清除中断标志位
        DMA1->IFCR |= DMA_IFCR_CTCIF1;
        // 关闭通道
        DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
        is_finished = 1;
    }
}
c 复制代码
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "dma.h"

const uint8_t src_data[4] = {10, 20, 30, 40};
uint8_t dest_data[4] = {0};
uint8_t is_finished = 0;

int main(void)
{
	USART1_init();
	DMA1_init();
	printf("hello world!\r\n");

	DMA1_transmitData((uint32_t)src_data, (uint32_t)dest_data, 4);
	while (1)
	{
		if (is_finished)
		{
			is_finished = 0;
			for (uint8_t i = 0; i < 4; i++)
			{
				printf("%d\t", dest_data[i]);
			}
		}
	}
}

3.HAL库实现

c 复制代码
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("hello\r\n");
  // 注册回调函数
  HAL_DMA_RegisterCallback(&hdma_memtomem_dma1_channel1, HAL_DMA_XFER_CPLT_CB_ID, dma_compeleteCallback);
  HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)src_data, (uint32_t)dest_data, 4);
  /* USER CODE END 2 */

  while (1)
  {
    if (is_finished)
    {
      is_finished = 0;
      for (uint8_t i = 0; i < 4; i++)
      {
        printf("%d\t", dest_data[i]);
      }
    }
  }
}

void dma_compeleteCallback(DMA_HandleTypeDef *_hdma)
{
  // 清除中断标志位,失能通道
  HAL_DMA_Abort_IT(&hdma_memtomem_dma1_channel1);
  is_finished = 1;
}
c 复制代码
HAL_StatusTypeDef HAL_DMA_RegisterCallback(DMA_HandleTypeDef *hdma, HAL_DMA_CallbackIDTypeDef CallbackID, void (*pCallback)(DMA_HandleTypeDef *_hdma))
  • pCallback : 指向用户定义的回调函数的指针。回调函数的原型应为 void Callback(DMA_HandleTypeDef *_hdma)

三、实验:RAM到外设(串口)

把RAM中的数据直接传输到usart1的Tx引脚,然后数据被发送到电脑端。

由于不同的通道对应着不同的外设,查阅手册了解到应该选择DMA1的通道4

1. 寄存器实现

c 复制代码
#include "dma.h"

void DMA1_init()
{
    // 1. 开启时钟
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    // 2. DMA相关配置
    // 2.1 DMA方向 存储器到外设 ROM===>USART1
    DMA1_Channel4->CCR |= DMA_CCR4_DIR; // 从存储器读
    // 2.2 数据宽度 8位
    DMA1_Channel4->CCR &= ~DMA_CCR4_MSIZE;
    DMA1_Channel4->CCR &= ~DMA_CCR4_PSIZE;
    // 2.3 地址自增
    // 外设(串口)不自增
    DMA1_Channel4->CCR |= DMA_CCR4_MINC;
    DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;
    // 2.4 传输完成中断
    DMA1_Channel4->CCR |= DMA_CCR4_TCIE;
    // 2.5 NVIC配置
    NVIC_SetPriorityGrouping(4);
    NVIC_SetPriority(DMA1_Channel4_IRQn, 1);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);

    // 2.6 串口DMA使能发送
    USART1->CR3 |= USART_CR3_DMAT;
}

// 源-存储器地址  目的-外设地址
void DMA1_transmitData(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen)
{
    // 外设地址
    DMA1_Channel4->CPAR = destAddr;
    // 存储器地址
    DMA1_Channel4->CMAR = srcAddr;
    // 传输长度
    DMA1_Channel4->CNDTR = dataLen;
    // 开启通道
    DMA1_Channel4->CCR |= DMA_CCR4_EN;
}

void DMA1_Channel4_IRQHandler(void)
{
    if (DMA1->ISR & DMA_ISR_TCIF4)
    {
        // 清除中断标志位
        DMA1->IFCR |= DMA_IFCR_CTCIF4;
        // 关闭通道
        DMA1_Channel4->CCR &= ~DMA_CCR4_EN;
    }
}
c 复制代码
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "dma.h"

uint8_t src_data[]= {'a','b','c','d'};

int main(void)
{
	USART1_init();
	DMA1_init();
	printf("hello world!\r\n");

	DMA1_transmitData((uint32_t)src_data, (uint32_t)&(USART1->DR), 4);
	while (1)
	{
	}
}

值得注意的是实验现象中hello word后面回车换行符号不见了,原因是进行DMA传输时,没有对串口缓冲区进行判空操作,直接将数据刷新进去,导致原来的数据没有发送完就直接覆盖掉。

为处理这个现象,可以在进行DMA传输之前适当延时。

2. HAL库实现

使用STM32CubeMX生成的代码,有关DMA的配置在串口配置文件中。

c 复制代码
uint8_t src_data[] = {'a', 'b', 'c', 'd','e'};

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Transmit_DMA(&huart1,src_data,5);
  /* USER CODE END 2 */

  while (1)
  {
  }
}
相关推荐
小鱼儿电子1 天前
46-基于STM32的智能宠物屋设计与实现
stm32·腾讯云·宠物屋·智能宠物屋
Jerry丶Li1 天前
十九、STM32的TIM(十)(编码器)
stm32·单片机·嵌入式硬件
IT阳晨。1 天前
【STM32】串口通信及相关实验和项目
stm32·单片机·嵌入式硬件
安庆平.Я1 天前
STM32——IWDG
stm32·单片机·嵌入式硬件
怀民民民1 天前
轮询&中断 串口实训
单片机·嵌入式硬件·串口·中断·轮询·学习日志·keill
kaka❷❷1 天前
STM32 单片机 ESP8266 联网 和 MQTT协议
stm32·单片机·嵌入式硬件·物联网·mqtt·esp8266
古译汉书2 天前
Stm32江科大入门教程--各章节详细笔记---查阅传送门
数据结构·stm32·单片机·嵌入式硬件·算法
一个学Java小白2 天前
LV.5 文件IO
stm32·单片机·嵌入式硬件
sheepwjl2 天前
《嵌入式硬件(十八):基于IMX6ULL的ADC操作》
单片机·嵌入式硬件·imx6ull·adc
2301_805962932 天前
AXF文件变量地址查找完全指南
stm32