ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

定时器生成PWM信号:从占空比到寄存器配置详解

定时器生成PWM信号:从占空比到寄存器配置详解

1. 从点亮一颗灯到控制一个世界:PWM到底解决了什么问题

先放下那句老套的“PWM就是脉宽调制”,我们直接看一个实际场景。你想让一颗LED实现呼吸灯效果,从最亮到最暗平滑过渡,而不是简单地开或关。用普通GPIO输出高低电平只能做到两态切换,再怎么延时也做不出那种“渐亮渐暗”的丝滑感。但如果你给GPIO接上一种特殊的开关方式:让它在极短的时间内反复开、关,并且每次开的时间占整个周期的比例不断变化,人眼感知到的平均亮度就会随这个比例线性变化。这个“开的时间占周期的时间比例”就叫占空比,这种开关方式就是PWM。

这里说的“极短的时间”,在大多数单片机场景下是微秒到毫秒级别。比如一个20kHz的PWM,周期是50微秒,每次开关切换都在这个尺度内完成,LED的物理惯性加上人眼的视觉暂留,就把高频开关“平均”成了连续的亮度。所以你真正要做的不是去写延时函数控制电平,而是让硬件自己在那哼哧哼哧地翻转电平,CPU在旁边歇着。这就是定时器的核心价值:把时间基准、计数、比较、自动翻转这些脏活累活全包了,你只需要配置几个寄存器。

PWM的应用远不止呼吸灯。电机调速、舵机角度控制、蜂鸣器音调、DC-DC电源的反馈调节、加热器功率控制、音频信号生成……凡是需要模拟量输出但手头只有数字IO的场合,PWM几乎是标配方案。我见过很多新手一上来就想着用DAC,其实在多数工况下PWM加RC滤波就能凑合出不错的模拟信号,成本还低。所以今天这篇文章就围绕“用定时器生成PWM信号”这个主题,把从原理到寄存器配置,再到实际踩坑的经验一次性讲透。

这篇文章适合谁看?如果你是刚接触单片机,想搞明白定时器除了计时还能干嘛;或者你已经会点灯,但看到数据手册里那一堆TIM、CCR、ARR、PSC就发怵;又或者你正在调电机、舵机,被PWM频率和死区折腾得头疼——那这篇内容对你应该挺有价值。我会尽量用实际例子和真实参数说话,不搞纸上谈兵。

2. 定时器生成PWM的本质:计数、比较、翻转

2.1 定时器到底在“定时”什么

很多人第一次学定时器,以为定时器就是一个会闹钟的计数器,到点触发中断。这么说没错,但只是它功能的一半。定时器更强大的地方在于:它不仅能“到点提醒”,还能“到点自动干活”,不需要CPU介入。生成PWM就是这种“自动干活”的典型场景。

以最常用的16位定时器为例,它内部有一个不断递增的计数器CNT,一个自动重装载寄存器ARR(或者说PER,不同厂商叫法不同),还有若干个比较寄存器CCR。时钟源经过预分频器PSC分频后,给计数器提供计数脉冲。计数器从0开始,每个脉冲加1,计到和ARR相等时,会归零重新开始(向上计数模式),或者向下计数、向上向下计数。这个从0到ARR再回到0的完整过程,就构成了一个时间周期。

那PWM怎么来的?关键在于比较寄存器CCR。计数器CNT在递增过程中,会实时和CCR进行比较。如果CNT小于CCR,输出引脚输出一种电平;如果CNT大于等于CCR,输出另一种电平。也就是说,CCR的值决定了在一个周期内,输出高电平持续的时间。你把CCR改大,高电平时间变长,占空比变大;改小,占空比变小。而后面的计数器翻转、归零、重新计数,全部由硬件自动完成,CPU完全不管。

2.2 占空比和频率的计算逻辑

PWM有两个核心参数:频率和占空比。频率由时钟源、预分频器和ARR共同决定,占空比由CCR和ARR共同决定。拿一个经典公式来说:

PWM频率 = 时钟源频率 / (PSC + 1) / (ARR + 1)

占空比 = CCR / (ARR + 1) (向上计数,且输出极性为高电平有效时)

这里为什么要加1?因为很多定时器的计数是从0开始的。如果PSC为0,ARR为99,那么计数器实际上计了0到99一共100个脉冲,除以100才是正确频率。很多人第一次配置时忘了这个+1,导致频率算出来的实际值差了一倍,这种细节后面实操部分还会再提。

