mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 19:12:40 +00:00
272 lines
6.6 KiB
C++
272 lines
6.6 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
#include "PCF8563_Class.hpp"
|
|
#include "esp_log.h"
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
|
|
static const char* TAG = "PCF8563";
|
|
|
|
namespace m5 {
|
|
|
|
static std::uint8_t bcd2ToByte(std::uint8_t value)
|
|
{
|
|
return ((value >> 4) * 10) + (value & 0x0F);
|
|
}
|
|
|
|
static std::uint8_t byteToBcd2(std::uint8_t value)
|
|
{
|
|
std::uint_fast8_t bcdhigh = value / 10;
|
|
return (bcdhigh << 4) | (value - (bcdhigh * 10));
|
|
}
|
|
|
|
PCF8563_Class::PCF8563_Class(i2c_master_bus_handle_t i2c_bus_handle, uint8_t addr) : _addr(addr), _init(false)
|
|
{
|
|
i2c_device_config_t dev_cfg = {
|
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
|
.device_address = _addr,
|
|
.scl_speed_hz = 400000,
|
|
};
|
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_handle, &dev_cfg, &_i2c_dev));
|
|
}
|
|
|
|
PCF8563_Class::~PCF8563_Class()
|
|
{
|
|
if (_i2c_dev) {
|
|
i2c_master_bus_rm_device(_i2c_dev);
|
|
}
|
|
}
|
|
|
|
bool PCF8563_Class::begin()
|
|
{
|
|
// TimerCamera's internal RTC sometimes failed to initialize, so execute a dummy write first
|
|
writeRegister8(0x00, 0x00);
|
|
_init = (writeRegister8(0x00, 0x00) == ESP_OK) && (writeRegister8(0x0E, 0x03) == ESP_OK);
|
|
return _init;
|
|
}
|
|
|
|
esp_err_t PCF8563_Class::writeRegister8(uint8_t reg, uint8_t value)
|
|
{
|
|
uint8_t buf[2] = {reg, value};
|
|
return i2c_master_transmit(_i2c_dev, buf, sizeof(buf), 1000);
|
|
}
|
|
|
|
uint8_t PCF8563_Class::readRegister8(uint8_t reg)
|
|
{
|
|
uint8_t val = 0;
|
|
esp_err_t err = i2c_master_transmit_receive(_i2c_dev, ®, 1, &val, 1, 1000);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "readRegister8 failed: %s", esp_err_to_name(err));
|
|
return 0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
esp_err_t PCF8563_Class::writeRegister(uint8_t reg, const uint8_t* data, size_t len)
|
|
{
|
|
if (len == 0) return ESP_OK;
|
|
uint8_t* buf = (uint8_t*)malloc(len + 1);
|
|
if (!buf) return ESP_ERR_NO_MEM;
|
|
|
|
buf[0] = reg;
|
|
memcpy(buf + 1, data, len);
|
|
|
|
esp_err_t err = i2c_master_transmit(_i2c_dev, buf, len + 1, 1000);
|
|
free(buf);
|
|
return err;
|
|
}
|
|
|
|
esp_err_t PCF8563_Class::readRegister(uint8_t reg, uint8_t* data, size_t len)
|
|
{
|
|
return i2c_master_transmit_receive(_i2c_dev, ®, 1, data, len, 1000);
|
|
}
|
|
|
|
esp_err_t PCF8563_Class::bitOn(uint8_t reg, uint8_t mask)
|
|
{
|
|
uint8_t val = readRegister8(reg);
|
|
return writeRegister8(reg, val | mask);
|
|
}
|
|
|
|
esp_err_t PCF8563_Class::bitOff(uint8_t reg, uint8_t mask)
|
|
{
|
|
uint8_t val = readRegister8(reg);
|
|
return writeRegister8(reg, val & ~mask);
|
|
}
|
|
|
|
bool PCF8563_Class::getVoltLow(void)
|
|
{
|
|
return readRegister8(0x02) & 0x80; // RTCC_VLSEC_MASK
|
|
}
|
|
|
|
bool PCF8563_Class::getDateTime(rtc_date_t* date, rtc_time_t* time)
|
|
{
|
|
if (!_init) return false;
|
|
|
|
std::uint8_t buf[7] = {0};
|
|
int start_reg = (time != nullptr) ? 0x02 : 0x05;
|
|
int len = ((date != nullptr) ? 4 : 0) + ((time != nullptr) ? 3 : 0);
|
|
|
|
if (len == 0) return false;
|
|
|
|
if (readRegister(start_reg, buf, len) != ESP_OK) {
|
|
return false;
|
|
}
|
|
|
|
int idx = 0;
|
|
if (time) {
|
|
time->seconds = bcd2ToByte(buf[idx++] & 0x7f);
|
|
time->minutes = bcd2ToByte(buf[idx++] & 0x7f);
|
|
time->hours = bcd2ToByte(buf[idx++] & 0x3f);
|
|
}
|
|
|
|
if (date) {
|
|
date->date = bcd2ToByte(buf[idx++] & 0x3f);
|
|
date->weekDay = bcd2ToByte(buf[idx++] & 0x07);
|
|
date->month = bcd2ToByte(buf[idx++] & 0x1f);
|
|
date->year = bcd2ToByte(buf[idx] & 0xff) + ((0x80 & buf[idx - 1]) ? 1900 : 2000);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PCF8563_Class::setDateTime(const rtc_date_t* date, const rtc_time_t* time)
|
|
{
|
|
if (!_init) return false;
|
|
|
|
std::uint8_t buf[7] = {0};
|
|
|
|
int idx = 0;
|
|
int reg_start = 0x05;
|
|
if (time) {
|
|
reg_start = 0x02;
|
|
buf[idx++] = byteToBcd2(time->seconds);
|
|
buf[idx++] = byteToBcd2(time->minutes);
|
|
buf[idx++] = byteToBcd2(time->hours);
|
|
}
|
|
|
|
if (date) {
|
|
buf[idx++] = byteToBcd2(date->date);
|
|
buf[idx++] = (uint8_t)(0x07u & date->weekDay);
|
|
buf[idx++] = (std::uint8_t)(byteToBcd2(date->month) + (date->year < 2000 ? 0x80 : 0));
|
|
buf[idx++] = byteToBcd2(date->year % 100);
|
|
}
|
|
if (idx == 0) {
|
|
return false;
|
|
}
|
|
return writeRegister(reg_start, buf, idx) == ESP_OK;
|
|
}
|
|
|
|
std::uint32_t PCF8563_Class::setTimerIRQ(std::uint32_t msec)
|
|
{
|
|
if (!_init) return 0;
|
|
|
|
std::uint8_t reg_value = readRegister8(0x01) & ~0x0C;
|
|
|
|
std::uint32_t afterSeconds = (msec + 500) / 1000;
|
|
if (afterSeconds <= 0) { // disable timer
|
|
writeRegister8(0x01, reg_value & ~0x01);
|
|
writeRegister8(0x0E, 0x03);
|
|
return 0;
|
|
}
|
|
|
|
std::size_t div = 1;
|
|
std::uint8_t type_value = 0x82;
|
|
if (afterSeconds < 270) {
|
|
if (afterSeconds > 255) {
|
|
afterSeconds = 255;
|
|
}
|
|
} else {
|
|
div = 60;
|
|
afterSeconds = (afterSeconds + 30) / div;
|
|
if (afterSeconds > 255) {
|
|
afterSeconds = 255;
|
|
}
|
|
type_value = 0x83;
|
|
}
|
|
|
|
writeRegister8(0x0E, type_value);
|
|
writeRegister8(0x0F, afterSeconds);
|
|
|
|
writeRegister8(0x01, (reg_value | 0x01) & ~0x80);
|
|
return afterSeconds * div * 1000;
|
|
}
|
|
|
|
int PCF8563_Class::setAlarmIRQ(const rtc_date_t* date, const rtc_time_t* time)
|
|
{
|
|
if (!_init) return 0;
|
|
|
|
union {
|
|
std::uint32_t raw;
|
|
std::uint8_t buf[4];
|
|
} data_u;
|
|
|
|
data_u.raw = ~0; // 0xFFFFFFFF
|
|
|
|
bool irq_enable = false;
|
|
if (time) {
|
|
if (time->minutes >= 0) {
|
|
irq_enable = true;
|
|
data_u.buf[0] = byteToBcd2(time->minutes) & 0x7f;
|
|
}
|
|
|
|
if (time->hours >= 0) {
|
|
irq_enable = true;
|
|
data_u.buf[1] = byteToBcd2(time->hours) & 0x3f;
|
|
}
|
|
}
|
|
if (date) {
|
|
if (date->date >= 0) {
|
|
irq_enable = true;
|
|
data_u.buf[2] = byteToBcd2(date->date) & 0x3f;
|
|
}
|
|
|
|
if (date->weekDay >= 0) {
|
|
irq_enable = true;
|
|
data_u.buf[3] = byteToBcd2(date->weekDay) & 0x07;
|
|
}
|
|
}
|
|
|
|
writeRegister(0x09, data_u.buf, 4);
|
|
|
|
if (irq_enable) {
|
|
bitOn(0x01, 0x02);
|
|
} else {
|
|
bitOff(0x01, 0x02);
|
|
}
|
|
|
|
return irq_enable;
|
|
}
|
|
|
|
bool PCF8563_Class::getIRQstatus(void)
|
|
{
|
|
return _init && (0x0C & readRegister8(0x01));
|
|
}
|
|
|
|
void PCF8563_Class::clearIRQ(void)
|
|
{
|
|
if (!_init) {
|
|
return;
|
|
}
|
|
bitOff(0x01, 0x0C);
|
|
}
|
|
|
|
void PCF8563_Class::disableIRQ(void)
|
|
{
|
|
if (!_init) {
|
|
return;
|
|
}
|
|
// disable alerm (bit7:1=disabled)
|
|
static constexpr const std::uint8_t buf[4] = {0x80, 0x80, 0x80, 0x80};
|
|
writeRegister(0x09, buf, 4);
|
|
|
|
// disable timer (bit7:0=disabled)
|
|
writeRegister8(0x0E, 0);
|
|
|
|
// clear flag and INT enable bits
|
|
writeRegister8(0x01, 0x00);
|
|
}
|
|
|
|
} // namespace m5
|