diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 8580080ee..452b922cf 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -136,5 +136,6 @@ Index | Define | Driver | Device | Address(es) | Bus2 | Descrip 93 | USE_AS33772S | xdrv_119 | AS33772S | 0x52 | Yes | AS33772S USB PD Sink Controller 94 | USE_RV3028 | xdrv_56 | RV3028 | 0x52 | Yes | RV-3028-C7 RTC Controller 95 | USE_AGS02MA | xsns_118 | AGS02MA | 0x1A | | TVOC Gas sensor + 96 | USE_RX8025 | xdrv_56 | RX8025 | 0x32 | Yes | RX8025 RTC NOTE: Bus2 supported on ESP32 only. diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 0d9ad1370..d578052b9 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -805,6 +805,7 @@ // #define USE_BM8563 // [I2cDriver59] Enable BM8563 RTC - used by M5Stack - support both I2C buses on ESP32 (I2C address 0x51) (+2.5k code) // #define USE_PCF85363 // [I2cDriver66] Enable PCF85363 RTC - used by Shelly 3EM (I2C address 0x51) (+0k7 code) // #define USE_RX8010 // [I2cDriver90] Enable RX8010 RTC - used by IOTTIMER - support both I2C buses on ESP32 (I2C address 0x32) (+0k7 code) +// #define USE_RX8025 // [I2cDriver90] Enable RX8025 RTC support both I2C buses on ESP32 (I2C address 0x32) // #define USE_RX8030 // [I2cDriver90] Enable RX8030 RTC - used by #23855 - support both I2C buses on ESP32 (I2C address 0x32) (+0k7 code) // #define USE_PCF85063 // [I2cDriver92] Enable PCF85063 RTC support (I2C address 0x51) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_56_rtc_chips.ino b/tasmota/tasmota_xdrv_driver/xdrv_56_rtc_chips.ino index c6671ad05..278647b8d 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_56_rtc_chips.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_56_rtc_chips.ino @@ -31,7 +31,10 @@ * Used by IOTTIMER (v1 and v2) * #define USE_RX8030 * RX8010 at I2C address 0x32 - * Used by #23855 + * Used by #23855 + * #define USE_RX8025 + * RX8025 at I2C address 0x32 + * Used by MSB Master G2 \*********************************************************************************************/ #define XDRV_56 56 @@ -727,6 +730,239 @@ void Rx8010Detected(void) { } } #endif // USE_RX8010 +/*********************************************************************************************\ + * RX8025T - Real Time Clock + * + * I2C Address: 0x32 +\*********************************************************************************************/ +#if defined(USE_RX8025) + +#define XI2C_96 96 // See I2CDEVICES.md +#define RX8025_ADDRESS 0x32 + +// RX8025T Register Addresses (per datasheet table 0..F) +#define RX8025_REG_SEC 0x00 +#define RX8025_REG_MIN 0x01 +#define RX8025_REG_HOUR 0x02 +#define RX8025_REG_WEEK 0x03 // bitfield 6..0 (Sun..Sat) +#define RX8025_REG_MDAY 0x04 +#define RX8025_REG_MONTH 0x05 // 01..12 +#define RX8025_REG_YEAR 0x06 // 00..99 (2000..2099) + +#define RX8025_REG_EXT 0x0D +#define RX8025_REG_FLAG 0x0E +#define RX8025_REG_CTRL 0x0F + +// FLAG bits (per table: ... VLF VDET) +#define RX8025_FLAG_VLF 1 +#define RX8025_FLAG_VDET 0 + +// CTRL RESET bit0 = stop status (datasheet) +#define RX8025_BIT_CTRL_RESET 0 + +// WEEK bitfield <-> Tasmota day_of_week (1..7, Sunday=1) +static uint8_t Rx8025WeekToWday(uint8_t week) { + week &= 0x7F; // ignore bit7 + for (uint8_t i = 0; i < 7; i++) { + if (week & (1U << i)) { return (uint8_t)(i + 1); } // Sun=1 + } + return 1; +} + +static uint8_t Rx8025WdayToWeek(uint8_t wday) { + if (wday < 1 || wday > 7) { wday = 1; } + return (uint8_t)(1U << (wday - 1)); // one-hot bits 0..6 +} + +static void Rx8025LogRaw(const char *tag, uint8_t bus, uint8_t addr, + const uint8_t data[7], uint8_t ctrl, uint8_t flag, uint8_t ext) { + AddLog(LOG_LEVEL_DEBUG, + PSTR("RTC: RX8025T %s bus=%d addr=0x%02X RAW[00..06]=%02X %02X %02X %02X %02X %02X %02X CTRL=%02X FLAG=%02X EXT=%02X"), + tag, bus, addr, + data[0], data[1], data[2], data[3], data[4], data[5], data[6], + ctrl, flag, ext); +} + +static void Rx8025PreInitIfNeeded(void) { + uint8_t ctrl = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + uint8_t flag = I2cRead8(RtcChip.address, RX8025_REG_FLAG, RtcChip.bus); + uint8_t ext = I2cRead8(RtcChip.address, RX8025_REG_EXT, RtcChip.bus); + + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T PRE-INIT CTRL=%02X FLAG=%02X EXT=%02X"), ctrl, flag, ext); + + // If VLF or VDET is set, datasheet says initialize registers before use. + if (flag & (_BV(RX8025_FLAG_VLF) | _BV(RX8025_FLAG_VDET))) { + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T VLF/VDET set -> clearing EXT/FLAG (arduino-style)")); + + // Keep CTRL upper bits, force reserved bits 2..1 = 0 + uint8_t ctrl_base = ctrl & 0xF8; + + // Enter stop status (RESET=1) + I2cWrite8(RtcChip.address, RX8025_REG_CTRL, ctrl_base | _BV(RX8025_BIT_CTRL_RESET), RtcChip.bus); + + // Clear EXT and FLAG (common minimal init used by reference libs) + I2cWrite8(RtcChip.address, RX8025_REG_EXT, 0x00, RtcChip.bus); + I2cWrite8(RtcChip.address, RX8025_REG_FLAG, 0x00, RtcChip.bus); + + // Exit stop status (RESET=0) + I2cWrite8(RtcChip.address, RX8025_REG_CTRL, ctrl_base, RtcChip.bus); + + ctrl = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + flag = I2cRead8(RtcChip.address, RX8025_REG_FLAG, RtcChip.bus); + ext = I2cRead8(RtcChip.address, RX8025_REG_EXT, RtcChip.bus); + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T POST-INIT CTRL=%02X FLAG=%02X EXT=%02X"), ctrl, flag, ext); + } +} + +/*-------------------------------------------------------------------------------------------*\ + * Read time from RX8025T and return epoch time +\*-------------------------------------------------------------------------------------------*/ +uint32_t Rx8025ReadTime(void) { + TIME_T tm; + + uint8_t data[7]; + I2cReadBuffer(RtcChip.address, RX8025_REG_SEC, data, 7, RtcChip.bus); + + uint8_t ctrl = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + uint8_t flag = I2cRead8(RtcChip.address, RX8025_REG_FLAG, RtcChip.bus); + uint8_t ext = I2cRead8(RtcChip.address, RX8025_REG_EXT, RtcChip.bus); + + Rx8025LogRaw("READ", RtcChip.bus, RtcChip.address, data, ctrl, flag, ext); + + tm.second = Bcd2Dec(data[0] & 0x7F); + tm.minute = Bcd2Dec(data[1] & 0x7F); + tm.hour = Bcd2Dec(data[2] & 0x3F); + + tm.day_of_week = Rx8025WeekToWday(data[3]); + tm.day_of_month = Bcd2Dec(data[4] & 0x3F); + + // RX8025: MONTH is 01..12 (no -1) + tm.month = Bcd2Dec(data[5] & 0x1F); + + // RX8025: YEAR is 00..99 for 2000..2099 => yOff = (2000-1970)+y2k = 30+y2k + uint8_t y2k = Bcd2Dec(data[6]); + tm.year = (uint8_t)(30 + y2k); + + uint32_t epoch = MakeTime(tm); + + AddLog(LOG_LEVEL_DEBUG, + PSTR("RTC: RX8025T DECODE y2k=%u -> yOff=%u (abs=%u) m=%u d=%u w=%u %02u:%02u:%02u -> epoch=%u"), + y2k, tm.year, (uint16_t)(1970 + tm.year), + tm.month, tm.day_of_month, tm.day_of_week, + tm.hour, tm.minute, tm.second, + epoch); + + return epoch; +} + +/*-------------------------------------------------------------------------------------------*\ + * Set RX8025T time from epoch +\*-------------------------------------------------------------------------------------------*/ +void Rx8025SetTime(uint32_t epoch_time) { + TIME_T tm; + BreakTime(epoch_time, tm); + + uint16_t abs_year = (uint16_t)(1970 + tm.year); + uint8_t y2k = 0; + if (abs_year < 2000) { y2k = 0; } + else if (abs_year > 2099) { y2k = 99; } + else { y2k = (uint8_t)(abs_year - 2000); } + + AddLog(LOG_LEVEL_DEBUG, + PSTR("RTC: RX8025T SET epoch=%u -> yOff=%u (abs=%u) m=%u d=%u w=%u %02u:%02u:%02u"), + epoch_time, tm.year, abs_year, + tm.month, tm.day_of_month, tm.day_of_week, + tm.hour, tm.minute, tm.second); + + // CTRL: clear reserved bits 2..1, preserve others + uint8_t ctrl0 = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + uint8_t ctrl_base = ctrl0 & 0xF8; + + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T CTRL before=%02X masked=%02X (set RESET bit0=stop)"), ctrl0, ctrl_base); + + // Enter stop status (RESET=1) + I2cWrite8(RtcChip.address, RX8025_REG_CTRL, ctrl_base | _BV(RX8025_BIT_CTRL_RESET), RtcChip.bus); + + uint8_t ctrl1 = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T CTRL after STOP(read)=%02X"), ctrl1); + + uint8_t data[7]; + data[0] = Dec2Bcd(tm.second); + data[1] = Dec2Bcd(tm.minute); + data[2] = Dec2Bcd(tm.hour); + data[3] = Rx8025WdayToWeek(tm.day_of_week); + data[4] = Dec2Bcd(tm.day_of_month); + + // RX8025: MONTH is 01..12 (no +1) + data[5] = Dec2Bcd(tm.month); + + data[6] = Dec2Bcd(y2k); + + AddLog(LOG_LEVEL_DEBUG, + PSTR("RTC: RX8025T WRITE y2k=%u RAW=%02X %02X %02X %02X %02X %02X %02X"), + y2k, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + + I2cWriteBuffer(RtcChip.address, RX8025_REG_SEC, data, 7, RtcChip.bus); + + // Optional readback for verification + uint8_t rb[7]; + I2cReadBuffer(RtcChip.address, RX8025_REG_SEC, rb, 7, RtcChip.bus); + uint8_t ctrl_rb = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + uint8_t flag_rb = I2cRead8(RtcChip.address, RX8025_REG_FLAG, RtcChip.bus); + uint8_t ext_rb = I2cRead8(RtcChip.address, RX8025_REG_EXT, RtcChip.bus); + Rx8025LogRaw("READBACK", RtcChip.bus, RtcChip.address, rb, ctrl_rb, flag_rb, ext_rb); + + // Exit stop status (RESET=0) + I2cWrite8(RtcChip.address, RX8025_REG_CTRL, ctrl_base, RtcChip.bus); + uint8_t ctrl2 = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T CTRL after CLEAR(read)=%02X"), ctrl2); + + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: %s re-synced (" D_UTC_TIME ") %s"), RtcChip.name, GetDateAndTime(DT_UTC).c_str()); +} + +/*-------------------------------------------------------------------------------------------*\ + * Detection +\*-------------------------------------------------------------------------------------------*/ +void Rx8025Detected(void) { + if (!RtcChip.detected && I2cEnabled(XI2C_96)) { + RtcChip.address = RX8025_ADDRESS; + + for (RtcChip.bus = 0; RtcChip.bus < 2; RtcChip.bus++) { + + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T DETECT try bus=%d addr=0x%02X"), RtcChip.bus, RtcChip.address); + + if (!I2cSetDevice(RtcChip.address, RtcChip.bus)) { + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T DETECT bus=%d -> I2cSetDevice FAIL"), RtcChip.bus); + continue; + } + + // Basic presence check: CTRL must be readable + if (!I2cValidRead(RtcChip.address, RX8025_REG_CTRL, 1, RtcChip.bus)) { + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: RX8025T DETECT bus=%d -> I2cValidRead(CTRL) FAIL"), RtcChip.bus); + continue; + } + + // If VLF/VDET set, clear them before using registers (datasheet requirement) + Rx8025PreInitIfNeeded(); + + // Debug snapshot of time regs at detect + uint8_t data[7]; + I2cReadBuffer(RtcChip.address, RX8025_REG_SEC, data, 7, RtcChip.bus); + uint8_t ctrl = I2cRead8(RtcChip.address, RX8025_REG_CTRL, RtcChip.bus); + uint8_t flag = I2cRead8(RtcChip.address, RX8025_REG_FLAG, RtcChip.bus); + uint8_t ext = I2cRead8(RtcChip.address, RX8025_REG_EXT, RtcChip.bus); + Rx8025LogRaw("DETECTED", RtcChip.bus, RtcChip.address, data, ctrl, flag, ext); + + RtcChip.detected = 1; + strcpy_P(RtcChip.name, PSTR("RX8025T")); + RtcChip.ReadTime = &Rx8025ReadTime; + RtcChip.SetTime = &Rx8025SetTime; + RtcChip.mem_size = -1; + break; + } + } +} +#endif // USE_RX8025 /*********************************************************************************************\ * RTC Detect and time set @@ -754,6 +990,10 @@ void RtcChipDetect(void) { #ifdef USE_PCF85063 Pcf85063Detected(); #endif // USE_PCF85063 +#ifdef USE_RX8025 + Rx8025Detected(); +#endif // USE_RX8025 + if (!RtcChip.detected) { return; }