1. 这次为什么要重新调PPS输出先说背景。标题里那个“again”不是随便写的我在这块 STM32MP157D-DK1 上折腾 PPSPulse Per Second秒脉冲已经不是第一回了。之前大多数时间在做 PPS 接收也就是把 GPS/北斗模组输出的 1PPS 引到板子上给系统做时间同步校准。但这次反过来了要从板子上主动输出一路秒脉冲去同步下游的设备。方向一反过来问题完全不一样踩坑的姿势也跟着变了。PPS 这个东西本质就是一个“每秒出现一次”的脉冲信号边沿要对齐到整秒时刻。通信领域、电力自动化、测量仪器、车载设备里到处都在用。GNSS 接收机的 1PPS 是时间同步的基准而很多嵌入式设备需要自己产出一路 1PPS 来给其他设备提供时基或者作为测试激励。好比一群人要对表总得有一个人喊“开始”PPS 输出就是那个“喊开始”的角色。既然是喊开始的人你这嗓子什么时候喊、喊得多准就直接决定了整条链路的时间精度。STM32MP157D-DK1 这颗芯片是双核 Cortex-A7 加 Cortex-M4 的组合跑 Linux 的时候 A7 负责大活M4 可以做实时任务。和纯 MCU 相比它在“输出秒脉冲”这件事上有个天然的优势定时器资源足够丰富TIM2、TIM3、TIM4、TIM5 这些都是可以灵活配置的而且内核里有现成的 PWM 驱动不用自己从寄存器开始写。但同时也引入了一个麻烦那就是 Linux 的调度不确定性。你如果想着用软件去翻转 GPIO 来产生秒脉冲抖动会大到让你怀疑人生。所以正确的思路是尽量让硬件定时器自己去输出CPU 只负责偶尔“校准”一下相位而不是每个周期都亲自下场。这篇文章适用的人群是那些要在嵌入式 Linux 板卡上实现 PPS 输出、或者是想把板子自己的时间基准延伸到外部设备的工程师。我会把从硬件引脚选择、内核配置、设备树修改、到用户空间相位校准的完整链路都讲一遍也会把这次调试中实际遇到的几个问题原样记录下来。2. 输出PPS之前先把链路设计想清楚2.1 接收和输出的难点完全不是一回事接收 PPS 相对来说要简单一些。外部的秒脉冲进来GPIO 上产生一个上升沿中断触发或者定时器捕获内核记录一个时间戳。难点在于中断延迟、系统负载这些因素会把时间戳推偏但信号本身不是你自己产的你只要“读准”就行。输出就不一样了。你不仅要保证脉冲周期是 1.000000000 秒还要确保上升沿的相位——也就是它和 UTC 整秒的偏差——尽可能小。周期跑偏下游设备每隔一段时间就会累积出一个偏移相位乱跳下游设备每次同步都会被带偏。这两个指标对应的工程做法是冲突的周期稳定靠得好得用硬件定时器连续翻转相位对齐则需要软件介入来修正。怎么平衡这是整个方案设计的核心。我先给自己定了三个硬指标周期误差在常温下小于 50 ppm最好能到 20 ppm 以内上升沿抖动pulse jitter小于 50 微秒在 UTC 整秒附近的偏差可校准不能出现几百毫秒级别的随意漂移第一个指标用晶体振荡器驱动定时器很容易满足。第二个指标如果让硬件定时器自动翻转也基本能做到。第三个才是真正要花时间的因为你得有一个“绝对参考”来校准相位否则你输出的秒脉冲只是在“自说自话”频率可能很准但和外部时钟的相位对不上。2.2 方案选型定时器PWM、软件翻转、专用PPS驱动怎么选在 STM32MP157D-DK1 上有几种可行的输出方案我把它们的实测特性整理成了一张表方案最小抖动相位校准能力实现复杂度备注GPIO hrtimer 软件翻转100 微秒以上理论上可做低负载一大就完蛋不推荐TIM 定时器 PWM 输出低于 1 微秒可通过比较值微调中本次采用的方案pps-gpio 内核驱动接收为主不适合输出低内核里的 pps-gpio 主要是输入方向外部 RTC 秒脉冲与 RTC 晶振相关难以动态校准中精度受限于 RTC 芯片这里需要特别解释一下为什么 pps-gpio 不适合做输出。内核里 pps-gpio 这个驱动的名字容易让人误会但实际上它只是把一个 GPIO 的中断事件转成 PPS 事件上报给内核的时间子系统方向是“输入”不是“输出”。你如果要输出就得去找 GPIO 翻转或者 PWM 这类能控制电平变化的机制。我最终选择了 TIM2 的 PWM 输出模式原因有三个。第一STM32MP157D 的定时器主频足够高用预分频器可以灵活调整到 1MHz计数到 1000000 就是 1 秒精度可控。第二PWM 模式下电平翻转完全由硬件比较电路完成CPU 不参与也就不会因为调度抖动而受影响。第三STM32 定时器有个“影子寄存器”机制你可以在运行时修改比较值等计数器溢出后自动生效这意味着可以在不影响当前周期的情况下微调下一个周期的相位。2.3 一个容易被忽略的前提PWM输出还需要一个“初始相位”直接用 PWM 输出 1Hz 方波第一秒的上升沿是从使能定时器那一刻开始的这个时刻没有和任何外部参考对齐。如果下游设备只是需要一个“秒级节拍”那完全没问题。但如果下游设备要求你的秒脉冲和 GPS 秒对齐你就必须在启动后做一次“相位锁定”让定时器的计数起点或者让第一个上升沿出现的时刻落在一个已知的 UTC 整秒上。这块在调试时最容易绊倒人。我一开始的思维是配置好 PWM 就完事了结果示波器一看脉冲确实是一秒一个但和我手里的参考秒脉冲之间差了 300 多毫秒而且每次复位板子这个偏差还都不一样。后来才意识到这个偏差取决于 Linux 启动时间、驱动加载时间、以及使能 PWM 的那一瞬间落在哪个毫秒刻度上。要解决它必须引入“外部参考 校准动作”而不是单纯依赖 PWM 本身。3. 硬件端准备引脚、电平、示波器一个都不能少3.1 STM32MP157D-DK1 上能拿来输出PWM的引脚有哪些DK1 开发板的排针和 Arduino 接口上引出了不少定时器通道。我这次选的是 TIM2_CH1默认复用引脚是 PA5。这个引脚在板子上通过 Arduino 接口的 D13 附近引出具体位置在原理图里很容易找到。选 PA5 有几个原因它和 ST-LINK 的调试口不冲突不会影响日常开发TIM2 是 32 位定时器计数器范围大虽然这次只需要数到 1000000但 32 位给你留了余量后面如果要输出更低频率也不至于捉襟见肘在 DK1 的默认设备树里PA5 没有被其它外设占用省去了和别的驱动抢引脚的麻烦如果 PA5 不可用TIM3_CH1 在 PB4、TIM4_CH1 在 PB6也都是可以替换的选项。这里有个常见的坑STM32MP157D 的引脚复用并不完全和老的 STM32 系列一样你需要对着参考手册里的 AF 表格逐行确认而不是只看引脚名。比如 PA5 到底是 TIM2_CH1 还是 SPI1_SCK、还是 ETH 相关信号取决于你在设备树 pinctrl 里选择了哪一组复用功能。3.2 电平匹配和波形观察的几个细节STM32MP157D-DK1 的 GPIO 输出是 3.3V 电平这个不需要额外处理。但如果下游设备是 5V 逻辑或者是一根传输距离超过 20 厘米的连接线建议在输出端串一个 33 欧姆到 100 欧姆的电阻用来抑制边沿反射和振铃。我这次实际测量发现不加电阻直接接示波器探头上升沿处有一个明显的过冲大概有 0.6V 左右的反弹虽然不至于损坏器件但会让下游设备的电平判断点移动给时间测量引入额外误差。另外如果输出端要驱动长线或者光耦最好加一级缓冲。我习惯用 74LVC1G17 这类施密特触发器缓冲器它能把边沿重新整理得干净利落。直接拿 GPIO 去推长线一是驱动能力有限二是线缆电容会把边沿拉长上升时间从几纳秒变成几百纳秒这对秒脉冲来说是不可接受的因为边沿越缓下游设备触发点的不确定度就越大。3.3 上电后先别急着配PWM手动验证一下引脚可控在改设备树之前先用系统自带的 libgpiod 工具验证引脚能不能正常控制。这一步很多人会跳过但实际上能帮你排掉一批硬件接线问题。# 查找 PA5 对应的 gpio 名称 gpiofind PA5 # 输出低电平、高电平测试 gpioset gpiofind PA50 gpioset gpiofind PA51如果命令报错说找不到这个 GPIO 或者操作被拒绝那大概率是引脚被其他驱动占用了。这时候不要急着去改设备树先去/sys/kernel/debug/gpio里看一眼这个引脚的 owner 是谁。我遇到过一次 PA5 被 SPI1 的驱动程序抢占的情况板子默认设备树里开了 spi1gpioset 一直报 “Device or resource busy”排查了好久才想起来去看 pinmux 状态。4. 内核配置与设备树把PWM挂到正确的位置上4.1 内核需要开启哪些配置项STM32MP157D 的官方内核或者你用的 OpenSTLinux 发行版通常已经默认打开了 STM32 Timer PWM 驱动但为了保险起见还是在编译内核前确认一下这几个配置项CONFIG_PWMy CONFIG_PWM_STM32y CONFIG_PWM_STM32_TIMy CONFIG_PPSy CONFIG_PPS_CLIENT_GPIOyPWM 相关的驱动是输出信号的基石必须开。PPS 相关配置虽然这次主要是做输出但后面做相位校准时还是需要接收外部参考秒脉冲的所以也一并打开。可以用下面这个命令检查当前运行内核是否已经包含这些模块zcat /proc/config.gz | grep -E PWM_STM32|PPS如果输出的grep结果为空说明当前内核没有导出 config需要去查看你编译内核时的.config文件。我这次用的是 Yocto 构建的镜像配置在源码目录的build/linux-stm32mp/下。4.2 设备树修改的具体写法在stm32mp157d-dk1.dts中TIM2 节点默认可能处于 disabled 状态你需要把它对应的 PWM 子节点打开并且配置好 pinctrl。以下是我实际使用的设备树片段timers2 { status okay; pwm2: pwm { pinctrl-0 tim2_ch1_pins_a; pinctrl-names default; #pwm-cells 3; status okay; }; };这段配置的含义是打开 TIM2 外设在该外设下启用一个 PWM 子节点子节点的引脚复用选择tim2_ch1_pins_a这一组。#pwm-cells 3表示消费 PWM 的节点需要传三个参数通常是 channel、period、flags。然后要确认tim2_ch1_pins_a在 pinctrl 头文件里存在。在stm32mp15-pinctrl.dtsi中可以看到类似这样的定义tim2_ch1_pins_a: tim2-ch1-0 { pins1 { pinmux STM32_PINMUX(A, 5, AF1); bias-disable; drive-push-pull; slew-rate 0; }; };这里最重要的就是那个AF1它告诉芯片 PA5 要切换到 TIM2_CH1 复用功能。如果你选错了 AF 编号PA5 可能变成了别的外设信号或者干脆是 GPIO 模式这时 PWM 必然没有输出。修改完 dts 之后重新编译 dtbmake dtbs然后把生成的stm32mp157d-dk1.dtb复制到 boot 分区重启即可。我没有用 Device Tree Overlay 的方式因为对这块板子来说直接改主 dts 更直观排查问题也方便。如果你在项目中不想动主 dts也可以用 u-boot 的 overlay 机制动态加载但调试时会多一层间接性。4.3 重启后怎么确认PWM驱动真的加载了重启后第一件事就是看/sys/class/pwm下有没有新的 pwmchip 出现ls /sys/class/pwm/正常情况下会出现pwmchip0或者更多编号。每个 pwmchip 代表一个定时器 PWM 控制器。但要注意STM32MP157D 有多个定时器即使你只开了 TIM2也可能出现多个 pwmchip 节点因为打开的设备树里可能还有别的 PWM 子节点。用下面的命令查看每个 pwmchip 对应哪个定时器cat /sys/class/pwm/pwmchip0/device/uevent这在你的发行版上可能略有差异但一般都能看到OF_NAME、OF_FULLNAME等属性里面会写清楚对应的定时器节点如果你在设备树里配置了 PWM 子节点但/sys/class/pwm下没有新设备最常见的原因有两个第一pinctrl 没生效导致引脚复用不对第二定时器的时钟没有被使能这通常和时钟框架设置有关需要检查内核日志里有没有定时器相关的错误。这时候建议顺手看一下dmesg | grep -i pwm和dmesg | grep -i timer里面经常会直接爆出错误原因。5. 用户空间实操让PA5真正输出1Hz脉冲5.1 先用sysfs快速验证PWM通道PWM 驱动加载成功后可以用 sysfs 接口快速导出通道并设置参数。假设 TIM2 对应的 pwmchip 是 pwmchip0通道是 pwm0echo 0 /sys/class/pwm/pwmchip0/export # 设置周期为 1 秒单位是纳秒 echo 1000000000 /sys/class/pwm/pwmchip0/pwm0/period # 设置占空比1ms 高电平 echo 1000000 /sys/class/pwm/pwmchip0/pwm0/duty_cycle # 使能输出 echo 1 /sys/class/pwm/pwmchip0/pwm0/enable此时用示波器或逻辑分析仪接 PA5应当能看到一个周期 1 秒、高电平 1 毫秒的脉冲。如果一切正常第一步就算成功了。这里有一个容易疑惑的点为什么高电平只给 1 毫秒而不是 500 毫秒50% 占空比因为 PPS 信号在接收端通常只关心上升沿脉冲宽度只要足够让接收端的中断或者捕获模块稳定触发就行。1 毫秒是 PPS 比较常见的脉宽兼顾了低功耗和信号完整性。如果你下游设备要求更宽的脉冲可以自行调整duty_cycle一般 10 毫秒以内都不影响边沿精度。5.2 周期计算预分频器和自动重载值怎么配在 sysfs 接口里你直接设置period和duty_cycle就行驱动会自己去换算定时器的预分频和比较值。但我建议你还是搞清楚背后的计算逻辑因为做相位校准的时候可以直接操作寄存器或者通过内核接口来微调。STM32MP157D 的 TIM2 挂在 APB1 总线上时钟默认一般是 100 MHz具体以你的 PLL 配置为准可以用clk_get_rate查。内核 PWM 驱动会默认选择一组参数使得输出频率准确。你设置period10000000001 秒时驱动实际上会计算预分频器和自动重载值让计数器从 0 数到某个数再溢出。如果想手动计算假设 TIM2 输入时钟 100 MHz预分频 PSC99则计数时钟为 1 MHz一个计数周期是 1 微秒。要产生 1 秒周期自动重载值 ARR 就是 1000000 - 1 999999。设置比较值 CCR999则高电平持续 1000 微秒也就是 1 毫秒。这样计算的最终时间基准是晶振。DK1 板上的晶振一般是 24 MHz经内部 PLL 倍频到处理器时钟再从 AHB/APB 分频到 TIM2。晶振本身的精度如果按 20 ppm 算输出 1PPS 每秒偏差不会超过 20 微秒这个量级对绝大多数应用是达标的。但如果你的板子用的是普通谐振器而不是晶振或者省掉了外部晶振那频率温度漂移就会明显变大需要注意。5.3 用C程序实现真正的相位校准sysfs 的方式适合快速验证但要做相位对齐还是得写个小程序。下面这段 C 代码的思路是通过clock_gettime拿到系统时间找到下一个整秒对应的绝对时刻然后控制 PWM 的输出让第一个上升沿出现在这个时刻之后约 1 毫秒处。这里不展示完整工程代码只给出核心逻辑#include stdio.h #include time.h #include fcntl.h #include unistd.h #include string.h #include sys/ioctl.h #include linux/pwm.h static long long get_next_second_ns(void) { struct timespec ts; clock_gettime(CLOCK_REALTIME, ts); return (long long)(ts.tv_sec 1) * 1000000000LL; } static void export_pwm(int chip, int channel) { char buf[64]; int fd; snprintf(buf, sizeof(buf), /sys/class/pwm/pwmchip%d/export, chip); fd open(buf, O_WRONLY); if (fd 0) { perror(export); return; } snprintf(buf, sizeof(buf), %d, channel); write(fd, buf, strlen(buf)); close(fd); usleep(200000); // 等待sysfs节点生成 } static void write_sysfs(const char *path, const char *value) { int fd open(path, O_WRONLY); if (fd 0) { perror(path); return; } write(fd, value, strlen(value)); close(fd); } int main(void) { int chip 0, channel 0; char path[128]; export_pwm(chip, channel); snprintf(path, sizeof(path), /sys/class/pwm/pwmchip%d/pwm%d/period, chip, channel); write_sysfs(path, 1000000000); snprintf(path, sizeof(path), /sys/class/pwm/pwmchip%d/pwm%d/duty_cycle, chip, channel); write_sysfs(path, 1000000); // 校准开始前先关闭输出等待一个整秒时刻 snprintf(path, sizeof(path), /sys/class/pwm/pwmchip%d/pwm%d/enable, chip, channel); write_sysfs(path, 0); long long next get_next_second_ns(); // 这里在实际项目中应当通过高精度延时让enable动作恰好发生在next附近 // 更精确的做法是使用timerfd或者CLOCK_MONOTONIC配合nanosleep write_sysfs(path, 1); return 0; }这段代码只是演示了“导出通道 - 设置周期/占空比 - 使能输出”的基本顺序但真正要让第一个脉冲边沿对齐到整秒还需要enable的操作发生在精确时刻。在 Linux 用户空间可以用timerfd配合CLOCK_REALTIME来实现毫秒级的定时误差在几十微秒到一两百微秒之间。如果要求更高就得借助内核驱动或者 M4 核来协作。我在实测中发现单纯靠 Linux 用户空间开启 PWM即使掐准了enable的时刻实际第一个上升沿和整秒偏差也在 100 到 300 微秒之间原因是write系统调用本身需要时间而且 PWM 驱动使能寄存器到计数器开始计数也有延迟。这个偏差如果不在你的容忍范围内就得上更硬的方案。5.4 一个实用的“粗对齐 细修正”组合既然用户空间直接开启 PWM 有不确定性一个很自然的改进是先用外部参考 PPS 测出当前输出相位偏差然后周期性地去微调 PWM 的周期或比较值把相位拉回来。简单做法是这样的TIM3_CH1 配置成输入捕获接收参考 1PPS比如来自 GPS 接收机TIM2_CH1 保持 PWM 输出每次外部参考 PPS 的上升沿到来时读取 TIM2 当前计数器值和 TIM3 捕获的时间戳算出“输出相位”相对“参考相位”的误差根据这个误差微调 TIM2 的 ARR自动重载值让下一个秒脉冲提前或推迟零点几微秒这本质上是一个软件锁相环。控制量可以很简单误差为正输出晚了就把 ARR 减 1误差为负输出早了就把 ARR 加 1。一次调整量非常小但经过几个周期后相位会被逐渐拉到零附近。为了防止超调我加了一个简单的比例系数当误差小于 50 微秒时不调整进入“死区”。这样 PPS 输出在绝大多数时间里保持硬件自动翻转的状态只有偶尔才被微调一次精度和稳定性兼顾。这种做法的好处是即使系统启动时初始相位是乱的只要有外部参考它也能在几秒内收敛到正确的对齐状态。我在测试中从启动到相位稳定大约花了 5 个秒脉冲周期。稳定之后用示波器对比外部参考上升沿偏差基本在正负 5 微秒以内抖动主要来自示波器本身的触发误差。6. 调试实录这次遇到的几个典型问题与解决过程6.1 配置了设备树但PA5没有任何波形这是我第一次尝试时最先碰到的问题用 sysfs 写 enable1 之后PA5 上量不到任何电平变化。排查过程如下第一反应是检查 sysfs 节点确认enable、period、duty_cycle都能正常读写说明 PWM 驱动本身加载了然后用cat /sys/kernel/debug/pwm查看 PWM 寄存器的实际状态发现通道已经 enable但输出极性显示为 normal寄存器状态看起来是正常的再查 pinctrl用cat /sys/kernel/debug/pinctrl/*/pinmux-pins | grep PA5发现 PA5 的当前功能是gpio而不是tim2_ch1_pwm进一步查设备树发现我改了timers2节点但宏TIM2_CH1_PA5没有生效因为 pinctrl 的tim2_ch1_pins_a定义里引用的引脚不是 PA5而是另一个引脚。最后才发现问题出在 pinctrl 引用的选择上。tim2_ch1_pins_a这一组在官方 dtsi 里可能绑定了 PA5但在某些内核版本里它绑定到了 PA0。解决办法是显式在 dts 里覆写 pinctrl 的 pinmuxpinctrl { tim2_ch1_pa5: tim2-ch1-pa5-0 { pins1 { pinmux STM32_PINMUX(A, 5, AF1); bias-disable; drive-push-pull; slew-rate 0; }; }; }; timers2 { status okay; pwm2: pwm { pinctrl-0 tim2_ch1_pa5; pinctrl-names default; #pwm-cells 3; status okay; }; };6.2 波形有输出但周期不是严格的1秒用示波器测 PWM 输出发现周期是 1.000023 秒左右偏差大约 23 ppm。这个量级对普通 PPS 应用来说勉强能用但如果要求长期稳定还得找出原因。我做的第一件事是确认 TIM2 的输入时钟频率。通过内核的时钟调试接口cat /sys/kernel/debug/clk/clk_tim2_ker/rate得到的结果是 100000000 Hz看起来没问题。再查驱动源码发现 PWM 驱动在处理period1000000000ns时因为要做预分频的参数搜索可能会选择一个不完美的分频组合导致实际输出周期和你设置的目标之间有一个小的量化误差。这属于驱动设计的权衡一般人不仔细看不会发现。解决办法是把预分频和自动重载值直接通过设备树属性或者自定义驱动参数固定下来不让驱动自己搜索。比如我在 dts 里给 PWM 子节点加了st,pwm-period-ns 1000000000这种自定义属性实际要看驱动支持与否然后改驱动让它优先使用这个值来计算分频系数。如果你不想改内核也可以接受这个 ppm 级的偏差同时用第 5.4 节的软件锁相环持续微调把长期平均周期拉回到 1 秒。6.3 用示波器看出来的上升沿抖动很大第一次用 PPS 接收模块对比时发现输出脉冲的上升沿相对参考 PPS 的偏差在 ±200 微秒左右波动远大于预期。排查原因后发现问题不在 PWM 输出本身而在于我用来对比的参考 PPS 是通过一个 USB 串口模块接入系统的USB 传输引入了大量抖动。用示波器的 CH1 和 CH2 同时测量两个信号时抖动立刻降到了 ±15 微秒以内。这个教训很重要不要用一个本身就很抖的信号去衡量另一个信号的抖动。测量 PPS 信号精度的底线是至少有一路参考信号来自硬件边沿比如 GPS 模块直接输出的电气信号经过示波器通道接入。如果两边都经过 USB、网络或者软件打点测出来的抖动数据没有任何参考价值。6.4 重启后配置丢失这算不上 bug但容易让人在调试中分心。sysfs 里设置的 PWM 参数在重启后都会清空所以不要把“手工配置好能工作”等同于“系统已经支持 PPS 输出”。你需要把这套配置固化到启动脚本里或者干脆写一个 systemd service在系统启动后自动完成 PWM 导出和参数设置。我的做法是在/etc/systemd/system/pps-out.service里写了一个简单的 oneshot 服务启动时执行一段脚本内容就是第 5.1 节那几条 echo 命令。如果要做得更规范可以用 C 程序来完成但脚本对于多数场景已经足够。6.5 常见问题速查表现象可能原因排查方法PA5 无输出pinctrl 没生效或引脚被占用查 /sys/kernel/debug/pinctrl 和 gpio 状态周期偏差达到几十 ppmPWM 驱动分频参数量化误差固定预分频和 ARR或使用软件锁相上升沿抖动大参考信号本身不可靠用示波器直接对比电气信号使能 PWM 后系统卡死定时器中断冲突或时钟配置错误检查 dmesg确认 TIM2 的时钟和中断输出频率是 2Hz 或 500Hzperiod 单位写错用了 ms 而非 ns确认 sysfs 的 period 单位是纳秒占空比太小下游设备检测不到脉宽设置过短适当增加到 1ms 或 10ms7. 最后说点实际体验这块板子的 PPS 输出在我把软件锁相那套逻辑跑通之后已经稳定运行了几天。刚开始那几天我时不时会拿示波器看一眼后来基本就不管了因为它一旦锁定到一个正确的相位就不会自己跑飞。你只需要保证外部参考 PPS 还在软件校准服务没有被杀掉它就会一直维持住这个状态。如果未来要在这个基础上做更严苛的同步可以考虑把相位校准逻辑从 A7 核挪到 M4 核上。Cortex-M4 可以直接访问定时器寄存器不需要经过 Linux 子系统延迟会小一个量级校准的平滑度也会更好。不过那会引入主核和协核之间的通信机制复杂度会上升不少。我个人觉得在多数工业应用里Linux 用户空间加硬件 PWM 的方案已经够用了精度和稳定性都在可接受范围内。如果你也恰好卡在 STM32MP157D-DK1 的 PPS 输出上希望这份记录能帮你少走一点弯路。特别是相位校准那一步不要幻想一配好设备树就能和 GPS 秒脉冲对齐那几乎是不可能的。想清楚你要的精度、愿意接受的复杂度然后从简单的 PWM 输出开始一步步加上校准这条路会顺利很多。