STM32--DMA

一、DNA基础

注释

二、实验

实验1

main.c

复制代码
int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    delay_init(72);                         /* 延时初始化 */
	  usart_init(115200);                     /* 串口初始化 */
    led_init();                             /* LED初始化 */
	
			 for(uint8_t i=0;i<5;i++)         /* 验证 */
		{
			printf("%c",buf_b[i]);
		}
				
    dma_init(DMA1_Channel1);
    while(1)
    { 
			if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC1))
			{
				__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC1);
	     for(uint8_t i=0;i<5;i++)
				{
					printf("%c",buf_b[i]);
				}
				
			}
			LED0(0);
      delay_ms(500);
      LED1(1);
			delay_ms(500);
			LED0(1);
      delay_ms(500);
      LED1(0);
			delay_ms(500);

    }
}

注意验证代码

添加完后需要包含

如果是自建.c和.h文件需要自建.h文件里面的内容

dma.h

复制代码
#ifndef __DMA_H
#define	__DMA_H

#include "./SYSTEM/sys/sys.h"

void dma_init(DMA_Channel_TypeDef* DMAx_CHx);
extern DMA_HandleTypeDef g_dma_handle;
extern uint8_t buf_a[5];
extern uint8_t buf_b[5];
#endif

这段代码是C语言中的头文件保护宏,用于避免头文件被重复包含,具体解释如下:

各部分含义

  1. #ifndef __DMA_H

    • #ifndef 是"if not defined"的缩写,意思是"如果__DMA_H这个标识符未被定义",则执行后续代码。

    • __DMA_H是自定义的标识符(通常以头文件名+下划线命名,如DMA.h对应__DMA_H),用于标记该头文件是否已被包含。

  2. #define __DMA_H

    • __DMA_H未定义时,执行这行代码,定义__DMA_H标识符,标记该头文件已被包含。
  3. #endif

    • 结束#ifndef的条件判断,标志着头文件保护宏的结束。

作用

头文件(如DMA.h)可能被多个.c文件包含,若没有保护宏,多次包含会导致重复定义错误(比如头文件中的函数声明、宏定义被重复编译)。

通过这三行代码,头文件只会被第一次包含时编译 ,后续包含会因__DMA_H已定义而跳过,避免重复定义问题。

示例补充

实际使用时,头文件的内容会写在#define __DMA_H#endif之间,例如:

复制代码
#ifndef __DMA_H
#define __DMA_H

// 头文件内容(如函数声明、宏定义、类型定义)
#include "stm32f1xx_hal.h"
extern DMA_HandleTypeDef g_dma_handle;
void dma_init(DMA_Channel_TypeDef* DMAx_CHx);

#endif

dma.c

复制代码
#include "./BSP/DMA/dma.h"
#include "./SYSTEM/delay/delay.h"

DMA_HandleTypeDef g_dma_handle;

uint8_t buf_a[5]={'h','e','l','l','o'};
uint8_t buf_b[5];

void dma_init(DMA_Channel_TypeDef* DMAx_CHx)
{

	  __HAL_RCC_DMA1_CLK_ENABLE();
/* Tx DMA配置 */
    g_dma_handle.Instance = DMAx_CHx;                             
    g_dma_handle.Init.Direction = DMA_MEMORY_TO_MEMORY;              // 部分HAL库中也可写作DMA_DIR_MEMORY_TO_MEMORY;             
    g_dma_handle.Init.PeriphInc = DMA_PINC_ENABLE;                 /* 外设非增量模式 */
    g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                     /* 存储器增量模式 */
    g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外设数据长度:8位 */
    g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;       /* 存储器数据长度:8位 */
    g_dma_handle.Init.Mode = DMA_NORMAL;                            /* DMA模式:正常模式 */
    g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;               /* 中等优先级 */
	
	
  	 HAL_DMA_Init(&g_dma_handle);
	  HAL_DMA_Start(&g_dma_handle,(uint32_t)buf_a,(uint32_t)buf_b,5);
	
}

注释:由于创建了dma文件存储dma.c和dma.h用来存储dma的初始化信息等,定义了变量buf_a和buf_b变量和句柄需g_dma_handle将其定义为全局变量方法如下(全局变量定义方法:)

