4. PWM与ICU模块:PWM Driver配置、ICU模块的输入捕获功能、PWM与ICU联合调试、实际波形测量案例
好,咱们进入第四章。这一章我打算把PWM和ICU放在一起讲。为什么?因为在实际项目中,这俩模块经常是成对出现的。你发一个PWM波出去,总得知道发得准不准吧?ICU就是干这个的——把发出去的波再抓回来,量一量它的脉宽和周期。
我个人习惯把这种组合叫做“闭环验证”。说白了,就是自己发的信号自己测,确保配置没跑偏。嗯,咱们一步步来。
4.1 PWM Driver配置要点
先聊PWM。AUTOSAR里的PWM Driver,其实没那么玄乎。它就是把底层定时器的PWM输出功能封装成标准接口。你配置好了,上层应用直接调用Pwm_SetDutyCycle()就能调占空比。
配置时,我建议你重点关注这几个参数:
- PwmChannel:每个PWM通道对应一个物理引脚。别搞混了,一个定时器的多个通道可能共享频率,但占空比可以独立设置。
- PwmPeriod:周期值。单位是定时器的时钟周期数。比如定时器时钟是10MHz,你想要1kHz的PWM,那周期值就是10000。
- PwmDutycycleDefault:默认占空比。上电后还没调之前,输出什么电平?我一般设成0%,避免设备一启动就乱转。
- PwmPolarity:极性。高电平有效还是低电平有效?这个要看你的驱动电路。我踩过坑,极性设反了,电机直接反转。
- PwmIdleState:空闲状态。PWM停止时,引脚输出什么?通常设成低电平。
核心配置示例(EB tresos Studio中):
PwmConfigSet {
PwmChannel = "PWM_CHANNEL_0";
PwmPeriod = 10000; // 10MHz时钟,1kHz周期
PwmDutycycleDefault = 0; // 默认0%占空比
PwmPolarity = HIGH; // 高电平有效
PwmIdleState = LOW; // 空闲低电平
PwmNotificationEnabled = FALSE; // 中断通知,一般不用
}
这里有个小细节:PwmPeriod的值决定了PWM的频率。如果你需要动态调频,那就得重新配置整个通道。我在项目中遇到过,客户要求电机在启动时用低频,运行后切高频。嗯,这个需求用PWM Driver的标准接口实现起来有点别扭,最后我是在底层直接操作寄存器搞定的。
4.2 ICU模块的输入捕获功能
ICU模块,全称是Input Capture Unit。它的核心能力就是测量外部信号的脉宽和周期。怎么测?靠的是定时器的捕获通道。当信号发生跳变时,硬件自动把当前定时器的计数值锁存到捕获寄存器里。你只需要读这个值,算一下差值,就能得到时间。
配置ICU时,这几个参数你得搞明白:
- IcuChannel:捕获通道。每个通道对应一个物理引脚,通常和PWM通道共用同一个定时器的不同通道。
- IcuMeasurementMode:测量模式。有三种:
EDGE_COUNT(边沿计数)、SIGNAL_MEASUREMENT(信号测量,测脉宽和周期)、PERIOD_MEASUREMENT(周期测量)。我90%的场景用SIGNAL_MEASUREMENT,因为它一次捕获就能同时拿到高电平时间和周期。 - IcuEdgeDetection:边沿检测。上升沿、下降沿、或者双边沿。测量脉宽时,通常用双边沿。
- IcuWakeupCapability:唤醒能力。如果MCU在休眠,信号来了能不能唤醒?低功耗项目里这个很重要。
我的经验:ICU的捕获精度取决于定时器的分辨率。比如定时器是16位的,时钟是10MHz,那最大能测的周期就是6.5536ms。超过这个值,定时器会溢出。怎么办?要么用32位定时器,要么在中断里做软件扩展。我曾经在项目里用16位定时器测20ms的PWM信号,最后靠溢出中断计数搞定了。
4.3 PWM与ICU联合调试
好,现在PWM在发波,ICU在抓波。怎么确保它们配合默契?我总结了一套调试流程,你照着做,基本不会出大问题。
- 先单独验证PWM:用示波器看PWM输出引脚。确认频率、占空比、极性都正确。这一步别省,我见过太多人上来就联调,结果PWM根本没输出。
- 再单独验证ICU:用信号发生器给ICU引脚输入一个已知的方波。比如1kHz、50%占空比。看ICU捕获到的值对不对。如果不对,检查边沿检测配置和定时器时钟。
- 物理连接:把PWM输出引脚和ICU输入引脚用杜邦线连起来。注意,如果PWM输出能力弱,可能需要加个缓冲器。我遇到过信号反射导致捕获值跳变的情况。
- 联调验证:启动PWM,读取ICU捕获结果。对比PWM设置的周期和占空比,看误差在不在可接受范围内。
注意:PWM和ICU如果共用同一个定时器,可能会有资源冲突。比如PWM占用了定时器的通道0和1,ICU想用通道2,但通道2的时钟分频和PWM不一样。这时候你得仔细看芯片手册,确认定时器的各个通道是否独立。我踩过这个坑,最后不得不换了一个定时器给ICU用。
4.4 实际波形测量案例
讲个真实案例吧。之前做一款车灯控制器,需要输出一个1kHz、75%占空比的PWM信号来控制LED亮度。同时,要用ICU测量这个PWM信号,确保实际输出和预期一致。
配置如下:
| 模块 | 参数 | 值 |
|---|---|---|
| PWM | 频率 | 1kHz |
| PWM | 占空比 | 75% |
| PWM | 极性 | 高电平有效 |
| ICU | 测量模式 | SIGNAL_MEASUREMENT |
| ICU | 边沿检测 | 双边沿 |
| ICU | 定时器时钟 | 10MHz |
ICU捕获到的原始数据是这样的:
捕获值1(上升沿到下降沿):7500 → 高电平时间 = 7500 / 10MHz = 750us
捕获值2(下降沿到上升沿):2500 → 低电平时间 = 2500 / 10MHz = 250us
周期 = 7500 + 2500 = 10000 → 10000 / 10MHz = 1ms → 频率1kHz
占空比 = 7500 / 10000 = 75%
你看,和预期完全一致。但实际调试时,我遇到了一个问题:ICU捕获到的周期值偶尔会跳变,从10000变成10001或9999。嗯,这是时钟抖动和信号噪声导致的。解决办法是在软件里做滤波,比如连续采集10次,取平均值。
滤波代码示例:
#define SAMPLE_COUNT 10
uint32_t periodBuffer[SAMPLE_COUNT];
uint32_t dutyBuffer[SAMPLE_COUNT];
uint8_t sampleIndex = 0;
void Icu_MeasurementCallback(uint32_t period, uint32_t duty) {
periodBuffer[sampleIndex] = period;
dutyBuffer[sampleIndex] = duty;
sampleIndex++;
if (sampleIndex >= SAMPLE_COUNT) {
sampleIndex = 0;
// 计算平均值
uint32_t avgPeriod = 0, avgDuty = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
avgPeriod += periodBuffer[i];
avgDuty += dutyBuffer[i];
}
avgPeriod /= SAMPLE_COUNT;
avgDuty /= SAMPLE_COUNT;
// 此时avgPeriod和avgDuty就是滤波后的值
}
}
这个案例告诉我们,理论计算和实际测量之间总有差距。ICU的作用就是帮你量化这个差距,然后决定要不要做补偿。
4.5 本章知识体系
下面这张图,是我画的PWM与ICU联合工作的核心逻辑。你看一眼,就能明白整个流程。
这张图里,PWM和ICU通过物理引脚连接,共享同一个定时器的时钟源。PWM负责输出,ICU负责捕获。捕获到的原始数据经过软件滤波后,就能得到稳定的测量结果。你想想看,这个闭环是不是很清晰?
最后说一句:PWM和ICU的联合调试,说白了就是“发出去,抓回来,算一算”。别把它想得太复杂。只要配置对了,硬件连接没问题,剩下的就是软件滤波和误差处理。我在项目里用这套方法,从来没出过大的偏差。