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 自动循环

外设到存储器

观看这两个实验

相关推荐
福尔摩斯张2 小时前
STM32数码管和LCD显示技术深度解析(超详细)
数据库·stm32·单片机·嵌入式硬件·mongodb
d111111111d2 小时前
STM32 DMA传输配置详解:数据宽度与传输方向设置指南
笔记·stm32·单片机·嵌入式硬件·学习
清风6666663 小时前
基于单片机的多路热电偶温度监测与报警器
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业
Archie_IT3 小时前
基于STM32F103C8T6标准库的OLED显示屏中文汉字显示实现_资料编号39
stm32·单片机·嵌入式硬件·mcu·硬件工程·信息与通信·信号处理
一路往蓝-Anbo3 小时前
STM32单线串口通讯实战(三):协议层设计 —— 帧结构、多机寻址与硬件唤醒
c语言·开发语言·stm32·单片机·嵌入式硬件·物联网
别了,李亚普诺夫4 小时前
DMA学习笔记
笔记·stm32
v先v关v住v获v取4 小时前
天然气管道内检测机器人检测节设计14张cad+三维图+设计说明书
科技·单片机·51单片机
无垠的广袤4 小时前
【上海晶珩睿莓 1 单板计算机】物联网环境监测终端
linux·python·嵌入式硬件·物联网·mqtt·home assistant
先知后行。4 小时前
ModBus协议
嵌入式硬件