出现这个表示实验成功

实验2

单次传输

其他串口是可以使用通道 4 的,但同一时间只能绑定一个串口(或其他外设)到通道 4

具体说明:

  1. 通道 4 的映射规则 :从图中可以看到,通道 4 可以接收多个外设的请求(比如USART1_TXTIM1_CH4SPI/I2S2_RX等),这些外设请求是 "逻辑或" 的关系 ------同一时间只能有一个外设占用通道 4

  2. 其他串口的支持 :以 STM32(如 F103 系列)为例,DMA1 通道 4 不仅支持USART1_TX,还可以支持其他串口(比如USART2_TXUSART3_TX等,具体取决于芯片型号的 DMA 请求映像表)。但配置时需要通过外设寄存器,将对应的串口 DMA 请求 "映射" 到通道 4,并确保同一时间只有一个串口(或外设)使用通道 4。

简单说:通道 4 不是某一个串口的 "专属通道",其他串口可以用,但同一时间只能有一个外设(含串口)占用通道 4

关联DMA1和串口1

这个函数这样配置:__HAL_LINKDMA()

main.c

复制代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "led.h"
#include "./BSP/DMA/dma.h"
//void led_init(void);                       /* LED初始化函数声明 */


int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    delay_init(72);                         /* 延时初始化 */
	  usart_init(115200);                     /* 串口初始化 */
    led_init();                             /* LED初始化 */
	  dma_init(DMA1_Channel4);                /*选用通道4*/
		
    while(1)
    { 
			if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC4))
			{
				__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC4);
	      LED0(0);
				delay_ms(10);
		    LED0(1);
				
			}
	   delay_ms(10);
    }
}

dma.c

复制代码
#include "./BSP/DMA/dma.h"
#include "./SYSTEM/delay/delay.h"

DMA_HandleTypeDef g_dma_handle;
extern  UART_HandleTypeDef g_uart1_handle;

uint8_t buf_a[5]={'h','e','l','l','o'};

void dma_init(DMA_Channel_TypeDef* DMAx_CHx)
{

	  __HAL_RCC_DMA1_CLK_ENABLE();
/* Tx DMA配置 */
    __HAL_LINKDMA(&g_uart1_handle,hdmatx,g_dma_handle);    //关联DMA1和串口1
	
	
    g_dma_handle.Instance = DMAx_CHx;                             
    g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;              // 部分HAL库中也可写作DMA_DIR_MEMORY_TO_MEMORY;             
    g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;                 /* 外设非增量模式 */
    g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                     /* 存储器增量模式 */
    g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外设数据长度:8位 */
    g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;       /* 存储器数据长度:8位 */
    g_dma_handle.Init.Mode = DMA_NORMAL;                            /* DMA模式:正常模式 */
    g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;               /* 中等优先级 */
	
	
  	HAL_DMA_Init(&g_dma_handle);
	    HAL_DMA_Start(&g_dma_handle, (uint32_t)buf_a, (uint32_t)&USART1->DR, 5);   //注意USART前面有&去地址符
	
}

DMA这样配置 目的地址(USART1->DR)

这样是不是就可以将数据传输到外设了?不是

某一个外设想要使用DMA功能就需要将他特定的位就是我们上图的第5步使能串口1的DMA

如下

复制代码
USART->CR3 = 1<<7    //使能串口1的DMA
  1. USART1:

    • 这是一个指向USART1外设的结构体指针。USART1是微控制器上的一个具体串口(Universal Synchronous Asynchronous Receiver Transmitter,通用同步异步收发器)外设实例。
  2. ->CR3:

    • CR3 是 USART 的 Control Register 3(控制寄存器3)。这个寄存器包含用于配置 USART 高级或特定功能的控制位,例如 DMA 使能、错误中断、硬件流控制等。
  3. 1 << 7:

    • 这是一个位操作 。它将数字 1(二进制 0000 0001)左移 7 位。

    • 结果是 1000 0000(二进制),即 0x80(十六进制)。

    • 这创建了一个在第7位 (Bit 7)为 1,而其他所有位都为 0 的掩码。

  4. USART1->CR3 |= 1<<7;:

    • 这行代码将 CR3 寄存器的值直接设置0x80

    • 不是设置或清除单个位,而是直接给整个寄存器赋值。这意味着它:

      • 将第7位设置为 1

      • 将第0位到第6位,以及第8位到第31位(如果存在)都清零

