[Storage] Add support for PCF8583 RTC with 240 bytes SRAM

This commit is contained in:
Ton Huisman
2026-08-17 17:08:01 +02:00
parent 1d185d5fb6
commit ac12667365
11 changed files with 348 additions and 7 deletions
+181
View File
@@ -1546,6 +1546,187 @@ void RTC_PCF8563::writeSqwPinMode(Pcf8563SqwPinMode mode) {
}
// END RTC_PCF8563 implementation
// START RTC_PCF8583 implementation
// Code inspired by https://github.com/xoseperez/pcf8583
/**************************************************************************/
/*!
@brief Start I2C for the PCF8583 and test succesful connection
@details Use altAddress() before calling begin() to use the alternate address (0x51)
@return True if Wire can find PCF8583 or false otherwise.
*/
/**************************************************************************/
boolean RTC_PCF8583::begin(TwoWire *wireInstance) {
RTCWireBus = wireInstance;
RTCWireBus->beginTransmission(_addr);
if (RTCWireBus->endTransmission() == 0) {
return true;
}
return false;
}
void RTC_PCF8583::altAddress() {
_addr = PCF8583_ADDRESS_1;
}
/**************************************************************************/
/*!
@brief Check the status of the VL bit in the VL_SECONDS register.
@details The PCF8583 does not support voltage-low detector.
@return False
*/
/**************************************************************************/
boolean RTC_PCF8583::lostPower(void) {
return false; // Not supported
}
/**************************************************************************/
/*!
@brief Set the date and time
@param dt DateTime to set
*/
/**************************************************************************/
void RTC_PCF8583::adjust(const DateTime &dt) {
uint16_t year = dt.year() - PCF8583_BASE_YEAR;
const uint8_t offset = year & 0xFC;
year -= offset;
RTCWireBus->beginTransmission(_addr);
RTCWireBus->_I2C_WRITE(PCF8583_VL_SECONDS); // start at location 2, VL_SECONDS
RTCWireBus->_I2C_WRITE(bin2bcd(dt.second()));
RTCWireBus->_I2C_WRITE(bin2bcd(dt.minute()));
RTCWireBus->_I2C_WRITE(bin2bcd(dt.hour()));
RTCWireBus->_I2C_WRITE(bin2bcd(dt.day()) | (year < 6));
RTCWireBus->_I2C_WRITE(bin2bcd(0)); // skip weekdays
RTCWireBus->_I2C_WRITE(bin2bcd(dt.month()));
RTCWireBus->_I2C_WRITE(bin2bcd(dt.year() - 2000));
RTCWireBus->endTransmission();
RTCWireBus->beginTransmission(_addr);
RTCWireBus->_I2C_WRITE(PCF8583_OFFSET_YEAR); // Update offset and year
RTCWireBus->_I2C_WRITE(offset);
RTCWireBus->_I2C_WRITE(year);
RTCWireBus->endTransmission();
}
/**************************************************************************/
/*!
@brief Get the current date/time
@return DateTime object containing the current date/time
*/
/**************************************************************************/
DateTime RTC_PCF8583::now() {
RTCWireBus->beginTransmission(_addr);
RTCWireBus->_I2C_WRITE((byte)PCF8583_VL_SECONDS);
RTCWireBus->endTransmission();
RTCWireBus->requestFrom(_addr, (size_t)6);
const uint8_t ss = bcd2bin(RTCWireBus->_I2C_READ() & 0x7F);
const uint8_t mm = bcd2bin(RTCWireBus->_I2C_READ() & 0x7F);
const uint8_t hh = bcd2bin(RTCWireBus->_I2C_READ() & 0x3F);
const uint8_t d_r = RTCWireBus->_I2C_READ();
const uint8_t d = bcd2bin(d_r & 0x3F);
const uint16_t year = d_r >> 6;
RTCWireBus->_I2C_READ(); // skip 'weekdays'
const uint8_t m = bcd2bin(RTCWireBus->_I2C_READ() & 0x1F);
const uint8_t last = read_i2c_register(_addr, PCF8583_LAST_YEAR, RTCWireBus);
uint8_t offset = read_i2c_register(_addr, PCF8583_OFFSET_YEAR, RTCWireBus);
// we have changed the year: happy new year!!!
if (last != year) {
// we have overflowed the year value: update the offset
if (last > year) {
offset += 4;
write_i2c_register(_addr, PCF8583_OFFSET_YEAR, offset, RTCWireBus);
}
write_i2c_register(_addr, PCF8583_LAST_YEAR, year, RTCWireBus);
}
const uint16_t y = PCF8583_BASE_YEAR + offset + year;
return DateTime(y, m, d, hh, mm, ss);
}
/**************************************************************************/
/*!
@brief Resets the STOP bit in register Control_1
*/
/**************************************************************************/
void RTC_PCF8583::start(void) {
const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus);
write_i2c_register(_addr, PCF8583_CONTROL_1, ctlreg & 0x7F,
RTCWireBus);
}
/**************************************************************************/
/*!
@brief Sets the STOP bit in register Control_1
*/
/**************************************************************************/
void RTC_PCF8583::stop(void) {
const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus);
write_i2c_register(PCF8523_ADDRESS, PCF8583_CONTROL_1, ctlreg | 0x80,
RTCWireBus);
}
/**************************************************************************/
/*!
@brief Is the PCF8583 running? Check the STOP bit in register Control_1
@return 1 if the RTC is running, 0 if not
*/
/**************************************************************************/
uint8_t RTC_PCF8583::isrunning() {
const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus);
return !(ctlreg >> 7); // bit 7 = 0 => running
}
/**************************************************************************/
/*!
@brief Read data from the PCF8583's NVRAM
@param buf Pointer to a buffer to store the data - make sure it's large
enough to hold size bytes
@param size Number of bytes to read
@param address Starting NVRAM address, from 0 to 235
*/
/**************************************************************************/
void RTC_PCF8583::readnvram(uint8_t *buf, uint8_t size, uint8_t address) {
const int addrByte = PCF8583_NVRAM + address;
RTCWireBus->beginTransmission(_addr);
RTCWireBus->_I2C_WRITE(addrByte);
RTCWireBus->endTransmission();
RTCWireBus->requestFrom(_addr, size);
for (uint8_t pos = 0; pos < size; ++pos) {
buf[pos] = RTCWireBus->_I2C_READ();
}
}
/**************************************************************************/
/*!
@brief Write data to the PCF8583 NVRAM
@param address Starting NVRAM address, from 0 to 248
@param buf Pointer to buffer containing the data to write
@param size Number of bytes in buf to write to NVRAM
*/
/**************************************************************************/
void RTC_PCF8583::writenvram(uint8_t address, uint8_t *buf, uint8_t size) {
const int addrByte = PCF8583_NVRAM + address;
RTCWireBus->beginTransmission(_addr);
RTCWireBus->_I2C_WRITE(addrByte);
for (uint8_t pos = 0; pos < size; ++pos) {
RTCWireBus->_I2C_WRITE(buf[pos]);
}
RTCWireBus->endTransmission();
}
// END RTC_PCF8583 implementation
/**************************************************************************/
/*!
@brief Convert the day of the week to a representation suitable for
+34
View File
@@ -59,6 +59,16 @@ class TimeSpan;
///< temperature value
#define DS3232_NVRAM 0x14 ///< Start of RAM registers - 236 bytes, 0x14 to 0xFF
#define PCF8583_ADDRESS 0x50 ///< I2C address for PCF8583
#define PCF8583_ADDRESS_1 0x51 ///< Alternate I2C address for PCF8583 (A0 = high)
#define PCF8583_CONTROL_1 0x00 ///< Control and status register 1
#define PCF8583_VL_SECONDS 0x02 ///< register address for VL_SECONDS
#define PCF8583_NVRAM 0x10 ///< Start of RAM registers - 240 bytes, 0x10 to 0xFF
#define PCF8583_OFFSET_YEAR 0x08
#define PCF8583_LAST_YEAR 0x09
#define PCF8583_BASE_YEAR 2012
/** Constants */
#define SECONDS_PER_DAY 86400L ///< 60 * 60 * 24
#define SECONDS_FROM_1970_TO_2000 \
@@ -457,6 +467,30 @@ protected:
TwoWire *RTCWireBus;
};
/**************************************************************************/
/*!
@brief RTC based on the PCF8583 chip connected via I2C and the Wire library
*/
/**************************************************************************/
class RTC_PCF8583 {
public:
boolean begin(TwoWire *wireInstance = &Wire);
void altAddress();
boolean lostPower(void);
void adjust(const DateTime &dt);
DateTime now();
void start(void);
void stop(void);
uint8_t isrunning();
void readnvram(uint8_t *buf, uint8_t size, uint8_t address);
void writenvram(uint8_t address, uint8_t *buf, uint8_t size);
protected:
TwoWire *RTCWireBus;
uint8_t _addr = PCF8583_ADDRESS;
};
/**************************************************************************/
/*!
@brief RTC using the internal millis() clock, has to be initialized before
+2 -1
View File
@@ -31,7 +31,7 @@
// #define FEATURE_SD 1 // Enable SD card support
// #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url
// #define FEATURE_EEPROM_EXTERNAL 1 // Enable support for AT24Cxxx EEPROM chips AT24C32(4kB)..AT24C1024(128kB), optional: AT24C2048(256kB) and FRAM MB85RC32(4kB)..MB85RC1M(128kB), optional: MB85RC2M(256kB)
// #define FEATURE_RTC_SRAM_STORAGE 1 // Enable storing values in supported RTC Clock chip SRAM (DS1307, DS3232)
// #define FEATURE_RTC_SRAM_STORAGE 1 // Enable storing values in supported RTC Clock chip SRAM (DS1307, DS3232, PCF8583)
#ifdef BUILD_GIT
# undef BUILD_GIT
@@ -229,6 +229,7 @@
#define FEATURE_SSDP 1
#define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307
#define FEATURE_EXT_RTC_PCF8583 1 // As we enable External RTC clock modules, we can also include PCF8583
#define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data
#ifdef ESP8266
@@ -21,6 +21,10 @@ uint8_t checkRTCSRAMEnabled() {
const ExtTimeSource_e extRtcType = Settings.ExtTimeSource();
if ((ExtTimeSource_e::DS1307 == extRtcType) ||
# if FEATURE_EXT_RTC_PCF8583
(ExtTimeSource_e::PCF8583 == extRtcType) ||
(ExtTimeSource_e::PCF8583a == extRtcType) ||
# endif // if FEATURE_EXT_RTC_PCF8583
(ExtTimeSource_e::DS3232 == extRtcType)) { // RTC with SRAM Configured?
return static_cast<uint8_t>(Settings.ExtTimeSource());
}
@@ -39,6 +43,11 @@ uint8_t getRTCI2CAddress() {
return DS1307_ADDRESS;
case ExtTimeSource_e::DS3232:
return DS3231_ADDRESS;
# if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
return PCF8583_ADDRESS;
# endif // if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::DS3231:
case ExtTimeSource_e::PCF8523:
case ExtTimeSource_e::PCF8563:
@@ -65,6 +74,10 @@ uint8_t selectRTCSRAMI2CBus() {
switch (type)
{
case ExtTimeSource_e::DS1307:
# if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
# endif // if FEATURE_EXT_RTC_PCF8583
I2CSelect_Max100kHz_ClockSpeed(i2cBus);
break;
case ExtTimeSource_e::DS3232:
@@ -96,6 +109,11 @@ uint32_t getRTCSRAMSize() {
return 56ul;
case ExtTimeSource_e::DS3232:
return 240ul;
# if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
return 240ul;
# endif // if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::DS3231:
case ExtTimeSource_e::PCF8523:
case ExtTimeSource_e::PCF8563:
@@ -175,6 +193,20 @@ bool writeRTCSRAMSlot(uint32_t slot,
}
return true;
}
# if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
{
RTC_PCF8583 rtc;
rtc.readnvram(_b, sizeof_rtcsram_slot, addr);
const SRAM_STORAGE_FLOAT_TYPE oldData = *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0];
if (!essentiallyEqual(oldData, data)) {
rtc.writenvram(addr, (uint8_t *)&data, sizeof_rtcsram_slot);
}
return true;
}
# endif // if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::DS3231:
case ExtTimeSource_e::PCF8523:
case ExtTimeSource_e::PCF8563:
@@ -210,6 +242,15 @@ SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot) {
rtc.readnvram(_b, sizeof_rtcsram_slot, addr);
return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0];
}
# if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
{
RTC_PCF8583 rtc;
rtc.readnvram(_b, sizeof_rtcsram_slot, addr);
return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0];
}
# endif // if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::DS3231:
case ExtTimeSource_e::PCF8523:
case ExtTimeSource_e::PCF8563:
+15
View File
@@ -711,6 +711,10 @@ To create/register a plugin, you have to :
#undef FEATURE_EXT_RTC
#endif
#define FEATURE_EXT_RTC 0
#ifdef FEATURE_EXT_RTC_PCF8583
#undef FEATURE_EXT_RTC_PCF8583
#endif
#define FEATURE_EXT_RTC_PCF8583 0
#ifdef FEATURE_SYSLOG
#undef FEATURE_SYSLOG
#endif
@@ -3209,6 +3213,10 @@ To create/register a plugin, you have to :
#undef FEATURE_EXT_RTC
#endif
#define FEATURE_EXT_RTC 0
#ifdef FEATURE_EXT_RTC_PCF8583
#undef FEATURE_EXT_RTC_PCF8583
#endif
#define FEATURE_EXT_RTC_PCF8583 0
#ifndef BUILD_NO_DEBUG
#define BUILD_NO_DEBUG
#endif
@@ -4357,6 +4365,13 @@ To create/register a plugin, you have to :
#endif
#endif
#endif // ifndef FEATURE_RTC_SRAM_STORAGE
#ifndef FEATURE_EXT_RTC_PCF8583
#if defined(PLUGIN_BUILD_MAX_ESP32)
#define FEATURE_EXT_RTC_PCF8583 1 // Also include PCF8583, that has 240 bytes of SRAM
#else // if defined(PLUGIN_BUILD_MAX_ESP32)
#define FEATURE_EXT_RTC_PCF8583 0 // Also include PCF8583, that has 240 bytes of SRAM
#endif // if defined(PLUGIN_BUILD_MAX_ESP32)
#endif
#else // if FEATURE_EXT_RTC
#define FEATURE_RTC_SRAM_STORAGE 0 // Not available
#endif // if FEATURE_EXT_RTC
+6 -1
View File
@@ -2,13 +2,18 @@
const __FlashStringHelper* toString(ExtTimeSource_e timeSource)
{
switch (timeSource) {
switch (timeSource)
{
case ExtTimeSource_e::None: break;
case ExtTimeSource_e::DS1307: return F("DS1307");
case ExtTimeSource_e::DS3231: return F("DS3231");
case ExtTimeSource_e::DS3232: return F("DS3232");
case ExtTimeSource_e::PCF8523: return F("PCF8523");
case ExtTimeSource_e::PCF8563: return F("PCF8563");
#if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583: return F("PCF8583 (0x50)");
case ExtTimeSource_e::PCF8583a: return F("PCF8583 (0x51)");
#endif // if FEATURE_EXT_RTC_PCF8583
}
return F("-");
}
+5
View File
@@ -11,6 +11,11 @@ enum class ExtTimeSource_e {
PCF8523,
PCF8563,
DS3232,
#if FEATURE_EXT_RTC_PCF8583
PCF8583,
PCF8583a,
#endif // if FEATURE_EXT_RTC_PCF8583
};
+43
View File
@@ -1030,6 +1030,30 @@ bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime)
unixtime = rtc.now().unixtime();
break;
}
#if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
{
I2CSelect_Max100kHz_ClockSpeed(i2cBus);
RTC_PCF8583 rtc;
if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) {
rtc.altAddress(); // Set alternative address (0x51)
}
if (!rtc.begin()) {
// Not found
break;
}
if (rtc.lostPower() || !rtc.isrunning()) {
// Cannot get the time from the module
break;
}
unixtime = rtc.now().unixtime();
break;
}
#endif // if FEATURE_EXT_RTC_PCF8583
}
if (unixtime != 0) {
@@ -1110,6 +1134,25 @@ bool ESPEasy_time::ExtRTC_set(uint32_t unixtime)
}
break;
}
#if FEATURE_EXT_RTC_PCF8583
case ExtTimeSource_e::PCF8583:
case ExtTimeSource_e::PCF8583a:
{
I2CSelect_Max100kHz_ClockSpeed(i2cBus);
RTC_PCF8583 rtc;
if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) {
rtc.altAddress(); // Set alternative address (0x51)
}
if (rtc.begin()) {
rtc.adjust(DateTime(unixtime));
rtc.start();
timeAdjusted = true;
}
break;
}
#endif // if FEATURE_EXT_RTC_PCF8583
}
if (timeAdjusted) {
+17 -3
View File
@@ -465,15 +465,29 @@ void addFormDstSelect(bool isStart, uint16_t choice) {
void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice)
{
addRowLabel(label);
const __FlashStringHelper * options[] =
{ F("None"), F("DS1307"), F("DS3231"), F("DS3232"), F("PCF8523"), F("PCF8563")};
const __FlashStringHelper * options[] = {
F("None"),
toString(ExtTimeSource_e::DS1307),
toString(ExtTimeSource_e::DS3231),
toString(ExtTimeSource_e::DS3232),
toString(ExtTimeSource_e::PCF8523),
toString(ExtTimeSource_e::PCF8563),
#if FEATURE_EXT_RTC_PCF8583
toString(ExtTimeSource_e::PCF8583),
toString(ExtTimeSource_e::PCF8583a),
#endif // if FEATURE_EXT_RTC_PCF8583
};
constexpr int optionValues[] = {
static_cast<int>(ExtTimeSource_e::None),
static_cast<int>(ExtTimeSource_e::DS1307),
static_cast<int>(ExtTimeSource_e::DS3231),
static_cast<int>(ExtTimeSource_e::DS3232),
static_cast<int>(ExtTimeSource_e::PCF8523),
static_cast<int>(ExtTimeSource_e::PCF8563)
static_cast<int>(ExtTimeSource_e::PCF8563),
#if FEATURE_EXT_RTC_PCF8583
static_cast<int>(ExtTimeSource_e::PCF8583),
static_cast<int>(ExtTimeSource_e::PCF8583a),
#endif // if FEATURE_EXT_RTC_PCF8583
};
const FormSelectorOptions selector(NR_ELEMENTS(optionValues), options, optionValues);
+1 -1
View File
@@ -103,7 +103,7 @@ void handle_eepromvars() {
html_TR();
html_table_header(F("External RTC SRAM"),
300);
html_table_header(F(""),
html_table_header(toString(Settings.ExtTimeSource()),
500);
html_table_header(F(""),
400);
+3 -1
View File
@@ -313,11 +313,13 @@ String getKnownI2Cdevice(uint8_t address) {
result += F("PCF8591,MCP3221,LM75A,INA219");
break;
case 0x50:
result += F("PCF8583,AT24Cxx,MB85RCxx");
break;
case 0x52:
result += F("AT24Cxx,MB85RCxx");
break;
case 0x51:
result += F("PCF8563,AT24Cxx,MB85RCxx");
result += F("PCF8563,PCF8583,AT24Cxx,MB85RCxx");
break;
case 0x53:
result += F("ADXL345,LTR390,AT24Cxx,MB85RCxx");