1. 项目概述与核心价值
如果你正在用MC68F375这颗老将做电机驱动、LED调光或者开关电源,那你肯定绕不开它的CTM9定时器模块,尤其是里面的PWM功能。手册里那几十页寄存器描述和时序图,初看确实头大,但一旦吃透,你会发现这个模块设计得非常精巧,尤其是它那个双缓冲寄存器机制,简直是实现平滑、无毛刺PWM输出的神器。我当年第一次用的时候,就因为没搞懂PWMA1和PWMA2的关系,调出来的电机声音总带杂音,后来把手册翻烂了才摸清门道。
简单来说,CTM9的PWM子模块(PWMSM)就是一个高度可配置的波形发生器。它的核心是一个16位向上计数器,配合两个比较器(一个比周期,一个比脉宽),来产生你想要的方波。但它的强大之处在于细节:比如如何在不干扰当前输出波形的前提下,偷偷改掉下一个周期的参数?如何实现0%和100%这种极限占空比的真稳态输出?时钟树该怎么配置才能兼顾频率范围和分辨率?这些手册里虽然都写了,但知识点散落在各个角落,缺乏一个从“为什么要这么设计”到“具体怎么配寄存器”的连贯视角。
这篇文章,我就结合自己踩过的坑和项目经验,把MC68F375 CTM9的PWM模块掰开揉碎了讲清楚。我们会从最基础的PWM波形生成原理开始,然后深入到双缓冲机制的运作细节,接着手把手带你推导频率和占空比的计算公式,并给出具体的寄存器配置步骤和代码片段。最后,我会分享几个实际调试中容易出问题的地方和排查技巧,比如更新时机不对导致的波形抖动、极限频率下的占空比限制等。无论你是刚开始接触这款MCU,还是想优化现有的PWM应用,相信这些内容都能给你带来直接的帮助。
2. CTM9 PWM模块核心架构与工作原理
要玩转CTM9的PWM,不能只盯着PWMSM那几个寄存器,得先把它放在整个CTM9的生态系统里看。CTM9就像一个定时器功能集市,里面不仅有PWM发生器(PWMSM),还有输入捕捉、输出比较、计数器等多种子模块。它们都通过一个叫做子模块总线(SMB)的内部高速公路连接在一起,可以共享时钟源和时基信号。PWMSM是其中专门负责产生高质量PWM波形的“专卖店”。
2.1 PWM波形生成的核心引擎:计数器与比较器
PWMSM生成波形的核心逻辑其实非常经典,可以把它想象成一个不断循环的“赛跑”:
- 发令枪响(周期开始):16位PWM计数器(PWMC)从1开始计数(注意,不是从0开始!这是一个关键细节)。
- 设置终点线(装载周期值):周期寄存器PWMA2里存放着本次“赛跑”的总长度。当计数器累加到和PWMA2的值相等时,表示一个周期结束。
- 设置折返点(装载脉宽值):脉宽寄存器PWMB2里存放着输出高电平的持续时间。当计数器累加到和PWMB2的值相等时,输出比较器会发出信号,将输出电平拉低。
- 循环赛跑:每个周期结束后,计数器复位,并自动从PWMA1和PWMB1(也就是我们软件直接操作的“前台”寄存器)加载新的周期和脉宽值到PWMA2和PWMB2(“后台”工作寄存器),开始下一个周期。
这个过程里,PWMA2和PWMB2这两个寄存器是我们无法直接读写的,它们由硬件自动从PWMA1/PWMB1更新,专门用于和计数器进行实时比较,确保比较动作的原子性和实时性,避免软件写入时产生干扰。
2.2 无毛刺更新的秘密:双缓冲寄存器机制
这是CTM9 PWM设计中最精妙也最容易用错的部分。为什么需要双缓冲?想象一下,你正在用PWM控制电机的转速,电机转得好好的。这时你想调整速度,如果直接修改正在用于比较的寄存器,万一修改操作正好发生在计数器比较的瞬间,可能会导致一个畸形的脉冲(毛刺),电机可能就会“咯噔”一下。
CTM9的解决方案是引入了前台缓冲(PWMA1/PWMB1)和后台工作(PWMA2/PWMB2)两套寄存器。
- PWMA1/PWMB1:软件可读写。我们程序员只和它们打交道。你可以在任何时刻(即使PWM正在输出)安全地向这两个寄存器写入新的周期和脉宽值。
- PWMA2/PWMB2:硬件专用,只读。它们才是真正和计数器比较的“现役”寄存器。硬件会在一个PWM周期结束的瞬间,自动将PWMA1/PWMB1的值拷贝到PWMA2/PWMB2中,用于下一个周期。这个拷贝动作是硬件自动完成的,非常快,且与计数器复位同步,因此不会对当前输出的波形产生任何影响。
这个机制保证了PWM参数更新的平滑性和无毛刺。你随时可以下达新的“指令”(写PWMA1/B1),但“执行”一定会等到当前周期圆满结束后才开始。
注意:手册特别警告,不建议以字节(8位)方式写入PWMA1或PWMB1。因为硬件在周期结束时是以字(16位)为单位从PWMA1/B1搬运到PWMA2/B2的。如果你先写低字节,后写高字节,而在两次写入之间恰好发生了周期结束和寄存器搬运,那么PWMA2/B2里就会得到一个由旧高字节和新低字节拼凑出来的错误值,导致输出异常。因此,务必使用16位写操作(例如C语言中的
PWMA1 = 0x1FFF;)来更新这些寄存器。
2.3 时钟链:一切精度的源头
PWM的频率和分辨率最终都取决于时钟。CTM9的时钟系统有点像一个多级瀑布。
- 系统时钟(fSYS):这是源头,比如常见的16.78MHz。
- 计数器预分频子模块(CPSM):这是第一级分频。它通过一个可配置的预分频器,产生多达8路不同频率的时钟信号(PCLK1~PCLK6等)。其中最关键的是第一级分频比,由CPCR寄存器的
DIV23位选择是除以2还是除以3。这个选择会直接影响所有后续分频的基准。 - PWMSM内部预分频器:PWMSM自己还有一个除以256的预分频器,可以从CPSM提供的时钟中进一步分频。
- PWMSM计数器时钟:最终,我们通过PWMSIC寄存器中的
CLK[2:0]位,从上述时钟链产生的多个时钟源中选择一个,作为PWM计数器(PWMC)的计数时钟。
这个多级分频结构提供了极大的灵活性。你可以为了获得更高的PWM频率而选择较高的时钟源(如fSYS/2),也可以为了获得更精细的占空比调节(更高的分辨率)而选择较低频率的时钟源,让计数器跑得慢一点,每个计数周期对应的物理时间更长。
3. 寄存器详解与配置流程
理解了架构,我们来看怎么用手。配置一个PWM通道,本质上就是设置好几个关键寄存器。我们以配置PWM通道5为例,假设系统时钟fSYS = 16.78 MHz。
3.1 核心寄存器映射与功能
CTM9的PWM模块有4个通道(5-8),每个通道都有完全独立的一套寄存器,地址是连续的。以PWM5为例,其寄存器基址偏移和功能如下:
| 地址偏移 | 寄存器名称 | 描述 |
|---|---|---|
| 0x00 | PWM5SIC | 状态、中断和控制寄存器。这是配置的起点和核心。 |
| 0x02 | PWM5A | 周期寄存器(PWMA1)。软件设置下一个PWM周期值的地方。 |
| 0x04 | PWM5B | 脉宽寄存器(PWMB1)。软件设置下一个PWM脉冲宽度值的地方。 |
| 0x06 | PWM5C | 计数器寄存器(PWMC)。只读,用于实时查看计数器当前值,调试时很有用。 |
3.2 PWMSIC寄存器:控制中枢
这个16位寄存器包含了启用PWM、选择时钟、设置极性、控制更新和中断的所有开关。我们逐位分析:
- 位[15] FLAG:周期完成标志。硬件在一个PWM周期结束时自动置1。它有两个重要作用:
- 通知软件:“当前周期已结束,PWMA1/B1里的新值已被加载,你现在可以放心地为我设置下下个周期的参数了。”
- 如果中断被启用,它会触发中断。清除这个标志需要“读-改-写”操作:先读寄存器(此时FLAG=1),然后向该位写0。如果在读和写之间又发生了周期结束,FLAG会再次被置1,导致清除失败,这点在中断服务程序里要特别注意。
- 位[14:12] IL[2:0]:中断级别。000表示禁用中断,001-111对应中断优先级1(最低)到7(最高)。如果不使用中断,务必设为000。
- 位[11] IARB3:中断仲裁位。与BIUSM模块配置寄存器中的
IARB[2:0]共同组成4位仲裁ID,用于解决同优先级中断的冲突。通常保持默认或根据系统中断规划设置。 - 位[7] PIN:输出引脚状态位。只读,可以实时读取PWM输出引脚的实际电平,用于监控。
- 位[5] LOAD:手动加载控制位。写1会立即触发一次从PWMA1/B1到PWMA2/B2的加载,并复位计数器。这是一个强力同步工具。通常我们依靠周期结束自动加载,但在某些需要严格同步多个PWM通道或立即应用新参数的场景,可以手动使用此位。
- 位[4] POL:输出极性控制。0表示正常极性(高电平有效),1表示反向极性(低电平有效)。结合下一位EN,共同决定引脚初始状态。
- 位[3] EN:PWM使能位。0关闭,1开启。重要:关闭PWM时,输出引脚电平由POL位决定(POL=0则输出低,POL=1则输出高)。为了避免关闭时产生毛刺,手册建议先设置脉宽为0(即PWMB1=0),等待一个完整的0%占空比周期输出后,再清除EN位。
- 位[2:0] CLK[2:0]:PWM计数器时钟源选择。这是决定PWM频率和分辨率的关键配置。具体选择见下表(假设CPSM的
DIV23=0,即第一级分频为/2):
| CLK2 | CLK1 | CLK0 | 时钟源 | 分频比 (对fSYS) | 计数时钟周期 (fSYS=16.78MHz) |
|---|---|---|---|---|---|
| 0 | 0 | 0 | PCLK1 | /2 | 0.119 µs |
| 0 | 0 | 1 | Prescaler /2 | /4 | 0.238 µs |
| 0 | 1 | 0 | Prescaler /4 | /8 | 0.477 µs |
| ... | ... | ... | ... | ... | ... |
| 1 | 1 | 1 | Prescaler /256 | /512 | 30.5 µs |
如果DIV23=1(第一级分频为/3),则所有分频比的基础变为3,例如CLK[2:0]=000时,时钟为fSYS/3,周期为0.179µs。
3.3 频率与占空比计算:从需求到寄存器值
这是实际编程中最常做的计算。我们定义:
fSYS: 系统时钟频率 (Hz)NCLOCK: CPSM第一级分频比 (2 或 3,由CPCR.DIV23决定)NCOUNTER: PWMSM计数器时钟分频比 (2, 4, 8, ..., 512, 768,由CLK[2:0]选择)fPWM: 期望的PWM输出频率 (Hz)DutyCycle: 期望的占空比 (0.0 ~ 1.0)
第一步:确定计数器时钟周期和最小脉宽PWM计数器的时钟频率fCLK = fSYS / (NCLOCK * NCOUNTER)。 最小脉冲宽度tPWMIN = 1 / fCLK。这代表了一个计数周期对应的物理时间,也是PWM时间分辨率的最小单位。
第二步:计算周期寄存器值(PWMA1)PWM的周期T = 1 / fPWM。 这个周期包含了若干个最小时间单位tPWMIN。所以,周期寄存器值PWMA1 = T / tPWMIN = fCLK / fPWM。公式推导:PWMA1 = fSYS / (NCLOCK * NCOUNTER * fPWM)重要约束:PWMA1必须是一个16位无符号整数,范围是1到65535(0x0001~0xFFFF)。值0x0000被用来表示65536个计数周期,这是一个特例。
第三步:计算脉宽寄存器值(PWMB1)脉宽时间tPULSE = DutyCycle * T。 脉宽寄存器值PWMB1 = tPULSE / tPWMIN = DutyCycle * PWMA1。公式:PWMB1 = (DutyCycle * fSYS) / (NCLOCK * NCOUNTER * fPWM) = DutyCycle * PWMA1约束:PWMB1是16位无符号整数,范围0~65535。它必须小于或等于PWMA1。
PWMB1 = 0: 输出恒低(0%占空比)。PWMB1 >= PWMA1: 输出恒高(100%占空比)。但注意一个边界情况:当PWMA1被设置为0x0000(即周期为65536个时钟)时,PWMB1最大只能设为0xFFFF,此时占空比为65535/65536 ≈ 99.998%,无法达到绝对的100%。
举例:假设fSYS=16.78MHz,NCLOCK=2(DIV23=0), 选择CLK[2:0]=001(即NCOUNTER=4)。想要一个fPWM=1kHz,占空比50%的PWM波。
fCLK = 16.78e6 / (2 * 4) = 2.0975 MHztPWMIN = 1 / 2.0975e6 ≈ 0.477 µs(与手册表13-12中/4一列的最小脉宽一致)PWMA1 = fCLK / fPWM = 2.0975e6 / 1000 ≈ 2097.5,取整为2097。- 实际输出频率
fPWM_actual = fCLK / 2097 ≈ 999.76 Hz,误差很小。 PWMB1 = 2097 * 0.5 ≈ 1048(取整)。
3.4 配置步骤与代码示例(C语言风格)
基于以上理解,一个完整的PWM通道初始化流程如下:
// 假设寄存器已映射到内存地址,例如: #define PWM5SIC (*(volatile uint16_t*)0xYFF228) #define PWM5A (*(volatile uint16_t*)0xYFF22A) #define PWM5B (*(volatile uint16_t*)0xYFF22C) // ... 其他通道和CPSM、BIUSM寄存器定义 void PWM5_Init(uint16_t period, uint16_t pulse_width) { // 步骤1: 配置CPSM时钟源 (以/2分频为例,选择PCLK2即fSYS/4) // 假设CPCR地址为0xYFF208,PRUN=1(运行),DIV23=0(/2),PSEL[1:0]=00(PCLK6=/64,但PWM不直接用PCLK6) // 注意:CPSM配置影响所有使用其时钟的模块,通常在上电初始化时全局配置一次。 // CPCR = (1 << 3) | (0 << 2) | (0 << 0); // PRUN=1, DIV23=0, PSEL=00 // 步骤2: 配置PWM5控制寄存器 (PWMSIC) // 先禁止PWM输出,避免配置过程中产生意外波形 PWM5SIC = 0x0000; // 清空所有位,EN=0, POL=0等 // 步骤3: 设置周期和脉宽寄存器 (双缓冲的前台寄存器) PWM5A = period; // 写入PWMA1 PWM5B = pulse_width; // 写入PWMB1 // 注意:此时PWMA2/B2还是旧值或随机值,输出尚未改变。 // 步骤4: 配置PWM5SIC的详细参数 uint16_t sic_value = 0; sic_value |= (0x001 << 12); // IL[2:0] = 001,设置中断级别1(若需中断) // sic_value |= (1 << 11); // IARB3,根据系统中断仲裁设置 sic_value |= (0 << 4); // POL = 0,正常极性(高电平有效) sic_value |= (1 << 3); // EN = 1,使能PWM模块 sic_value |= (0x001 << 0); // CLK[2:0] = 001,选择时钟源为 fSYS/4 (对应NCOUNTER=4) // 此时FLAG位可能因使能而自动置1,但我们先不管。 PWM5SIC = sic_value; // 一次性写入配置,PWM开始运行。 // 硬件动作:EN从0->1会触发:输出翻转位置位,计数器从1开始计数,FLAG置位。 // 第一个周期将使用刚刚写入PWMA1/B1的值(它们已被加载到PWMA2/B2)。 } // 运行时动态更新占空比 void PWM5_UpdateDutyCycle(uint16_t new_pulse_width) { // 安全更新:直接写入PWMB1即可。硬件会在当前周期结束后自动将其载入PWMB2。 PWM5B = new_pulse_width; // 如果需要立即更新(例如用于同步),可以在此处设置LOAD位: // PWM5SIC |= (1 << 5); // 设置LOAD位 // 但通常不建议频繁使用,以免破坏波形连续性。 } // 运行时动态更新频率和占空比 void PWM5_UpdatePeriodAndPulse(uint16_t new_period, uint16_t new_pulse_width) { // 同时更新周期和脉宽。由于是双缓冲,可以连续写入。 PWM5A = new_period; PWM5B = new_pulse_width; // 同样,新值将在下一个周期生效。 }4. 高级应用与深度解析
掌握了基础配置,我们来看看一些更深入的话题和实际应用中容易遇到的“坑”。
4.1 极限频率与最小脉宽
手册里有一句非常关键的话:“在周期寄存器中写入0x0002,在脉宽寄存器中写入0x0001,是在给定PWM时钟周期下获得最大可能输出频率的条件。”
我们来解读一下:PWM周期 =(PWMA1 + 1)个计数时钟周期?不对,这里有个细节。计数器是从1开始计数到PWMA2(匹配后周期结束)。所以,一个完整的PWM周期实际包含的计数时钟周期数等于PWMA2的值。例如,PWMA2=2,则计数器计数序列为:1 -> 2(匹配,周期结束),这包含了2个时钟周期。因此,最小周期(最高频率)发生在PWMA1=1时(因为PWMA1会加载到PWMA2)。此时周期为2个计数时钟。
那么为什么手册说0x0002和0x0001呢?我推测这里的“周期寄存器值”指的是我们软件设置的PWMA1,而PWMA1=2加载到PWMA2后,周期就是2个计数时钟。同时,PWMB1=1,脉宽是1个计数时钟,产生一个50%占空比的方波。这确实是能产生稳定PWM波形(非0%或100%)的最高频率。因为如果PWMA1=1,周期为2个时钟,那么要产生脉冲(非0%),PWMB1只能为1(占空比50%)或大于1(即>=2,此时是100%占空比)。PWMA1=1, PWMB1=1的组合与PWMA1=2, PWMB1=1在波形上是一样的(都是50%占空比,周期2时钟),但前者PWMA1的值更小。可能手册为了强调“最大频率”且脉宽非零,选取了PWMA1=2这个例子。在实际应用中,为了获得最高频率,我们应设置PWMA1为尽可能小的值(但不能为0),同时PWMB1小于PWMA1。
最小脉宽就是1个计数时钟周期tPWMIN。它决定了你能控制的最短高电平时间,也决定了PWM的时间分辨率。例如,fCLK=2.0975MHz时,tPWMIN≈0.477µs。这意味着你调节占空比的最小时间步进是0.477µs。
4.2 0%与100%占空比的实现与陷阱
这是PWM用于DA转换或数字电源中的关键特性。CTM9的PWMSM对这两个极限情况有硬件上的特殊处理,以实现真正的稳态输出,无毛刺。
- 0% 占空比:设置
PWMB1 = 0x0000。硬件会使输出触发器始终处于复位状态,输出恒低(或恒高,取决于POL位)。关键点:即使输出恒定,内部的16位PWM计数器仍在继续运行!这意味着当你将脉宽从0改为一个非零值时,变化会同步发生在下一个PWM周期的开始,输出会干净地从低电平跳变为PWM波形,没有中间态。 - 100% 占空比:设置
PWMB1 >= PWMA1。硬件会使输出触发器始终处于置位状态,输出恒高(或恒低,取决于POL位)。同样,计数器也在运行。 - 一个重要边界陷阱:当
PWMA1被设置为0x0000时,这表示PWM周期被设置为65536个计数时钟(这是16位计数器从1计数到0x0000溢出所需的周期数,0x0000是溢出值)。在这种情况下,PWMB1的最大有效值是0xFFFF(65535)。因此,最大占空比 = 65535 / 65536 ≈ 99.998%,你无法获得绝对的100%占空比。如果你的应用要求真正的100%导通(例如在电机H桥控制中用于刹车),你需要换一种方式,比如直接控制GPIO口输出,或者使用POL位反转输出极性来等效实现。
4.3 同步更新与LOAD位的使用
双缓冲机制保证了更新无毛刺,但更新发生在下一个周期开始。有时我们需要多个PWM通道严格同步更新参数,或者需要立即应用一个新参数(例如响应紧急事件)。这时就需要LOAD位(PWMSIC.5)。
向LOAD位写1会产生一个同步加载事件:
- PWMA1的值立即加载到PWMA2。
- PWMB1的值立即加载到PWMB2。
- PWM计数器(PWMC)被重置为0x0001。
- 状态机和输出触发器被复位。
- FLAG位被置1。
- 如果新的PWMB2值不为0,输出触发器被置位(开始一个新的高脉冲)。
使用场景:
- 多通道同步:配置好几个PWM通道的PWMA1/B1后,同时向它们的LOAD位写1,所有通道会在同一个时钟边沿开始使用新参数。
- 紧急参数切换:需要立即停止或改变PWM输出时。
- 初始化后的首次启动:在设置好参数但尚未使能(EN=0)时,写LOAD位可以确保PWMA2/B2被正确初始化,然后再使能,获得一个确定的首个波形。
注意事项:滥用LOAD位会打断当前周期,可能导致输出一个不完整的脉冲。在要求波形连续性的场合(如音频、精密电机驱动),应优先依赖周期结束的自然更新。
4.4 中断的应用与FLAG位处理
PWMSM在每个PWM周期结束时可以产生中断(通过设置IL[2:0]非零)。中断标志就是FLAG位。处理中断时,一个标准的流程是:
- 进入中断服务程序(ISR)。
- 读取PWMxSIC寄存器。这个读操作是清除FLAG位流程的一部分。
- 计算并更新PWMA1和/或PWMB1寄存器,为下下个周期准备新参数。(因为当前刚结束的周期已经加载了你在上个中断里设置的参数)。
- 向FLAG位写0,清除中断标志。务必确保这个写0操作发生在步骤2的读操作之后,且在下个周期结束(新的FLAG置位)之前。通常紧接在读操作之后写回即可。
- 退出ISR。
一个常见的坑:在ISR中更新参数后,忘记及时清除FLAG位。如果FLAG一直为1,即使后续周期结束,也无法再次置位(因为硬件检测到它已是1),可能导致你错过一次中断,或者无法进入下一次中断。更糟糕的是,如果使用“读-改-写”清除时被更高优先级中断打断,且打断期间发生了周期结束,FLAG会被重新置1,导致清除失败。在实时性要求高的系统中,需要考虑关中断或使用原子操作来保护FLAG的清除过程。
5. 实战配置案例与调试技巧
理论说再多,不如实际调一次。下面我以一个具体的电机控制场景为例,展示完整的配置思路和调试方法。
场景:使用MC68F375的PWM5通道控制一个有刷直流电机。系统时钟fSYS = 16.78 MHz。要求PWM频率为20kHz(超出人耳范围,减少噪音),并能在0-100%范围内平滑调速。
5.1 时钟与参数计算
20kHz属于较高频率。我们需要在频率和分辨率之间权衡。查手册表13-12(使用/2选项)。
- 目标频率
fPWM = 20,000 Hz。 - 我们希望分辨率尽量高,即
tPWMIN尽量小,这样调速更细腻。 - 遍历
CLK[2:0选项,计算所需的PWMA1值,并检查是否在1-65535范围内,同时计算实际分辨率(步进数 =PWMA1)。
以CLK[2:0]=000(NCOUNTER=2,fCLK = fSYS/2 = 8.39 MHz)为例:PWMA1 = fCLK / fPWM = 8.39e6 / 20000 ≈ 419.5,取整419。 实际频率fPWM_actual = 8.39e6 / 419 ≈ 20023.9 Hz,误差可接受。 此时,tPWMIN = 1 / 8.39e6 ≈ 0.119 µs。PWM周期T = 1/20000 = 50 µs。分辨率:一个周期内共有419个最小时间单位。这意味着占空比可以以1/419 ≈ 0.24%的步进进行调节。对于许多电机应用来说,这个分辨率足够了。
如果选择CLK[2:0]=001(NCOUNTER=4,fCLK=4.195MHz),则PWMA1≈209,分辨率约为0.48%。虽然分辨率降低,但计数器值更小。我们选择CLK[2:0]=000以获得更好分辨率。
5.2 初始化代码实现
// 宏定义和寄存器映射(同上,略) #define SYS_CLK_FREQ_HZ 16780000UL #define TARGET_PWM_FREQ_HZ 20000UL #define PWM_CLK_DIV_N (2UL) // CLK[2:0]=000, NCOUNTER=2 #define PWM_CLK_DIV_M (2UL) // 假设CPSM DIV23=0, NCLOCK=2 void PWM5_InitForMotor(void) { uint32_t pwm_clk_freq; uint16_t period_reg_val, max_duty_reg_val; // 1. 计算PWM计数器时钟频率 // fCLK = fSYS / (NCLOCK * NCOUNTER) pwm_clk_freq = SYS_CLK_FREQ_HZ / (PWM_CLK_DIV_M * PWM_CLK_DIV_N); // 应为8.39MHz // 2. 计算周期寄存器值 PWMA1 // PWMA1 = fCLK / fPWM period_reg_val = (uint16_t)(pwm_clk_freq / TARGET_PWM_FREQ_HZ); // 计算值约419 // 确保值在有效范围内(1-65535),且不为0 if(period_reg_val == 0) period_reg_val = 1; if(period_reg_val > 0xFFFF) period_reg_val = 0xFFFF; // 3. 初始脉宽设为0(电机停止) uint16_t init_pulse_width = 0; // 4. 配置CPSM (全局一次) // 假设CPCR地址为0xYFF208 // 设置 PRUN=1 (运行), DIV23=0 (/2), PSEL=00 (PCLK6=/64) // *(volatile uint16_t*)0xYFF208 = (1 << 3) | (0 << 2) | (0 << 0); // 5. 配置PWM5寄存器 PWM5SIC = 0x0000; // 先禁用,清空配置 PWM5A = period_reg_val; // 设置周期 PWM5B = init_pulse_width; // 设置初始脉宽为0,0%占空比 // 6. 配置PWM5SIC: 使能、正常极性、时钟源选择、禁用中断 uint16_t sic_cfg = 0; // IL[2:0] = 000 (禁用中断) // IARB3 = 0 (假设) // PIN位只读,不管 // LOAD = 0 (不使用手动加载) sic_cfg |= (0 << 4); // POL = 0, 高电平有效 sic_cfg |= (1 << 3); // EN = 1, 使能PWM sic_cfg |= (0x000 << 0); // CLK[2:0] = 000, 选择 fSYS/2 作为时钟源 // 注意:CLK值需要根据CPSM的DIV23位和所需分频对照表13-17确定。 // 这里0x000对应的是 fSYS/2 (当DIV23=0时)。 PWM5SIC = sic_cfg; // 使能后,FLAG位会立即置1。由于脉宽为0,输出应为恒低。 } // 设置电机速度,duty范围 0.0 ~ 1.0 void PWM5_SetMotorSpeed(float duty) { uint16_t period = PWM5A; // 读取当前周期值 uint16_t new_pulse; if(duty <= 0.0f) { new_pulse = 0; // 0% 占空比,停止 } else if(duty >= 1.0f) { new_pulse = period; // 100% 占空比 (注意边界情况,若period=0xFFFF,则非真100%) // 更稳健的做法:如果需要真100%,可以关闭PWM,直接置位输出引脚。 } else { // 计算脉宽寄存器值,并四舍五入 new_pulse = (uint16_t)(duty * (float)period + 0.5f); // 确保至少为1(如果需要输出脉冲的话) if(new_pulse == 0 && duty > 0.0f) { new_pulse = 1; } // 确保不超过周期值 if(new_pulse > period) { new_pulse = period; } } // 更新脉宽寄存器(双缓冲,下一周期生效) PWM5B = new_pulse; }5.3 调试技巧与常见问题排查
没有输出或输出常高/常低:
- 检查引脚复用:确认MCU的PWM输出引脚已正确配置为外设功能,而非GPIO。
- 检查使能位:确认PWMSIC寄存器的
EN位已设置为1。 - 检查极性位:确认
POL位设置是否符合预期。EN=1, POL=0为高电平有效脉冲;EN=1, POL=1为低电平有效脉冲。 - 检查脉宽值:如果
PWMB1设置为0,输出恒低(POL=0时)。如果PWMB1 >= PWMA1,输出恒高(POL=0时)。用示波器测量引脚,用调试器读取PIN位(PWMSIC.7)对比。 - 检查时钟源:确认
CLK[2:0]选择了一个有效的、正在运行的时钟源。检查CPSM的PRUN位是否为1。
输出频率不对:
- 验证计算:重新计算
PWMA1值。使用示波器测量实际周期,反推计数时钟频率,检查是否与CLK[2:0]和fSYS的设定相符。 - 检查CPSM配置:
DIV23和PSEL[1:0]位会影响PCLK1~PCLK6的频率,但PWM的CLK[2:0]选择的是其中一路。确保你理解整个时钟链。 - 读取计数器:在运行时读取只读的
PWMC寄存器,看它是否在循环计数。如果不动,可能是时钟没进来或模块被冻结(检查FREEZE信号和BIUSM的FRZ、STOP位)。
- 验证计算:重新计算
占空比不准或无法调到很小/很大:
- 分辨率限制:回顾
tPWMIN。如果你期望的脉宽时间小于tPWMIN,那么除了0,最小的脉宽就是1个计数时钟,对应的占空比是1/PWMA1。例如PWMA1=419,则最小非零占空比约为0.24%。 - 100%占空比陷阱:如前所述,当
PWMA1=0x0000(周期65536)时,无法实现理论100%。如果需要,考虑使用POL位反转,或者直接控制GPIO。 - 计算精度:在软件中计算
PWMB1 = duty * PWMA1时,注意浮点数转整数的舍入误差。建议使用(duty * PWMA1 + 0.5)进行四舍五入。
- 分辨率限制:回顾
更新参数时出现波形毛刺:
- 确保使用双缓冲:只更新
PWMA1和PWMB1,不要试图直接操作后台寄存器。 - 避免字节操作:使用16位写操作一次性更新寄存器。
- 同步更新多通道:如果需要多个PWM通道同时切换参数,先更新所有通道的PWMA1/B1,然后同时向它们的
LOAD位写1。 - 关闭时的顺序:要无毛刺关闭PWM,应先设置
PWMB1=0(输出0%占空比),等待一个完整周期(可以检查FLAG位),然后再清除EN位。
- 确保使用双缓冲:只更新
中断不触发或触发异常:
- 检查中断使能:PWMSIC的
IL[2:0]不能为000。 - 检查全局中断:确认CPU的中断总开关已打开。
- 正确清除FLAG:在ISR中必须执行“读PWMxSIC -> 写FLAG位为0”的序列。检查汇编代码,确保编译器没有优化掉“读”操作。
- 中断嵌套与仲裁:如果系统中有多个中断源,检查BIUSM中的
IARB[2:0]和PWMSIC中的IARB3位,确保CTM9的中断仲裁ID是唯一的。
- 检查中断使能:PWMSIC的
通过以上步骤,你应该能系统地配置和调试MC68F375的CTM9 PWM模块。记住,理解双缓冲机制和时钟树是灵活运用这个模块的关键。在实际项目中,建议将PWM配置和操作封装成独立的驱动函数,并充分利用FLAG位和中断来实现精确的时序控制。