CCR和ARR都是整数,所以占空比的分辨率受ARR的限制。ARR越大,你可以调节的占空比步进就越小。比如ARR为99,那占空比只能以1%步进变化;ARR为999,就能做到0.1%步进。但ARR大了,在相同频率下,计数器的时钟就得更高,这对定时器时钟源的上限有要求。所以实际设计时要权衡:需要什么样的频率分辨率,就选多大的ARR。

2.3 边缘对齐还是中心对齐,这是个问题

大多数入门教程只教你配置PWM模式1或模式2,也就是所谓的边缘对齐模式。在这种模式下,计数器从0递增到ARR然后归零,输出电平在CNT到达CCR时翻转一次。这种模式实现简单,占空比更新及时,适合电机调速、LED调光这类不要求对称波形的场景。

但如果你在做开关电源、逆变器这类需要严格对称PWM的场合,边缘对齐模式就不够看了。这时候得用中心对齐模式:计数器先从0递增到ARR,再从ARR递减到0,形成一个三角波。比较输出既可以在递增时触发,也可以在递减时触发,生成的PWM波形相对于周期中心是对称的。

中心对齐模式能显著减少偶次谐波,对于EMI和电源纹波控制很有帮助。但代价是:同样频率下,计数器的计数次数是边缘对齐的两倍,因此对时钟频率的要求更高;而且更新占空比的时机要小心,如果处理不当,可能在半周期处出现毛刺。对于初学者,我建议先从边缘对齐模式入手,把基础弄扎实了再碰中心对齐。

3. 配置定时器PWM的完整实操:以某款主流MCU为例

3.1 选型:先看定时器资源,再看引脚功能

先说明一下,不同厂商的定时器外设命名和寄存器细节有差异,但核心逻辑完全一致。我以市面上最普及的Cortex-M内核MCU为例,它的定时器模块通常分为基本定时器、通用定时器和高级定时器。基本定时器只能做定时,不能输出PWM;通用定时器才有PWM输出通道;高级定时器在通用定时器基础上多了互补输出、死区插入、刹车输入等,专门用于电机控制、电源转换等场景。

看你手头的应用选型:如果只是调个LED亮度和蜂鸣器,随便一个通用定时器通道就够了;如果要做无刷电机驱动,那就必须用高级定时器,因为你需要互补PWM和死区,防止上下桥臂直通。这里提醒一句,别把任意GPIO都当成PWM输出脚用,定时器的输出通道是固定的,要看数据手册里的复用功能映射。很多新手把PWM配置在了普通GPIO上,折腾半天波形出不来,其实只是没打开复用功能。

3.2 寄存器级别的配置步骤,手把手走一遍

下面我以通用定时器某个通道输出PWM为例,完整走一遍寄存器配置流程。假设系统时钟为72MHz,目标是输出50Hz的舵机控制信号,占空比可在2.5%到12.5%之间调节(对应0.5ms到2.5ms的脉宽)。

第一步,配置时钟。使能定时器时钟和对应GPIO端口时钟。然后再把GPIO引脚复用为定时器输出功能。这里两个时钟都要打开,漏一个都不行——别问我为什么知道,我当年漏过GPIO时钟,PWM死活不出来,查了半天才发现引脚根本没通电。

第二步,配置GPIO为复用推挽输出,速度设为高速。对于PWM这类高频翻转信号,GPIO的翻转速率必须足够快,否则波形边沿会变缓,影响精度。一般设置成50MHz或更高比较稳妥。

第三步,计算并写入PSC和ARR。目标频率50Hz,那么PSC和ARR需要满足:

72MHz / (PSC+1) / (ARR+1) = 50Hz

为了让舵机控制有足够的脉宽分辨率,我们希望ARR选得大一些。取ARR+1为20000,即ARR=19999,那么PSC+1 = 72MHz / 50 / 20000 = 72,所以PSC=71。这样在50Hz频率下,计数器计数周期是20000,每个计数对应的时间是1秒 / 50 / 20000 = 0.001秒 = 1微秒。CCR的值就直接等于脉宽微秒数,方便到离谱。舵机中位1.5ms对应CCR=1500,范围0.5ms到2.5ms对应CCR=500到2500。

第四步,配置定时器的PWM模式。这里选择向上计数,PWM模式1,输出极性为高电平有效。然后设置CCR的初始值,比如先设1500。

第五步,使能定时器的PWM输出。这步注意,不是使能定时器就完了,有些定时器的输出还需要单独使能主输出,尤其是高级定时器,有个MOE主输出使能位,忘了置1,前面配置全白搭。通用定时器没有这一步,但高级定时器必须做。

