/** * file rk3588_pcf8563_rtc.c * brief RK3588平台 PCF8563 RTC芯片 完整开发指南 * author zhilin_tang Technology * * 芯片型号PCF8563 (NXP/Philips) * 接口类型I2C * 主要特性极低功耗(0.25μA)内置32.768kHz振荡器可编程时钟输出 * * 本分析涵盖 * 1. 硬件引脚连接与设备树配置 * 2. RTC子系统架构树形分析 * 3. PCF8563驱动源码树形分析 * 4. 关键函数调用树形分析 * 5. 测试工具与命令树形分析 * 6. 应用层调用树形分析 */一、硬件引脚连接分析1.1 根据您提供的引脚信息定位 I2C 接口根据RK3588 引脚分布表找到了可用于连接 PCF8563 的 I2C 接口【RK3588 I2C 资源分析】 ​ ┌─────────────┬──────────────┬─────────────────┬───────────────────┬─────────────┐ │ I2C 接口 │ 引脚位置 │ 引脚编号 │ 功能定义 │ 可用状态 │ ├─────────────┼──────────────┼─────────────────┼───────────────────┼─────────────┤ │ I2C2_SCL │ T28 │ GPIO0_B7 │ I2C2_SCL_M0 │ ✅ 可用 │ │ I2C2_SDA │ T31 │ GPIO0_C0 │ I2C2_SDA_M0 │ ✅ 可用 │ ├─────────────┼──────────────┼─────────────────┼───────────────────┼─────────────┤ │ I2C3_SCL │ G27 │ GPIO1_C1 │ I2C3_SCL_M0 │ 可能被占用 │ │ I2C3_SDA │ G29 │ GPIO1_C0 │ I2C3_SDA_M0 │ 可能被占用 │ ├─────────────┼──────────────┼─────────────────┼───────────────────┼─────────────┤ │ I2C5_SCL │ AL27 │ GPIO4_A6 │ I2C5_SCL_M2 │ 音频可能用 │ │ I2C5_SDA │ AM27 │ GPIO4_A7 │ I2C5_SDA_M2 │ 音频可能用 │ └─────────────┴──────────────┴─────────────────┴───────────────────┴─────────────┘ ​ 【结论】推荐使用 I2C2 (GPIO0_B7/T28 GPIO0_C0/T31) 连接 PCF85631.2 PCF8563 引脚定义与 RK3588 连接【PCF8563 引脚功能表】 ​ ┌─────────────┬─────────────────┬─────────────────┬─────────────────────────────┐ │ 引脚号 │ 引脚名称 │ 功能描述 │ RK3588 连接 │ ├─────────────┼─────────────────┼─────────────────┼─────────────────────────────┤ │ 1 │ OSCI │ 振荡器输入 │ 32.768kHz 晶振 │ │ 2 │ OSCO │ 振荡器输出 │ 32.768kHz 晶振 │ │ 3 │ /INT │ 中断输出(开漏) │ GPIO (可选) │ │ 4 │ VSS │ 地 │ GND │ │ 5 │ SDA │ I2C 数据线 │ GPIO0_C0 (T31) │ │ 6 │ SCL │ I2C 时钟线 │ GPIO0_B7 (T28) │ │ 7 │ CLKOUT │ 时钟输出(开漏) │ NC (可选) │ │ 8 │ VDD │ 电源 (1.0-5.5V) │ VCC3V3 (3.3V) │ └─────────────┴─────────────────┴─────────────────┴─────────────────────────────┘ ​ 【硬件连接要点】 1. 晶振: 32.768kHz 石英晶体两端分别接 OSCI 和 OSCO 2. 上拉电阻: SDA/SCL 需要 4.7kΩ 上拉电阻到 VDD 3. 备用电池: VDD 可接 3.3V 主电源同时可接备用电池 (VBAT) 4. 中断引脚: /INT 为开漏输出需要上拉电阻二、设备树完整配置2.1 设备树文件rk3588-pcf8563.dtsi根据 RK3588 设备树规范PCF8563 作为 I2C 从设备需要配置在对应的 I2C 总线节点下。/** * file rk3588-pcf8563.dtsi * brief RK3588 PCF8563 RTC 设备树配置 * author zhilin_tang Technology * * note 设计模式分析采用复合模式(Composite Pattern) * 将 PCF8563 RTC 设备作为 I2C 总线的子节点 * 通过设备树描述硬件资源驱动通过 of_match_table 匹配。 * * remark I2C 地址: PCF8563 从地址为 0x51 (7位地址) * 写地址: 0xA2, 读地址: 0xA3 */ ​ /dts-v1/; /plugin/; ​ / { compatible rockchip,rk3588; }; ​ /* 1. 启用 I2C2 控制器 */ i2c2 { status okay; pinctrl-names default; pinctrl-0 i2c2m0_xfer; /* 使用 M0 引脚组 */ clock-frequency 100000; /* I2C 时钟 100kHz */ /* 2. PCF8563 RTC 设备节点 */ pcf8563: rtc51 { compatible nxp,pcf8563; reg 0x51; /* I2C 设备地址 */ /* 可选: 中断引脚配置 */ interrupt-parent gpio0; interrupts RK_PC4 IRQ_TYPE_LEVEL_LOW; /* 可选: 时钟输出配置 */ #clock-cells 0; clock-frequency 32768; status okay; }; }; ​ /* 3. 引脚复用配置 */ pinctrl { i2c2 { i2c2m0_xfer: i2c2m0-xfer { rockchip,pins 0 RK_PB7 RK_FUNC_2 pcfg_pull_none_smt, /* I2C2_SCL */ 0 RK_PC0 RK_FUNC_2 pcfg_pull_none_smt; /* I2C2_SDA */ }; }; };2.2 设备树插件编译与部署#!/bin/bash # compile_pcf8563.sh - 设备树插件编译脚本 ​ echo 编译 PCF8563 设备树插件 ​ # 1. 编译设备树插件 dtc - -I dts -O dtb -o rk3588-pcf8563.dtbo rk3588-pcf8563.dtsi ​ # 2. 复制到设备树插件目录 sudo cp rk3588-pcf8563.dtbo /boot/dtb/overlay/ ​ # 3. 在 /boot/uEnv.txt 中启用插件 if ! grep -q rk3588-pcf8563 /boot/uEnv.txt; then echo dtoverlay/boot/dtb/overlay/rk3588-pcf8563.dtbo | sudo tee -a /boot/uEnv.txt fi ​ echo 部署完成请重启系统 三、RTC 子系统架构树形分析3.1 Linux RTC 子系统目录树【Linux RTC 子系统源码目录树】 ​ drivers/rtc/ │ ├── class.c # RTC 设备类管理 ★ │ ├── rtc_device_register() # 注册 RTC 设备 │ ├── rtc_device_unregister() # 注销 RTC 设备 │ └── rtc_class_create() # 创建 RTC 类 │ ├── interface.c # RTC 接口层 ★ │ ├── rtc_read_time() # 读取时间 │ ├── rtc_set_time() # 设置时间 │ ├── rtc_read_alarm() # 读取闹钟 │ ├── rtc_set_alarm() # 设置闹钟 │ └── rtc_irq_set_state() # 中断状态设置 │ ├── rtc-dev.c # 字符设备接口 ★ │ ├── rtc_dev_open() # 打开设备 │ ├── rtc_dev_read() # 读取设备 │ ├── rtc_dev_ioctl() # IOCTL 命令处理 │ └── rtc_dev_register() # 注册字符设备 │ ├── rtc-sysfs.c # sysfs 接口 │ ├── rtc_sysfs_add_device() # 添加 sysfs 属性 │ └── rtc_sysfs_del_device() # 删除 sysfs 属性 │ ├── rtc-proc.c # proc 文件系统接口 │ ├── rtc_proc_add_device() # 添加 proc 条目 │ └── rtc_proc_del_device() # 删除 proc 条目 │ ├── hctosys.c # 系统启动时间同步 ★ │ └── rtc_hctosys() # 启动时从 RTC 读取时间 │ ├── rtc-pcf8563.c # PCF8563 底层驱动 ★★★ │ ├── pcf8563_probe() # I2C 设备探测 │ ├── pcf8563_read_time() # 读取时间 │ ├── pcf8563_set_time() # 设置时间 │ ├── pcf8563_read_alarm() # 读取闹钟 │ └── pcf8563_irq() # 中断处理 │ └── rtc-pcf8563.h # 寄存器定义头文件3.2 RTC 驱动层次关系图【RTC 驱动架构层次】 ​ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 用户空间 (User Space) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 应用程序 (date/hwclock) │ │ │ │ glibc time() 函数 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 系统调用层 (System Call) │ │ │ │ /dev/rtc0 字符设备节点 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 内核空间 (Kernel Space) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ RTC 核心层 (rtc-dev.c) │ │ │ │ rtc_dev_ioctl() - 处理 IOCTL 命令 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ RTC 接口层 (interface.c) │ │ │ │ rtc_read_time() / rtc_set_time() │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ RTC 驱动层 (rtc-pcf8563.c) ★★★ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ struct rtc_class_ops pcf8563_rtc_ops { │ │ │ │ │ │ .read_time pcf8563_read_time, │ │ │ │ │ │ .set_time pcf8563_set_time, │ │ │ │ │ │ .read_alarm pcf8563_read_alarm, │ │ │ │ │ │ .set_alarm pcf8563_set_alarm, │ │ │ │ │ │ .alarm_irq_enable pcf8563_irq_enable, │ │ │ │ │ │ }; │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ I2C 子系统 (i2c-core.c) │ │ │ │ i2c_transfer() - I2C 数据传输 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 硬件层 (Hardware) │ │ RK3588 I2C 控制器 PCF8563 芯片 │ └─────────────────────────────────────────────────────────────────────────────┘四、PCF8563 驱动源码树形分析4.1 驱动源码内部结构【drivers/rtc/rtc-pcf8563.c 内部结构树】 ​ rtc-pcf8563.c │ ├── 1. 寄存器定义区 │ ├── PCF8563_REG_STAT1 # 状态寄存器1 (0x00) │ ├── PCF8563_REG_STAT2 # 状态寄存器2 (0x01) │ ├── PCF8563_REG_SEC # 秒寄存器 (0x02) │ ├── PCF8563_REG_MIN # 分寄存器 (0x03) │ ├── PCF8563_REG_HR # 时寄存器 (0x04) │ ├── PCF8563_REG_DAY # 日寄存器 (0x05) │ ├── PCF8563_REG_WDAY # 星期寄存器 (0x06) │ ├── PCF8563_REG_MON # 月寄存器 (0x07) │ ├── PCF8563_REG_YEAR # 年寄存器 (0x08) │ ├── PCF8563_REG_ALARM_MIN # 闹钟分 (0x09) │ ├── PCF8563_REG_ALARM_HR # 闹钟时 (0x0A) │ ├── PCF8563_REG_ALARM_DAY # 闹钟日 (0x0B) │ ├── PCF8563_REG_ALARM_WDAY # 闹钟星期 (0x0C) │ ├── PCF8563_REG_CLKOUT # 时钟输出控制 (0x0D) │ ├── PCF8563_REG_TIMER_CTL # 定时器控制 (0x0E) │ └── PCF8563_REG_TIMER # 定时器值 (0x0F) │ ├── 2. 寄存器位定义 │ ├── PCF8563_STAT1_VL # 电压低标志 (bit 7) │ ├── PCF8563_STAT2_TF # 定时器标志 (bit 3) │ ├── PCF8563_STAT2_AF # 闹钟标志 (bit 2) │ ├── PCF8563_STAT2_TIE # 定时器中断使能 (bit 1) │ └── PCF8563_STAT2_AIE # 闹钟中断使能 (bit 0) │ ├── 3. 私有数据结构体 │ ├── struct pcf8563_data # 驱动私有数据 │ │ ├── struct rtc_device *rtc # RTC 设备指针 │ │ ├── struct i2c_client *client # I2C 客户端 │ │ ├── int irq # 中断号 │ │ └── bool voltage_low # 电压低标志 │ ├── 4. 辅助函数区 │ ├── pcf8563_read_regs() # 批量读寄存器 │ ├── pcf8563_write_regs() # 批量写寄存器 │ ├── pcf8563_read_byte() # 读单字节 │ └── pcf8563_write_byte() # 写单字节 │ ├── 5. 核心时间操作函数 ★★★ │ ├── pcf8563_read_time() # 读取时间 │ │ ├── 读取 0x02-0x08 寄存器 │ │ ├── BCD 转二进制 │ │ ├── 检查电压低标志 │ │ └── 填充 struct rtc_time │ │ │ └── pcf8563_set_time() # 设置时间 │ ├── 二进制转 BCD │ ├── 写入 0x02-0x08 寄存器 │ └── 清除电压低标志 │ ├── 6. 闹钟操作函数 │ ├── pcf8563_read_alarm() # 读取闹钟 │ ├── pcf8563_set_alarm() # 设置闹钟 │ └── pcf8563_irq_enable() # 中断使能 │ ├── 7. 中断处理函数 │ ├── pcf8563_irq() # 中断处理 │ │ ├── 读取状态寄存器 │ │ ├── 判断闹钟中断 │ │ ├── 判断定时器中断 │ │ └── rtc_update_irq() # 通知 RTC 核心层 │ ├── 8. RTC 操作结构体 │ ├── struct rtc_class_ops pcf8563_rtc_ops │ │ ├── .read_time pcf8563_read_time │ │ ├── .set_time pcf8563_set_time │ │ ├── .read_alarm pcf8563_read_alarm │ │ ├── .set_alarm pcf8563_set_alarm │ │ └── .alarm_irq_enable pcf8563_irq_enable │ ├── 9. I2C 驱动接口 │ ├── pcf8563_probe() # I2C 设备探测 │ │ ├── 分配私有数据 │ │ ├── 初始化 I2C 客户端 │ │ ├── 注册 RTC 设备 │ │ ├── 设置中断 │ │ └── 配置时钟输出 │ │ │ ├── pcf8563_remove() # 设备移除 │ ├── pcf8563_of_match[] # 设备树匹配表 │ └── struct i2c_driver pcf8563_driver │ └── 10. 模块初始化 ├── module_i2c_driver() # 注册 I2C 驱动 └── MODULE_DEVICE_TABLE(i2c, pcf8563_id)五、关键函数树形分析5.1 驱动 Probe 函数/** * brief PCF8563 I2C 设备探测函数 * param client I2C 客户端 * param id I2C 设备 ID * return 0 成功负数错误码 * * note 设计模式分析采用建造者模式(Builder Pattern) * 逐步构建驱动私有数据结构完成资源分配、硬件初始化、 * RTC 设备注册等步骤。 * * remark 内核版本差异不同内核版本寄存器定义略有不同 * Linux 5.10 已稳定支持 PCF8563。 */ static int pcf8563_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct pcf8563_data *pcf8563; struct rtc_device *rtc; int err; /* 步骤1分配私有数据 */ pcf8563 devm_kzalloc(client-dev, sizeof(struct pcf8563_data), GFP_KERNEL); if (!pcf8563) return -ENOMEM; /* 步骤2初始化 I2C 客户端 */ pcf8563-client client; i2c_set_clientdata(client, pcf8563); /* 步骤3检查芯片是否存在 */ err pcf8563_read_byte(client, PCF8563_REG_STAT1); if (err 0) { dev_err(client-dev, PCF8563 not detected\n); return -ENODEV; } /* 步骤4初始化硬件配置 */ /* 清除所有中断标志 */ pcf8563_write_byte(client, PCF8563_REG_STAT2, 0); /* 配置时钟输出 (默认关闭) */ pcf8563_write_byte(client, PCF8563_REG_CLKOUT, 0); /* 步骤5注册 RTC 设备 */ rtc devm_rtc_device_register(client-dev, client-name, pcf8563_rtc_ops, THIS_MODULE); if (IS_ERR(rtc)) { dev_err(client-dev, Unable to register RTC device\n); return PTR_ERR(rtc); } pcf8563-rtc rtc; /* 步骤6配置中断如果可用 */ if (client-irq 0) { err devm_request_threaded_irq(client-dev, client-irq, NULL, pcf8563_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT, pcf8563, pcf8563); if (err) { dev_warn(client-dev, Unable to request IRQ %d\n, client-irq); } else { device_init_wakeup(client-dev, 1); } } /* 步骤7检查电池电压 */ if (pcf8563_read_byte(client, PCF8563_REG_STAT1) PCF8563_STAT1_VL) { dev_warn(client-dev, Low voltage detected, RTC time is not reliable\n); rtc-uie_unsupported 1; } dev_info(client-dev, PCF8563 RTC driver registered\n); return 0; }5.2 读取时间函数/** * brief 读取 RTC 时间 * param dev RTC 设备 * param tm 时间结构体指针 * return 0 成功负数错误码 * * note 设计模式分析采用模板方法模式(Template Method Pattern) * 读取流程固定读取寄存器 → BCD 转二进制 → 填充结构体 * * remark BCD 码说明PCF8563 寄存器使用 BCD 编码 * 需要转换为二进制才能被内核使用。 */ static int pcf8563_read_time(struct device *dev, struct rtc_time *tm) { struct i2c_client *client to_i2c_client(dev); struct pcf8563_data *pcf8563 i2c_get_clientdata(client); unsigned char buf[7]; int err; /* 步骤1读取时间寄存器 (0x02-0x08) */ err pcf8563_read_regs(client, PCF8563_REG_SEC, buf, sizeof(buf)); if (err) return err; /* 步骤2检查电压低标志 */ if (buf[0] PCF8563_STAT1_VL) { dev_warn(dev, RTC voltage low, time invalid\n); return -EINVAL; } /* 步骤3BCD 转二进制 */ tm-tm_sec bcd2bin(buf[0] 0x7F); tm-tm_min bcd2bin(buf[1] 0x7F); tm-tm_hour bcd2bin(buf[2] 0x3F); tm-tm_mday bcd2bin(buf[3] 0x3F); tm-tm_wday buf[4] 0x07; tm-tm_mon bcd2bin(buf[5] 0x1F) - 1; tm-tm_year bcd2bin(buf[6]) 100; /* PCF8563 年份范围 00-99 */ /* 步骤4检查有效性 */ if (rtc_valid_tm(tm) 0) { dev_err(dev, Invalid time read from RTC\n); return -EINVAL; } return 0; }5.3 设置时间函数/** * brief 设置 RTC 时间 * param dev RTC 设备 * param tm 时间结构体指针 * return 0 成功负数错误码 * * note 设计模式分析采用模板方法模式 * 设置流程固定二进制转 BCD → 写入寄存器 → 清除电压标志 */ static int pcf8563_set_time(struct device *dev, struct rtc_time *tm) { struct i2c_client *client to_i2c_client(dev); unsigned char buf[7]; int err; /* 步骤1二进制转 BCD */ buf[0] bin2bcd(tm-tm_sec); buf[1] bin2bcd(tm-tm_min); buf[2] bin2bcd(tm-tm_hour); buf[3] bin2bcd(tm-tm_mday); buf[4] tm-tm_wday 0x07; buf[5] bin2bcd(tm-tm_mon 1); buf[6] bin2bcd(tm-tm_year - 100); /* 步骤2写入时间寄存器 */ err pcf8563_write_regs(client, PCF8563_REG_SEC, buf, sizeof(buf)); if (err) return err; /* 步骤3清除电压低标志 */ err pcf8563_write_byte(client, PCF8563_REG_STAT1, PCF8563_STAT1_VL_CLEAR); if (err) return err; return 0; }六、测试工具与命令树形分析6.1 RTC 测试工具树【RTC 测试工具树】 ​ 系统命令 │ ├── date # 系统时间管理 │ ├── date # 查看系统时间 │ ├── date -s 2026-03-25 10:30:00 # 设置系统时间 │ └── date %s # 显示时间戳 │ ├── hwclock # 硬件时钟管理 ★★★ │ ├── hwclock -r # 读取硬件时钟 │ ├── hwclock -w # 系统时间写入硬件时钟 │ ├── hwclock -s # 硬件时钟写入系统时间 │ ├── hwclock -f /dev/rtc1 # 指定设备 │ └── hwclock --debug # 调试模式 │ └── timedatectl # systemd 时间管理 ├── timedatectl status # 查看状态 ├── timedatectl set-time 2026-03-25 10:30:00 └── timedatectl set-ntp false # 关闭 NTP ​ 调试接口 │ ├── /sys/class/rtc/rtc0/ # sysfs 接口 ★ │ ├── date # 读取日期 │ ├── time # 读取时间 │ ├── since_epoch # 时间戳 │ ├── name # RTC 设备名称 │ ├── max_user_freq # 最大用户频率 │ └── wakealarm # 唤醒闹钟 │ ├── /proc/driver/rtc # proc 接口 │ └── cat /proc/driver/rtc # 查看 RTC 状态 │ └── /dev/rtc0 # 字符设备节点 └── ls -l /dev/rtc* # 查看设备节点6.2 完整测试脚本#!/bin/bash # rtc_debug.sh - PCF8563 RTC 完整测试脚本 ​ echo echo RK3588 PCF8563 RTC 测试 echo ​ # 颜色定义 RED\033[0;31m GREEN\033[0;32m YELLOW\033[0;33m NC\033[0m ​ # 1. 检查 I2C 设备 echo -e \n[1] 检查 I2C 设备... i2cdetect -y 2 | grep -E 51|PCF if [ $? -eq 0 ]; then echo -e ${GREEN} ✅ PCF8563 I2C 设备已识别 (地址 0x51)${NC} else echo -e ${RED} ❌ PCF8563 I2C 设备未识别${NC} echo 请检查硬件连接和上拉电阻 fi ​ # 2. 检查 RTC 设备 echo -e \n[2] 检查 RTC 设备... ls -la /dev/rtc* echo cat /proc/driver/rtc 2/dev/null || echo /proc/driver/rtc 不存在 ​ # 3. 查看 RTC sysfs 信息 echo -e \n[3] RTC sysfs 信息: for rtc in /sys/class/rtc/rtc*; do if [ -d $rtc ]; then echo -e ${YELLOW} $(basename $rtc):${NC} echo 名称: $(cat $rtc/name) echo 日期: $(cat $rtc/date) echo 时间: $(cat $rtc/time) echo 时间戳: $(cat $rtc/since_epoch) fi done ​ # 4. 测试系统时间与硬件时间同步 echo -e \n[4] 测试系统时间与硬件时间同步... ​ # 获取当前系统时间 echo -e ${YELLOW} 当前系统时间:${NC} date ​ # 获取当前硬件时间 echo -e ${YELLOW} 当前硬件时间:${NC} hwclock -r 2/dev/null || echo hwclock 读取失败 ​ # 5. 设置测试时间并验证 echo -e \n[5] 设置测试时间并验证... ​ # 记录测试开始时间 TEST_TIME2026-03-25 12:34:56 echo -e ${YELLOW} 设置系统时间为: $TEST_TIME${NC} sudo date -s $TEST_TIME ​ echo -e ${YELLOW} 将系统时间写入硬件时钟...${NC} sudo hwclock -w ​ echo -e ${YELLOW} 验证硬件时钟:${NC} sudo hwclock -r ​ # 6. 断电模拟测试 echo -e \n[6] 断电模拟测试... echo -e ${YELLOW} 请手动断开开发板电源 10 秒后重新上电${NC} echo -e ${YELLOW} 上电后执行: date 和 hwclock${NC} echo -e ${YELLOW} 按回车键继续测试...${NC} read ​ # 7. 测试 RTC 报警功能 (可选) echo -e \n[7] 测试 RTC 报警功能... if [ -f /sys/class/rtc/rtc0/wakealarm ]; then # 设置 5 秒后报警 CURRENT$(cat /sys/class/rtc/rtc0/since_epoch) ALARM$((CURRENT 5)) echo $ALARM /sys/class/rtc/rtc0/wakealarm echo -e ${GREEN} 已设置 5 秒后报警${NC} sleep 6 dmesg | tail -5 | grep -i rtc else echo -e ${YELLOW} 当前 RTC 不支持 wakealarm${NC} fi ​ echo -e \n echo -e ${GREEN}测试完成${NC} echo 七、应用层调用树形分析7.1 应用层调用流程【应用层调用 RTC 的完整路径】 ​ 用户程序 (C 语言) │ ▼ time_t t time(NULL) # 获取系统时间 (glibc) │ ▼ glibc: __time() 系统调用 │ ▼ 内核: sys_time() → do_gettimeofday() │ ▼ 内核: rtc_read_time() (如果系统时间从 RTC 同步) ​ ───────────────────────────────────────────────────────────────── ​ 用户程序 (直接操作 RTC) │ ▼ fd open(/dev/rtc0, O_RDWR) # 打开 RTC 设备 │ ▼ ioctl(fd, RTC_RD_TIME, rtc_tm) # 读取 RTC 时间 │ ▼ 内核: rtc_dev_ioctl() │ ▼ 内核: rtc_read_time() → pcf8563_read_time() │ ▼ 硬件: I2C 读取 PCF8563 寄存器 ​ ───────────────────────────────────────────────────────────────── ​ 命令行工具调用 │ ▼ hwclock -r # 读取硬件时钟 │ ▼ fork() exec() → 执行 hwclock 程序 │ ▼ open(/dev/rtc0) ioctl(RTC_RD_TIME)7.2 应用层编程示例/** * rtc_app.c - RTC 应用层编程示例 * 编译: gcc -o rtc_app rtc_app.c * 运行: sudo ./rtc_app */ ​ #include stdio.h #include stdlib.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/rtc.h #include time.h ​ /** * brief 读取 RTC 时间 * param rtc_dev RTC 设备路径 (如 /dev/rtc0) * return 0 成功-1 失败 */ int read_rtc_time(const char *rtc_dev) { int fd; struct rtc_time rtc_tm; fd open(rtc_dev, O_RDONLY); if (fd 0) { perror(open rtc device); return -1; } if (ioctl(fd, RTC_RD_TIME, rtc_tm) 0) { perror(ioctl RTC_RD_TIME); close(fd); return -1; } printf(RTC Time: %04d-%02d-%02d %02d:%02d:%02d\n, rtc_tm.tm_year 1900, rtc_tm.tm_mon 1, rtc_tm.tm_mday, rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); close(fd); return 0; } ​ /** * brief 设置 RTC 时间 * param rtc_dev RTC 设备路径 * param year, month, day, hour, min, sec 时间参数 * return 0 成功-1 失败 */ int set_rtc_time(const char *rtc_dev, int year, int month, int day, int hour, int min, int sec) { int fd; struct rtc_time rtc_tm; fd open(rtc_dev, O_RDWR); if (fd 0) { perror(open rtc device); return -1; } rtc_tm.tm_year year - 1900; rtc_tm.tm_mon month - 1; rtc_tm.tm_mday day; rtc_tm.tm_hour hour; rtc_tm.tm_min min; rtc_tm.tm_sec sec; rtc_tm.tm_isdst -1; if (ioctl(fd, RTC_SET_TIME, rtc_tm) 0) { perror(ioctl RTC_SET_TIME); close(fd); return -1; } printf(RTC Time set to: %04d-%02d-%02d %02d:%02d:%02d\n, year, month, day, hour, min, sec); close(fd); return 0; } ​ /** * brief 设置 RTC 闹钟 * param rtc_dev RTC 设备路径 * param seconds 秒数后触发闹钟 * return 0 成功-1 失败 */ int set_rtc_alarm(const char *rtc_dev, int seconds) { int fd; struct rtc_time rtc_tm; unsigned long data; fd open(rtc_dev, O_RDWR); if (fd 0) { perror(open rtc device); return -1; } /* 读取当前时间 */ if (ioctl(fd, RTC_RD_TIME, rtc_tm) 0) { perror(ioctl RTC_RD_TIME); close(fd); return -1; } /* 设置闹钟时间 */ rtc_tm.tm_sec seconds; if (rtc_tm.tm_sec 60) { rtc_tm.tm_sec - 60; rtc_tm.tm_min; } if (ioctl(fd, RTC_ALM_SET, rtc_tm) 0) { perror(ioctl RTC_ALM_SET); close(fd); return -1; } /* 启用闹钟中断 */ data 1; if (ioctl(fd, RTC_AIE_ON, data) 0) { perror(ioctl RTC_AIE_ON); close(fd); return -1; } printf(Alarm set for %02d:%02d:%02d\n, rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); close(fd); return 0; } ​ int main(int argc, char *argv[]) { const char *rtc_dev /dev/rtc0; printf( RTC Application Test \n); /* 读取当前 RTC 时间 */ read_rtc_time(rtc_dev); /* 设置新时间 */ set_rtc_time(rtc_dev, 2026, 3, 25, 14, 30, 0); /* 验证设置 */ read_rtc_time(rtc_dev); /* 设置 10 秒后闹钟 */ set_rtc_alarm(rtc_dev, 10); return 0; }八、常见问题排查问题现象可能原因排查命令解决方案I2C 无设备硬件连接问题i2cdetect -y 2检查电源、上拉电阻、晶振时间不更新电池电压低dmesg \| grep rtc更换纽扣电池时间跳变晶振问题示波器测量更换 32.768kHz 晶振hwclock 失败驱动未加载ls /dev/rtc*检查设备树配置系统时间不同步hctosys 未执行dmesg \| grep hctosys检查内核配置闹钟不触发中断未配置cat /proc/interrupts检查中断引脚九、总结项目内容芯片型号PCF8563 (NXP)I2C 地址0x51 (7位)驱动路径drivers/rtc/rtc-pcf8563.c内核配置CONFIG_RTC_DRV_PCF8563y设备节点/dev/rtc0测试命令hwclock -r,hwclock -w,datesysfs 接口/sys/class/rtc/rtc0/关键配置要点I2C 地址PCF8563 从地址为0x51写地址0xA2读地址0xA3设备树将 PCF8563 节点配置在对应的 I2C 总线下晶振必须使用 32.768kHz 石英晶体负载电容约 12.5pF备用电池CR2032 纽扣电池电压低于 1.0V 时 VL 标志置位上拉电阻SCL/SDA 需要 4.7kΩ 上拉电阻到 VDD