第一次拿到蓝桥杯嵌入式赛题的PWM控制需求时,我盯着"自动/手动双模式切换"这个要求发了半小时呆。最让我纠结的不是技术实现,而是那个看似简单的初始状态设定:上电默认自动模式,但占空比显示固定10%。这就像让你用自动挡开车,却要求转速表永远显示2000转——实际转速明明在变,仪表盘却要装傻。
这种设计矛盾在嵌入式开发中太常见了。我的第一版程序就栽在这个坑里:自动模式下用ADC读取电位器电压实时计算占空比,切换到手动模式时,这个动态值直接作为基准进行±10%调节。结果用户旋转过电位器后,手动调节的占空比变成了6%→16%→26%...完全偏离题目要求的10%步进。深夜调试时,示波器上跳变的PWM波形就像在嘲笑我的设计漏洞。
关键突破点出现在重新理解"模式隔离"概念时。正确的架构应该像双车道高速公路:
c复制float actual_duty; // 实际输出占空比
const float display_duty = 10.0; // 固定显示值
STM32G431的定时器资源比F系列丰富不少,但PWM配置依然有玄机。题目要求两路不同频率(100Hz/200Hz)的PWM,我最初想用TIM3的两个通道走捷径,结果发现同一个定时器的不同通道只能共享ARR值——这意味着频率必须相同。凌晨三点查参考手册时,终于在第987页找到关键说明:
同一TIMx实例的所有通道共用预分频器和自动重载寄存器
硬件配置的黄金法则由此诞生:
具体到代码层面,CubeMX配置后要重点检查这三个参数:
c复制htim16.Instance->PSC = 169; // 100Hz: 170MHz/(169+1)/(9999+1)
htim17.Instance->PSC = 84; // 200Hz: 170MHz/(84+1)/(9999+1)
htim16.Instance->ARR = 9999; // 共同ARR值保证10%精度
ADC处理函数里那个看似多余的if((V >= -0.000001) && (V <= 0.000006)),是用血的教训换来的。最初我直接写if(V == 0),调试时发现电位器归零后PWM输出偶尔还会有杂波。用逻辑分析仪抓取发现,某些情况下ADC转换后的浮点数实际是0.000004之类的小数。
嵌入式浮点运算三大铁律:
后来我专门写了浮点比较宏定义,放在头文件里复用:
c复制#define FLOAT_EQ(a,b) (fabs((a)-(b)) < 1e-6)
#define FLOAT_GT(a,b) ((a)-(b) > 1e-6)
#define FLOAT_LT(a,b) ((b)-(a) > 1e-6)
模式切换时的参数跳变问题,本质是状态管理混乱。我把整个系统抽象成有限状态机(FSM),用枚举类型明确定义所有状态:
c复制typedef enum {
AUTO_DATA_VIEW = 0,
AUTO_PARA_VIEW,
MANUAL_DATA_VIEW,
MANUAL_PARA_VIEW
} SystemState;
每个状态对应的处理逻辑用switch-case组织,特别要注意状态转移时的清理工作。比如从自动模式切到手动模式时,必须执行:
c复制case AUTO_TO_MANUAL:
__HAL_TIM_SetCompare(&htim16, TIM_CHANNEL_1, display_duty);
HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET);
break;
在Systick中断里放按键检测是个危险操作,我遇到过因为处理函数执行时间过长导致ADC采样失准的情况。后来改用分层定时策略:
用逻辑分析仪抓取的定时序列显示,这种设计能确保即使某个任务卡住,也不会影响其他功能的时序。关键是要合理设置任务超时保护:
c复制if(HAL_GetTick() - last_adc_time > 150) {
// 触发看门狗或系统复位
}
赛后复盘时,我尝试用面向对象思想重构代码。把PWM控制器抽象成结构体:
c复制typedef struct {
TIM_HandleTypeDef *htim;
float duty;
float freq;
uint8_t mode;
} PWM_Controller;
void PWM_SetDuty(PWM_Controller *dev, float duty) {
if(dev->mode == AUTO_MODE) {
dev->duty = duty;
} else {
dev->duty = round(duty/10)*10; // 手动模式强制10%步进
}
__HAL_TIM_SetCompare(dev->htim, TIM_CHANNEL_1, dev->duty);
}
这种封装让模式切换的逻辑更清晰,也方便后期扩展多路PWM控制。其实嵌入式开发不是非要写晦涩难懂的底层代码,良好的抽象同样重要。