PWM基础与信号特性
做霍尔传感器调试,PWM是绕不开的核心。说白了,霍尔传感器输出的就是PWM信号——占空比代表磁场强度,频率代表转速或者位置信息。这一章我把PWM的基础掰开揉碎了讲,全是实战中能用上的东西。
PWM占空比与频率
PWM信号就两个关键参数:频率和占空比。频率决定信号快慢,占空比决定有效电平占比。
占空比 = 高电平时间 / 整个周期时间 × 100%
频率 = 1 / 周期时间
举个例子。一个20kHz的PWM信号,周期就是50微秒。如果高电平占了25微秒,占空比就是50%。我调试霍尔传感器时,经常遇到占空比跳变的情况——磁场稍微一动,占空比就跟着变。这时候用万用表测不准,必须上示波器。
| 参数 | 典型值范围 | 霍尔传感器常见值 |
|---|---|---|
| 频率 | 1 Hz ~ 1 MHz | 1 kHz ~ 100 kHz |
| 占空比 | 0% ~ 100% | 10% ~ 90% |
| 高电平电压 | 3.3V / 5V | 3.3V(常用) |
注意:霍尔传感器输出的PWM占空比通常不会到0%或100%,因为传感器内部有死区保护。我曾经遇到过客户说占空比只有2%,以为是传感器坏了,结果一查是磁场太弱,传感器进入了最小脉宽保护模式。
PWM生成方式
生成PWM信号有两种主流方式:硬件定时器和软件模拟。两种我都用过,各有各的坑。
硬件定时器生成
这是最推荐的方式。MCU内部有专门的定时器模块,配置好预分频、自动重装载值和比较值,PWM就自动输出了,CPU不用管。
// 以STM32为例,配置TIM2输出PWM
// 预分频器:72MHz / 72 = 1MHz
// 自动重装载:50 → 20kHz
// 比较值:25 → 50%占空比
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseStructure.TIM_Period = 50 - 1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_Pulse = 25;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
硬件定时器的好处是精准、不占CPU。我做过一个项目,同时输出4路不同频率的PWM,CPU负载几乎为零。缺点就是引脚固定,不能随便换。
软件模拟生成
有时候硬件定时器不够用,或者引脚被占用了,就得用软件模拟。原理很简单:用定时器中断,在中断里翻转IO口。
// 软件模拟PWM,50%占空比,1kHz
// 定时器中断每500微秒触发一次
volatile uint16_t pwm_count = 0;
const uint16_t pwm_period = 1000; // 1ms周期
const uint16_t pwm_duty = 500; // 500us高电平
void TIM_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update)) {
pwm_count++;
if (pwm_count == 1) {
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 拉高
} else if (pwm_count == pwm_duty) {
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 拉低
} else if (pwm_count >= pwm_period) {
pwm_count = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
我的经验:软件模拟PWM只适合低频场景,超过10kHz就别想了。中断响应时间、GPIO翻转速度都会引入抖动。我试过用软件模拟50kHz的PWM,结果波形抖得像心电图,根本没法用。
PWM信号测量方法
拿到霍尔传感器的PWM输出,怎么测?我常用的方法有三种。
- 示波器直接测量:最直观,能看到波形全貌。后面会细讲。
- 频率计/逻辑分析仪:适合批量测试,只看频率和占空比数值。
- MCU输入捕获:嵌入式系统里最常用的方法,用定时器捕获上升沿和下降沿。
输入捕获的原理很简单:配置定时器捕获通道,检测到上升沿时记录计数值,检测到下降沿时再记录一次。两个计数值一减,高电平时间就出来了。
// 输入捕获测量PWM占空比和频率
// 假设定时器时钟1MHz,每个计数1微秒
volatile uint32_t rise_time = 0;
volatile uint32_t fall_time = 0;
volatile uint32_t period = 0;
volatile uint8_t capture_done = 0;
void TIM_IRQHandler(void) {
if (TIM_GetITStatus(TIM4, TIM_IT_CC1)) {
uint32_t capture = TIM_GetCapture1(TIM4);
if (TIM_GetFlagStatus(TIM4, TIM_FLAG_CC1OF)) {
// 溢出处理,这里简化了
TIM_ClearFlag(TIM4, TIM_FLAG_CC1OF);
}
if (TIM_GetCapture1(TIM4) & 0x01) { // 假设用CC1S位判断边沿
rise_time = capture;
} else {
fall_time = capture;
period = rise_time - fall_time; // 实际需要处理溢出
capture_done = 1;
}
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
}
}
// 主循环中读取
if (capture_done) {
uint32_t high_time = fall_time - rise_time;
uint32_t duty = (high_time * 100) / period;
// duty就是占空比百分比
capture_done = 0;
}
我曾经踩过的坑:输入捕获一定要处理定时器溢出!如果PWM频率很低,周期比定时器溢出周期还长,捕获到的计数值会乱套。我有个项目测10Hz的PWM,忘了处理溢出,结果占空比一会儿30%一会儿80%,查了两天才发现是溢出没处理。
示波器使用技巧
示波器是调试PWM的利器。我见过太多工程师拿着示波器乱按,波形都看不清楚就开始分析。这里分享几个实用技巧。
自动设置 vs 手动设置
很多人上来就按Auto Set,图省事。但Auto Set有时候会误判,尤其是信号不稳定的时候。我建议先手动设置:
- 时基:先设到1ms/div,看到波形后再调整。频率高就缩小,频率低就放大。
- 电压档位:先设到2V/div,保证波形在屏幕中间。
- 触发模式:用上升沿触发,触发电平设在信号幅值的一半左右。
测量占空比的正确姿势
示波器都有自动测量功能,直接调出Duty Cycle测量项就行。但要注意:
- 确保示波器捕捉到了完整的周期。如果时基太大,波形压缩在一起,测量结果不准。
- 打开平均模式(Average),可以滤掉噪声。我一般设16次或32次平均。
- 如果占空比跳变很快,用余辉模式(Persistence)能看到变化范围。
小技巧:测量高精度占空比时,把时基放大到只显示1~2个周期。这样示波器的采样点都集中在这几个周期上,测量精度更高。我调试一个要求占空比精度0.1%的传感器时,就是用这个方法测出来的。
常见波形问题排查
| 波形现象 | 可能原因 | 解决方法 |
|---|---|---|
| 上升沿/下降沿有毛刺 | 信号反射、地线过长 | 缩短地线,加100pF电容滤波 |
| 占空比抖动 | 电源噪声、传感器振动 | 检查电源纹波,加固传感器安装 |
| 频率不稳定 | 时钟源不准、温度影响 | 用外部晶振,检查温度补偿 |
| 幅值偏低 | 输出驱动能力不足 | 加缓冲器或上拉电阻 |
探头的正确使用
探头是示波器的一部分,用不对全白搭。我见过有人用×10档测3.3V信号,结果波形只有330mV,还以为传感器坏了。
- 测3.3V/5V的PWM,用×1档,带宽够用。
- 测高频PWM(>10MHz),用×10档,减少负载效应。
- 地线夹子越短越好。长地线会引入噪声,我习惯用地弹簧。
- 测量前先做探头补偿:把探头接到示波器自带的方波输出上,调补偿电容直到波形方正。
一句话总结:PWM调试,示波器是眼睛,输入捕获是手,硬件定时器是心脏。这三样玩转了,霍尔传感器的PWM输出调试就成功了一大半。