cwwvb/cwwvb.ino

325 lines
7.9 KiB
C++

// SPDX-FileCopyrightText: 2021 Jeff Epler
//
// SPDX-License-Identifier: GPL-3.0-only
#define PIN_OUT (12)
#define PIN_PDN (10)
#define PIN_MON (5)
#include <algorithm>
#include <atomic>
#include "SAMDTimerInterrupt.h"
#include "SAMD_ISR_Timer.h"
#include "decoder.h"
#define MONITOR_LL (0)
#define MONITOR_SYM (0)
#define AUTO_STEERING (1)
// SAMD51 Hardware Timer only TC3
// We override this below, but the value puts the predivider into a good range
// for us (/8 prescaler)
constexpr int TIMER0_INTERVAL_MS = 20;
SAMDTimer ITimer0(TIMER_TC3);
int cc;
int tick_subsec;
WWVBDecoder<> dec;
constexpr int CENTRAL_COUNT = 3000000 / dec.SUBSEC;
static_assert(CENTRAL_COUNT <= 65535);
struct Critical {
Critical() { noInterrupts(); }
~Critical() { interrupts(); }
};
typedef void (*work)();
struct Work {
int head, tail;
static constexpr int size = 6;
work todo[size];
void put(work fun) {
todo[head] = fun;
head = (head + 1) % size;
if (head == tail)
abort();
}
work take() {
Critical _;
work result = nullptr;
if (tail != head) {
result = todo[tail];
tail = (tail + 1) % size;
}
return result;
}
bool empty() {
Critical _;
return tail == head;
}
} wq;
static void tick();
static void try_decode();
std::atomic<int> introduced_error;
void TimerHandler0(void) {
int i = digitalRead(PIN_OUT);
digitalWrite(PIN_MON, HIGH);
digitalWrite(PIN_MON, LOW);
TC3->COUNT16.CC[0].reg = cc;
#if MONITOR_LL
putc(i ? '_' : '#', stderr);
#endif
if (introduced_error.load()) {
introduced_error.fetch_sub(1);
return;
}
if (dec.update(i)) {
wq.put(try_decode);
}
auto subsec = mod_diff<dec.SUBSEC>(dec.subsec, tick_subsec);
if (subsec == 0) {
wq.put(tick);
}
}
void moveto(int x, int y) { printf("\033[%d;%dH", y, x); }
int ss_I, ss_P;
void set_tc(int n, bool hold) {
moveto(1, 25);
// max 1% adjustment
if (n > CENTRAL_COUNT / 100)
n = CENTRAL_COUNT / 100;
if (n < -(int)CENTRAL_COUNT / 100)
n = -(int)CENTRAL_COUNT / 100;
cc = CENTRAL_COUNT + n;
moveto(1, 24);
fflush(stdout);
fprintf(stderr, "Steer %+4d CC = %5d I = %+5d P=%+5d %.4s\n", n, cc, ss_I,
ss_P, hold ? "HOLD" : "INTG");
}
void steer_tc(int delta) { set_tc(cc - CENTRAL_COUNT + delta, false); }
bool ever_set;
wwvb_time w;
void display_time() {
time_t now = w.to_utc();
struct tm tm;
gmtime_r(&now, &tm);
moveto(1, 1);
char buf[32];
strftime(buf, sizeof(buf), "%FT%TZ", &tm);
printf("%s ", buf);
tm = w.apply_zone_and_dst(6, true);
strftime(buf, sizeof(buf), "%FT%T", &tm);
printf("%s C%cT ls=%d ly=%d dst=%d dut1=%+2d", buf, tm.tm_isdst ? 'D' : 'S',
w.ls, w.ly, w.dst, w.dut1);
}
void loop() {
while (Serial.available() > 0) {
int c = Serial.read();
if (c == 'x') {
introduced_error.fetch_add(5);
}
#if !AUTO_STEERING
if (c == '+' || c == '=') {
steer_tc(1);
}
if (c == '-') {
steer_tc(-1);
}
if (c == '0') {
set_tc(0);
}
#endif
}
{
Critical _;
if (wq.empty()) {
__WFI();
return;
}
}
digitalWrite(PIN_LED, HIGH);
wq.take()();
fflush(stdout);
digitalWrite(PIN_LED, LOW);
}
void try_decode() {
decltype(dec) snapshot;
{
Critical _;
snapshot = dec;
}
#if AUTO_STEERING
// Try to steer the start-of-subsec to the "0" value
// This is a simple PI control, which should
// settle with almost no phase error. (or, more likely, oscillate
// around two nearby values)
bool hold = snapshot.health < snapshot.HEALTH_97PCT;
ss_P = mod_diff<snapshot.SUBSEC>(snapshot.sos, 0);
if (!hold) {
ss_I += ss_P;
}
set_tc(ss_P * 4 + ss_I / 20, hold);
#endif
{
#define ROWS (22)
char screen[ROWS][snapshot.SUBSEC];
memset(screen, ' ', sizeof(screen));
int max_counts = 0;
int max_edges = 0;
for (int i = 0; i < snapshot.SUBSEC; i++) {
max_counts = std::max(max_counts, (int)snapshot.counts[i]);
max_edges = std::max(max_edges, (int)abs(snapshot.edges[i]));
}
for (int i = 0; i < ROWS; i++) {
screen[i][snapshot.sos] = '.';
}
for (int i = 0; i < snapshot.SUBSEC; i++) {
int j = i;
while (j >= snapshot.SUBSEC)
j -= snapshot.SUBSEC;
{
int r =
ROWS / 2 - snapshot.edges[j] * (ROWS - 1) / max_edges / 2;
screen[r][i] = '_';
}
{
int r = ROWS - 1 -
snapshot.counts[j] * ROWS /
(1 + snapshot.BUFFER / snapshot.SUBSEC);
screen[r][i] = '#';
}
}
moveto(1, 2);
for (int i = 0; i < ROWS; i++) {
printf("%.*s|\n", snapshot.SUBSEC, screen[i]);
}
}
{
char buf[snapshot.SYMBOLS];
for (int i = 0; i < sizeof(buf); i++) {
static const char sym2char[] = "012?";
buf[i] = sym2char[snapshot.symbols.at(i)];
}
moveto(1, 25);
printf("%.*s health=%3d%%", sizeof(buf), buf,
snapshot.health * 100 / snapshot.MAX_HEALTH);
}
if (snapshot.symbols.at(snapshot.SYMBOLS - 1) == 2) {
if (snapshot.decode_minute(w)) {
tick_subsec = mod_diff<snapshot.SUBSEC>(snapshot.sos, 5);
// Must advance by seconds instead of by a minute, because
// if this just-received minute has a leap second,
// advancing 1 minute leaves us at the wrong moment, because we're
// at just 60 seconds into the minute. (23:59:60 instead of
// 00:00:00 next day)
w.advance_seconds(60);
ever_set = true;
display_time();
}
}
}
void tick() {
// if (ever_set) {
w.advance_seconds();
display_time();
//}
}
extern "C" int write(int file, char *ptr, int len);
void setup() {
pinMode(PIN_PDN, OUTPUT);
pinMode(PIN_MON, OUTPUT);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_OUT, INPUT_PULLUP);
digitalWrite(PIN_PDN, 0);
Serial.begin(115200);
while (!Serial) { /* wait for connect */
}
ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerHandler0);
// 59999 counts [the value you get with the above interval] is about
// 300ppm off on my HW.
// 300ppm _slow_ on my HW. Each adjustment step is about 16.67ppm.
// Making the number _BIGGER_ makes the `sos` value decrease over time
// (because it makes the interrupt _slower_)
// and making it _SMALLER_ makes the `sos` value incrase over time.
// (making the interrupt _faster_)
set_tc(0, false);
printf("\033[2J");
}
// This bridges from stdio output to Serial.write
#include <errno.h>
#undef errno
extern int errno;
extern "C" int _write(int file, char *ptr, int len);
int _write(int file, char *ptr, int len) {
if (file < 1 || file > 3) {
errno = EBADF;
return -1;
}
if (file == 3) { // File 3 does not do \n -> \r\n transformation
Serial.write(ptr, len);
return len;
}
// color stderr bold
static bool red;
bool is_stderr = (file == 2);
if (is_stderr != red) {
if (is_stderr) {
Serial.write("\033[95m");
} else {
Serial.write("\033[0m");
}
red = is_stderr;
}
int result = len;
for (; len--; ptr++) {
int c = *ptr;
if (c == '\n')
Serial.write('\r');
Serial.write(c);
}
return result;
}
extern "C" int write(int file, char *ptr, int len);
int write(int file, char *ptr, int len) __attribute__((alias("_write")));