第六步,使能定时器。计数器开始跑,PWM就出来了。

3.3 用代码实现动态改变占空比

静态输出一个固定占空比的PWM意义不大,实际项目里肯定要动态调节。比如通过ADC读取电位器电压,然后映射到舵机角度。这里的核心操作就是更新CCR寄存器。

有个细节需要注意:在边缘对齐模式下,如果ARR不变,直接往CCR里写新值,新的占空比会在下一个计数周期开始生效。这是硬件行为的正常表现,不等于写入失败。但在中心对齐模式下,如果写入时机不对,可能导致波形不对称,所以某些高端MCU提供了预装载功能,就是先把新值写入影子寄存器,等到更新事件发生时才真正复制到工作寄存器。如果你不想在控制过程中产生毛刺,建议把预装载使能打开。

代码层面,动态更新占空比通常有两种写法。一种是直接写CCR寄存器,比如TIMx->CCR1 = new_value;,简单粗暴,适合绝大多数场景。另一种是通过定时器的DMA功能,把一段预设的占空比变化表通过DMA自动搬移到CCR寄存器,实现无CPU干预的平滑渐变。比如做呼吸灯,你可以用DMA把正弦表按一定速率写入CCR,CPU全程休眠,效果还特别顺滑。这个方法我在实际项目里用过,省心又高效。

下面给个简单的代码示例,展示初始化函数和更新占空比函数。

