之前已经学会了串口的中断的模式

DMA对发送和接收都有用

DMA可以用于两种方向

方向 作用
DMA发送 CPU把数据交给DMA,DMA自动发出去,CPU干别的
DMA接收 DMA自动收数据到内存,收完才通知CPU

串口用DMA和不用DMA的区别

一、不用DMA(中断模式)

代码示例

// 发送
HAL_UART_Transmit(&huart1, "Hello", 5, 100);  // 阻塞发送,CPU在这里等

// 接收(中断模式)
HAL_UART_Receive_IT(&huart1, &rx_data, 1);     // 每收1字节进一次中断

发送过程

CPU: 我要发"Hello"
CPU: 把'H'放进发送寄存器
CPU: 等待发送完成(1ms)
CPU: 把'e'放进发送寄存器
CPU: 等待发送完成(1ms)
CPU: 把'l'放进发送寄存器
...

CPU全程被占用,发送期间干不了别的事。

接收过程

收到1字节 → 中断 → 保存 → 重新开启接收 → 退出中断
收到1字节 → 中断 → 保存 → 重新开启接收 → 退出中断
...

每字节都进中断,数据量大时CPU被频繁打断。


二、用DMA模式

代码示例

// 发送DMA
HAL_UART_Transmit_DMA(&huart1, tx_buffer, 100);  // 告诉DMA发100字节,然后返回

// 接收DMA
HAL_UART_Receive_DMA(&huart1, rx_buffer, 100);   // 告诉DMA收100字节,然后返回

发送过程

CPU: 我要发100字节
CPU: 告诉DMA:数据在tx_buffer,发100个
DMA: 开始工作(自动把数据一个一个发出去)
CPU: 继续干别的事(不用等)
...
DMA发完后 → 中断通知CPU:"发完了"

CPU只参与开始和结束,发送过程不占用CPU。

接收过程

CPU: 我要收100字节
CPU: 告诉DMA:数据收到rx_buffer
DMA: 开始工作(自动收数据)
CPU: 继续干别的事
...
DMA收满100字节后 → 中断通知CPU:"收完了"

100个字节只进一次中断,CPU不被打断。

串口DMA的普通模式 vs 循环模式

一、普通模式(NORMAL)

配置

hdma_usart1_tx.Init.Mode = DMA_NORMAL;

发送过程

CPU: 我要发100字节
CPU: 启动DMA发送
DMA: 发第1字节 → 发第2字节 → ... → 发第100字节
DMA: 发完了!产生中断通知CPU
CPU: 收到中断,知道发送完成
DMA: 停止工作,不再发送

特点:发一次就停,下次要发需要CPU重新启动DMA。

接收过程

CPU: 我要收100字节
CPU: 启动DMA接收
DMA: 收到1字节存buffer[0] → 收到2字节存buffer[1] → ... → 收到100字节存buffer[99]
DMA: 收满了!产生中断通知CPU
CPU: 收到中断,知道100字节收完了
DMA: 停止工作,不再接收

特点:收一次就停,下次要收需要CPU重新启动DMA。


二、循环模式(CIRCULAR)

配置

hdma_usart1_tx.Init.Mode = DMA_CIRCULAR;

发送过程

CPU: 我要循环发送这100字节
CPU: 启动DMA循环发送
DMA: 发第1字节 → 第2字节 → ... → 第100字节
DMA: 发完了?不!从头再来
DMA: 发第1字节 → 第2字节 → ... → 第100字节
DMA: 发完了?不!从头再来
DMA: 永远不停,一直循环发...

特点:发完自动重头开始,永远不停,不需要CPU干预。

接收过程

CPU: 我要循环接收数据
CPU: 启动DMA循环接收
DMA: 收到1字节存buffer[0] → 收到2字节存buffer[1] → ... → 收到100字节存buffer[99]
DMA: 收满了?不!从头覆盖
DMA: 收到101字节存buffer[0] → 收到102字节存buffer[1] → ...
DMA: 永远不停,一直循环覆盖...

特点:收满自动重头覆盖,永远不停,CPU不知道什么时候数据更新了。

问题:CPU不知道数据什么时候更新了

如果DMA一直循环覆盖buffer,CPU根本不知道:

  • buffer[0]现在是第1帧还是第100帧?

  • 我读到的数据是旧的还是新的?

  • 什么时候数据更新了?

这就是为什么你的ADC用普通模式,不用循环模式。

那循环模式怎么用?

循环模式不能单独用,需要配合半满中断双缓冲

方法1:半满中断

// 设置循环模式,开启半满中断和全满中断
HAL_DMA_RegisterCallback(&hdma_adc1, HAL_DMA_XFER_HALFCPLT_CB_ID, HalfCpltCallback);
HAL_DMA_RegisterCallback(&hdma_adc1, HAL_DMA_XFER_CPLT_CB_ID, CpltCallback);

流程

buffer[0]~[63] 采满 → 半满中断 → CPU处理前半段
buffer[64]~[127] 采满 → 全满中断 → CPU处理后半段
DMA自动重头开始,继续覆盖

这样CPU知道哪部分是新的数据。


方法2:双缓冲

// 设置两个buffer,DMA在两个buffer之间来回切换
HAL_DMAEx_MultiBufferStart(&hdma_adc1, src, dst1, dst2, length);

流程

DMA写buffer[0] → CPU处理buffer[1]
DMA写buffer[1] → CPU处理buffer[0]
DMA写buffer[0] → CPU处理buffer[1]
...

CPU永远在处理"DMA不在写的那个buffer",不会冲突。

注意事项

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