跳转到内容
新建笔记

i.MX8MP PWM3:时钟、周期、FIFO 与启动

i.MX8MP 的 PWM 使用 16 位向上计数器、12 位预分频器和 4×16 位采样 FIFO。计算波形时,需要先确定当前实例的真实时钟路径,再把一个周期的计数长度换算成 PERIOD;这两步分别出错,都会导致“寄存器写进去了,但频率不对”。

本文以 PWM3、NXP RM Rev.3 与 MCUXpresso SDK MCUX_2.16.000 的 drivers/ipwm 驱动为依据。保留原笔记的全部 21 幅图,图中较早的解释与精确值在相邻文字中修正。GPT 的 32 位计数器、STM32 的 PWM 定时器和其他 NXP PWM IP,不应直接套用这里的寄存器公式。

原笔记开头列出的 32 位计数器、两路输入捕获、三路输出比较属于 GPT。GPT 支持 restart 和 free-run:前者仅由比较通道 1 的匹配重新开始计数,后者在比较事件后继续计数直至回绕;输入捕获可选择触发边沿,输出比较可选择输出动作。捕获、比较和 rollover 可以产生相应中断,低功耗或调试状态下是否继续运行也有独立控制。这些功能应查 GPT 章节;本文后面的 16 位周期、采样 FIFO 和占空比公式属于独立的 PWM 模块。

时钟路径可概括为:源时钟及固定分频 → root MUX → root PRE/POST 分频 → CCGR 门控 → PWM 内部 CLKSRC → 内部预分频 → 计数器。

CCM 与 PLL、时钟根、门控、GPC 和复位模块的关系

图中体现了 CCM、PLL、门控、GPC 与复位模块的关系。配置某个时钟门不等于已经配置 PLL、解除外设复位或取得跨核资源权限。

PWM3 root 的索引为 105,相对 CCM 的目标寄存器偏移为 0xb480;RM Table 5-1 列出的该 root 最大频率为 66 MHz。这个上限不是上电后的默认值。

PWM3 root MUX输入
024 MHz 参考时钟
1SYSTEM PLL2 / 10
2SYSTEM PLL1 / 5
3SYSTEM PLL1 / 20
4SYSTEM PLL3
5EXT_CLK2,需知道板上实际输入频率
6SYSTEM PLL1 / 10
7VIDEO PLL

PWM3 时钟根索引、偏移、66 MHz 上限及八种 MUX 输入

PWM3 从时钟选择、PRE 和 POST 分频到 CCGR42 的路径

若当前选择 SYSTEM PLL1/5,则模块 root 频率是:

froot=fPLL15 dpre dpostf_{root}=\frac{f_{PLL1}}{5\,d_{pre}\,d_{post}}

但 MUX 不是 2 时不能使用这个式子。SDK 的 CLOCK_GetRootPreDivider()、CLOCK_GetRootPostDivider() 已经把寄存器编码加 1,分别返回实际的 1~8、1~64 分频数;不应再次加 1,也不应把异常的 0 静默改成 1。

RM Table 5-2 把 PWM3 的 ipg_clk、ipg_clk_highfreq 和 ipg_clk_s 都接到 PWM3_CLK_ROOT,门控都是 CCGR42。通用 PWM IP 中 CLKSRC=3 的“32 kHz”名称,在这个 SoC 集成上不代表另一根独立的 32 kHz 输入。SDK 枚举注释沿用了通用名称,应以 SoC 时钟连接为准。

i.MX8MP 的三种 PWM 时钟输入均来自对应 PWM 时钟根

PWM3 对应 CCGR42,寄存器偏移为 0x42a0

CCGR42 的偏移为 0x42a0。每个域的门控设置占一个两位字段,域依赖、PLL/root、权限与低功耗状态共同决定时钟是否可用。

ROM 时钟门控列表中的 PWM1 至 PWM4 行

原图中的 ROM 门控表不能用来假设启动后 PWM3 一定处于可用状态。Linux、U-Boot 或 M7 启动代码都可能改变相关配置,应确认当前所有者。

PWM 的预分频器、16 位计数器、比较器与四项采样 FIFO

设 root 频率为 frootf_{root},PWM 内部 PRESCALER 字段为 pp,实际内部除数为 p+1p+1,范围 1~4096。对于 PERIOD=0…0xfffe:

N=PERIOD+2,fPWM=froot(p+1)NN=PERIOD+2,\qquad f_{PWM}=\frac{f_{root}}{(p+1)N}

