189 lines
5.2 KiB
C++
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;
|
|
}
|
|
}
|
|
} |