Skip to content

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 示例”,不是完整校时方案。

源码阅读入口:

02_ESP-IDF/04_I2C_PCF85063/main/main.cpp
02_ESP-IDF/04_I2C_PCF85063/components/user_app/user_app.cpp
02_ESP-IDF/04_I2C_PCF85063/components/port_bsp/i2c_bsp.cpp
02_ESP-IDF/04_I2C_PCF85063/components/port_bsp/i2c_equipment.cpp

关键硬件配置:

项目配置
I2C portI2C_NUM_0
SCLGPIO14
SDAGPIO13
PCF85063 地址0x51
设备访问速率300000 Hz
demo 初始时间2025-09-09 20:15:30
全局构造 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 打印时间
函数/方法所在文件作用初学者需要理解的点
I2cMasterBuscomponents/port_bsp/i2c_bsp.cpp封装 ESP-IDF I2C master bus。板级 I2C 初始化不散落在业务代码里。
app_main()main/main.cppESP-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每秒读取并打印时间。周期任务负责观察运行现象。

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() 处中止。

现象可能原因排查方式
一直读不到 RTCI2C 地址、SCL/SDA、供电或芯片型号不匹配。确认地址 0x51、SCL GPIO14、SDA GPIO13。
每次重启时间都回到固定值demo 主动调用 Rtc_SetTime()产品代码应只在校时时写 RTC。
时间不准RTC 需要晶振和校准,且受温度影响。长期计时要配合 SNTP 或校准策略。
多个 I2C 设备冲突地址冲突或总线时序问题。用 I2C 扫描确认设备地址。
代码里同时有其他 I2C 设备残留demo 复用了 BSP 文件。UserApp_AppInit() 实际调用链为准。

RTC 更适合封装成时间服务:

TimeService
-> 初始化 I2C RTC
-> 读取 RTC 时间
-> 接受 SNTP/云端校时
-> 写回 RTC
-> 输出当前时间 snapshot

应用层不应该关心 PCF85063 的寄存器和 I2C 回调,只需要调用 get_time() 或订阅时间 snapshot。这样以后换 RTC 芯片时,业务层不用重写。