计数从 0 开始,到 PERIOD+1 后回到 0;一个周期有 NN 个计数时钟。写 PERIOD=0 得到两个计数时钟;写 0xffff 与 0xfffe 的周期效果相同,而且 RM 禁止在非零 REPEAT 下使用 PERIOD=0xffff。使用规范值 0xfffe 表达最长的 65536 个计数时钟更清楚。

在默认极性 POUTC=00 下,输出在周期开始置高、比较匹配时清低。对 0<SAMPLE<N0<SAMPLE<N:

Dhigh=SAMPLEN,thigh=SAMPLEfroot/(p+1)D_{high}=\frac{SAMPLE}{N},\qquad t_{high}=\frac{SAMPLE}{f_{root}/(p+1)}

例如在已确认的 66 MHz root、内部不分频条件下产生 200 kHz:N=330N=330,因此 PERIOD=328。精确 50% 应设置 SAMPLE=165;SAMPLE=164 是约 49.697%,高电平约 2.485 μs。

66 MHz 和 200 kHz 示例,PERIOD 为 328,图中 SAMPLE 164 仅约 49.697%

这张原图的周期值 328 正确,但 164 不能标为精确 50%。如果实际 root 为 24 MHz,同样 200 kHz、50% 则需要 PERIOD=118、SAMPLE=60。

PWM 时钟源的通用字段说明,具体连接以 i.MX8MP CCM 表为准

此图介绍 CLKSRC 的通用名称;在 i.MX8MP 上仍要采用上一节的实际 root 连接,不能从“低频时钟”字样直接代入 32768 Hz。

内部预分频器说明,除以 33 后仍需配置周期寄存器

此图中的预分频范围 1~4096 正确。66 MHz 除以 33 只得到 2 MHz 计数时钟;若要 1 kHz 输出,还需 N=2000N=2000,即 PERIOD=1998。

PWM 周期寄存器与计数值的原始解释图

PWM 计数周期与 0xffff 特例的原始解释图

这两图应结合 PERIOD+2 和 0xffff 特例阅读。写 PWMPR 会将计数器清零并开始新周期,不是保证无毛刺的影子更新;在线改周期会改变当前波形相位。

SAMPLE 164 与 330 个周期计数对应约 49.7% 占空比

该图已经把 164/330 写为约 49.7%;近似描述可以保留,精确 50% 时使用 165。

3. 极性、FIFO 和重复采样

跳转到“3. 极性、FIFO 和重复采样”
字段或值行为
POUTC=00rollover 置高、compare 清低;SAMPLE 表示高电平计数长度
POUTC=01rollover 清低、compare 置高;SAMPLE 对应低电平计数长度
POUTC=10/11PWM 输出断开;不能由这个描述推断板外引脚必为低、高阻或安全状态
SAMPLE=0POUTC=00 时恒低,POUTC=01 时恒高
SAMPLE>PERIOD+1不发生有效比较翻转;可表达恒定输出,但受 16 位采样值范围限制
REPEAT=0/1/2/3每个 FIFO 采样值使用 1/2/4/8 个周期,改变采样消耗速度,不改变基础 PWM 周期

PWM 输出极性、计数比较与波形关系

FIFO 有四个 16 位位置。写 PWMSAR 是入队,读取则得到当前使用或将使用的值,不保证立即读回刚写的值。FIFO 耗尽后 PWM 继续使用最后的占空比,直到新数据到达或关闭 PWM。新的采样值在周期边界使用,前面仍有排队值或 REPEAT 时,会推迟到它轮到被消费。

FIFOAV[2:0] 是当前已有数据的数量,0~4;名字中的 available 不能理解为剩余空位。FWM=0/1/2/3 分别在至少有 1/2/3/4 个空位时满足 FIFO empty 条件,FE 并不总等于 FIFO 数量为 0。

RM 的功能描述称“FIFO 满时继续写入产生 FWE”;但同一 Rev.3 的 12.2.4 启动序列明确要求预装 1~3 个值,不写第四个,并警告启动受 FWE 影响。原图和原文的“第四次/第五次写出错”差异有这个手册背景。启动示例遵循更具体的序列,只预装一个有效采样值;不使用四次预写来赌边界行为。

4. 六个寄存器及启动顺序

跳转到“4. 六个寄存器及启动顺序”

