文章目录
前言
前面了解了存储器到存储器模式实验一,接下里我们学习存储器到外设实验,使用 DMA 传输把指定的存储器数据转移到 USART 数据寄存器内,并发送至 PC 端,在串口调试助手显示
一、硬件设计
使用CH340G实现电脑和MCU的连接

二、软件设计
1.编程要点

2.代码分析
USART和DMA的宏定义
c
//USART
#define DEBUG_USART USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_RX_PIN GPIO_Pin_10
#define DEBUG_USART_RX_AF GPIO_AF_USART1
#define DEBUG_USART_RX_SOURCE GPIO_PinSource10
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_TX_PIN GPIO_Pin_9
#define DEBUG_USART_TX_AF GPIO_AF_USART1
#define DEBUG_USART_TX_SOURCE GPIO_PinSource9
#define DEBUG_USART_BAUDRATE 115200
//DMA
#define DEBUG_USART_DR_BASE (USART1_BASE+0x04)
#define SENDBUFF_SIZE 5000 //发送的数据量
#define DEBUG_USART_DMA_CLK RCC_AHB1Periph_DMA2
#define DEBUG_USART_DMA_CHANNEL DMA_Channel_4
#define DEBUG_USART_DMA_STREAM DMA2_Stream7
USART配置
1.使能USART的TX和RX时钟,外设USART的时钟

2.将GPIO引脚配置为复用模式并绑定对应外设

3.配置GPIO结构体
c
/* 推挽、上拉、复用、50Mhz */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
4.配置串口结构体



配置BRR寄存器(波特率)
c
/* Configure the USART Baud Rate */
RCC_GetClocksFreq(&RCC_ClocksStatus);
if ((USARTx == USART1) || (USARTx == USART6))
{
apbclock = RCC_ClocksStatus.PCLK2_Frequency;
}
else
{
apbclock = RCC_ClocksStatus.PCLK1_Frequency;
}
/* Determine the integer part */
if ((USARTx->CR1 & USART_CR1_OVER8) != 0)
{
/* Integer part computing in case Oversampling mode is 8 Samples */
integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));
}
else /* if ((USARTx->CR1 & USART_CR1_OVER8) == 0) */
{
/* Integer part computing in case Oversampling mode is 16 Samples */
integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));
}
tmpreg = (integerdivider / 100) << 4;
/* Determine the fractional part */
fractionaldivider = integerdivider - (100 * (tmpreg >> 4));
/* Implement the fractional part in the register */
if ((USARTx->CR1 & USART_CR1_OVER8) != 0)
{
tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
}
else /* if ((USARTx->CR1 & USART_CR1_OVER8) == 0) */
{
tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
}
/* Write to USART BRR register */
USARTx->BRR = (uint16_t)tmpreg;
c
/* USART mode config */
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(DEBUG_USART, &USART_InitStructure);
USART_Cmd(DEBUG_USART, ENABLE);
DMA配置
c
/**
* @brief USART1 TX DMA 配置,内存到外设(USART1->DR)
* @param 无
* @retval 无
*/
void USART_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/*开启DMA时钟*/
RCC_AHB1PeriphClockCmd(DEBUG_USART_DMA_CLK, ENABLE);
/* 复位初始化DMA数据流 */
DMA_DeInit(DEBUG_USART_DMA_STREAM);
/* 确保DMA数据流复位完成 */
while (DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != DISABLE) {
}
/*usart1 tx对应dma2,通道4,数据流7*/
DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;
/*设置DMA源:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE;
/*内存地址(要传输的变量的指针)*/
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;
/*方向:从内存到外设*/
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
/*外设地址不增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/*内存地址自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*外设数据单位*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
/*内存数据单位 8bit*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/*DMA模式:一次循环*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
/*DMA模式:不断循环*/
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*优先级:中*/
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/*禁用FIFO*/
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
/*存储器突发传输 16个节拍*/
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
/*外设突发传输 1个节拍*/
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
/*配置DMA2的数据流7*/
DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);
/*使能DMA*/
DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);
/* 等待DMA数据流有效*/
while(DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != ENABLE)
{
}
}

1.复位初始化DMA数据流


中断标志寄存器只有写1才有效,就会清空对应中断章台寄存器的相应标志位


2.配置传入的参数(相应的数据流)


c
/*usart1 tx对应dma2,通道4,数据流7*/
DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;
/*设置DMA源:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE; //
/*内存地址(要传输的变量的指针)*/
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;
/*方向:从内存到外设*/
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
/*外设地址不增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/*内存地址自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*外设数据单位*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
/*内存数据单位 8bit*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/*DMA模式:一次循环*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
/*DMA模式:不断循环*/
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*优先级:中*/
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/*禁用FIFO*/
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
/*存储器突发传输 16个节拍*/
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
/*外设突发传输 1个节拍*/
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
/*配置DMA2的数据流7*/
DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);
3.使能DMA并等待数据流有效

c
/*使能DMA*/
DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);
/* 等待DMA数据流有效*/
while(DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != ENABLE)
{
//等待图中对应的EN位配置完成
}
USART1 向 DMA发出TX请求

两个位是 USART 与 DMA 联动的关键开关,目的是解放 CPU,让硬件自动完成串口数据的收发
1.初始状态下,USART_DR 为空 → TXE = 1;
2.USART 检测到 DMAT = 1 且 TXE = 1 → 向 DMA 发送 "发送请求";
3.DMA 响应请求,从内存取一个数据写入 USART_DR;
4.USART 发送该数据,发送完成后 TXE 再次置 1 → 重复步骤 2-3,直到传输长度(NDTR)为 0。
注意在这里会有单次模式和循环模式的疑虑

