2021-09-25 20:00:36 +02:00
|
|
|
#include "rtc.h"
|
|
|
|
#include "i2c.h"
|
|
|
|
|
|
|
|
|
|
|
|
enum rtc_regs {
|
|
|
|
RTCSEC,
|
|
|
|
RTCMIN,
|
|
|
|
RTCHOUR,
|
|
|
|
RTCWKDAY,
|
|
|
|
RTCDATE,
|
|
|
|
RTCMTH,
|
|
|
|
RTCYEAR,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define RTC_I2C_ADDR (0xDE)
|
|
|
|
|
|
|
|
#define RTCSEC_ST (1 << 7)
|
|
|
|
#define RTCWKDAY_OSCRUN (1 << 5)
|
|
|
|
#define RTCWKDAY_VBAT (1 << 3)
|
|
|
|
|
|
|
|
|
|
|
|
enum rtc_phase {
|
|
|
|
RTC_PHASE_READ_START,
|
|
|
|
RTC_PHASE_READ_READY,
|
|
|
|
RTC_PHASE_STOP,
|
|
|
|
RTC_PHASE_WAIT_STOP,
|
|
|
|
RTC_PHASE_UPDATE,
|
|
|
|
RTC_PHASE_START,
|
|
|
|
RTC_PHASE_WAIT_START,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum i2c_phase {
|
|
|
|
I2C_PHASE_IDLE,
|
|
|
|
I2C_PHASE_ADDR,
|
|
|
|
I2C_PHASE_DATA,
|
|
|
|
I2C_PHASE_READY,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct process {
|
|
|
|
enum rtc_phase rtc_phase;
|
|
|
|
uint8_t data[7];
|
|
|
|
bool running;
|
|
|
|
rtc_time_t time;
|
|
|
|
bool time_valid;
|
|
|
|
bool new_time_valid;
|
|
|
|
|
|
|
|
enum i2c_phase i2c_phase;
|
|
|
|
bool i2c_pending;
|
|
|
|
bool i2c_write;
|
|
|
|
uint8_t i2c_address;
|
|
|
|
uint8_t i2c_length;
|
|
|
|
bool i2c_first_read_done;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct process p;
|
|
|
|
|
|
|
|
|
|
|
|
static const uint8_t rtc_regs_bit_mask[7] = {
|
|
|
|
0b01111111,
|
|
|
|
0b01111111,
|
|
|
|
0b00111111,
|
|
|
|
0b00000111,
|
|
|
|
0b00111111,
|
|
|
|
0b00011111,
|
|
|
|
0b11111111
|
|
|
|
};
|
|
|
|
|
|
|
|
static void sanitize_time (uint8_t *data) {
|
|
|
|
for (int i = 0; i < 7; i++) {
|
|
|
|
data[i] &= rtc_regs_bit_mask[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rtc_time_t *rtc_get_time (void) {
|
|
|
|
return &p.time;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rtc_is_time_valid (void) {
|
|
|
|
return p.time_valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rtc_is_time_running (void) {
|
|
|
|
return p.running;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rtc_set_time (rtc_time_t *time) {
|
2021-11-16 22:37:48 +01:00
|
|
|
p.time.second = time->second;
|
|
|
|
p.time.minute = time->minute;
|
|
|
|
p.time.hour = time->hour;
|
|
|
|
p.time.weekday = time->weekday;
|
|
|
|
p.time.day = time->day;
|
|
|
|
p.time.month = time->month;
|
|
|
|
p.time.year = time->year;
|
2021-09-25 20:00:36 +02:00
|
|
|
p.new_time_valid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void rtc_init (void) {
|
|
|
|
p.rtc_phase = RTC_PHASE_READ_START;
|
|
|
|
p.running = false;
|
|
|
|
p.time_valid = false;
|
|
|
|
p.new_time_valid = false;
|
|
|
|
|
|
|
|
p.i2c_phase = I2C_PHASE_IDLE;
|
|
|
|
p.i2c_pending = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void process_rtc (void) {
|
|
|
|
if (p.i2c_phase == I2C_PHASE_IDLE) {
|
|
|
|
switch (p.rtc_phase) {
|
|
|
|
case RTC_PHASE_READ_START:
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = false;
|
|
|
|
p.i2c_address = RTCSEC;
|
|
|
|
p.i2c_length = sizeof(p.data);
|
|
|
|
p.rtc_phase = RTC_PHASE_READ_READY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_READ_READY:
|
|
|
|
p.time_valid = (!i2c_failed());
|
|
|
|
if (p.new_time_valid) {
|
|
|
|
p.rtc_phase = RTC_PHASE_STOP;
|
|
|
|
break;
|
|
|
|
} else if (p.time_valid) {
|
|
|
|
p.running = p.data[RTCSEC] & RTCSEC_ST;
|
|
|
|
sanitize_time(p.data);
|
|
|
|
p.time.second = p.data[RTCSEC];
|
|
|
|
p.time.minute = p.data[RTCMIN];
|
|
|
|
p.time.hour = p.data[RTCHOUR];
|
|
|
|
p.time.weekday = p.data[RTCWKDAY];
|
|
|
|
p.time.day = p.data[RTCDATE];
|
|
|
|
p.time.month = p.data[RTCMTH];
|
|
|
|
p.time.year = p.data[RTCYEAR];
|
|
|
|
}
|
|
|
|
p.rtc_phase = RTC_PHASE_READ_START;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_STOP:
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = true;
|
|
|
|
p.i2c_length = 2;
|
|
|
|
p.i2c_first_read_done = false;
|
|
|
|
p.data[0] = RTCSEC;
|
|
|
|
p.data[1] = 0x00;
|
|
|
|
p.rtc_phase = RTC_PHASE_WAIT_STOP;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_WAIT_STOP:
|
|
|
|
if (p.i2c_first_read_done) {
|
|
|
|
if (!(p.data[0] & RTCWKDAY_OSCRUN)) {
|
|
|
|
p.rtc_phase = RTC_PHASE_UPDATE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = false;
|
|
|
|
p.i2c_address = RTCWKDAY;
|
|
|
|
p.i2c_length = 1;
|
|
|
|
p.i2c_first_read_done = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_UPDATE:
|
|
|
|
sanitize_time((uint8_t *)(&p.time));
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = true;
|
|
|
|
p.i2c_length = 7;
|
|
|
|
p.data[0] = RTCMIN;
|
|
|
|
p.data[1] = p.time.minute;
|
|
|
|
p.data[2] = p.time.hour;
|
|
|
|
p.data[3] = p.time.weekday | RTCWKDAY_VBAT;
|
|
|
|
p.data[4] = p.time.day;
|
|
|
|
p.data[5] = p.time.month;
|
|
|
|
p.data[6] = p.time.year;
|
|
|
|
p.rtc_phase = RTC_PHASE_START;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_START:
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = true;
|
|
|
|
p.i2c_length = 2;
|
|
|
|
p.i2c_first_read_done = false;
|
|
|
|
p.data[0] = RTCSEC;
|
|
|
|
p.data[1] = p.time.second | RTCSEC_ST;
|
|
|
|
p.rtc_phase = RTC_PHASE_WAIT_START;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PHASE_WAIT_START:
|
|
|
|
if (p.i2c_first_read_done) {
|
|
|
|
if (p.data[0] & RTCWKDAY_OSCRUN) {
|
|
|
|
p.new_time_valid = false;
|
|
|
|
p.rtc_phase = RTC_PHASE_READ_START;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.i2c_pending = true;
|
|
|
|
p.i2c_write = false;
|
|
|
|
p.i2c_address = RTCWKDAY;
|
|
|
|
p.i2c_length = 1;
|
|
|
|
p.i2c_first_read_done = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!i2c_busy()) {
|
|
|
|
switch (p.i2c_phase) {
|
|
|
|
case I2C_PHASE_IDLE:
|
|
|
|
if (p.i2c_pending) {
|
|
|
|
p.i2c_pending = false;
|
|
|
|
p.i2c_phase = p.i2c_write ? I2C_PHASE_DATA : I2C_PHASE_ADDR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I2C_PHASE_ADDR:
|
|
|
|
i2c_trx(RTC_I2C_ADDR, &p.i2c_address, 1, true, false);
|
|
|
|
p.i2c_phase = I2C_PHASE_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I2C_PHASE_DATA:
|
|
|
|
i2c_trx(RTC_I2C_ADDR, p.data, p.i2c_length, p.i2c_write, true);
|
|
|
|
p.i2c_phase = I2C_PHASE_READY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I2C_PHASE_READY:
|
|
|
|
p.i2c_phase = I2C_PHASE_IDLE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|