PWM1~4 的基址分别为 0x30660000、0x30670000、0x30680000、0x30690000,寄存器偏移相同:

寄存器偏移访问关键内容
PWMCR0x00R/WEN、SWR、REPEAT、PRESCALER、CLKSRC、POUTC、BCTR/HCTR、低功耗/调试位、FWM
PWMSR0x04部分 W1C、部分只读FWE[6]、CMP[5]、ROV[4]、FE[3];FIFOAV[2:0] 只读
PWMIR0x08R/WCIE[2]、RIE[1]、FIE[0],分别允许比较、回绕、FIFO empty 中断
PWMSAR0x0cR/W低 16 位 SAMPLE;写入 FIFO
PWMPR0x10R/W低 16 位 PERIOD;复位值 0xfffe
PWMCNR0x14只读低 16 位当前计数值,读取不改变计数

BCTR 控制采样字节交换;HCTR 控制总线写入数据的半字交换。普通 16 位数值写入不需要打开这些交换功能。

启动时先保证实例已停、时钟可访问、当前核有权限。需要清空旧 FIFO 时执行 SWR 并等待其自清除;SWR 保留 STOPEN/DOZEN/WAITEN/DBGEN,其他相关寄存器复位。随后在 EN=0 时设置计数时钟、预分频、输出极性、REPEAT 和 FWM,配置所需中断,写入 1~3 个初始值,处理 FWE/CMP/ROV,写 PERIOD,最后置 EN。

PWM 软件复位、初始 FIFO 数据与启动序列说明

PWM 配置与状态寄存器操作的原始示例

PWMSR 的事件位用写 1 清除,不要用普通寄存器的“读后按位清 0”方式;FIFOAV 则不能写清。FE 反映水位条件,清除后仍可能因条件满足再次出现。CLKSRC 只在 EN=0 时改变。

清 EN 会复位预分频器与计数器,尚未消费的 FIFO 内容保留,重启可能继续使用它们;SWR 才清空 FIFO。低功耗模式下还需相应模式允许位和源时钟都有效。调试暂停时是否继续输出由 DBGEN 及系统时钟状态决定,不能因为 CPU 停住就假定外部 ADC 不再被触发。

A53 GIC 表中的 PWM3 SPI 相对编号 83

Cortex-M7 中断表中的 PWM3 外设中断号 83

RM 的 A53 GIC 表用 SPI 相对编号 83 表示 PWM3;Linux 设备树也是 <GIC_SPI 83 ...>。这对应的 GIC 硬件 INTID 为 115,Linux 分配的虚拟 IRQ 则未必等于 83 或 115。M7 的 CMSIS 设备头另定义 PWM3_IRQn=83;数值相同不代表两种中断控制器的编号语义相同。

PWM3 可选 pad 及 ALT 复用模式列表

padPWM3 复用模式
SPDIF_TXALT1
I2C3_SDAALT1
GPIO1_IO10ALT2
SAI5_RXCALT2
GPIO1_IO14ALT5

GPIO1_IO10 的 PWM3 ALT2 复用与 SION 字段

GPIO1_IO14 的 PWM3 ALT5 复用与 SION 字段

SION 是强制输入路径的控制,不是 PWM 输出使能。需要结合具体底板确认 pad 是否引出、被哪个器件占用、电压域和驱动能力;不能由 SoC 的候选复用表推导所有板卡都能用这五个脚。A53/Linux 的 PWM、pinctrl、时钟和电源管理必须与 M7 的所有权安排一致。

6. 先验证参数,再启动

跳转到“6. 先验证参数,再启动”

原 fl_pwm_start_convst_hz() 把 root_hz / output_hz 直接写入 PERIOD,少减了 2;还固定按 PWM3 的 PLL1/5 计算时钟,忽略 MUX 和传入实例。其 lowPulseTicks 在默认极性下实际决定高电平长度,并且四次预装 FIFO 与上面的启动约束冲突。静默截断、夹紧到 16 位范围也会改变请求的波形。

