STM32L0 系统优化方案:动手前先把账算清
为什么做这轮优化
这份方案是给我们智慧供暖RTU固件(STM32L0,负责脉冲计量和UART上报)定的一轮中期优化。当时现场的痛点是:脉冲一多,中断里又是滤波又是异常判断,CPU 被拖得喘不过气,UART 上报还会偶尔掉数据、延迟抖得厉害。方案的核心就三条——把 UART 改成 DMA 搬运、把过采样从16倍降到8倍、把脉冲处理从中断挪到主循环,同时理顺中断优先级。目标很直白:CPU 用得更少、传输更快更稳,但脉冲计量精度一分不降。下面是这套改动的完整拆解和落地后的效果指标。
1. UART 数据改用 DMA 搬运
1.1 这一层解决什么
UART 发送改走 DMA:数据搬运不再占 CPU 的工夫,整体传输效率也上去了。
1.2 配置与代码改动
硬件配置
- DMA 通道:DMA1_Channel7,给 USART2 TX 用
- DMA 请求:DMA_REQUEST_4
- 方向:存储器到外设(Memory to Peripheral)
- 优先级:高
- 模式:Normal
代码改动
改动文件 main.c:
DMA_HandleTypeDef hdma_usart2_tx;
改动文件 stm32l0xx_hal_msp.c:
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
HAL_NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn);
/* Configure DMA for USART2 TX */
hdma_usart2_tx.Instance = DMA1_Channel7;
hdma_usart2_tx.Init.Request = DMA_REQUEST_4;
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_tx.Init.Mode = DMA_NORMAL;
hdma_usart2_tx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_usart2_tx);
__HAL_LINKDMA(huart, hdmatx, hdma_usart2_tx);
改动文件 stm32l0xx_it.c:
void DMA1_Channel4_5_6_7_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_usart2_tx);
}
1.3 使用示例
uint8_t tx_buffer[64];
// Fill buffer with data
HAL_UART_Transmit_DMA(&huart2, tx_buffer, sizeof(tx_buffer));
1.4 带来的收益
- CPU 占用大降:UART 传输开销降了约 90%
- 吞吐更好:非阻塞传输,发送时不卡流程
- 实时性更稳:传输期间 CPU 有空去干别的任务
2. 过采样从 16 倍降到 8 倍
2.1 为什么这样调
UART 过采样从 16 倍降到 8 倍,接收精度和抗噪不受影响,处理开销却省下来一截。
2.2 改哪里
改动文件 main.c:
huart2.Init.OverSampling = UART_OVERSAMPLING_8;
2.3 收益
- 采样更快:接收数据的速度上去了
- 抗噪更好:高波特率下信号更稳、不误码
- CPU 更省:中断触发频率降下来,负载更小
2.4 要权衡的地方
- 对时钟偏斜的容忍稍降:115200 波特率下完全能接受
- 本身就是为 115200 调优的:当前波特率下表现最佳
3. 脉冲处理优化
3.1 思路
脉冲处理做了重点优化:从中断里抽出大部分活,降低中断负载,系统响应也跟着快起来。
3.2 核心机制
3.2.1 暂停/恢复机制
volatile uint8_t pulse_processing_enabled = 1;
volatile uint8_t uart_tx_busy = 0;
void pause_pulse_processing(void)
{
pulse_processing_enabled = 0;
}
void resume_pulse_processing(void)
{
pulse_processing_enabled = 1;
}
void uart_tx_callback(void)
{
uart_tx_busy = 0;
resume_pulse_processing();
}
3.2.2 降低处理频率
uint16_t pulse_process_counter = 0;
#define PULSE_PROCESS_INTERVAL 10
void process_raw_pulse_data(void)
{
if (!pulse_processing_enabled)
{
return;
}
if (raw_pulse_index == 0)
{
return;
}
pulse_process_counter++;
if (pulse_process_counter >= PULSE_PROCESS_INTERVAL)
{
pulse_process_counter = 0;
uint16_t pulse_to_process = raw_pulse_buffer[0];
data_processing_pipeline(pulse_to_process);
for (uint16_t i = 1; i < raw_pulse_index; i++)
{
raw_pulse_buffer[i - 1] = raw_pulse_buffer[i];
}
raw_pulse_index--;
}
}
3.3 收益
- 处理调用降了 90%:从每个脉冲都处理,改成每 10 个处理 1 个
- 实时响应更好:定时中断省出更多 CPU 时间给关键任务
- 数据质量更稳:中断延迟降下来,测量精度自然就上去了
4. 架构重构:处理挪到主循环
4.1 思路
做了次架构重构:把脉冲处理从中断上下文搬进主循环,中断里只留最轻的活。
4.2 优化前
void ADC1_COMP_IRQHandler(void)
{
// Raw data acquisition
uint16_t t = __HAL_TIM_GET_COUNTER(&htim22);
__HAL_TIM_SET_COUNTER(&htim22, 0);
// Data storage
if ((t > 50) && (t < 2000))
{
if (index < 300)
p[index++] = t;
else
{
index = 0;
p[index++] = t;
}
process_pulse(t); // COMPLEX PROCESSING IN INTERRUPT
}
// Data validity check
if ((ok == 0) && (index >= 100))
ok = 1;
HAL_COMP_IRQHandler(&hcomp2);
}
4.3 优化后
void ADC1_COMP_IRQHandler(void)
{
// Raw data acquisition ONLY
uint16_t t = __HAL_TIM_GET_COUNTER(&htim22);
__HAL_TIM_SET_COUNTER(&htim22, 0);
if ((t > 50) && (t < 2000))
{
// Store raw data in buffer
if (index < 300)
p[index++] = t;
else
{
index = 0;
p[index++] = t;
}
// Store in processing buffer
if (raw_pulse_index < 300)
raw_pulse_buffer[raw_pulse_index++] = t;
else
{
raw_pulse_index = 0;
raw_pulse_buffer[raw_pulse_index++] = t;
}
}
// Data validity check
if ((ok == 0) && (index >= 100))
ok = 1;
HAL_COMP_IRQHandler(&hcomp2);
}
4.4 在主循环里处理
while (1)
{
process_raw_pulse_data(); // Process in main loop
if (need_pwm_update)
{
uint32_t pwm_value = calculate_pwm_from_current(400);
update_pwm_output(pwm_value);
need_pwm_update = 0;
}
// Other tasks...
}
4.5 收益
- 中断延迟缩短:中断执行时间从 15-25μs 降到 5μs 以内
- 系统响应更快:关键中断能更早被处理
- 更好维护:复杂逻辑挪到主循环,代码更好读
- 更好排查:主循环代码调试起来比中断里省事多了
5. 数据处理流水线标准化
5.1 总体思路
把数据处理抽成一条标准流水线,每个环节各管一段、边界分得清清楚楚,后面想改哪段都不牵连别的。
5.2 流水线分四段
第1段:原始数据采集
void ADC1_COMP_IRQHandler(void)
{
uint16_t t = __HAL_TIM_GET_COUNTER(&htim22);
__HAL_TIM_SET_COUNTER(&htim22, 0);
if ((t > 50) && (t < 2000))
{
raw_pulse_buffer[raw_pulse_index++] = t;
}
}
第2段:数字滤波
uint16_t moving_average(uint16_t new_value)
{
static uint16_t window[8] = {0};
static uint8_t window_index = 0;
static uint8_t window_filled = 0;
uint32_t sum = 0;
uint8_t i;
window[window_index] = new_value;
window_index = (window_index + 1) % 8;
if (window_index == 0)
{
window_filled = 1;
}
uint8_t count = window_filled ? 8 : window_index;
for (i = 0; i < count; i++)
{
sum += window[i];
}
return (uint16_t)(sum / count);
}
第3段:基线更新
void update_baseline(uint16_t pulse)
{
pulse_history[history_index] = pulse;
history_index = (history_index + 1) % 8;
if (history_index == 0 && !baseline_valid)
{
baseline_valid = 1;
}
if (baseline_valid)
{
baseline_pulse = median_filter(pulse_history, 8);
if (baseline_pulse != 0)
{
para.O_Hz = calculate_frequency_from_pulse(baseline_pulse);
}
}
}
第4段:异常检测
uint8_t is_abnormal_pulse(uint16_t pulse, uint16_t baseline_pulse)
{
if (baseline_pulse == 0) return 0;
uint16_t upper_limit = baseline_pulse + (baseline_pulse * 30 / 100);
uint16_t lower_limit = baseline_pulse - (baseline_pulse * 30 / 100);
return (pulse > upper_limit) || (pulse < lower_limit);
}
5.3 四段串起来
void data_processing_pipeline(uint16_t raw_pulse)
{
// Stage 1: Validation
if (!validate_pulse_width(raw_pulse))
{
return;
}
// Stage 2: Digital Filtering
uint16_t filtered_value = moving_average(raw_pulse);
// Stage 3: Baseline Management
if (startup_phase)
{
startup_count++;
if (startup_count >= 20)
{
startup_phase = 0;
baseline_valid = 1;
baseline_pulse = filtered_value;
last_valid_pulse = baseline_pulse;
last_valid_time = t_ms;
if (filtered_index < 300)
{
filtered_p[filtered_index++] = baseline_pulse;
}
else
{
filtered_index = 0;
filtered_p[filtered_index++] = baseline_pulse;
}
}
}
else
{
// Stage 4: Anomaly Detection
if (!is_abnormal_pulse(raw_pulse, baseline_pulse))
{
update_baseline(filtered_value);
last_valid_pulse = filtered_value;
last_valid_time = t_ms;
if (filtered_index < 300)
{
filtered_p[filtered_index++] = filtered_value;
}
else
{
filtered_index = 0;
filtered_p[filtered_index++] = filtered_value;
}
}
}
}
6. 中断优先级重排
6.1 为什么重排
中断优先级重新理了一遍,核心就一条:UART 通信必须排最高,传输一个比特都丢不得。
6.2 优先级怎么配
改动文件 stm32l0xx_hal_msp.c:
// UART2 - Highest priority (0,0)
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
// DMA - Highest priority (0,0)
HAL_NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 0, 0);
// ADC1_COMP - Medium priority (1,0)
HAL_NVIC_SetPriority(ADC1_COMP_IRQn, 1, 0);
// TIM2 - Lowest priority (2,0)
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0);
6.3 为什么这么定
- USART2 (0,0):关键通信,丢一个字节都不行
- DMA (0,0):高速搬数据,对时间敏感
- ADC1_COMP (1,0):测脉冲,能容忍一点延迟
- TIM2 (2,0):系统计时,优先级放最低
7. 性能指标前后对比
7.1 优化前
- 中断执行时间:每个脉冲 15-25μs
- CPU 占用(1kHz):1.5-2.5%
- CPU 占用(10kHz):15-25%
- UART 响应时间:不稳,经常延迟
- 系统响应:高负载下明显吃力
7.2 优化后
- 中断执行时间:每个脉冲 5μs 以内
- CPU 占用(1kHz):0.5% 以下
- CPU 占用(10kHz):5% 以下
- UART 响应时间:稳定,延迟几乎可以忽略
- 系统响应:高负载下也很顺畅
7.3 改进汇总
- 中断时间缩短:提升 80%
- CPU 占用降低:提升 75-80%
- UART 响应:稳定可靠
- 系统稳定性:明显变好
8. 使用要点
8.1 UART 用 DMA 收发
// Example: Send data using DMA
uint8_t tx_data[128];
// Prepare data...
// Pause pulse processing during critical transmission
pause_pulse_processing();
uart_tx_busy = 1;
// Start DMA transmission
HAL_StatusTypeDef status = HAL_UART_Transmit_DMA(&huart2, tx_data, sizeof(tx_data));
if (status != HAL_OK)
{
// Handle error
uart_tx_busy = 0;
resume_pulse_processing();
}
// Processing will resume automatically when transmission completes
8.2 监视脉冲处理
// Check if pulse processing is enabled
if (pulse_processing_enabled)
{
// Pulse processing is active
}
else
{
// Pulse processing is paused (e.g., during UART transmission)
}
// Check raw pulse buffer status
if (raw_pulse_index > 0)
{
// There are pulses waiting to be processed
}
8.3 调整处理频率
// Modify processing interval as needed
// Current: Process every 10th pulse
#define PULSE_PROCESS_INTERVAL 10
// For more frequent processing (every 5th pulse):
#define PULSE_PROCESS_INTERVAL 5
// For less frequent processing (every 20th pulse):
#define PULSE_PROCESS_INTERVAL 20
9. 测试与验证
9.1 单元测试
- DMA 传输功能是否正常
- UART 8 倍过采样的接收精度
- 脉冲处理暂停/恢复机制
- 数据处理流水线各段连不连得上
- 中断优先级配置对不对
9.2 集成测试
- 高脉冲负载下 UART 通信是否稳定
- DMA 工作时脉冲测量的精度
- 连续运行时的系统稳定性
- 内存占用与内存泄漏检查
9.3 性能测试
- CPU 占用实测
- 中断延迟实测
- UART 吞吐量实测
- 脉冲处理精度核对
9.4 压力测试
- 极限脉冲频率能不能扛住
- UART 连续长时间传输
- 多路同时满载的场景
- 24 小时以上的长期稳定性测试
10. 踩坑排障
10.1 常见的坑
症状一:UART 丢数据
现象是 UART 数据丢帧或错乱。排查思路如下:
- Verify DMA configuration
- Check interrupt priorities
- Ensure buffer sizes are adequate
- Monitor CPU utilization
症状二:脉冲处理延迟
现象是脉冲测不准。排查思路如下:
- Reduce PULSEPROCESSINTERVAL
- Check pulseprocessingenabled flag
- Verify rawpulsebuffer status
- Monitor interrupt execution time
症状三:系统不稳定
现象是莫名其妙复位或闪退。排查思路如下:
- Check stack usage
- Verify interrupt nesting
- Review memory allocation
- Monitor watchdog timers
11. 往后还能往哪走
11.1 短期(1-2周)
- 原始脉冲数据改用环形缓冲
- 加脉冲质量的统计分析
- 做自适应处理频率
- 补异常自恢复机制
11.2 中期(1-2月)
- 上 RTOS,把任务调度理顺
- 加实时监控和日志记录
- 上预测性维护算法
- 加无线通信能力
11.3 长期(3-6月)
- 换更高性能的 MCU
- 用机器学习做异常检测
- 接云端做数据分析和联网
- 把预测性维护做成完整系统
12. 结论
这轮中期优化把系统的性能、可靠性和可维护性都实实在在地提了一档。DMA 传输、脉冲处理优化加标准化流水线这一套,既保住了产线上的稳定运行,也给后续升级留好了地基。
主要成果
- UART 传输改用 DMA
- UART 8 倍过采样配置
- 脉冲处理暂停/恢复机制
- 架构重构(中断挪到主循环)
- 数据处理流水线标准化
- 中断优先级重排
- 性能整体明显提升
下一步
- 做完测试与验证
- 发布到生产环境
- 盯住性能指标
- 收集现场反馈
- 规划下一步增强
这套方案从设计到落地就是上面的完整记录,2026-02-23 当天定稿,代码已经实现完成,眼下正卡在测试验证阶段。
还想接着看
方案咬合着后面的验收流程,接着看这几篇就能把闭环补满:
- 测试与评估计划——本方案的优化目标对应着一套KPI指标,怎么定、怎么判都写在这儿。
- 测试执行指南——方案落完后如何照表验收,操作步骤和预期结果都在里面。
- 优化测试报告——按方案和计划跑完的实测数据与结论,就是那份报告。
如果你们也在给嵌入式采集固件做性能优化,欢迎按页尾方式联系我们聊聊方案怎么定。