diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1214b99..b34b9398e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [9.4.0.4] ### Added - Version bump to signal new features to Hass +- Support for BM8563 RTC chip (I2C) found in M5Stack Core2 and M5StickC ## [9.4.0.3] 20210515 ### Added diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 8d5e34459..5ceff0408 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -91,4 +91,5 @@ Index | Define | Driver | Device | Address(es) | Description 55 | USE_EZOPMP | xsns_78 | EZOPMP | 0x61 - 0x70 | Peristaltic Pump 56 | USE_SEESAW_SOIL | xsns_81 | SEESOIL | 0x36 - 0x39 | Adafruit seesaw soil moisture sensor 57 | USE_TOF10120 | xsns_84 | TOF10120 | 0x52 | Time-of-flight (ToF) distance sensor - 58 | USE_MPU6886 | xsns_85 | MPU6886 | 0x68 | MPU6886 M5Stack \ No newline at end of file + 58 | USE_MPU6886 | xsns_85 | MPU6886 | 0x68 | MPU6886 M5Stack + 59 | USE_BM8563 | xdrv_56 | BM8563 | 0x51 | BM8563 RTC from M5Stack \ No newline at end of file diff --git a/lib/lib_i2c/BM8563_RTC/library.properties b/lib/lib_i2c/BM8563_RTC/library.properties new file mode 100644 index 000000000..df7811a93 --- /dev/null +++ b/lib/lib_i2c/BM8563_RTC/library.properties @@ -0,0 +1,9 @@ +name=BME8563 +version= +author= +maintainer= +sentence=Driver for BM8563 RTC +paragraph=Driver for BM8563 RTC +category=Driver +url= +architectures=esp8266,esp32 diff --git a/lib/lib_i2c/BM8563_RTC/src/BM8563.cpp b/lib/lib_i2c/BM8563_RTC/src/BM8563.cpp new file mode 100644 index 000000000..0775ae51e --- /dev/null +++ b/lib/lib_i2c/BM8563_RTC/src/BM8563.cpp @@ -0,0 +1,352 @@ +#include "BM8563.h" + +BM8563::BM8563() +{ +} + +void BM8563::begin(void) +{ + WriteReg(0x00,0x00); + WriteReg(0x01,0x00); + WriteReg(0x0D,0x00); +} + +void BM8563::WriteReg(uint8_t reg, uint8_t data) +{ + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(reg); + myWire->write(data); + myWire->endTransmission(); +} + +uint8_t BM8563::ReadReg(uint8_t reg) +{ + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(reg); + myWire->endTransmission(); + myWire->requestFrom(BM8563_ADRESS, 1); + return myWire->read(); +} + +void BM8563::GetBm8563Time(void) +{ + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(0x02); + myWire->endTransmission(); + myWire->requestFrom(BM8563_ADRESS, 7); + while (myWire->available()) + { + + trdata[0] = myWire->read(); + trdata[1] = myWire->read(); + trdata[2] = myWire->read(); + trdata[3] = myWire->read(); + trdata[4] = myWire->read(); + trdata[5] = myWire->read(); + trdata[6] = myWire->read(); + } + + DataMask(); + Bcd2asc(); + Str2Time(); +} + +void BM8563::Str2Time(void) +{ + + Second = (asc[0] - 0x30) * 10 + asc[1] - 0x30; + Minute = (asc[2] - 0x30) * 10 + asc[3] - 0x30; + Hour = (asc[4] - 0x30) * 10 + asc[5] - 0x30; + /* + uint8_t Hour; + uint8_t Week; + uint8_t Day; + uint8_t Month; + uint8_t Year; + */ +} + +void BM8563::DataMask() +{ + + trdata[0] = trdata[0] & 0x7f; //秒 + trdata[1] = trdata[1] & 0x7f; //分 + trdata[2] = trdata[2] & 0x3f; //时 + + trdata[3] = trdata[3] & 0x3f; //日 + trdata[4] = trdata[4] & 0x07; //星期 + trdata[5] = trdata[5] & 0x1f; //月 + + trdata[6] = trdata[6] & 0xff; //年 +} +/******************************************************************** +函 数 名: void Bcd2asc(void) +功 能: bcd 码转换成 asc 码,供Lcd显示用 +说 明: +调 用: +入口参数: +返 回 值:无 +***********************************************************************/ +void BM8563::Bcd2asc(void) +{ + uint8_t i, j; + for (j = 0, i = 0; i < 7; i++) + { + asc[j++] = (trdata[i] & 0xf0) >> 4 | 0x30; /*格式为: 秒 分 时 日 月 星期 年 */ + asc[j++] = (trdata[i] & 0x0f) | 0x30; + } +} + +uint8_t BM8563::Bcd2ToByte(uint8_t Value) +{ + uint8_t tmp = 0; + tmp = ((uint8_t)(Value & (uint8_t)0xF0) >> (uint8_t)0x4) * 10; + return (tmp + (Value & (uint8_t)0x0F)); +} + +uint8_t BM8563::ByteToBcd2(uint8_t Value) +{ + uint8_t bcdhigh = 0; + + while (Value >= 10) + { + bcdhigh++; + Value -= 10; + } + + return ((uint8_t)(bcdhigh << 4) | Value); +} + +void BM8563::GetTime(RTC_TimeTypeDef *RTC_TimeStruct) +{ + + //if() + uint8_t buf[3] = {0}; + + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(0x02); + myWire->endTransmission(); + myWire->requestFrom(BM8563_ADRESS, 3); + + while (myWire->available()) + { + + buf[0] = myWire->read(); + buf[1] = myWire->read(); + buf[2] = myWire->read(); + } + + RTC_TimeStruct->Seconds = Bcd2ToByte(buf[0] & 0x7f); //秒 + RTC_TimeStruct->Minutes = Bcd2ToByte(buf[1] & 0x7f); //分 + RTC_TimeStruct->Hours = Bcd2ToByte(buf[2] & 0x3f); //时 +} + +void BM8563::SetTime(RTC_TimeTypeDef *RTC_TimeStruct) +{ + + if (RTC_TimeStruct == NULL) + return; + + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(0x02); + myWire->write(ByteToBcd2(RTC_TimeStruct->Seconds)); + myWire->write(ByteToBcd2(RTC_TimeStruct->Minutes)); + myWire->write(ByteToBcd2(RTC_TimeStruct->Hours)); + myWire->endTransmission(); +} + +void BM8563::GetDate(RTC_DateTypeDef *RTC_DateStruct) +{ + + uint8_t buf[4] = {0}; + + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(0x05); + myWire->endTransmission(); + myWire->requestFrom(BM8563_ADRESS, 4); + + while (myWire->available()) + { + + buf[0] = myWire->read(); + buf[1] = myWire->read(); + buf[2] = myWire->read(); + buf[3] = myWire->read(); + } + + RTC_DateStruct->Date = Bcd2ToByte(buf[0] & 0x3f); + RTC_DateStruct->WeekDay = Bcd2ToByte(buf[1] & 0x07); + RTC_DateStruct->Month = Bcd2ToByte(buf[2] & 0x1f); + + if (buf[2] & 0x80) + { + RTC_DateStruct->Year = 1900 + Bcd2ToByte(buf[3] & 0xff); + } + else + { + RTC_DateStruct->Year = 2000 + Bcd2ToByte(buf[3] & 0xff); + } +} + +void BM8563::SetDate(RTC_DateTypeDef *RTC_DateStruct) +{ + + if (RTC_DateStruct == NULL) + return; + myWire->beginTransmission(BM8563_ADRESS); + myWire->write(0x05); + myWire->write(ByteToBcd2(RTC_DateStruct->Date)); + myWire->write(ByteToBcd2(RTC_DateStruct->WeekDay)); + + if (RTC_DateStruct->Year < 2000) + { + + myWire->write(ByteToBcd2(RTC_DateStruct->Month) | 0x80); + myWire->write(ByteToBcd2((uint8_t)(RTC_DateStruct->Year % 100))); + } + else + { + /* code */ + myWire->write(ByteToBcd2(RTC_DateStruct->Month) | 0x00); + myWire->write(ByteToBcd2((uint8_t)(RTC_DateStruct->Year % 100))); + } + + myWire->endTransmission(); +} + +int BM8563::SetAlarmIRQ(int afterSeconds) +{ + uint8_t reg_value = 0; + reg_value = ReadReg(0x01); + + if (afterSeconds < 0) + { + reg_value &= ~(1 << 0); + WriteReg(0x01, reg_value); + reg_value = 0x03; + WriteReg(0x0E, reg_value); + return -1; + } + + uint8_t type_value = 2; + uint8_t div = 1; + if (afterSeconds > 255) + { + div = 60; + type_value = 0x83; + } + else + { + type_value = 0x82; + } + + afterSeconds = (afterSeconds / div) & 0xFF; + WriteReg(0x0F, afterSeconds); + WriteReg(0x0E, type_value); + + reg_value |= (1 << 0); + reg_value &= ~(1 << 7); + WriteReg(0x01, reg_value); + return afterSeconds * div; +} + +int BM8563::SetAlarmIRQ(const RTC_TimeTypeDef &RTC_TimeStruct) +{ + uint8_t irq_enable = false; + uint8_t out_buf[4] = {0x80, 0x80, 0x80, 0x80}; + + if (RTC_TimeStruct.Minutes >= 0) + { + irq_enable = true; + out_buf[0] = ByteToBcd2(RTC_TimeStruct.Minutes) & 0x7f; + } + + if (RTC_TimeStruct.Hours >= 0) + { + irq_enable = true; + out_buf[1] = ByteToBcd2(RTC_TimeStruct.Hours) & 0x3f; + } + + //out_buf[2] = 0x00; + //out_buf[3] = 0x00; + + uint8_t reg_value = ReadReg(0x01); + + if (irq_enable) + { + reg_value |= (1 << 1); + } + else + { + reg_value &= ~(1 << 1); + } + + for (int i = 0; i < 4; i++) + { + WriteReg(0x09 + i, out_buf[i]); + } + WriteReg(0x01, reg_value); + + return irq_enable ? 1 : 0; +} + +int BM8563::SetAlarmIRQ(const RTC_DateTypeDef &RTC_DateStruct, const RTC_TimeTypeDef &RTC_TimeStruct) +{ + uint8_t irq_enable = false; + uint8_t out_buf[4] = {0x80, 0x80, 0x80, 0x80}; + + if (RTC_TimeStruct.Minutes >= 0) + { + irq_enable = true; + out_buf[0] = ByteToBcd2(RTC_TimeStruct.Minutes) & 0x7f; + } + + if (RTC_TimeStruct.Hours >= 0) + { + irq_enable = true; + out_buf[1] = ByteToBcd2(RTC_TimeStruct.Hours) & 0x3f; + } + + if (RTC_DateStruct.Date >= 0) + { + irq_enable = true; + out_buf[2] = ByteToBcd2(RTC_DateStruct.Date) & 0x3f; + } + + if (RTC_DateStruct.WeekDay >= 0) + { + irq_enable = true; + out_buf[3] = ByteToBcd2(RTC_DateStruct.WeekDay) & 0x07; + } + + uint8_t reg_value = ReadReg(0x01); + + if (irq_enable) + { + reg_value |= (1 << 1); + } + else + { + reg_value &= ~(1 << 1); + } + + for (int i = 0; i < 4; i++) + { + WriteReg(0x09 + i, out_buf[i]); + } + WriteReg(0x01, reg_value); + + return irq_enable ? 1 : 0; +} + +void BM8563::clearIRQ() +{ + uint8_t data = ReadReg(0x01); + WriteReg(0x01, data & 0xf3); +} +void BM8563::disableIRQ() +{ + clearIRQ(); + uint8_t data = ReadReg(0x01); + WriteReg(0x01, data & 0xfC); +} diff --git a/lib/lib_i2c/BM8563_RTC/src/BM8563.h b/lib/lib_i2c/BM8563_RTC/src/BM8563.h new file mode 100644 index 000000000..dc7867b22 --- /dev/null +++ b/lib/lib_i2c/BM8563_RTC/src/BM8563.h @@ -0,0 +1,82 @@ +#ifndef __MB8563_H__ +#define __MB8563_H__ + +#include + +#define BM8563_ADRESS 0x51 + +typedef struct +{ + uint8_t Hours; + uint8_t Minutes; + uint8_t Seconds; +}RTC_TimeTypeDef; + + +typedef struct +{ + uint8_t WeekDay; + uint8_t Month; + uint8_t Date; + uint16_t Year; +}RTC_DateTypeDef; + +class BM8563 { +public: + BM8563(); + #ifdef ESP32 + void setBus(uint32_t _bus) { myWire = _bus ? &Wire1 : &Wire; }; + #else + void setBus(uint32_t _bus) { myWire = &Wire; }; + #endif + + void begin(void); + void GetBm8563Time(void); + + void SetTime(RTC_TimeTypeDef* RTC_TimeStruct); + void SetDate(RTC_DateTypeDef* RTC_DateStruct); + + void GetTime(RTC_TimeTypeDef* RTC_TimeStruct); + void GetDate(RTC_DateTypeDef* RTC_DateStruct); + + int SetAlarmIRQ(int afterSeconds); + int SetAlarmIRQ( const RTC_TimeTypeDef &RTC_TimeStruct); + int SetAlarmIRQ( const RTC_DateTypeDef &RTC_DateStruct, const RTC_TimeTypeDef &RTC_TimeStruct); + + void clearIRQ(); + void disableIRQ(); + +public: + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Week; + uint8_t Day; + uint8_t Month; + uint8_t Year; + uint8_t DateString[9]; + uint8_t TimeString[9]; + + uint8_t asc[14]; + + +private: + TwoWire * myWire = &Wire; // default to Wire (bus 0) + void Bcd2asc(void); + void DataMask(); + void Str2Time(void); + void WriteReg(uint8_t reg, uint8_t data); + uint8_t ReadReg(uint8_t reg); + uint8_t Bcd2ToByte(uint8_t Value); + uint8_t ByteToBcd2(uint8_t Value); + +private: + + /*Define an array to store the time data read */ + uint8_t trdata[7]; + /* Define an array to store the converted asc code time data */ + //uint8_t asc[14]; + +}; + +#endif // __MB8563_H__ diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 4b23b25b9..9286a7fe0 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -628,7 +628,8 @@ // #define USE_EZORGB // [I2cDriver55] Enable support for EZO's RGB sensor (+0k5 code) - Shared EZO code required for any EZO device (+1k2 code) // #define USE_EZOPMP // [I2cDriver55] Enable support for EZO's PMP sensor (+0k3 code) - Shared EZO code required for any EZO device (+1k2 code) // #define USE_SEESAW_SOIL // [I2cDriver56] Enable Capacitice Soil Moisture & Temperature Sensor (I2C addresses 0x36 - 0x39) (+1k3 code) -// #define USE_MPU6886 // [I2cDriver58] Enable MPU6886 - found in M5Stack - support 2 I2C buses on ESP32 (I2C address 0x68) (+2k code) +// #define USE_MPU6886 // [I2cDriver58] Enable MPU6886 - found in M5Stack - support both I2C buses on ESP32 (I2C address 0x68) (+2k code) +// #define USE_BM8563 // [I2cDriver58] Enable BM8563 RTC - found in M5Stack - support both I2C buses on ESP32 (I2C address 0x51) (+2.5k code) // #define USE_DISPLAY // Add I2C Display Support (+2k code) #define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0 diff --git a/tasmota/xdrv_56_BM8563_RTC.ino b/tasmota/xdrv_56_BM8563_RTC.ino new file mode 100644 index 000000000..96eeeefde --- /dev/null +++ b/tasmota/xdrv_56_BM8563_RTC.ino @@ -0,0 +1,152 @@ +/* + xdrv_52_9_berry.ino - Berry scripting language + + Copyright (C) 2021 Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifdef USE_I2C +#ifdef USE_BM8563 + +#define XDRV_56 56 +#define XI2C_59 59 // See I2CDEVICES.md + +#include "BM8563.h" + +struct { + BM8563 Rtc; + bool rtc_ready = false; + bool ntp_time_ok = false; +} bm8563_driver; + +/*********************************************************************************************\ + * + * +\*********************************************************************************************/ +void BM8563Detect(void) { +#ifdef ESP32 + if (!I2cSetDevice(BM8563_ADRESS, 0)) { + if (!I2cSetDevice(BM8563_ADRESS, 1)) { return; } // check on bus 1 + bm8563_driver.Rtc.setBus(1); // switch to bus 1 + I2cSetActiveFound(BM8563_ADRESS, "BM8563", 1); + } else { + I2cSetActiveFound(BM8563_ADRESS, "BM8563", 0); + } +#else + if (!I2cSetDevice(BM8563_ADRESS)) { return; } + I2cSetActiveFound(BM8563_ADRESS, "BM8563"); +#endif + + bm8563_driver.Rtc.begin(); + bm8563_driver.rtc_ready = true; +} + + +uint32_t BM8563GetUtc(void) { + if (!bm8563_driver.rtc_ready) return 0; + RTC_TimeTypeDef RTCtime; + // 1. read has errors ??? + bm8563_driver.Rtc.GetTime(&RTCtime); +// core2_globs.Rtc.GetTime(&RTCtime); + RTC_DateTypeDef RTCdate; + bm8563_driver.Rtc.GetDate(&RTCdate); + TIME_T tm; + tm.second = RTCtime.Seconds; + tm.minute = RTCtime.Minutes; + tm.hour = RTCtime.Hours; + tm.day_of_week = RTCdate.WeekDay; + tm.day_of_month = RTCdate.Date; + tm.month = RTCdate.Month; + tm.year = RTCdate.Year - 1970; + return MakeTime(tm); +} + +void BM8563SetUtc(uint32_t epoch_time) { + if (!bm8563_driver.rtc_ready) return; + TIME_T tm; + BreakTime(epoch_time, tm); + RTC_TimeTypeDef RTCtime; + RTCtime.Hours = tm.hour; + RTCtime.Minutes = tm.minute; + RTCtime.Seconds = tm.second; + bm8563_driver.Rtc.SetTime(&RTCtime); + RTC_DateTypeDef RTCdate; + RTCdate.WeekDay = tm.day_of_week; + RTCdate.Month = tm.month; + RTCdate.Date = tm.day_of_month; + RTCdate.Year = tm.year + 1970; + bm8563_driver.Rtc.SetDate(&RTCdate); +} + +void InitTimeFromRTC(void) { + if (bm8563_driver.rtc_ready && Rtc.utc_time < START_VALID_TIME) { + // set rtc from chip + Rtc.utc_time = BM8563GetUtc(); + + TIME_T tmpTime; + TasmotaGlobal.ntp_force_sync = true; // Force to sync with ntp + BreakTime(Rtc.utc_time, tmpTime); + Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); + Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); + AddLog(LOG_LEVEL_INFO, PSTR("I2C: Set time from BM8563 to RTC (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + if (Rtc.local_time < START_VALID_TIME) { // 2016-01-01 + TasmotaGlobal.rules_flag.time_init = 1; + } else { + TasmotaGlobal.rules_flag.time_set = 1; + } + } +} + + +void BM8563EverySecond(void) { + if (bm8563_driver.rtc_ready) { + if (!bm8563_driver.ntp_time_ok && Rtc.utc_time > START_VALID_TIME && abs(Rtc.utc_time - BM8563GetUtc()) > 3) { + BM8563SetUtc(Rtc.utc_time); + AddLog(LOG_LEVEL_INFO, PSTR("I2C: Write Time TO BM8563 from NTP (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + bm8563_driver.ntp_time_ok = true; + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ +bool Xdrv56(uint8_t function) +{ + bool result = false; + if (!I2cEnabled(XI2C_59)) { return false; } + + switch (function) { + // case FUNC_PRE_INIT: // we start Berry in pre_init so that other modules can call Berry in their init methods + case FUNC_INIT: + BM8563Detect(); + InitTimeFromRTC(); + break; + + case FUNC_EVERY_SECOND: + BM8563EverySecond(); + break; + + case FUNC_SAVE_BEFORE_RESTART: + break; + } + return result; +} + +#endif // USE_BM8563 +#endif // USE_I2C