下面只接受能够整除得到的频率和非恒定波形。它显式返回失败,让调用者决定是否接受其他频率;root_hz 必须是调用时真实有效且稳定的模块 root 频率,divider 是实际除数,high_ticks 是内部计数时钟个数。

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint16_t prescaler_field;
uint16_t period_field;
uint16_t sample_field;
} pwm_wave_plan_t;
bool pwm_plan_exact(uint32_t root_hz, uint32_t output_hz,
uint32_t divider, uint32_t high_ticks,
pwm_wave_plan_t *plan)
{
if (plan == NULL || root_hz == 0U || output_hz == 0U ||
divider == 0U || divider > 4096U) {
return false;
}
uint64_t denominator = (uint64_t)output_hz * divider;
if (root_hz % denominator != 0U) {
return false;
}
uint64_t ticks = root_hz / denominator;
if (ticks < 2U || ticks > 65536U ||
high_ticks == 0U || high_ticks >= ticks) {
return false;
}
pwm_wave_plan_t result = {
(uint16_t)(divider - 1U),
(uint16_t)(ticks - 2U),
(uint16_t)high_ticks
};
*plan = result;
return true;
}

在同一源文件加入下面的 PWM3 启动包装。调用前须已完成板级 root 配置、引脚和权限准备,并保证 PWM3 由本调用者独占;代码不重配任何 PLL。它先计算参数,失败时不触碰 PWM。reset_poll_limit 是轮询次数上限,不是微秒超时。

#include "fsl_pwm.h"
#include "fsl_clock.h"
bool pwm3_start_checked(uint32_t output_hz, uint32_t divider,
uint32_t high_ticks, uint32_t reset_poll_limit)
{
if (reset_poll_limit == 0U || !CLOCK_IsRootEnabled(kCLOCK_RootPwm3)) {
return false;
}
/* SDK uses a configured constant for EXT_CLK2, not a frequency meter. */
if (CLOCK_GetRootMux(kCLOCK_RootPwm3) == 5U) {
return false;
}
uint32_t root_hz = CLOCK_GetClockRootFreq(kCLOCK_Pwm3ClkRoot);
pwm_wave_plan_t plan;
if (root_hz > 66000000U ||
!pwm_plan_exact(root_hz, output_hz, divider, high_ticks, &plan)) {
return false;
}
CLOCK_EnableClock(kCLOCK_Pwm3);
if ((PWM3->PWMCR & PWM_PWMCR_EN_MASK) != 0U) {
return false; /* An active instance must first be stopped by its owner. */
}
pwm_config_t config;
PWM_GetDefaultConfig(&config);
config.prescale = plan.prescaler_field;
config.clockSource = kPWM_PeripheralClock;
config.outputConfig = kPWM_SetAtRolloverAndClearAtcomparison;
config.sampleRepeat = kPWM_EachSampleOnce;
if (PWM_Init(PWM3, &config) != kStatus_Success) {
return false;
}
PWM_SoftwareReset(PWM3);
while ((PWM3->PWMCR & PWM_PWMCR_SWR_MASK) != 0U &&
reset_poll_limit != 0U) {
--reset_poll_limit;
}
if ((PWM3->PWMCR & PWM_PWMCR_SWR_MASK) != 0U) {
return false;
}
if (PWM_Init(PWM3, &config) != kStatus_Success) {
return false;
}
PWM_DisableInterrupts(PWM3, kPWM_FIFOEmptyInterruptEnable |
kPWM_RolloverInterruptEnable |
kPWM_CompareInterruptEnable);
PWM_SetSampleValue(PWM3, plan.sample_field);
if ((PWM_GetStatusFlags(PWM3) & kPWM_FIFOWriteErrorFlag) != 0U) {
return false;
}
PWM_clearStatusFlags(PWM3, kPWM_FIFOWriteErrorFlag |
kPWM_CompareFlag | kPWM_RolloverFlag);
PWM_SetPeriodValue(PWM3, plan.period_field);
if ((PWM_GetStatusFlags(PWM3) & kPWM_FIFOWriteErrorFlag) != 0U) {
return false;
}
PWM_StartTimer(PWM3);
return true;
}

CLOCK_GetClockRootFreq() 按当前 MUX、源频率模型和实际 PRE/POST 分频计算;它不是示波器,仍依赖晶振、PLL 和 SDK 常量正确。本例拒绝 EXT_CLK2,实际项目如果使用它,应提供经确认的频率路径。

这个例子不需要 IRQ,初始化后让硬件持续产生波形。正常极性下若需求是“低脉冲长 L 个计数”,则应明确使用 high_ticks=N-L,或改为反相极性并同步调整协议;不能只改参数名称。驱动 ADC CONVST 还要核对有效边沿、高低脉宽下限、忙信号以及引脚电气条件,并在实际 pad 上测量频率、占空比与启动第一脉冲。