
开发环境价值一块 STM32MP257 评估板ST 官方 SDKYocto 或 OpenSTLinux构建的完整镜像问题可以稳定复现便于反复实验串口终端或者 SSH 登录到板卡 Linux 系统排查和验证的入口设备树源码、内核源码及对应的 BSP 包定位最终根因的关键可断电重启的供电方案验证“重启后时间是否保留”的必要条件环境差异可能导致现象不同。比如有的板卡外接了一个 I2C 接口的 RTC 芯片有的是用 STM32MP257 内部 RTC这两条路径的排查方向差异很大。我这次遇到的是内部 RTC也就是芯片自带的备份域 RTC所以后文会围绕这条线往下走。1.2 第一轮排查先确认时间“流”到了哪一层遇到这类问题我习惯先做一轮快速的“分层排查”不急着改代码。Linux 系统里时间至少分成三层内核系统时间、硬件 RTC 时间、用户态显示的本地时间。date命令改的是内核系统时间hwclock命令负责和硬件 RTC 同步。如果/dev/rtc0这个设备节点根本不存在那hwclock就没有操作对象硬件时间自然也就写不进去。这一轮排查我做了几件事# 1. 看看当前系统时间 date # 2. 尝试设置系统时间 date -s 2025-06-01 10:00:00 # 3. 确认系统时间是否设置成功 date # 4. 查看 RTC 设备节点是否存在 ls -l /dev/rtc* # 5. 尝试读取硬件时间 hwclock -r执行结果如下表所示问题已经很明显了操作结果解读date -s 2025-06-01 10:00:00成功无报错系统时间可以写内核的CLOCK_REALTIME是正常的date显示2025-06-01 10:00:00系统时间确实被改了ls -l /dev/rtc*ls: cannot access /dev/rtc*: No such file or directory内核没有注册 RTC 设备这是第一根断点hwclock -r/dev/rtc0: No such file or directory用户态无法访问硬件 RTC到这里问题已经收敛了一大半不是“本地时间”这个概念上的设置失败而是在硬件时间这一层断掉了。系统时间是“活”的但你让它和 RTC 同步时找不到 RTC 设备。1.3 为什么date -s会“假成功”很多人会在这里被迷惑明明date -s成功了为什么重启后时间还是丢了这是因为date -s只修改了内核运行在内存里的软件时钟。RAM 里的数据在断电后必然会消失所以如果你没有把时间同步到 RTC也没有通过 NTP 等服务持久化时间那么每次冷启动后系统时间都会回到内核默认值通常是 1970-01-01 00:00:00 或者 BSP 里固件设置的某个基准时间。换句话说整个链路是用户执行 date -s ↓ 内核更新 system timeRAM 中 ↓ 如果没有 RTC 驱动 / 设备节点 ↓ hwclock -w 失败硬件 RTC 时间不会更新 ↓ 断电重启RAM 清空一切回到原点所以当用户说“Cannot Set Local Time on STM32MP257”时我第一反应往往是先别去找时区配置先确认hwclock能不能通。RTC 设备节点不存在后面所有关于本地时间、时区、永久保存的讨论都是空中楼阁。2. 到底是谁在管 STM32MP257 的本地时间完整链路拆解既然要把这个问题讲透就不能只停在“换一个设备树然后重编”的层面。我习惯先把链路拆清楚这样后续排查才不会靠猜。2.1 系统时间、硬件 RTC、时区三个角色三种职责很多人把“时间”当成一个东西但在 Linux 上时间至少拆成三部分。内核系统时间System Time由 Linux 内核维护底层依赖定时器中断和clocksource驱动。它本质上是一个从某个基准点开始计数的单调值同时内核会维护一个墙上时钟wall clock供用户态使用。date、stat、touch等命令看到的时间都来自这里。硬件 RTC 时间Hardware Time独立于操作系统的时钟芯片通常有一块纽扣电池或超级电容供电即使主系统断电也能继续走时。STM32MP257 的内部 RTC 位于备份域由VBAT引脚供电。时区与本地时间Local Time内核和 RTC 本身并不关心“北京时间”还是“纽约时间”它们内部统一使用 UTC。用户态通过/etc/localtime、/etc/timezone或TZ环境变量把 UTC 转换为本地时间显示。所以“设置本地时间”这个动作实际拆开是设置 UTC 时间、配置时区、让 RTC 保存 UTC 时间。三者之间的关系可以用一句话概括RTC 保存 UTC内核维护 UTC显示层转换成本地时间。如果你在嵌入式系统里没有配置任何时区文件那么系统默认会按 UTC 显示看起来时间就好像“慢了 8 小时”或者“快了 X 小时”但这通常不是“不能设置”的根因而是“设置之后显示不对”的另一个问题。2.2 STM32MP257 的 RTC 硬件路径STM32MP257 这颗芯片的 RTC 有几个特点排查前必须有数内部 RTC 位于备份电源域需要给VBAT引脚供电。很多评估板出厂时没有焊接电池或超级电容导致 RTC 在断电后无法保持走时看起来就像“时间没有保存”。RTC 的时钟源通常来自外部低速晶振LSE32768 Hz也有部分设计用内部 RC 振荡器。如果 LSE 没有起振或起振失败RTC 可能无法正常计数。在 OpenSTLinux 软件栈里RTC 的访问路径可能经过 Secure Monitor 或 OP-TEE。也就是说不一定 A35 侧 Linux 内核能直接访问 RTC 寄存器驱动和硬件之间可能隔着一层固件接口。2.3 “本地时间”和“RTC 时间”在不同层级的存储方式结合 STM32MP257 的典型 BSPLinux 系统中时间的流转路径大致如下硬件 RTC备份域 --- 内核 rtc-stm32 驱动 --- /dev/rtc0 ↑ | | hwclock / timedatectl | | ↓ 用户态工具 --- 系统时间内核 UTC --- 应用层读取正常情况下系统启动时内核会读取 RTC 并初始化系统时间。之后如果修改了系统时间需要通过hwclock -w写回 RTC。切换时区只影响显示层不改变 RTC 或系统时间的“绝对瞬间”。所以如果“不能设置本地时间”指的是设置任意时间后重启丢失那基本可以锁定在 RTC 驱动、设备树或硬件供电这三方面。如果指的是设置后立即生效但显示成 UTC那才是时区配置问题。先区分这两种现象排查路径会完全不同。3. 从/dev/rtc0消失入手定位驱动与设备树断点接下来进入正题。我这边的现象是/dev/rtc0不存在所以排查方向很明确让内核先注册出 RTC 设备。而一个 RTC 设备要出现在/dev下需要同时满足设备树里有对应节点且状态使能、内核配置勾选了对应驱动、驱动 probe 成功。3.1 先检查内核配置RTC 子系统有没有被编译进去第一件事确认内核里 RTC 框架和对应驱动是否启用。如果你的 BSP 镜像是在 Yocto 里构建的可以用菜单配置来检查但在板子上最快的方式是# 如果内核开启了 CONFIG_IKCONFIG zcat /proc/config.gz | grep -i rtc如果没有IKCONFIG也可以在源码目录下检查.configgrep -i rtc .config重点关注这几项CONFIG_RTC_CLASSy CONFIG_RTC_DRV_STM32y CONFIG_RTC_DRV_STM32_LO_DATE_SUPPORTy我这次板子上的结果比较诡异CONFIG_RTC_CLASSy有但CONFIG_RTC_DRV_STM32没有。这意味着内核对 RTC 支持已经打开但是没有编译 STM32 自己的 RTC 驱动。这种配置偏差在量产板和评估板之间经常出现有时候是 BSP 默认配置只开了某个目标板的驱动换芯片型号后没同步更新。如果不方便查看.config还可以直接看内核模块是否存在find /lib/modules/$(uname -r) -name *rtc*如果驱动被编译成模块但lsmod | grep rtc没有加载那还要看模块加载顺序和依赖。3.2 检查设备树rtc 节点是否被正确使能内核配置是对的还不够。设备树里还需要有一个rtc节点并且状态为okay。STM32MP257 的芯片级设备树一般在stm32mp257f.dtsi或者stm32mp25xf.dtsi这类文件中板级设备树文件则位于arch/arm64/boot/dts/st/下。排查方法是用板级设备树覆盖文件的最终状态看rtc节点的 status 到底是什么。在运行中的系统上可以通过设备树二进制导出节点来确认# 找到 rtc 节点的设备树路径通常在 /proc/device-tree/soc/rtcxxxx cat /proc/device-tree/soc/rtc*/status如果输出是disabled或者路径不存在那就是设备树的问题。常见的表现有两种板级 dts 文件里写了rtc { status disabled; };直接把这个外设关掉了。芯片级 dtsi 里本来就是status disabled而板级 dts 没有覆盖为okay。ST 很多外设默认在 dtsi 里是disabled由板级文件显式使能这种做法可以降低芯片默认功耗和启动时的资源占用。在 BSP 源码里检查时可以这样搜索# 在板级 dts 中搜索 rtc grep -rn rtc arch/arm64/boot/dts/st/stm32mp257*.dts # 查看 rtc 节点定义 grep -n -A 20 rtc: arch/arm64/boot/dts/st/stm32mp257f.dtsi我这次就是在这里发现了问题板级 dts 里确实没有写status disabled但也没有显式写status okay。奇怪的是我照着另一个板子 dts 对比发现它们写法几乎一样。这就意味着问题可能不在设备树状态而在更底层。3.3 驱动 probe 失败dmesg 里没有 rtc-stm32 的注册信息排除掉内核配置和设备树使能问题后我继续在运行日志里找线索。如果驱动 probe 成功dmesg里通常会有类似这样的信息rtc-stm32 5c004000.rtc: registered as rtc0 rtc-stm32 5c004000.rtc: setting system clock to ...但我这边查dmesg | grep -i rtc输出完全是空的。这意味着驱动甚至没有进入 probe 流程或者根本没被注册。此时需要确认驱动是否作为平台驱动被内核识别# 查看平台设备是否生成 ls -l /sys/bus/platform/devices/*rtc*如果这个路径下找不到设备说明设备树里rtc节点没有被正确转换为platform device。问题可能在节点地址、compatible属性和驱动匹配之间。还有一次我碰到类似情况是因为 dts 里rtc节点的clocks属性引用的时钟不存在导致驱动在probe早期的devm_clk_get阶段就返回-EPROBE_DEFER然后又一直等不到时钟就绪。这种情况比较隐蔽要看完整启动日志里有没有-EPROBE_DEFER相关的提示。检查手段# 查看 clk 是否 ready cat /sys/kernel/debug/clk/clk_summary 2/dev/null | grep lse在 STM32MP257 里RTC 依赖 LSE 时钟。如果外部 32768 Hz 晶振没焊接、没起振或者时钟树配置里 LSE 不可用RTC 驱动 probe 同样会失败。这里有一个很容易忽略的细节即使系统时间完全正常也不代表 LSE 没问题因为系统时间的时基可以来自其他时钟源。3.4 一个容易忽略的权限问题来自 OP-TEE 或 TF-A 的地址保护前面几层都正常但 RTC 仍不可用的情况我建议把视线抬到安全固件层级。STM32MP2 系列支持 TrustZone。如果安全世界TEE/OP-TEE把 RTC 的寄存器或者中断配置成了安全外设非安全世界Linux在尝试访问时就会被拦截轻则操作无效重则直接触发异常。这种情况下Linux 内核里即使有驱动也会因为访问被拒而 probe 失败。排查这个方向最直接的办法是看 OP-TEE 的日志以及检查 TF-A 的设备树配置中是否有关于 RTC 或备份域的安全属性设置。在 ST 提供的 BSP 中stm32mp257f-ev1.dts或板级 dts 里可能包含类似secure-status disabled这样的标记。如果发现 RTC 节点被标记为 secure那么 Linux 侧需要同时配合修改 TF-A/OP-TEE 的配置光改内核 dts 是没用的。我在实际项目里遇到过类似的坑当时不是 RTC是一个 GPIO 控制器在 OP-TEE 侧被配置成了 secure 外设导致 Linux 侧无论如何操作都没反应。排查了半天最后发现是安全固件配置的问题。所以这类问题不能只盯着 Linux 内核看要把整个软件栈当成一条流水线来查。4. 修复方案与验证让重启后的本地时间不再“穿越”定位到根因之后修复就相对直接了。我遇到的根因是内核配置缺少CONFIG_RTC_DRV_STM32同时板级设备树里没有显式启用 RTC 节点。下面给出完整的修复流程和验证清单也可以作为你处理同类问题时的参考。4.1 内核配置与设备树补丁首先确认内核配置。在 Yocto 工程或者 OpenSTLinux SDK 内核源码目录下执行cd linux-stm32mp make menuconfig进入路径Device Drivers - Real Time Clock - STM32 RTC (ON/OFF)确认STM32 RTC选项被勾选为*编入内核而不是M模块。如果你是直接改配置文件则检查.config中是否有CONFIG_RTC_CLASSy CONFIG_RTC_DRV_STM32y CONFIG_RTC_DRV_STM32_LO_DATE_SUPPORTy然后检查设备树。在板级 dts 文件中显式添加或确认以下内容rtc { status okay; /* * 有些 BSP 版本还要求配置 LSE 时钟相关属性 * 具体以你拿到的内核源码里的绑定文档为准 * Documentation/devicetree/bindings/rtc/st,stm32-rtc.yaml */ };如果芯片级 dtsi 中rtc节点的compatible属性是st,stm32mp25-rtc或类似名称要确保驱动源码中的of_device_id表和这个字符串匹配。我习惯把这类修改拆成两个独立 commit一个专门改内核配置一个专门改设备树。这样回溯问题时能快速确认是哪一层出了错。重新编译内核和设备树后烧录到板卡启动后依次验证。4.2 重启后验证 RTC 设备与读写进入系统后第一件事还是看/dev/rtc0ls -l /dev/rtc0正常情况下应该看到crw-rw----这种字符设备节点。如果看到了再执行# 读取 RTC 时间 hwclock -r # 写入当前系统时间到 RTC hwclock -w # 再次读取确认 hwclock -r此时hwclock -r应该能输出一个有效的日期时间和date显示的时间一致。接下来最关键的一步是断电重启。只有断电重启后系统时间仍能恢复到接近 RTC 保存的时间才说明整条链路是通的。验证命令如下date hwclock -r如果系统时间接近 RTC 时间说明 RTC 已经在启动阶段被读取并初始化了系统时间。另外可以再确认一下内核日志dmesg | grep -i rtc正常的输出类似rtc-stm32 5c004000.rtc: registered as rtc0 rtc-stm32 5c004000.rtc: setting system clock to 2025-06-01T02:00:00 UTC看到registered as rtc0和setting system clock就说明链路完全打通。4.3 时区与本地时间配置别让时间在显示层“背锅”设备通到 RTC 之后接下来才是“本地时间”的环节。如果你在北京时区UTC8但系统启动后显示的时间总是和 UTC 一致那就是时区配置问题和 RTC 无关。在嵌入式 Linux 里最稳妥的时区配置方式是使用/etc/localtime。我建议优先用timedatectl# 查看当前时区状态 timedatectl # 设置时区 timedatectl set-timezone Asia/Shanghai如果系统里没有timedatectl有些裁剪过的 BSP 没有 systemd那就手动拷贝时区文件# 复制时区文件到 /etc/localtime cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime注意/etc/localtime是文件还是符号链接都可以关键是内容必须正确。同时如果是 Yocto/Buildroot 构建的镜像还可以在发行版配置中直接预设tzdata和默认时区避免每次都要手工配置。一个容易踩的细节是hwclock -r读到的默认是 UTCdate显示的是本地时间。当 RTC 驱动正常、系统时间和 RTC 同步后如果/etc/adjtime里写了LOCALhwclock会把 RTC 的本地时间当作本地时间处理这会导致 UTC 和本地时间混用后错乱。所以最推荐的策略是RTC 始终保存 UTC显示层负责转换。这在多系统启动、多开发人员维护时最不容易出错。4.4 一次完整的验证清单修复完成后建议按下表逐项验证不要只测单项就认为完工验证项命令或操作预期结果设备节点ls -l /dev/rtc0节点存在且为字符设备RTC 驱动加载dmesg | grep -i rtc出现registered as rtc0系统时间写入date -s 2025-06-01 10:00:00date显示正确系统时间同步到 RTChwclock -w无报错RTC 读取hwclock -r时间接近系统时间断电重启断电等待 10 秒后重启date时间接近 RTC 时间时区显示timedatectl时区为Asia/Shanghai且本地时间正确LSE 时钟状态cat /sys/kernel/debug/clk/clk_summaryLSE 时钟处于开启状态这组验证做完基本可以确认“本地时间无法设置”的问题已经解决。如果其中某一步失败回到对应层级继续查。比如 RTC 节点存在但读写失败优先看供电和 LSE 起振状态时区显示不对优先看/etc/localtime和/etc/adjtime。5. 后续踩坑与时间同步的长期维护设备树和内核配置修好只是“能用”。真正在产品上稳定运行还有几个坑值得提前规避。5.1 NTP 与本地手动时间的“抢方向盘”BSP 镜像里如果启用了systemd-timesyncd或chronyd并且板卡能联网那么即使你手动设置了时间NTP 服务也可能会在几秒后把时间“纠正”回来。从用户视角看就像“我设置的时间总是被重置”。如果你希望手动设置的时间在短期内不被覆盖可以临时停止时间同步服务systemctl stop systemd-timesyncd # 或者 systemctl stop chronyd如果是产品化场景建议先想清楚需求的优先级是设备依赖外部网络校时还是以本地 RTC 为准。很多工控场景要求设备在断网期间也能保持合理走时那就应该把 RTC 作为主时间源同时让 NTP 只在网络可用时做后台校准。这里没有“万能配置”取决于你的业务。我在某个项目里见过一个比较隐蔽的问题NTP 配置了多个地址但 BSP 里/etc/ntp.conf没配iburst参数导致每次启动后要等很久才能完成时间校准。如果用户在这个窗口期写入日志时间戳就是乱的。后来我把iburst加上并把 RTC 校时提到 NTP 之前日志时间戳才稳定下来。5.2 断电 RTC 走时与备份电源的考量当hwclock -w成功、断电重启后时间依然正确往往会让开发者松一口气。但如果你用的是超级电容而不是纽扣电池电容的电荷保持时间是有限的。如果整机长期断电RTC 一样会回到初始状态。STM32MP257 内部 RTC 在备份域VBAT引脚需要独立的电源路径。在做产品化设计时需要确认 VBAT 是否有足够的后备电源以及 RTC 在 VDD 掉电后是否仍保持走时。另外还要注意即使 RTC 正常走时晶振的温漂和电容负载不匹配也会导致每天几秒到几十秒的误差。对于需要精确定时的场景建议在系统启动后用 NTP 或 PTP 协议做周期性校准。如果产品生命周期中有 RTC 精度指标要求在设计阶段就要考虑 LSE 晶振的匹配电容选型。5.3 特殊坑文件系统时间戳与现实时间错位这个问题在嵌入式 Linux 里很常见。由于根文件系统在构建时可能没有记录完整的时间戳或者系统启动了但没有正确初始化时间新写入文件的时间就会显示成 1970 年或 2000 年左右的“默认时间”。这个现象和“不能设置时间”不是同一个问题但它会让人误判为时间设置失败。解决思路如果板卡有 RTC且 RTC 时间是正确的那只要保证启动流程能正确执行读取 RTC 的步骤文件系统时间戳就会正常。如果板卡没有 RTC 或 RTC 断电时没法保持走时建议在构建根文件系统时使用fakeroot固定文件时间戳或者在应用层依赖monotonic时间而非墙上时间避免被墙上时间“带偏”。我的经验是日志系统最好同时记录CLOCK_MONOTONIC和CLOCK_REALTIME两个时间戳。前者用于计算间隔后者用于和外部时间标对齐。即使在 RTC 完全失效的极端情况下间隔时间仍然可信。5.4 多板卡差异与 BSP 版本升级的回归测试最后一点是关于团队协作的。STM32MP257 的 BSP 会持续更新内核版本和设备树定义也可能随之变化。我遇到过一个问题在旧版本 BSP 上 RTC 正常换到新版本后rtc节点的地址或compatible变了但板级 dts 没有同步更新导致/dev/rtc0又消失了。建议把 RTC 相关的验证项做成一个自动化冒烟测试脚本每次换 BSP、换板卡时跑一遍。脚本内容不复杂就是我们在第 4 节列出的验证命令串起来最后检查返回状态和关键输出。这样即使将来换了内核大版本也能在几分钟内发现问题而不是等到产线上批量烧录后才暴露。就我个人维修这类问题的习惯而言遇到“时间设置不了”时一定先做分层定位。先把/dev/rtc0是否存在、hwclock -r能否读取、断电重启后时间是否保持这三个点查清楚再决定往内核、设备树、安全固件还是时区配置方向查。很多时候所谓的神秘故障其实只是某一层的配置被人为改动过或者评估板硬件上少焊了一个电池/电容。