1. 为什么要在STM32F103上折腾UART4的DMA?
如果你用过STM32的普通串口中断收发,肯定遇到过这样的烦恼:每收一个字节,CPU就得跳进中断服务函数一次;每发一个字节,又得跳进去一次。想象一下,你的设备以115200的波特率接收一条100字节的指令,CPU就得被硬生生打断100次去搬数据。这还没算上发送的打断次数。CPU大部分时间都在忙着进出中断,真正处理业务逻辑的时间反而被挤占了,系统效率自然高不起来。
这时候,DMA(直接存储器访问) 就该登场了。你可以把它理解成单片机内部一个非常勤快且专业的“数据搬运工”。我们只需要告诉它:数据在哪(源地址),要搬到哪去(目标地址),搬多少(数据量)。然后它就能在后台,不经过CPU,自己默默地把活干了。对于串口来说,这意味着:接收时,DMA自动把串口数据寄存器(DR)里的字节搬到我们指定的内存数组里;发送时,DMA又把内存数组里的数据搬到串口数据寄存器。整个过程,CPU只需要在开始配置一下,结束时处理一次中断即可。
UART4 在STM32F103系列里是个挺有用的外设,尤其当你USART1、2、3都被占用时。但它的DMA通道和USART1-3是分开的,需要特别注意。根据STM32F103的参考手册,UART4的接收(RX)对应DMA2的通道3,发送(TX)对应DMA2的通道5。这个对应关系是硬件固定的,写代码时可不能搞错。
所以,把UART4和DMA结合起来,目标就非常明确了:实现高效、稳定的高速串口通信,把CPU从繁琐的字节级搬运工作中解放出来,让它能更专注于应用层逻辑,同时还能轻松处理长度不确定的数据帧。
2. 核心配置:从零搭建UART4的DMA收发环境
要玩转DMA,首先得把舞台搭好。这里我们分三步走:配置UART4本身、配置DMA接收、配置DMA发送。我会把每个步骤的关键点和容易踩的坑都讲清楚。
2.1 UART4 GPIO与串口基础初始化
任何外设使用前,时钟是第一步。UART4挂载在APB1总线下,它的TX(PC10)和RX(PC11)引脚则属于GPIOC,时钟在APB2。所以初始化顺序是:
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // GPIOC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); // UART4时钟
// 2. 配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
// TX引脚配置为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// RX引脚配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// 3. 配置UART4串口参数
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
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(UART4, &USART_InitStructure);
这里有个我实测中遇到的“坑”,关于发送使能和清除TC(发送完成)标志的顺序。如果顺序不对,可能导致程序卡死。正确的做法是先使能串口,再操作TC标志:
USART_Cmd(UART4, ENABLE); // 必须先使能串口!
USART_ClearFlag(UART4, USART_FLAG_TC); // 清除发送完成标志
// 等待一下,确保空闲帧发出
while (USART_GetFlagStatus(UART4, USART_FLAG_TC) == RESET);
USART_ClearFlag(UART4, USART_FLAG_TC); // 再次清除
2.2 DMA接收初始化:如何准备一个“数据蓄水池”
DMA接收的核心思想是:开辟一片内存作为缓存区(比如255字节),让DMA一直守着串口数据寄存器。一旦有数据进来,DMA就自动把它搬到缓存区,同时更新内部计数器。我们初始化时,要告诉DMA这片缓存区在哪,以及它该以什么方式工作。
#define UART4_DMA_RX_BUFFER_MAX_LENGTH (255)
uint8_t UART4_DMA_RX_Buffer[UART4_DMA_RX_BUFFER_MAX_LENGTH];
void UART4_DMA_Rx_Configuration(void) {
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE)

1121

被折叠的 条评论
为什么被折叠?