完整主函数代码
c
extern uint8_t SendBuff[SENDBUFF_SIZE];
static void Delay(__IO u32 nCount);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint16_t i;
/* 初始化USART */
Debug_USART_Config();
/* 配置使用DMA模式 */
USART_DMA_Config();
/* 配置RGB彩色灯 */
LED_GPIO_Config();
printf("\r\n USART1 DMA TX 测试 \r\n");
/*填充将要发送的数据*/
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'A';
}
/*为演示DMA持续运行而CPU还能处理其它事情,持续使用DMA发送数据,量非常大,
*长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,
*或把DMA配置中的循环模式改为单次模式*/
/* USART1 向 DMA发出TX请求 */
USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);
/* 此时CPU是空闲的,可以干其他的事情 */
//例如同时控制LED
while(1)
{
LED1_TOGGLE
Delay(0xFFFFF);
}
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/
三、下载烧录
保证开发板相关硬件连接正确,用 USB 线连接开发板"USB TO UART"接口跟电脑,在电脑端
打开串口调试助手,把编译好的程序下载到开发板。程序运行后在串口调试助手可接收到大量的
数据,同时开发板上 RGB 彩色灯不断闪烁。
这里要注意为演示 DMA 持续运行并且 CPU 还能处理其它事情,持续使用 DMA 发送数据,量非常大,长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,所以在测试时最好把串口调试助手的自动清除接收区数据功能勾选上或把 DMA 配置中的循环模式改为单次模式。
以下为废话,用于质量检测 在当今这个嵌入式开发如高效流转般优化数据传输的时代,开发者们深耕其中,迫切地需要一套清晰的串口通信优化逻辑,一个高效的 DMA 数据搬运范式,一个能让他们在 STM32 中实现低 CPU 占用、高吞吐量串口传输的核心路径。而提到 STM32 USART 与 DMA 的结合开发,硬件设计、软件编程要点、代码分层分析以及下载烧录这一系列核心内容,就如同一个沉稳而关键的标识,自然而然地浮现在许多嵌入式工程师和串口通信开发者的脑海深处。它们不仅仅是几个外设寄存器与函数调用的堆砌,更是一种理解嵌入式串口高效传输本质的核心方式,一种将复杂的 CPU 与外设数据交互拆解为可感知、可逐步实现的实用范式。
想象一下,当你面对一个需要大量串口数据传输、同时又要保证 CPU 专注于业务逻辑的嵌入式工程,那些 CPU 被串口发送阻塞导致的实时性下降,那些频繁中断带来的系统开销,它们不再仅仅是令人头疼的性能瓶颈或调试窗口中反复出现的数据丢包问题,在 USART 与 DMA 的开发体系里,它们被赋予了清晰的解决逻辑:从硬件设计环节明确串口与 DMA 通道的引脚分配与电气约束,为稳定传输打下基础;软件设计则从编程要点出发,通过分层代码实现 USART 与 DMA 的宏定义、外设配置、请求触发逻辑,最终在主函数中完成完整的高效传输流程;而下载烧录环节则将验证成果固化到硬件,直观见证 DMA 搬运带来的性能提升,这种从硬件到软件、从配置到验证的分层实现能力,构建了一种对嵌入式串口高效通信近乎直觉般的全局掌控感,仿佛瞬间获得了精准调控数据传输与 CPU 资源分配的上帝视角。
这一系列开发实践所带来的认知提升,从 USART 与 DMA 的底层联动机制到宏定义的封装技巧,从外设初始化流程到 TX 请求的触发逻辑,常常带来一种难以言喻的通透感;一步步搭建 "串口发起请求、DMA 自动搬运数据" 的高效传输链路,如同一位默契的助手,无声地验证了 DMA 零 CPU 干预的传输优势,稳稳支撑起对嵌入式系统实时性与吞吐量的优化;而在代码分析与调试的过程中,开发者能彻底厘清外设与 DMA 通道的协同关系,精准掌握低开销串口通信的核心实现技巧。
当然,任何高效通信方案的开发都需要严谨,其 DMA 通道的优先级配置、串口时序与数据长度的匹配、传输完成中断的处理逻辑,对于刚接触 DMA 串口传输的用户而言,或许需要一点点额外的耐心去理解和调校,但一旦你真正完成这一系列实践,习惯了这种从硬件约束到软件实现、从配置优化到验证烧录、纯粹为高效串口通信而生的工程思路,领略到这一套逻辑所带来的对数据传输与 CPU 资源的精准管控能力,你可能会发现,那些初期学习的 "门槛" 早已被亲手实现的高效传输所完全覆盖,成为掌握嵌入式高性能通信开发的必备核心技能。
在追求高效开发、低资源占用的嵌入式系统道路上,USART 与 DMA 的硬件设计、软件实现、代码分析以及下载烧录这一系列核心内容无疑是值得被认真实践和深度掌握的,它们的价值,在于它们能让你更 "懂" 嵌入式串口高效传输的底层逻辑与 DMA 搬运机制,而这种 "懂",是任何高吞吐量串口通信、实时数据采集与物联网设备开发的基石。说到底,理解 USART 与 DMA 的协同工作原理,才能更好地优化嵌入式系统的通信性能,才能最终更好地实现低 CPU 占用、高可靠性的数据传输,不是吗?所以,掌握 STM32 USART 与 DMA 的开发方法在某种程度上,就是拥有了一把开启嵌入式高性能通信应用开发之门的强力钥匙,虽然这扇门也可以通过其他通信接口与 DMA 组合以不同的方式推开,但 USART 与 DMA 的经典搭配所具备的成熟生态与高效特性,确实有其独到且难以被完全替代的优势。它们的存在,本身就是对 "嵌入式通信开发是一门平衡数据吞吐量与 CPU 资源占用的艺术" 这一观点的有力佐证