void pwm_init(void) { // 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // GPIO复用为TIM3_CH1,对应PA6 GPIO_InitTypeDef gpio = {0}; gpio.GPIO_Pin = GPIO_Pin_6; gpio.GPIO_Mode = GPIO_Mode_AF; gpio.GPIO_Speed = GPIO_Speed_50MHz; gpio.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOA, &gpio); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3); // 定时器时基配置 TIM_TimeBaseInitTypeDef tb = {0}; tb.TIM_Prescaler = 71; // 72MHz / 72 = 1MHz tb.TIM_CounterMode = TIM_CounterMode_Up; tb.TIM_Period = 19999; // 1MHz / 20000 = 50Hz tb.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &tb); // PWM通道配置 TIM_OCInitTypeDef oc = {0}; oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_Pulse = 1500; // 初始1.5ms脉宽 oc.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &oc); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); } void pwm_set_pulse_us(uint16_t pulse_us) { if (pulse_us < 500) pulse_us = 500; if (pulse_us > 2500) pulse_us = 2500; TIM_SetCompare1(TIM3, pulse_us); }

这段代码里的TIM_SetCompare1最终也就是写入CCR1寄存器,库函数帮你做了一层封装。实际工程中,用寄存器直写和用库函数都可以,但从可维护性角度,用库函数封装好一点,方便以后换芯片。

3.4 用逻辑分析仪看波形,验证参数是否和预期一致

配置完之后,别急着高兴,拿逻辑分析仪或者示波器看一下实际波形。这一步在大多数入门教程里都被忽略了,但恰恰是最重要的一步——寄存器配置对了没有,波形会直接告诉你答案。

把探针接到PWM输出引脚上,查看周期和脉宽。以我上面这个例子,周期应该是20ms,脉宽初始应该是1.5ms。如果测出来周期不对,先检查预分频和重装载值的算数有没有出错,别忘+1。如果测出来完全没有波形,先检查GPIO复用配置,再检查定时器主输出使能。

用逻辑分析仪还有一个好处:可以观察占空比动态更新时波形是否有毛刺。把占空比设置成一直在变,观察边沿位置是否抖动。如果抖动明显,多半是CCR写入与计数器运行不同步导致的,这时打开预装载功能大多能解决。

4. 频率、分辨率、谐波:PWM参数设计中的权衡艺术

4.1 如何选择PWM频率,从负载特性反推

很多刚接触PWM的人会问:频率到底设多少合适?这个没有标准答案,完全由负载决定。LED调光,频率必须高于人眼闪烁感知阈值,一般在200Hz以上就看不到闪了,但更推荐1kHz以上,一则更稳,二则也能避免和摄像头的采样频率产生差拍,不然拍视频时会出现滚动条纹,那叫一个痛苦。电机驱动则不同,频率低了电机会发出明显的啸叫,而且电流纹波大,力矩波动明显;频率高了,MOS管的开关损耗会显著上升,驱动芯片发热。一般直流有刷电机选10kHz到20kHz比较合适,既超出人耳听觉范围,又不会让开关损耗过于夸张。舵机控制则比较特殊,它看的是脉宽而不是占空比频率,所以接收标准PWM信号一般就是50Hz,脉宽从0.5ms到2.5ms。你把频率升高到200Hz,舵机大概率不转,因为它内部电路宽度判断的是高电平持续时间和周期之间的比例关系。

对于开关电源这类应用,PWM频率通常由电感尺寸、开关管耐压、控制环路带宽共同决定,常见在100kHz到1MHz之间。这时边缘对齐模式已经不太够用,中心对齐、死区控制、逐周期限流这些高级功能就得上场了。

4.2 占空比分辨率:想精细还是想快速

占空比分辨率直接取决于ARR。ARR越大,可表示的占空比级数越多。比如ARR为255,占空比调节步进是1/256,大约0.39%。对于LED调光,这个级别大概够用,但呼吸灯如果想特别平滑,0.39%的步进在大约10%以内的低亮度区间仍然能看出阶跃感。ARR为1023时,步进约0.098%,肉眼基本就分辨不出来了。

但ARR变大,代价有两个:一是计数器要数更多次才完成一个周期,如果时钟频率固定,你最大能输出的PWM频率就降低了;二是更新占空比时,如果使用DMA或中断,每次更新的数据量变大,但实时性要求不一定跟得上。所以在设计阶段,先明确你能忍受多大的占空比步进,再反推最小ARR,然后计算在目标频率下需要的时钟频率,最后对照芯片定时器的时钟上限,看是否可行。如果时钟不够,就得在分辨率和频率之间做取舍。

4.3 死区时间:高级定时器才能给你的安全感

无刷电机、H桥、半桥驱动这类拓扑,最怕的事情就是上下管同时导通,那基本上就是短路,轻则炸MOS管,重则烧板子。防止同时导通的关键在于:当一个管子关断后,必须等一段安全时间再打开另一个管子,这段等待时间就是死区时间。

高级定时器里有个专门机制,叫死区发生器。它能自动在一对互补PWM之间插入可配置的时间间隔。比如你配置死区为1微秒,那么上管关断后,下管要等1微秒才导通;反过来也一样。这样一来,不管你怎么修改占空比,硬件都会自动保证两个开关管之间有一段互锁时间,软件层面完全不用操心。

死区时间设多少,取决于功率管的关断延迟和电荷抽取时间。太短不安全,太长会引入额外的功率损耗并降低输出波形质量。在实际调试中,可以通过示波器观察互补PWM的交叉点,确保在开关过程中没有重叠,同时尽量把死区设到满足安全的最小值。我用过一个模块,死区设置小了,上电瞬间电流就飙到保护值;死区调大一倍,就稳定了。这个参数确实得靠现场试,不同批次管子特性还有差异。

5. 让PWM更聪明:DMA、中断与多通道协同

5.1 用DMA搬运数据,CPU零负担

如果只是每秒更新几次占空比,直接写寄存器就够了。但在某些实时性要求高的场合,比如音频播放、三相电机控制,占空比可能需要以几十kHz的频率刷新,这时候如果用CPU在中断里频繁写CCR,你会浪费大量时间,而且中断响应延迟还会导致波形抖动。

解决方案是用DMA。你事先在内存里放一张占空比变化表,然后配置DMA把这张表按指定速率自动搬到CCR寄存器。DMA搬运一次数据,CCR更新一次,完全不需要CPU参与。配合定时器的更新事件触发DMA传输,可以实现非常精确时序的PWM序列。

我实际做过一个简易音频播放器,用16kHz的采样率生成语音。就是把语音采样数据做映射,通过DMA输出到定时器的CCR,生成对应脉宽的PWM,再经过低通滤波器还原声音。整段播放过程中CPU几乎零占用,还能同时跑显示和按键扫描。DMA就是这么好用。

DMA配置时要注意两个细节:一是数据宽度要和寄存器匹配,CCR是16位,你定义的数据表也应该是16位数组;二是DMA传输模式选循环模式还是正常模式,循环模式适合播放循环音频,正常模式适合播放一次就停。

5.2 定时器中断配合PWM实现精确动作序列

PWM不能解决所有问题,有些场景需要精确控制“什么时候开始改变PWM”。比如你要做一个步进电机的S形加速曲线,每一阶段对应不同的脉冲频率和占空比,那就需要定时器中断来切换参数。

常见做法是:定时器每隔一段固定时间产生更新中断,在中断服务函数里根据当前所处的阶段,修改ARR和CCR的值。这里有个要注意的坑:在中断里修改ARR时,如果预装载没有打开,新的ARR值会在当前计数周期立即生效,可能导致周期突变,波形异常。所以最好把ARR的预装载也打开,让改动在下一次更新事件时统一生效。

另一个经验:中断服务函数里不要做耗时的计算,应该提前把每个阶段的参数算好放进数组,中断里只做查表和赋值。我见过有人在中断里跑浮点运算,直接把中断周期拖到溢出,导致整个控制链路失稳。做嵌入式,永远不要把中断当普通函数用。

5.3 多通道PWM同步输出,避免相位错乱

在很多电机控制场景中,你需要同时输出三路或者六路PWM,而且通道之间必须有严格的相位关系。比如三相逆变器,三个桥臂的PWM相差120度电角度,如果相位不对,电机转矩会剧烈波动。

同一定时器的多个通道天然是同步的,因为它们共享同一个计数器。比如TIM1的CH1、CH2、CH3,它们的CNT是同一个,只要分别设置不同的CCR值,输出波形之间的相位偏移就是确定的,不会出现累计误差。不同定时器之间的通道同步就要麻烦一些,需要配置从模式,把一个定时器作为另一个的主模式,通过触发信号实现同步启动。如果不想搞得那么复杂,建议优先把多路PWM放在同一个定时器上,不足再考虑定时器同步。

多通道还有一个坑:当你同时更改多个通道的CCR时,如果预装载没开,每个通道的写入生效时间可能不同,导致在某一瞬间出现错误的占空比组合。这在电机控制中是致命问题——上下桥臂的组合状态瞬间错乱,可能直接短路。所以务必打开预装载,让所有通道的更新在同一时刻生效。

6. 常见问题排查与避坑指南

6.1 PWM完全没有输出

先别怀疑芯片坏了,按照下面这个顺序排查最有效率:

  1. 引脚有没有复用对。查阅数据手册,确认你选的定时器通道和引脚映射关系,有些芯片引脚有多个复用功能,要用正确的AF配置。

  2. GPIO有没有被其他外设占用,比如JTAG、调试接口。某些芯片的PA13到PA15和PB3、PB4是调试口,默认不能当普通IO用,需要关闭调试功能才释放出来。我把PA15复用成PWM输出时踩过这个坑,配置了半天没反应,最后发现是JTAG占着。

  3. 定时器时钟是否使能。可能听上去低级,但新手或换平台时很容易漏掉RCC时钟配置。不止定时器时钟,GPIO时钟也一样,漏一个都不工作。

  4. 高级定时器的主输出是否使能。前面提过,高级定时器有一个控制主输出的位,默认是关闭的。如果你用的是通用定时器没这个问题,但换到高级定时器必须置1。

  5. 检查计数器有没有在走。通过调试器读CNT寄存器,看它是否递增。如果CNT都没动,那就是时基配置有问题,重点查PSC和ARR有没有写进去。

6.2 占空比有变化,但频率不对

频率不对,先检查ARR+1和PSC+1的计算。一个极容易犯的错:把ARR设成19999,PSC设成71,以为和上面例子一样,但系统时钟不是72MHz而是8MHz,结果实际频率就完全不是50Hz。这种情况下,如果手头有示波器,一测就能发现。如果没有示波器,可以用另一个定时器测量输入信号的频率,或者用计数器和定时器估算,也能算出来。

还有一个可能:你在运行时修改了ARR,但同时又修改了PSC,两个修改的生效时间不一致,导致计数器一段时间内跑得乱七八糟,最终输出的平均频率和期望值有偏差。修改PSC对计数器运作的影响比修改ARR更复杂,某些芯片甚至要求先停止计数器再修改PSC。所以如果你在动态调频,优先改ARR,不要轻易动PSC。

6.3 占空比调不到0%或100%

很多人把CCR设成0,以为输出的就是全低电平,但实际可能还有很窄的高电平脉冲;把CCR设成ARR+1,以为全高,但实际可能还有下拉的毛刺。这是因为PWM输出在计数比较过程中,比较器在CNT等于CCR时翻转,如果CCR为0,在CNT从0开始递增时,第一个瞬间就会产生比较匹配,输出状态会经历微小的逻辑转换,具体结果取决于输出极性和计数模式。

如果你需要严格的0%或100%占空比,也就是输出恒低或恒高,不要试图靠PWM通道自身实现,正确的做法是通过刹车输入或强制输出功能。很多定时器支持强制输出高或低电平,比如调用库函数里TIM_ForcedOC1Config这类接口,让通道强制输出某个电平。这样比靠CCR边缘情况可靠得多。

另外,电机控制在占空比极值处,上下桥臂的切换逻辑容易出现竞争风险。即便钳位到0%,高压侧管子关断,低压侧管子也不能长时间处于线性放大区。这时候通常要配合同步整流控制和死区保护,才能保证可靠。

6.4 输出波形有毛刺或抖动

毛刺的来源很多,最常见的是CCR写入时机和计数器运行不同步。解决方式是打开预装载,让新值在下一次更新事件时才生效。

另一个来源是电源噪声。PWM引脚高速翻转时,如果板子地平面处理不好,电流突变会在电源轨上产生尖峰,这些尖峰耦合到参考电压或IO口,会导致比较器翻转抖动。此时可以降低GPIO翻转速度,或者在引脚附近加一个小电容滤波,但要注意电容过大会让波形边沿变缓,影响时序,所以通常加几十皮法级别的电容就够了。

如果抖动发生在动态更新占空比时,而且你在用DMA传输,那检查DMA的优先级和总线仲裁。如果DMA被其他外设抢占过久,CCR的更新间隔就不均匀,导致占空比抖动。可以适当提高DMA优先级,或者把更新表的长度缩短,减少单次传输时间。

6.5 中断频繁触发导致主程序卡死

有些应用需要定时器中断来刷新占空比,但如果刷新的频率过高,比如每50微秒触发一次中断,而中断服务函数里的处理时间超过50微秒,那就会造成中断嵌套或丢失,主程序完全被饿死。

解决思路有几种:一是降低PWM刷新频率,看是否满足系统响应需求;二是把中断中的处理尽可能精简,只做必要的寄存器赋值,算法移到主循环或低优先级中断中处理;三是直接换成DMA搬运,彻底绕开中断。我个人倾向于第三种,能不用中断就不用中断。做运动控制、电源控制这种实时系统时,中断越多越容易出事,DMA才是真正的朋友。

6.6 一个容易被忽略的问题:引脚带负载能力

PWM输出引脚的驱动能力有限,直接驱动LED或小蜂鸣器没问题,但直接驱动大功率MOS管或者电机就不够看了。很多新手把PWM引脚直接接到MOS栅极,结果MOS管半开半闭,发热严重,波形也乱七八糟。正确做法是PWM先经过栅极驱动器,再驱动功率管。栅极驱动器的选型要考虑峰值电流、传播延迟和死区兼容性。高频PWM下,栅极驱动器的传输延迟会造成占空比失真,这时需要用死区补偿或者校准手段。

我自己做过一个直流电机调速器,一开始直接PWM接MOS,波形在慢速时还能看,频率一上10kHz,MOS直接烫手,后来加了栅极驱动芯片,并且串了一个几十欧姆的栅极电阻,波形和温升才正常。这类问题不是定时器配置能解决的,但确实是PWM应用里最常见归类错误。

7. 想到哪说到哪:一点个人经验

最后分享几个我自己在实际项目中养成的习惯,不算系统性的教程,但用顺了之后确实能少走不少弯路。

第一,配置任何外设之前,先花十分钟把数据手册里相关章节的寄存器框图和时序图画出来,别急着敲代码。定时器这种外设,寄存器之间耦合特别多,只看库函数不看手册,非常容易写出一堆“看起来对但实际错”的配置。第二,所有关键的定时参数,比如ARR、PSC、CCR的计算,不要手算,写一个小工具函数或者直接用公式在注释里标注,方便后面调参。第三,尽量在项目早期就把逻辑分析仪接上,从第一个PWM波形开始就验证,别攒到最后一起调试,不然出错根本不知道是哪一步引入的。

我记得第一次调三相电机驱动时,死区时间设得太小,一上电电流直接冲顶,保护电路反复触发,当时我以为是程序逻辑问题,折腾了两天,后来用示波器一看,互补PWM在切换时上下管有明显重叠导通,才意识到是死区问题。改成硬件死区之后,一切安静了。从那以后,我养成了“先看波形,再改代码”的调试顺序,这句话也送给你。

如果你正打算用定时器做PWM,别被那一堆寄存器吓到。核心就三件事:时基定了频率,比较定了宽度,极性定了电平。把这三件事想清楚,再复杂的PWM应用也只是在这个基础上叠加各种触发、DMA和同步机制而已。多动手焊板子、接示波器,踩几次坑,自然就熟练了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进