STM32F446 USART DMA模式仅单次发送 连续传输配置咨询
问题根因
代码存在4个核心配置/逻辑错误,导致第二次及之后的传输无法正常执行:
- DMA状态标志清除逻辑错误:
DMA1->HISR是只读状态寄存器,无法通过写操作清除标志位。循环中对HISR写0的操作完全无效,第一次传输完成后TCIF6标志会一直置位。 - 标志位判断逻辑错误:等待传输完成的判断使用了逻辑与
&&而非按位与&。原判断!(DMA1->HISR && DMA_HISR_TCIF6)等价于判断HISR寄存器整体是否为0,只要HISR任意位置位就会直接跳出等待,根本没有检测TCIF6位的真实状态。 - DMA流配置时序错误:对DMA流写0禁用后,没有等待硬件将EN位清零就修改配置参数。STM32F4的DMA流关闭存在硬件延迟,EN位清零前写入的配置寄存器、地址寄存器、传输长度寄存器都会被硬件忽略。
- USART DMA模式使能时序错误:
USART_CR3_DMAT位在DMA启动之后才被设置,第一次传输后重复操作该位容易引发USART DMA请求时序异常。
修复方案
按照以下步骤调整代码即可实现轮询模式下的多次连续DMA发送,无需开启任何中断:
- 将USART的DMA发送模式(DMAT)使能放到USART初始化阶段,不要在主循环中重复配置。
- 禁用DMA流后增加轮询逻辑,等待硬件将EN位清零后再修改DMA配置。
- 修正DMA标志清除逻辑:对
DMA1->HIFCR寄存器的对应清除位写1,完成Stream6的标志清除。 - 将所有标志位判断中的逻辑与
&&替换为按位与&,确保正确检测对应位状态。 - 显式配置DMA的外设/内存数据宽度为8位,避免默认值异常导致的传输错误。
修正后的核心代码
初始化部分调整
// 原USART初始化代码后增加DMAT配置,不需要放到循环里 USART2->BRR = ( ( ( uartdiv / 16 ) << USART_BRR_DIV_Mantissa_Pos ) | ( ( uartdiv % 16 ) << USART_BRR_DIV_Fraction_Pos ) ); USART2->CR1 |= ( USART_CR1_RE | USART_CR1_TE | USART_CR1_UE ); USART2->CR3 |= USART_CR3_DMAT; // 初始化阶段一次性开启USART DMA发送模式
主循环调整
while ( 1 ) { // 禁用DMA流,等待硬件确认关闭后再修改配置 DMA1_Stream6->CR &= ~(DMA_SxCR_EN); while( (DMA1_Stream6->CR & DMA_SxCR_EN) != 0 ); // 清空DMA配置位 DMA1_Stream6->CR &= ~(DMA_SxCR_CHSEL | DMA_SxCR_PL | DMA_SxCR_PSIZE | DMA_SxCR_MSIZE | DMA_SxCR_DIR | DMA_SxCR_MINC); // 配置DMA参数 DMA1_Stream6->CR |= (DMA_SxCR_CHSEL_2); // 选择通道4(对应USART2_TX) DMA1_Stream6->CR |= (0x0 << DMA_SxCR_PL_Pos); // 配置为低优先级 DMA1_Stream6->CR |= (DMA_SxCR_MINC); // 内存地址自增 DMA1_Stream6->CR |= (DMA_SxCR_DIR_0); // 传输方向:内存到外设 DMA1_Stream6->CR |= (0x0 << DMA_SxCR_PSIZE_Pos); // 外设数据宽度:8位 DMA1_Stream6->CR |= (0x0 << DMA_SxCR_MSIZE_Pos); // 内存数据宽度:8位 // 配置地址和传输长度 DMA1_Stream6->PAR = (uint32_t)&USART2->DR; DMA1_Stream6->M0AR = (uint32_t)TX_buffer; DMA1_Stream6->NDTR = 2; // 每次传输2字节 // 清除所有残留DMA标志:写HIFCR寄存器清除,不要写只读的HISR DMA1->HIFCR |= (DMA_HIFCR_CTCIF6 | DMA_HIFCR_CHTIF6 | DMA_HIFCR_CTEIF6 | DMA_HIFCR_CDMEIF6 | DMA_HIFCR_CFEIF6); // 清除USART传输完成标志 USART2->SR &= ~USART_SR_TC; // 启动DMA传输 DMA1_Stream6->CR |= DMA_SxCR_EN; // 等待传输完成:使用按位与判断标志位 while( (DMA1->HISR & DMA_HISR_TCIF6) == 0 ); while( (USART2->SR & USART_SR_TC) == 0 ); // 此处添加运算逻辑,可修改TX_buffer内容、调整下一次传输长度 for(volatile uint32_t i=0; i<10000; i++); // 模拟运算延时,方便观察串口输出 }
补充说明
- 该实现完全基于轮询,不需要配置任何DMA或USART中断,符合“计算-发送-计算-发送”的使用场景,不需要开启DMA循环模式。
- 每次传输前可根据需求修改
NDTR值(传输字节数)和M0AR值(发送缓冲区首地址),适配不同长度的数据发送。 - 传输完成后不要关闭
USART_CR3_DMAT位,保持初始化时的开启状态即可,不会影响正常运算逻辑。
内容的提问来源于stack exchange,提问作者Inor
相关产品推荐
相关产品推荐