实现连续传输

方法一

注释:

(1)使用方法一的时候需在主函数里面调用dma_t_enable()这个函数

(2)上述两个方框的内容可以使用方法二中介绍的HAL_UART_Transmit_DMA()函数代替。

方法二

复制代码
int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    delay_init(72);                         /* 延时初始化 */
	  usart_init(115200);                     /* 串口初始化 */
    led_init();                             /* LED初始化 */
	  dma_init(DMA1_Channel4);                /*选用通道4*/
		
    while(1)
    { 
			HAL_UART_Transmit_DMA(&g_uart1_handle,buf_a,5);
			
			if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC4))
			{
				HAL_UART_DMAStop(&g_uart1_handle);
				
				__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC4);
	      LED0(0);
				delay_ms(10);
		    LED0(1);
				
			}
	   delay_ms(10);
    }
}

两个函数的关系与对比

特性 HAL_UART_Transmit_DMA() HAL_UART_DMAStop()
目的 启动DMA传输 停止DMA传输
阻塞性 非阻塞,立即返回 阻塞,立即停止
状态影响 设置状态为BUSY 设置状态为READY
硬件操作 使能DMA和串口DMA功能 禁用DMA和串口DMA功能
典型用途 开始数据传输 取消/停止数据传输

详细说明

HAL_UART_Transmit_DMA()

  • 功能:启动通过DMA进行的串口数据传输

  • 行为:配置DMA参数并启动传输,函数立即返回,不等待传输完成

  • 使用场景:需要后台数据传输而不阻塞主程序时使用

HAL_UART_DMAStop()

  • 功能:停止正在进行的串口DMA传输

  • 行为:立即终止DMA传输,清理相关标志位

  • 使用场景:需要中断或取消正在进行的传输时使用

思考

想要连续发送使用时每一次使用完HAL_UART_Transmit_DMA()都需要HAL_UART_DMAStop()清除标志位这句话对吗

这句话不完全准确 ,连续使用HAL_UART_Transmit_DMA()时,是否需要HAL_UART_DMAStop()要分场景:

  1. 若用「正常模式(DMA_NORMAL)」连续发送
  • 需要先停止 DMA,再重新启动 :正常模式下,一次 DMA 传输完成后,DMA 通道会自动关闭(CCR寄存器的EN位清零),同时UART的 DMA 发送功能也会被 HAL 库自动禁用。此时若要连续发送,必须先调用HAL_UART_DMAStop()(或HAL_DMA_Abort()清除传输完成标志、释放 DMA 通道 ,再重新调用HAL_UART_Transmit_DMA()启动下一次传输。例:

    c

    运行

    复制代码
    // 第一次发送
    HAL_UART_Transmit_DMA(&huart1, buf1, 5);
    // 等待传输完成(可通过中断回调)
    while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET);
    // 停止DMA,清除标志
    HAL_UART_DMAStop(&huart1);
    // 第二次发送
    HAL_UART_Transmit_DMA(&huart1, buf2, 5);
  1. 若用「循环模式(DMA_CIRCULAR)」连续发送
  • 不需要手动停止 DMA :循环模式下,DMA 传输完成后会自动重启(通道保持使能),持续循环发送数据,此时无需调用HAL_UART_DMAStop(),直接保持 DMA 运行即可实现连续发送。例:

    c

    运行

    复制代码
    // 配置DMA为循环模式后,一次启动即可持续发送
    HAL_UART_Transmit_DMA(&huart1, buf, 5);
    // 无需手动停止,DMA会循环发送buf中的数据

结论

  • 正常模式连续发送:需要先调用HAL_UART_DMAStop()清除标志、释放通道,再重新启动

  • 循环模式连续发送:不需要手动停止,DMA 自动循环

外设到存储器

观看这两个实验

相关推荐
国科安芯11 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao13 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉16 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者17 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间18 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious18 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32