📝 踩坑日记:为什么在使用 DMA 发送时要使用静态变量?
⚠️ 问题描述
当你在函数内部定义一个局部数组(如 uint8_t data[10];)并将其地址传给 DMA 进行异步发送时,存在一个严重的问题:
函数执行完毕后,局部变量所在的栈空间会被释放,而此时 DMA 可能仍在传输该内存区域的数据。
这将导致以下后果:
- 数据被覆盖或损坏;
- 程序行为不可预测;
- 引发 HardFault 或系统崩溃。
✅ 解决方案:使用 static 关键字
为了解决这个问题,可以将缓冲区定义为 静态变量(static uint8_t data[10];),其特点如下:
| 特性 | 描述 |
|---|---|
| 存储位置 | 静态存储区(不是栈) |
| 生命周期 | 整个程序运行期间都存在 |
| 初始化 | 只初始化一次 |
因此,在函数返回后,data 缓冲区的内容仍然有效,DMA 可以安全地继续访问它,直到传输完成。
🔍 示例代码片段
uint8_t *Serial_Screen_SendFloat(uint16_t address, float power)
{
static uint8_t data[10] = {0}; // 使用 static 避免DMA发送过程中数据被销毁
// 填充数据包...
HAL_UART_Transmit_DMA(&huart3, data, 10); // 使用DMA发送
return data; // 返回指针是安全的,因为 data 是静态变量
}
🧠 扩展理解:其他解决方案
除了使用 static,还有以下几种方式也可以解决此问题:
✅ 方法一:使用全局变量
uint8_t data[10]; // 全局变量,生命周期贯穿整个程序
优点:生命周期长;缺点:不推荐滥用全局变量。
✅ 方法二:动态分配内存(适用于 RTOS)
uint8_t *data = malloc(10);
// ...
free(data); // 在 DMA 传输完成后释放
适用于支持动态内存管理的系统(如 FreeRTOS)。
✅ 方法三:阻塞发送(调试阶段可用)
HAL_UART_Transmit(&huart3, data, 10, HAL_MAX_DELAY);
DMA 不再必要,但会阻塞 CPU,不适合实时性强的场景。
📌 总结
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| 局部变量 + DMA | ❌ 不推荐 | 栈内存释放后 DMA 访问非法地址 |
static 局部变量 |
✅ 推荐 | 安全、简洁,适用于裸机开发 |
| 全局变量 | ⚠️ 慎用 | 易造成耦合,不利于维护 |
| 动态内存分配 | ✅(RTOS 中) | 灵活但需注意内存泄漏 |
| 阻塞发送 | ✅(调试可用) | 影响性能,不适合高频发送 |
📝 小贴士
- 如果你使用的是中断或 DMA 完成回调机制,请确保在回调函数中不再使用已释放的内存。
- 使用
static虽然解决了内存生命周期问题,但要注意线程安全性和重入问题(多个任务并发访问同一缓冲区)。 - 若使用 RTOS,建议配合信号量或队列进行同步处理。
📚 参考资料
- STM32 HAL UART DMA 发送详解
- 《嵌入式系统软件设计基础》—— 静态变量与栈内存管理
- C 语言进阶:
static、volatile、const关键字详解

浙公网安备 33010602011771号