深入解析PWM模块:从信号生成到同步与故障处理
深入解析PWM模块:从信号生成到同步与故障处理
如果你正在用微控制器驱动电机、调光LED或者做开关电源,那你肯定绕不开PWM(脉冲宽度调制)。这东西听起来高大上,说白了就是用数字信号的高低电平时间比例,来模拟一个连续变化的电压或电流。但真要把PWM用稳、用精,尤其是在多通道、高可靠性的场合,光知道占空比公式可远远不够。你得摸透硬件模块里计数器怎么跑、比较器怎么匹配、信号怎么同步,还得知道万一出事了怎么安全“刹车”。我这些年折腾过不少MCU的PWM模块,从简单的8位机到复杂的ARM Cortex-M系列,发现很多坑其实都藏在数据手册的细节里。今天,我就以TI的Tiva™系列微控制器(比如TM4C129x)的PWM模块为例,把它从信号生成到同步、再到故障处理这条链路上的核心机制和实操要点,掰开揉碎了讲清楚。无论你是刚开始接触电机控制,还是想优化现有的电源设计,这里面的门道都值得你花时间琢磨。
1. PWM模块整体架构与核心设计思路
Tiva™微控制器的PWM模块远不止一个简单的定时器加比较器。它是一个高度集成、功能可配置的子系统,专为复杂的实时控制应用设计,比如三相无刷直流电机(BLDC)的六步换相、精密数字电源的多相交错并联等。理解其整体架构,是避免“盲人摸象”、正确配置和发挥其性能的前提。
1.1 模块化设计:从生成到输出的流水线
这个PWM模块的设计非常模块化,信号像流水线一样经过多个处理阶段,每个阶段你都可以进行精细调控。整个流程可以概括为以下几个核心环节:
- 时间基准与计数器:这是PWM的“心脏”。每个PWM发生器(Generator)都有自己的一个计数器,它按照设定的时钟源(通常是系统时钟分频后)进行计数。计数模式决定了PWM信号的“对齐”方式,是边沿对齐(Count-Down或Count-Up模式)还是中心对齐(Count-Up/Down模式),这直接影响了谐波特性,在电机控制和电源应用中至关重要。
- 信号发生器:这是PWM的“大脑”。计数器在运行过程中会产生一系列“事件”,比如计数值到达装载值(LOAD)、归零(ZERO)、与比较器A匹配(CMPA)、与比较器B匹配(CMPB)等。信号发生器(PWM Generator)的核心工作,就是根据你预先设定的“动作规则”(在
PWMnGENA和PWMnGENB寄存器中配置),在这些事件发生时,决定对应的PWM输出引脚(pwmA和pwmB)应该做什么:保持原样、翻转、拉高还是拉低。正是通过编排这些事件和动作,我们才生成了特定占空比和相位的PWM波形。 - 死区发生器:这是功率电路的“保险丝”。当我们用一对互补的PWM信号(比如
pwmA和pwmB)去驱动一个半桥或全桥的上下两个开关管(如MOSFET)时,最怕的就是两个管子同时导通,造成电源直通短路,瞬间烧毁器件。死区发生器的作用,就是在原pwmA信号的基础上,生成两路新信号pwmA‘和pwmB‘。pwmA‘是原信号上升沿延迟后的结果,pwmB‘是原信号反相后、其上升沿(对应原信号下降沿)再延迟的结果。这样,在两路信号状态切换的瞬间,会插入一段两者都为低电平的“死区时间”,确保一个管子完全关断后,另一个管子才开启。 - 输出控制块:这是最后的“总闸门”和“化妆师”。它负责管理哪些PWM信号最终能输出到芯片引脚(
MnPWMn)。你可以通过PWMENABLE寄存器一键启用或禁用所有输出,这在电机换相时特别有用。此外,它还集成了故障安全处理(发生故障时强制输出安全电平)和最终输出极性反转(PWMINVERT)的功能。这里有个极易忽略的细节:输出反相是在故障安全值强制输出之后进行的。也就是说,如果你设置了故障时输出高电平(PWMFAULTVAL对应位为1),同时又使能了该通道的反相(PWMINVERT对应位为1),那么实际引脚输出将是低电平。这个顺序务必理清。
这种流水线设计的好处是显而易见的:你可以独立配置波形生成逻辑、死区保护参数和最终输出使能/极性,互不干扰,灵活性极高。
1.2 为什么需要同步与故障处理?
在单路PWM控制一个LED亮度的简单场景下,可能不需要考虑同步和复杂故障。但在工业级应用中,这两点是保证系统稳定和安全的生命线。
- 同步的必要性:想象一下控制一个三相逆变器驱动电机。你需要6路PWM信号(三个半桥,每个桥臂上下管各一路)。这6路信号必须有严格、一致的时序关系,比如中心对齐、相位互差120度。如果每个PWM发生器的计数器各自为政,哪怕初始值设得一样,由于启动时间的微小差异或时钟漂移,它们的相位关系也会逐渐错乱,导致电机震动、噪音甚至失控。PWM模块提供的计数器同步(
PWMSYNC)和寄存器更新同步(全局/局部同步)机制,就是为了让多个发生器能像一支训练有素的军队,统一步调,协同工作。 - 故障处理的必要性:在电机驱动或大功率电源中,过流、过温、母线电压异常等情况随时可能发生。一旦检测到这类故障,系统必须在微秒级甚至更短的时间内做出反应,将PWM输出强制拉到一个安全的电平(通常是全关断),保护功率器件和负载。PWM模块的故障处理单元,允许你将外部故障引脚(
MnFAULTn)、ADC数字比较器触发甚至调试器暂停事件,配置为故障源,并自动接管输出,这比用软件中断响应要可靠和快速得多。
理解了这些设计初衷,我们再深入每个环节的细节,你就会明白每个寄存器位背后的意义,而不仅仅是死记硬背配置值。
2. 核心细节解析:信号生成、死区与同步机制
2.1 计数模式与信号生成:不仅仅是占空比
很多教程只告诉你:占空比 = (比较器值) / (装载值)。但在Tiva™的PWM模块里,故事要丰富得多。计数模式的选择,直接决定了波形的“形状”和生成逻辑。
Count-Down(递减)模式:计数器从LOAD值开始递减,减到0后,立即重载LOAD值并开始下一个周期。这种模式生成的是边沿对齐的PWM信号。信号的事件只有:装载(load)、归零(zero)、匹配A下降(cmpA down)、匹配B下降(cmpB down)。要生成一个简单的PWM,你通常会在zero事件时将输出置高,在cmpA down事件时置低。此时,占空比 = (LOAD-CMPA) / (LOAD+ 1)。注意,因为是从LOAD减到0,所以匹配点发生在“下降”过程。
Count-Up/Down(递增/递减)模式:计数器从0开始递增到LOAD,然后再递减回0,如此循环。这种模式生成的是中心对齐的PWM信号。事件翻倍了:除了zero和load,还有匹配A上升(cmpA up)、匹配A下降(cmpA down)、匹配B上升(cmpB up)、匹配B下降(cmpB down)。中心对齐PWM的谐波特性更好,电磁干扰(EMI)更低,在电机控制和音频应用中几乎是标配。要生成一个中心对齐的PWM,你可以在cmpA up事件置高,在cmpA down事件置低。
实操心得:理解“动作-事件”编程模型配置
PWMnGENA/PWMnGENB寄存器时,你是在为pwmA/pwmB这两个输出信号,定义一张“事件-动作”映射表。例如,要生成中心对齐PWM,你可能会这样配置PWMnGENA:
ACTLOAD= 0: 在load事件无动作(或设为无关)。ACTZERO= 0: 在zero事件无动作。ACTCMPAU= 1: 在cmpA up事件驱动为高。ACTCMPAD= 2: 在cmpA down事件驱动为低。ACTCMPBU= 0: 在cmpB up事件无动作(因为cmpB事件控制的是pwmB信号)。ACTCMPBD= 0: 在cmpB down事件无动作。 这种灵活性允许你生成非对称、带相位偏移甚至更复杂的波形,而不仅仅是简单的PWM。
2.2 死区生成器:参数计算与配置陷阱
死区时间不是随便设的。它必须大于你所使用的功率开关管(MOSFET或IGBT)的“关断延迟时间”减去“开启延迟时间”,以确保绝对安全,但又不能太长,否则会降低最大占空比,影响输出效率。
参数计算:死区时间由PWMnDBRISE(上升沿延迟)和PWMnDBFALL(下降沿延迟)两个寄存器控制。其时间计算公式为:死区时间 = (RISE/FALL 寄存器值) * (PWM 时钟周期)例如,PWM时钟为10MHz(周期100ns),DBRISE设置为10,则pwmA‘的上升沿将延迟 10 * 100ns = 1μs。pwmB‘的上升沿(对应原pwmA下降沿)则由DBFALL控制。
配置陷阱:
- 使能死区后,
pwmB信号丢失:这是手册明确指出的,但很容易忘记。一旦使能死区发生器(PWMnDBCTL中ENABLE位置1),原始的pwmB信号就不再使用。pwmA‘和pwmB‘两路输出都将由原始的pwmA信号派生而来。这意味着,如果你原本希望pwmA和pwmB是两个独立占空比的信号,就不能使能死区。死区仅用于需要互补驱动对管的场景。 - 死区插入影响有效占空比:死区时间会“吃掉”一部分有效的导通时间。在计算理论占空比时,需要考虑这个因素。尤其是在追求极限占空比(接近0%或100%)时,死区可能导致无法达到目标。
- 中断与触发基于原始事件:这是关键!
PWMnINTEN(中断使能)和ADC触发选择器,监听的是cmpA up/down等原始事件,而不是经过死区延迟后的pwmA‘信号边沿。如果你用PWM事件来触发ADC在特定时刻采样(比如在电机控制中采样相电流),必须意识到ADC触发会发生在死区插入之前的时间点。这个细节在同步采样设计中至关重要。
2.3 同步机制详解:让多个PWM发生器步调一致
同步是高级PWM应用的核心。Tiva™提供了多层次同步机制。
1. 计数器同步(基础同步): 通过向PWMSYNC寄存器的SYNC0~SYNC3位写1,可以立即将对应PWM发生器的计数器复位到0。你可以同时设置多个位,让多个发生器在同一时刻清零重启。这个操作是立即生效的。通常用于系统初始化时,让所有PWM发生器从同一起跑线开始。但要注意,这只是让它们的“时间原点”对齐了一次,后续如果各自的LOAD值不同,或者因为其他操作导致计数器不同步,它们又会错开。
2. 寄存器更新同步(高级同步): 这是实现动态、无毛刺调整PWM参数的关键。想象一下电机在高速运行,你需要实时调整PWM频率或占空比。如果直接写LOAD或CMPA寄存器,新值立即生效,可能会在当前的PWM周期中间切断或拉长一个脉冲,产生极窄的“毛刺”脉冲,这对功率电路可能是灾难性的。
- 局部同步:将对应控制寄存器(如
PWMnLOAD)的更新模式配置为“局部同步”。当你写入新值时,硬件不会立即应用,而是等到本发生器的计数器下一次归零(zero事件)时,才用新值替换旧值。这保证了参数更新一定发生在PWM周期的边界,避免了周期内的毛刺。 - 全局同步:这是更强大的功能。你需要先在各个PWM发生器的
PWMnCTL寄存器中,将LOADUPD、CMPAUPD等位设置为全局同步模式。然后,当你修改了多个发生器的多个参数(比如同时改了三个桥臂的占空比)后,再向主控制寄存器PWMCTL的GLOBALSYNCx位写1。此时,所有被设置为全局同步模式的寄存器更新,会排队等待,直到所有相关发生器的计数器都达到零时,才一次性全部生效。这确保了多个相关联的PWM通道能在完全相同的时刻切换到新的参数集,对于多相电机控制或交错并联电源来说,是维持平衡、避免冲击电流的必备手段。
注意事项:同步更新的“队列”与“触发”全局同步不是自动的。流程是:1) 配置寄存器为全局同步模式。2) 写入新的参数值到各个寄存器(此时值被缓存)。3)最后设置
PWMCTL中的GLOBALSYNCx位。这个GLOBALSYNCx位就像一个触发器,拉高后,硬件会等待同步条件满足,然后一次性应用所有缓存的值,完成后该位会自动清零。如果你先触发同步再写参数,新参数将不会在本次同步中生效。
3. 实操过程:从零配置一个中心对齐带死区的互补PWM对
理论说再多,不如动手配一遍。假设我们要用PWM发生器0,产生一对中心对齐、带死区的互补PWM信号(MnPWM0和MnPWM1),用于驱动一个半桥。系统时钟80MHz,PWM频率20kHz,死区时间1μs,初始占空比50%。
3.1 系统与时钟初始化
首先,必须开启相关模块的时钟。这是所有外设配置的第一步,但常常被忽略导致调试时没有输出。
// 1. 使能PWM模块时钟 (假设使用PWM0模块) SYSCTL->RCGCPWM |= (1 << 0); // 置位RCGCPWM寄存器的第0位,使能PWM0模块时钟 // 2. 使能对应GPIO端口的时钟 (假设PWM0输出到PF0, PF1,端口F) SYSCTL->RCGCGPIO |= (1 << 5); // 使能GPIO Port F时钟 // 3. 等待时钟稳定(通常需要几个周期,保险起见用循环或延时) __asm__ volatile("nop"); __asm__ volatile("nop"); __asm__ volatile("nop"); // 4. 配置GPIO引脚为PWM功能 // 先解锁PF0(如果它被NMI等功能锁定,在TM4C上常见) GPIOF->LOCK = 0x4C4F434B; // 解锁GPIO Commit寄存器 GPIOF->CR |= (1 << 0); // 允许修改PF0 GPIOF->LOCK = 0; // 重新锁定 GPIOF->AFSEL |= (1 << 0) | (1 << 1); // PF0, PF1启用备用功能 GPIOF->PCTL &= ~(0xFF << 0); // 清除PF0和PF1的引脚控制位 GPIOF->PCTL |= (0x4 << 0) | (0x4 << 4); // 配置PF0为M0PWM0,PF1为M0PWM1 (具体值查数据手册表) GPIOF->DEN |= (1 << 0) | (1 << 1); // 使能PF0, PF1数字功能3.2 PWM时钟配置与发生器基础设置
接下来配置PWM模块的时钟源和分频。我们希望PWM时钟为40MHz(这样便于计算)。
// 5. 配置PWM时钟分频 // 使用PWM分频器,分频系数设为2 (PWMDIV = 0x0) PWM0->_0_CTL = 0; // 先停止PWM发生器0 PWM0->_0_GENA = 0; PWM0->_0_GENB = 0; // 配置PWMCC寄存器:使用PWM分频器(USEPWMDIV=1),分频值2 (PWMDIV=0) // PWMCC寄存器偏移是0xFC8,需要根据你的头文件或寄存器定义来访问。 // 假设我们直接操作寄存器地址: *(volatile uint32_t *)(PWM0_BASE + 0xFC8) = (1 << 0); // USEPWMDIV=1, PWMDIV=0 (除以2) // 现在PWM时钟 = 系统时钟80MHz / 2 = 40MHz,周期25ns。3.3 计算并设置周期、比较值与死区
这是核心计算部分。
计算PWM周期装载值: PWM频率 = 20kHz,周期 T = 1/20,000 = 50μs。 PWM时钟周期 = 1/40MHz = 25ns。 一个PWM周期需要的时钟 ticks 数 = 50μs / 25ns = 2000 ticks。 对于中心对齐的Count-Up/Down模式,计数器从0递增到LOAD,再递减回0。��此,
LOAD值应设置为 (PWM周期 ticks / 2) - 1?错!这是一个常见的误区。在Count-Up/Down模式下,LOAD寄存器存储的是计数器需要递增到的目标值。计数器从0到LOAD,再从LOAD到0,总共经历 (LOAD + 1) * 2 个 ticks? 让我们仔细分析:计数器从0开始,递增到LOAD(经历了LOAD+1个值,从0到LOAD),然后递减回0(又经历了LOAD+1个值,从LOAD到0)。所以一个完整的PWM周期是 2 * (LOAD + 1) 个时钟 ticks。 因此,LOAD= (PWM周期 ticks / 2) - 1 = (2000 / 2) - 1 = 999。计算比较器值(占空比): 目标占空比50%。在中心对齐模式下,我们希望高电平脉冲位于周期中心。通常我们在
cmpA up事件(计数器递增到CMPA值)时置高,在cmpA down事件(计数器递减到CMPA值)时置低。这样,高电平的宽度是 2 * (LOAD - CMPA) 个 ticks? 不对。实际上,高电平时间是从cmpA up到cmpA down。计数器从CMPA递增到LOAD,再递减回CMPA。这段时间的ticks数是: (LOAD - CMPA) * 2? 更准确地说,从cmpA up到周期中心点(LOAD)再到cmpA down,计数器经过了 (LOAD - CMPA) (上升段) + (LOAD - CMPA) (下降段) = 2 * (LOAD - CMPA) 个 ticks。但注意,cmpA up和cmpA down事件发生在计数器等于CMPA的时刻,所以高电平时间对应的时钟 ticks 数 = 2 * (LOAD - CMPA)。 占空比 = 高电平时间 / 总周期时间 = [2 * (LOAD - CMPA)] / [2 * (LOAD + 1)] = (LOAD - CMPA) / (LOAD + 1)。 已知占空比=0.5,LOAD=999。代入公式:0.5 = (999 - CMPA) / (999 + 1) = (999 - CMPA) / 1000。 解得:CMPA = 999 - 500 = 499。 对于互补信号pwmB,我们使用死区发生器生成,所以CMPB在此例中用于其他目的或不用,我们主要配置CMPA。计算死区寄存器值: 死区时间 = 1μs。 PWM时钟周期 = 25ns。 所需 ticks 数 = 1μs / 25ns = 40 ticks。 因此,设置
DBRISE= 40,DBFALL= 40。
// 6. 配置PWM发生器0为Count-Up/Down模式,并设置同步更新模式(可选) PWM0->_0_CTL = 0x00000000; // 确保计数器停止,模式为默认(可能需先配置为Up/Down模式,具体看寄存器位定义) // 在TM4C的PWMCTL寄存器中,每个发生器有一个MODE位。0为Down,1为Up/Down。 // 我们需要设置PWM0CTL的MODE位为1。假设其位定义中,第0位是ENABLE,第1位是MODE。 PWM0->_0_CTL |= (1 << 1); // 设置MODE位为1,启用Count-Up/Down模式 // 7. 设置周期 (LOAD值) PWM0->_0_LOAD = 999; // 对应20kHz中心对齐PWM // 8. 设置比较器A值 (占空比) PWM0->_0_CMPA = 499; // 50%占空比 // 9. 配置死区发生器 PWM0->_0_DBRISE = 40; // 上升沿延迟1us PWM0->_0_DBFALL = 40; // 下降沿延迟1us PWM0->_0_DBCTL = 0x1; // 使能死区发生器,模式为上升沿和下降沿都延迟(典型配置)3.4 配置动作发生器与启动
现在配置在什么事件下,pwmA信号做什么动作。
// 10. 配置发生器A的动作 (pwmA信号) // 我们需要在计数器递增到CMPA时置高,递减到CMPA时置低。 // PWMnGENA寄存器位定义(简化): // ACTCMPAD[1:0]: CMPA down动作, ACTCMPAU[1:0]: CMPA up动作 // 00: 无动作,01: 反转,10: 驱动低,11: 驱动高 // 我们设置:CMPA up时驱动高(11),CMPA down时驱动低(10),其他事件无动作。 uint32_t genA_config = 0; genA_config |= (0x3 << 8); // ACTCMPAU = 11 (驱动高),假设位8-9是ACTCMPAU genA_config |= (0x2 << 4); // ACTCMPAD = 10 (驱动低),假设位4-5是ACTCMPAD // 注意:不同型号/头文件寄存器位域定义可能不同,此处为示意。请务必查阅具体的数据手册。 // 对于TM4C129,PWM0GENA寄存器地址偏移0x060。标准驱动库可能提供宏或函数。 // 例如,使用TivaWare库:PWMGenConfigure(PWM0_BASE, PWM_GEN_0, PWM_GEN_MODE_UP_DOWN | ...); // 以及 PWMGenActionSet(PWM0_BASE, PWM_GEN_0, PWM_ACTION_HIGH_ON_UP_CMPA | PWM_ACTION_LOW_ON_DOWN_CMPA); PWM0->_0_GENA = genA_config; // 写入配置 // 11. 启动计数器 PWM0->_0_CTL |= (1 << 0); // 设置ENABLE位为1,启动PWM发生器0计数器 // 12. 使能PWM输出到引脚 // PWMENABLE寄存器:第0位使能PWM0A (MnPWM0),第1位使能PWM0B (MnPWM1) // 因为我们使能了死区,pwmB'信号由死区发生器从pwmA生成,所以需要同时使能两路输出。 *(volatile uint32_t *)(PWM0_BASE + 0x008) |= (1 << 0) | (1 << 1); // 使能PWM0和PWM1输出经过以上步骤,你应该能在PF0和PF1引脚上测量到一对中心对齐、带有1μs死区时间的互补PWM信号。
4. 故障处理机制与高级同步应用实录
4.1 故障输入配置与安全状态管理
故障处理的目标是:当异常发生时,硬件能自动、快速地将PWM输出强制到一个预设的安全状态(通常是全低,关断所有开关管),无需CPU干预。
配置步骤:
选择并配置故障源:
- 外部故障引脚:通过
PWMnFLTSEN寄存器配置对应MnFAULTn引脚的逻辑极性(高电平有效还是低电平有效)。 - ADC数字比较器:在ADC模块中配置数字比较器,当其触发时,可作为故障源。
- 调试器暂停:通常作为一个可选的故障源,用于调试时暂停PWM输出。
- 通过
PWMnFLTSRC0和PWMnFLTSRC1寄存器,选择哪些故障源对本PWM发生器有效。
- 外部故障引脚:通过
配置故障行为:
- 在
PWMnCTL寄存器中,配置故障处理模式:LATCH位:决定故障是锁存的(直到软件清除)还是瞬时的(故障信号消失即恢复)。对于ADC比较器这类脉冲故障源,必须设置为锁存模式(1),否则可能错过短暂故障。MINFLTPER位:是否启用最小故障周期计数器。如果启用,故障信号必须持续超过PWMnMINFLTPER寄存器设定的时间,才被认定为有效故障,可以滤除噪声毛刺。
- 在
PWMFAULT寄存器中,为每个PWM输出通道(FAULT0~FAULT7)选择故障时的行为:是继续传递生成的pwmA‘/pwmB‘信号(0),还是强制输出PWMFAULTVAL寄存器中指定的安全值(1)。 - 在
PWMFAULTVAL寄存器中,设定每个通道故障时输出的安全值(0或1)。务必结合输出极性反转(PWMINVERT)来考虑最终引脚电平!
- 在
中断处理(可选):
- 可以通过
PWMINTEN寄存器,将故障状态提升到控制器中断,以便CPU在故障发生后进行日志记录、状态恢复等操作。 - 故障状态可以从
PWMnFLTSTAT0/1寄存器中读取,以判断具体是哪个故障源触发的。
- 可以通过
示例:配置外部低电平有效的故障引脚
// 假设使用FAULT0引脚(可能对应某个GPIO,需查阅引脚复用表)作为PWM发生器0的故障源 // 1. 配置故障引脚为低电平有效 PWM0->_0_FLTSEN = 0x1; // 假设第0位控制FAULT0极性,0为高有效,1为低有效。 // 2. 配置故障源选择:使用外部故障引脚0 PWM0->_0_FLTSRC0 = 0x1; // 选择故障源0为外部引脚 // 3. 配置故障控制:锁存模式,启用最小故障周期 PWM0->_0_CTL |= (1 << 2); // 设置LATCH位 (假设位2) PWM0->_0_CTL |= (1 << 3); // 设置MINFLTPER位 (假设位3) PWM0->_0_MINFLTPER = 40; // 设置最小故障周期为40个PWM时钟周期(例如1us @40MHz) // 4. 配置故障输出行为:发生故障时,PWM0和PWM1输出强制为0(安全状态) *(volatile uint32_t *)(PWM0_BASE + 0x010) |= (1 << 0) | (1 << 1); // 设置FAULT0和FAULT1位为1,启用强制输出 *(volatile uint32_t *)(PWM0_BASE + 0x024) &= ~((1 << 0) | (1 << 1)); // 设置PWMFAULTVAL的PWM0和PWM1位为0(输出低电平) // 5. (可选)使能故障中断 *(volatile uint32_t *)(PWM0_BASE + 0x014) |= (1 << 0); // 使能PWM0发生器故障中断 // 还需要在NVIC中使能PWM中断,并编写中断服务函数清除中断标志。4.2 多发生器全局同步实战:三相电机控制
假设我们要用三个PWM发生器(Gen0, Gen1, Gen2)产生六路中心对齐PWM,驱动一个三相桥式电路,并且要求三相互差120度电角度。
基础配置:分别配置三个发生器为Count-Up/Down模式,设置相同的
LOAD值(决定频率),以及各自的CMPA/CMPB值(决定占空比和相位)。相位差是通过偏移比较器匹配点来实现的,而不是偏移计数器起点。实现120度相位差: 电角度120度对应时间上的1/3个PWM周期。一个PWM周期对应的计数器 ticks 总数为
2 * (LOAD + 1)。 因此,相位差 ticks 数 = (1/3) *2 * (LOAD + 1)=(2/3) * (LOAD + 1)。 假设LOAD = 999,则(LOAD + 1) = 1000。 Gen0 (U相) 的CMPA值设为CMPA_U(用于占空比控制)。 Gen1 (V相) 的CMPA值需要偏移(2/3)*1000 ≈ 667 ticks。但注意,在中心对齐模式下,CMPA值影响的是脉冲中心的位置。为了实现V相滞后U相120度,我们需要让V相的cmpA up事件比U相的cmpA up事件晚发生667个ticks。由于计数器是双向的,这需要仔细计算。一种更直观的方法是使用相位寄存器(如果模块支持),但Tiva™ PWM模块没有直接的硬件相位寄存器。通常做法是:- 让所有发生器同步启动(计数器从0开始)。
- 通过软件计算,将V相的
CMPA值设置为(CMPA_U + 相位差偏移) mod (LOAD+1),并考虑上下计数。或者,更常见的方案是使用PWM发生器之间的同步链或主从模式,但Tiva™的PWM模块不支持硬件相位偏移。因此,实现精确的120度软件相位差在中心对齐模式下比较复杂,通常边沿对齐模式更容易通过延迟加载比较值来实现。这揭示了芯片选型时,若需要多相严格移相,需选择支持硬件相位偏移功能的PWM模块。
全局同步更新: 即使无法硬件移相,在动态调整占空比(比如进行FOC矢量控制)时,我们仍希望三个相位的占空比同时更新。
// 假设已经配置好三个发生器为Up/Down模式,并设置了LOAD和CMPA值。 // 1. 将三个发生器的LOAD和CMPA更新模式设置为全局同步 PWM0->_0_CTL |= (1 << 4); // 设置LOADUPD位为全局同步 (假设位4) PWM0->_0_CTL |= (1 << 5); // 设置CMPAUPD位为全局同步 (假设位5) // 对PWM1CTL, PWM2CTL做同样设置... // 2. 当需要更新占空比时,写入新的CMPA值(这些值会被缓存) PWM0->_0_CMPA = new_cmpa_u; PWM1->_0_CMPA = new_cmpa_v; PWM2->_0_CMPA = new_cmpa_w; // 3. 触发全局同步更新,等待所有计数器归零后一次性生效 *(volatile uint32_t *)(PWM0_BASE + 0x000) |= (1 << 0) | (1 << 1) | (1 << 2); // 设置GLOBALSYNC0,1,2位 // 硬件会自动在条件满足后更新,并清除这些位。
4.3 常见问题与排查技巧实录
问题1:PWM完全没有输出。
- 检查清单:
- 时钟是否开启:确认
RCGCPWM和对应RCGCGPIO寄存器位已置位,并等待了足够时钟稳定周期(通常至少3个系统时钟)。 - GPIO复用是否正确:确认
AFSEL和PCTL寄存器已正确配置为PWM功能。特别留意某些引脚可能默认被锁定(如NMI功能),需要先解锁(见前面GPIO配置代码)。 - PWM发生器是否启用:确认
PWMnCTL寄存器的ENABLE位已置1。 - 输出是否使能:确认
PWMENABLE寄存器对应位已置1。 - 计数器是否在运行:读取
PWMnCOUNT寄存器,看其值是否在变化。
- 时钟是否开启:确认
问题2:PWM输出频率或占空比不对。
- 计算错误:双重检查
LOAD和CMPA/CMPB的计算公式,区分Count-Down和Count-Up/Down模式。Count-Up/Down模式下的周期是2 * (LOAD + 1) * T_pwmclk。 - 时钟分频未生效:确认
PWMCC寄存器配置正确,并且USEPWMDIV位已置1。测量实际PWM时钟频率。 - 动作发生器配置错误:检查
PWMnGENA/B寄存器,确保在正确的事件(如cmpA downvscmpA up)设置了正确的动作(驱动高/低)。用逻辑分析仪捕捉pwmA(死区前)信号有助于判断。
问题3:死区时间不生效或效果不对。
- 死区未使能:确认
PWMnDBCTL寄存器的ENABLE位已置1。 pwmB信号异常:记住,使能死区后,原始的pwmB信号被忽略。pwmB‘完全由pwmA反相并加延迟产生。如果你需要两路独立PWM,不能使能死区。- 延迟值太小:死区时间必须大于功率管的开关延迟。计算出的ticks数如果为0或太小,可能无法产生有效死区。确保
DBRISE和DBFALL值大于0。
问题4:多路PWM无法同步或同步后仍有微小偏移。
- 同步时机:
PWMSYNC是立即同步计数器,但如果你在计数器运行的不同时刻去使能各个发生器,它们又会错开。最佳实践是:先配置所有发生器但不要启动(ENABLE=0),然后通过PWMSYNC一次性同步所有计数器,最后再同时置位各个PWMnCTL的ENABLE位。 - 更新毛刺:动态更新
LOAD或CMP值时,务必使用同步更新模式(局部或全局),避免在周期中间更新产生窄脉冲。 - 硬件局限性:对于非常高的同步精度要求(如纳秒级对齐),需考虑信号从芯片内部到引脚输出的微小路径延迟差异。这在大多数应用中可忽略,但在极高频率或多相并联电源中可能需要通过PCB布局和门极驱动来补偿。
问题5:故障保护不动作。
- 故障源未激活:检查故障引脚电平是否达到设定的有效极性。用万用表或示波器测量。
- 故障未锁存:如果是瞬态故障(如ADC比较器脉冲),确保
LATCH位已置1,否则故障信号消失后输出会自动恢复。 - 输出强制值被反转:检查
PWMFAULTVAL和PWMINVERT的组合。如果你在PWMFAULTVAL中设置了故障输出高,但同时PWMINVERT也置位,实际引脚输出是低。这常常是故障保护失效的原因。 - 最小故障周期过滤:如果启用了
MINFLTPER,但故障脉冲宽度小于设定值,故障会被忽略。根据你的保护响应速度要求合理设置此值。
调试PWM,尤其是涉及同步和故障的复杂应用,逻辑分析仪是你的最佳伙伴。同时捕获多路PWM信号、故障引脚和关键寄存器写入时刻,能帮你清晰地看到时序关系,快速定位问题根源。最后,反复阅读数据手册中关于PWM章节的时序图,那些图往往比大段文字更能说明问题。
