ESP32-S3-RLCD I2C RTC 示例拆解
04_I2C_PCF85063 演示通过 I2C 访问 PCF85063 RTC 芯片:启动时设置一个固定时间,然后每秒读取并打印 RTC 当前时间。
I2C 是一主多从的串行总线,只需要两根信号线:
| 信号 | 含义 |
|---|---|
| SDA | 数据线 |
| SCL | 时钟线 |
主机负责发起读写,从机通过 7-bit 地址区分。PCF85063 是一个 RTC,也就是 Real Time Clock。它的职责是持续计时,即使主控重启,也能在有后备电源时继续保持时间。
RTC 的常见使用方式:
系统启动 -> 初始化 I2C -> 初始化 RTC -> 必要时写入当前时间 -> 周期读取时间 -> 上层用于显示、日志或定时任务这个 demo 每次启动都会写入固定时间,因此它是“读写 RTC API 示例”,不是完整校时方案。
硬件与工程入口
Section titled “硬件与工程入口”源码阅读入口:
02_ESP-IDF/04_I2C_PCF85063/main/main.cpp02_ESP-IDF/04_I2C_PCF85063/components/user_app/user_app.cpp02_ESP-IDF/04_I2C_PCF85063/components/port_bsp/i2c_bsp.cpp02_ESP-IDF/04_I2C_PCF85063/components/port_bsp/i2c_equipment.cpp关键硬件配置:
| 项目 | 配置 |
|---|---|
| I2C port | I2C_NUM_0 |
| SCL | GPIO14 |
| SDA | GPIO13 |
| PCF85063 地址 | 0x51 |
| 设备访问速率 | 300000 Hz |
| demo 初始时间 | 2025-09-09 20:15:30 |
关键流程总图
Section titled “关键流程总图”全局构造 I2cMasterBus(14, 13, 0) -> i2c_new_master_bus()
app_main() -> UserApp_AppInit() -> Rtc_Setup(&I2cbus, 0x51) -> i2c_master_bus_add_device() -> rtc.begin(I2cDevCallback) -> Rtc_SetTime(2025, 9, 9, 20, 15, 30) -> xTaskCreate(Rtc_LoopTask)
Rtc_LoopTask() -> Rtc_GetTime() -> rtc.getDateTime() -> 每 1000ms 打印时间关键方法速查
Section titled “关键方法速查”| 函数/方法 | 所在文件 | 作用 | 初学者需要理解的点 |
|---|---|---|---|
I2cMasterBus | components/port_bsp/i2c_bsp.cpp | 封装 ESP-IDF I2C master bus。 | 板级 I2C 初始化不散落在业务代码里。 |
app_main() | main/main.cpp | ESP-IDF 应用入口。 | 入口只交给 UserApp_AppInit()。 |
UserApp_AppInit() | components/user_app/user_app.cpp | 设置 RTC、写初始时间、创建读取任务。 | demo 业务流程集中在这里。 |
Rtc_Setup() | components/port_bsp/i2c_equipment.cpp | 添加 PCF85063 设备并初始化 RTC 库。 | 绑定设备地址和寄存器读写回调。 |
I2cDevCallback() | components/port_bsp/i2c_equipment.cpp | 把第三方库读写请求转换为 ESP-IDF I2C 操作。 | 这是“库适配层”,不是业务逻辑。 |
Rtc_SetTime() | components/port_bsp/i2c_equipment.cpp | 写入 RTC 时间。 | 示例每次启动写固定时间。 |
Rtc_GetTime() | components/port_bsp/i2c_equipment.cpp | 读取 RTC 当前时间。 | 上层不需要知道 PCF85063 寄存器细节。 |
Rtc_LoopTask() | components/user_app/user_app.cpp | 每秒读取并打印时间。 | 周期任务负责观察运行现象。 |
关键代码讲解
Section titled “关键代码讲解”I2C 总线在全局对象中创建:
I2cMasterBus I2cbus(14, 13, 0);这里的三个参数对应 SCL、SDA 和 I2C port。I2C 总线是共享资源,同一条总线上可以挂多个设备,只要地址不同。
Rtc_Setup(&I2cbus, 0x51) 做两件事:
把 PCF85063 加到 I2C bus把 SensorPCF85063 库的寄存器读写绑定到 I2cDevCallback()这类封装很常见:第三方传感器库通常提供通用接口,项目需要把它适配到当前平台的 I2C 读写 API。
写时间:
Rtc_SetTime(2025, 9, 9, 20, 15, 30);读时间:
Rtc_GetTime(&timerData);demo 的重点是 API 路径,不是时间来源。真实产品一般不会每次启动都写固定时间,而是从 SNTP、云端、用户设置或上一次保存状态校时。
烧录运行后,串口每秒输出 RTC 时间。因为示例启动时写入固定时间,读数会从该时间附近开始递增。
如果 RTC 未接好、I2C 地址不对或总线初始化失败,程序可能在 ESP_ERROR_CHECK() 处中止。
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| 一直读不到 RTC | I2C 地址、SCL/SDA、供电或芯片型号不匹配。 | 确认地址 0x51、SCL GPIO14、SDA GPIO13。 |
| 每次重启时间都回到固定值 | demo 主动调用 Rtc_SetTime()。 | 产品代码应只在校时时写 RTC。 |
| 时间不准 | RTC 需要晶振和校准,且受温度影响。 | 长期计时要配合 SNTP 或校准策略。 |
| 多个 I2C 设备冲突 | 地址冲突或总线时序问题。 | 用 I2C 扫描确认设备地址。 |
| 代码里同时有其他 I2C 设备残留 | demo 复用了 BSP 文件。 | 以 UserApp_AppInit() 实际调用链为准。 |
工程迁移思路
Section titled “工程迁移思路”RTC 更适合封装成时间服务:
TimeService -> 初始化 I2C RTC -> 读取 RTC 时间 -> 接受 SNTP/云端校时 -> 写回 RTC -> 输出当前时间 snapshot应用层不应该关心 PCF85063 的寄存器和 I2C 回调,只需要调用 get_time() 或订阅时间 snapshot。这样以后换 RTC 芯片时,业务层不用重写。