RK3588 + PCF8563 RTC芯片 完整开发指南
/** * @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) 连接 PCF8563
1.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: rtc@51 { 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; } /* ========== 步骤3:BCD 转二进制 ========== */ 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_TIME="2026-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_PCF8563=y |
| 设备节点 | /dev/rtc0 |
| 测试命令 | hwclock -r,hwclock -w,date |
| sysfs 接口 | /sys/class/rtc/rtc0/ |
关键配置要点:
I2C 地址:PCF8563 从地址为
0x51,写地址0xA2,读地址0xA3设备树:将 PCF8563 节点配置在对应的 I2C 总线下
晶振:必须使用 32.768kHz 石英晶体,负载电容约 12.5pF
备用电池:CR2032 纽扣电池,电压低于 1.0V 时 VL 标志置位
上拉电阻:SCL/SDA 需要 4.7kΩ 上拉电阻到 VDD
