6.蓝桥杯串口DMA模式
·
之前已经学会了串口的中断的模式
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",不会冲突。
注意事项






更多推荐

所有评论(0)