DCF77_Clock_Control/clock.cpp

189 lines
5.2 KiB
C++

/**
* Copyright (C) 2022 Julian Metzler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/eeprom.h>
#include "clock.h"
// Actual, currently displayed time
uint8_t clock_curHour = 0;
uint8_t clock_curMinute = 0;
// Desired time (might be different in case of catching up)
uint8_t clock_setHour = 0;
uint8_t clock_setMinute = 0;
// Status data in EEPROM
uint32_t* clock_eepromStartAddr = 0;
uint16_t clock_eepromWriteCounter = 0;
// Millisecond counter of last loop execution, used for non-blocking timing
uint32_t clock_lastPulseStartTimestamp = 0;
// Stores if there is a pulse currently ongoing
uint8_t clock_pulseOngoing = 0;
void clock_init() {
BRIDGE_PWM_DDR |= (1 << BRIDGE_PWM_PIN);
BRIDGE_DIR_DDR |= (1 << BRIDGE_DIR_PIN);
BRIDGE_DIS_DDR |= (1 << BRIDGE_DIS_PIN);
clock_pulseEnd();
//clock_initEeprom();
clock_eepromStartAddr = clock_findEepromStartAddress();
clock_updateDataFromEeprom();
clock_setHour = clock_curHour;
clock_setMinute = clock_curMinute;
}
void clock_loop(uint32_t loopTimestamp) {
if (clock_pulseOngoing) {
if ((loopTimestamp - clock_lastPulseStartTimestamp) >= CLOCK_PULSE_DURATION) {
clock_pulseEnd();
clock_advanceCurrent();
clock_writeDataToEeprom();
}
} else {
uint16_t pulsesNeeded = clock_calcPulsesNeeded();
if (pulsesNeeded > 0) {
clock_pulseStart(clock_getPolarity());
clock_lastPulseStartTimestamp = loopTimestamp;
}
}
}
void clock_pulseStart(uint8_t polarity) {
clock_pulseOngoing = 1;
if (polarity) BRIDGE_DIR_PORT |= (1 << BRIDGE_DIR_PIN);
BRIDGE_PWM_PORT |= (1 << BRIDGE_PWM_PIN);
BRIDGE_DIS_PORT &= ~(1 << BRIDGE_DIS_PIN);
}
void clock_pulseEnd() {
BRIDGE_DIS_PORT |= (1 << BRIDGE_DIS_PIN);
BRIDGE_PWM_PORT &= ~(1 << BRIDGE_PWM_PIN);
BRIDGE_DIR_PORT &= ~(1 << BRIDGE_DIR_PIN);
clock_pulseOngoing = 0;
}
void clock_advanceCurrent() {
clock_curMinute++;
if (clock_curMinute >= 60) {
clock_curMinute = 0;
clock_curHour++;
if (clock_curHour >= 12) {
clock_curHour = 0;
}
}
}
void clock_advanceSet() {
clock_setMinute++;
if (clock_setMinute >= 60) {
clock_setMinute = 0;
clock_setHour++;
if (clock_setHour >= 12) {
clock_setHour = 0;
}
}
}
void clock_setTime(uint8_t hour, uint8_t minute) {
clock_setHour = hour % 12;
clock_setMinute = minute % 60;
}
uint16_t clock_calcPulsesNeeded() {
// Calculate the number of pulses needed to get from the current time to the desired time
int16_t numPulses = 0;
if (clock_setHour < clock_curHour) {
numPulses += (clock_setHour + 12 - clock_curHour) * 60;
} else {
numPulses += (clock_setHour - clock_curHour) * 60;
}
if (clock_setMinute < clock_curMinute) {
// In this case, we need to subtract because this already involves increasing the hour by one
numPulses -= (clock_curMinute - clock_setMinute);
} else {
numPulses += clock_setMinute - clock_curMinute;
}
if (numPulses < 0) numPulses += 720;
return (uint16_t)numPulses;
}
uint8_t clock_getPolarity() {
// Get the polarity required for the UPCOMING minute transition
return clock_curMinute & 1;
}
void clock_initEeprom() {
for (uint8_t* i = 0; i < (uint8_t*)EEPROM_SIZE; i++) {
eeprom_write_byte(i, 0x00);
}
}
uint32_t* clock_findEepromStartAddress() {
// Scan through EEPROM to find the first location with less than
// the number of cycles specified for wear leveling
// Entry format: <16 Bit Write Counter> <8 Bit Hour> <8 Bit Minute>
// To calculate the time until wrap-around, not taking into account
// Time changed that require a large series of pulses:
// Lifetime (years) = (EEPROM size / 4) * EEPROM_MAX_WRITE_COUNTER / 525960
uint16_t* startAddr = 0;
uint32_t counter = 0;
do {
counter = eeprom_read_word(startAddr);
if (counter < EEPROM_MAX_WRITE_COUNTER) {
break;
}
startAddr += 4;
// This should only happen after about 35555 years
if ((uint32_t*)startAddr > (uint32_t*)(EEPROM_SIZE - 4)) {
return 0;
}
} while(true);
return (uint32_t*)startAddr;
}
void clock_updateDataFromEeprom() {
uint32_t data = eeprom_read_dword(clock_eepromStartAddr);
clock_eepromWriteCounter = data >> 16;
clock_curHour = (data >> 8) & 0xFF;
clock_curMinute = data & 0xFF;
}
void clock_writeDataToEeprom() {
clock_eepromWriteCounter++;
uint32_t eepromValue = (((uint32_t)clock_eepromWriteCounter) << 16);
eepromValue |= (((uint16_t)clock_curHour) << 8);
eepromValue |= clock_curMinute;
eeprom_update_dword(clock_eepromStartAddr, eepromValue);
if (clock_eepromWriteCounter >= EEPROM_MAX_WRITE_COUNTER) {
clock_eepromStartAddr += 4;
clock_eepromWriteCounter = 0;
// This should only happen after about 35555 years
if (clock_eepromStartAddr > (uint32_t*)(EEPROM_SIZE - 4)) {
clock_eepromStartAddr = 0;
}
}
}