一、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语言中的头文件保护宏,用于避免头文件被重复包含,具体解释如下:
各部分含义
-
#ifndef __DMA_H-
#ifndef是"if not defined"的缩写,意思是"如果__DMA_H这个标识符未被定义",则执行后续代码。 -
__DMA_H是自定义的标识符(通常以头文件名+下划线命名,如DMA.h对应__DMA_H),用于标记该头文件是否已被包含。
-
-
#define __DMA_H- 当
__DMA_H未定义时,执行这行代码,定义__DMA_H标识符,标记该头文件已被包含。
- 当
-
#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。
具体说明:
-
通道 4 的映射规则 :从图中可以看到,通道 4 可以接收多个外设的请求(比如
USART1_TX、TIM1_CH4、SPI/I2S2_RX等),这些外设请求是 "逻辑或" 的关系 ------同一时间只能有一个外设占用通道 4。 -
其他串口的支持 :以 STM32(如 F103 系列)为例,DMA1 通道 4 不仅支持
USART1_TX,还可以支持其他串口(比如USART2_TX、USART3_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
-
USART1:- 这是一个指向USART1外设的结构体指针。USART1是微控制器上的一个具体串口(Universal Synchronous Asynchronous Receiver Transmitter,通用同步异步收发器)外设实例。
-
->CR3:CR3是 USART 的 Control Register 3(控制寄存器3)。这个寄存器包含用于配置 USART 高级或特定功能的控制位,例如 DMA 使能、错误中断、硬件流控制等。
-
1 << 7:-
这是一个位操作 。它将数字
1(二进制0000 0001)左移7位。 -
结果是
1000 0000(二进制),即 0x80(十六进制)。 -
这创建了一个在第7位 (Bit 7)为
1,而其他所有位都为0的掩码。
-
-
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()要分场景:
- 若用「正常模式(
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);
- 若用「循环模式(
DMA_CIRCULAR)」连续发送
-
不需要手动停止 DMA :循环模式下,DMA 传输完成后会自动重启(通道保持使能),持续循环发送数据,此时无需调用
HAL_UART_DMAStop(),直接保持 DMA 运行即可实现连续发送。例:c
运行
// 配置DMA为循环模式后,一次启动即可持续发送 HAL_UART_Transmit_DMA(&huart1, buf, 5); // 无需手动停止,DMA会循环发送buf中的数据
结论
-
正常模式连续发送:需要先调用
HAL_UART_DMAStop()清除标志、释放通道,再重新启动; -
循环模式连续发送:不需要手动停止,DMA 自动循环。
外设到存储器
观看这两个实验
