wwvbdecode-2011/wwvbdecode.cc

491 lines
13 KiB
C++

#define __STDC_CONSTANT_MACROS 1
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#ifdef AVR
#define printf(...) (0)
#endif
struct wwvb_t {
int16_t yday; // 1..365, or 1..366 in leap years
int8_t hour; // 0..23
int8_t minute; // 0..60
int8_t second; // 0..60
int8_t year; // 0..99, indicating 2000..2099
unsigned ls:1; // true if a leap second is coming
unsigned ly:1; // true if it's a leap year, at least after feb 28
unsigned dst:2; // 2-bit dst indicator code
};
extern void set_time(const wwvb_t &t);
extern void next_second();
namespace {
int8_t isly(int8_t year) {
return (year == 0 || year % 100) && ((year % 4) == 0);
}
int16_t last_yday(wwvb_t &t) {
return 365 + isly(t.year);
}
// When the leap second flag is set, the last minute of the last hour
// of the last day of that month has an extra second inserted
//
// This function returns 59 for most minutes, and 60 for leap minutes.
//
// Some documents indicate that leap seconds might occur a the end of any
// month, while others indicate that they occur only in June and December.
// Possibly this code should be revised to account for the possibility of
// leap seconds in other months.
uint8_t last_second(wwvb_t &t) {
if(!t.ls) return 59;
if(t.hour != 23) return 59;
if(t.minute != 59) return 59;
if(t.ly) {
if(t.yday == 182 || t.yday == 366) return 60;
} else {
if(t.yday == 181 || t.yday == 365) return 60;
}
return 59;
}
// Advance to exactly the beginning of the next minute
void advance_minute(wwvb_t &t) {
// If the past minute ended with a leap second, reset the flag
if(last_second(t) == 60) t.ls = 0;
t.second = 0;
++t.minute;
if(t.minute < 60) return;
// new hour
t.minute = 0;
++t.hour;
if(t.hour < 24) return;
// new day
t.hour = 0;
++t.yday;
if(t.yday <= last_yday(t)) return;
// new year
t.yday = 1;
t.year += 1;
t.ly = isly(t.year);
}
// Advance by exactly one second
void advance_second(wwvb_t &t) {
++t.second;
if(t.second <= last_second(t)) return;
t.second = 0;
advance_minute(t);
}
// Advance or decrease the time according to the timezone offset
// in minutes and seconds. Note: for negative non-hour offsets, h and m
// should both be negative. e.g., for instance the NST offset of -3:30
// would be passed as h=-3, m=-30
void apply_tz(wwvb_t &t, int8_t h, int8_t m) {
t.minute += m;
t.hour += h;
// Now, both hours and minutes can be outside their normal range
// Adjust minutes until they are again in the range [0..60)
if(m < 0) {
m += 60;
t.hour -= 1;
} else if(m > 60) {
m -= 60;
t.hour -= 1;
}
// Now minutes are in the range [0..60) but hours can be outside
// the range.
if(t.hour < 0) {
t.hour += 24;
t.yday -= 1;
} else if(t.hour > 24) {
t.hour -= 24;
t.yday += 1;
}
// Now, minutes and hours are in their normal range, but the year
// day can be outside the year.
// in practice, only one 'if' will run
if(t.yday <= 0) {
t.year--;
t.yday = last_yday(t) - t.yday;
t.ly = isly(t.year);
} else if(t.yday > last_yday(t)) {
t.yday -= last_yday(t);
t.year++;
t.ly = isly(t.year);
}
// Now
}
int8_t leapyears_before(int8_t year) {
return (year + 3) / 4;
}
// monday = 0, sunday = 6
#define DOW_YDAY1_YEAR0 5 // january 1, 2000 = saturday
int8_t get_dow(wwvb_t &t) {
int16_t dow = DOW_YDAY1_YEAR0;
dow += t.year;
dow += leapyears_before(t.year);
dow += t.yday-1;
return dow % 7;
}
bool operator==(const wwvb_t &a, const wwvb_t &b) {
return a.year == b.year && a.yday == b.yday
&& a.hour == b.hour && a.minute == b.minute
&& a.second == b.second
&& a.dst == b.dst && a.ly == b.ly
&& a.ls == b.ls;
}
const uint8_t NSAMPLES = 120;
const int8_t DEBOUNCE_TC = 10;
const int16_t COUNTER_SLOP = 100;
// 10 for DEBOUNCE_TC, 30 for receiver rise/fall time, 5 for light propagation
const int16_t SIGNAL_DELAY = 40;
uint8_t wwvb_buf[(NSAMPLES+3)/4];
int8_t wwvb_pos;
enum wwvb_state_t { STATE_FIND_POLARITY, STATE_CAPTURE_TIME };
wwvb_state_t wwvb_state;
bool wwvb_polarity;
int8_t wwvb_counter;
bool wwvb_denoised;
int16_t counter;
int8_t sos_counter;
void WWVB_PUT(uint8_t v) {
uint8_t idx = wwvb_pos / 4;
uint8_t s = 2 * (wwvb_pos % 4);
uint8_t m = 3 << s;
wwvb_buf[idx] = (wwvb_buf[idx] & ~m) | (v << s);
wwvb_pos ++;
if(wwvb_pos == NSAMPLES) wwvb_pos = 0;
}
uint8_t WWVB_GET(int8_t i) {
i = i - NSAMPLES + wwvb_pos;
if(i < 0) i += NSAMPLES;
uint8_t idx = i / 4;
uint8_t s = 2*(i % 4);
return (wwvb_buf[idx] >> s) & 3;
}
void decode_one_minute(uint8_t pos, wwvb_t &t) {
// The minute running from pos..pos+60 is already validated
// so there's no need to check mark bits or "always zero" bits
// .. everything from WWVB_GET is just a 0 or a 1
#define G(p, w) (WWVB_GET((p)+pos) ? (w) : 0)
t.minute = G(1, 40) + G(2, 20) + G(3, 10)
+ G(5, 8) + G(6, 4) + G(7, 2) + G(8, 1);
t.hour = G(12, 20) + G(13, 10)
+ G(15, 8) + G(16, 4) + G(17, 2) + G(18, 1);
t.yday = G(22, 200) + G(23, 100)
+ G(25, 80) + G(26, 40) + G(27, 20) + G(28, 10)
+ G(30, 8) + G(31, 4) + G(32, 2) + G(33, 1);
t.year = G(45, 80) + G(46, 40) + G(47, 20) + G(48, 10)
+ G(50, 8) + G(51, 4) + G(52, 2) + G(53, 1);
t.ly = G(55, 1);
t.ls = G(56, 1);
t.dst = G(57, 2) + G(58, 1);
t.second = 0;
}
void denoise_step(bool &denoised, int8_t &counter, bool value) {
if(value) {
if(0 && counter <= 0) counter = 1;
else if(counter == DEBOUNCE_TC) denoised = value;
else counter++;
} else {
if(0 && counter >= 0) counter = -1;
else if(counter == -DEBOUNCE_TC) denoised = value;
else counter--;
}
}
bool counter_near(int16_t n) {
return counter > (n - COUNTER_SLOP) && counter <= (n + COUNTER_SLOP);
}
const char *format_wwvbtime(const wwvb_t &t) {
static char buf[80];
snprintf(buf, sizeof(buf), "%4d/%03d %d:%02d:%02d ly=%d ls=%d dst=%d",
t.year + 2000, t.yday, t.hour, t.minute, t.second, t.ls, t.ly, t.dst);
return buf;
}
// bit i is 1 if it must be a mark, 0 if it must not be a mark
const uint64_t markmask =
(UINT64_C(1) << 0) |
(UINT64_C(1) << 9) |
(UINT64_C(1) << 19) |
(UINT64_C(1) << 29) |
(UINT64_C(1) << 39) |
(UINT64_C(1) << 49) |
(UINT64_C(1) << 59);
#define MARK(i) do { if(WWVB_GET(i) != 2 || WWVB_GET(i+60) != 2) return false; } while(0)
#define NOMARK(i) do { if(WWVB_GET(i) == 2 || WWVB_GET(i+60) == 2) return false; } while(0)
bool try_set_time(wwvb_t &new_t) {
// Check for markers over 2 minutes
for(int i=0; i<60; i++)
if(markmask & (UINT64_C(1) << i)) MARK(i); else NOMARK(i);
wwvb_t t0, t1;
decode_one_minute(0, t0);
decode_one_minute(60, t1);
printf("first minute %s\n", format_wwvbtime(t0));
printf("second minute %s\n", format_wwvbtime(t1));
advance_minute(t0);
printf("advanced %s\n", format_wwvbtime(t0));
if(t0 == t1) {
t1.second = 59;
new_t = t1;
return true;
}
return false;
}
#ifdef TUNE
/* TIMER1 runs at 16MHz nominal. We want a 1ms interrupt.
* That makes the nominal TOP value 16000. This wide adjustment range allows
* us to accomodate real clock frequencies of 15MHz..16MHz. The expected clock
* variations are actually on the order of ppm, not 6%.
*/
extern void set_timer_top(uint16_t top);
const int16_t MIN_TOP=15000, MAX_TOP=17000, NOMINAL_TOP=16000;
int16_t top = NOMINAL_TOP;
static uint8_t counter_history;
void steer_counter(uint16_t pulse_period_measured) {
counter_history <<= 2;
if(measured < 1000) counter_history |= 1;
else if(measured > 1000) counter_history |= 2;
if(counter_history == 0x55 && top > min_top)
top--; /* Last 4 samples were all too short */
else if(counter_history == 0x88 && top < max_top)
top++; /* Last 4 samples were all too long */
set_timer_top(top);
}
#endif
bool pending_set_time;
bool pps_good;
wwvb_t pending_time;
int16_t free_running_ms;
}
void wwvb_receive_loop(bool raw_wwvb) {
bool old_wwvb_denoised = wwvb_denoised;
denoise_step(wwvb_denoised, wwvb_counter, raw_wwvb);
bool edge = wwvb_denoised ^ old_wwvb_denoised;
bool wwvb = wwvb_denoised ^ wwvb_polarity;
bool rising_edge = edge && wwvb;
bool falling_edge = edge && !wwvb;
switch(wwvb_state) {
case STATE_FIND_POLARITY:
if(rising_edge) {
if(counter_near(1000)) {
sos_counter++;
if(sos_counter == 5) {
goto set_state_capture_time;
}
} else { sos_counter = 0; wwvb_polarity = !wwvb_polarity; }
}
case STATE_CAPTURE_TIME:
if(rising_edge) {
if(!counter_near(1000)) {
goto set_state_find_polarity;
}
if(pending_set_time) {
set_time(pending_time);
pending_set_time = false;
free_running_ms = 1000 + SIGNAL_DELAY;
}
}
if(falling_edge) {
if(counter_near(200)) WWVB_PUT(0);
else if(counter_near(500)) WWVB_PUT(1);
else if(counter_near(800)) {
WWVB_PUT(2);
pending_set_time = try_set_time(pending_time);
} else
goto set_state_find_polarity;
}
static uint8_t seconds_unset;
seconds_unset ++;
if(seconds_unset == 0) goto set_state_find_polarity;
}
free_running_ms ++;
if(free_running_ms >= 1000) {
free_running_ms -= 1000;
next_second();
}
if(rising_edge) {
pps_good = counter_near(1000);
#ifdef TUNE
if(pps_good && counter != 1000) steer_counter(pps_good > 1000);
#endif
counter = 0;
} else if(counter > 1000 + COUNTER_SLOP) {
pps_good = false;
counter ++;
} else
counter ++;
return;
set_state_capture_time:
counter = 0;
memset(wwvb_buf, 0, sizeof(wwvb_buf));
wwvb_state = STATE_CAPTURE_TIME;
return;
set_state_find_polarity:
sos_counter = 0;
wwvb_state = STATE_FIND_POLARITY;
return;
}
#ifndef AVR
#include <stdio.h>
#include <stdlib.h>
void set_time(const wwvb_t &t) {
printf("set time %d/%03d %d:%02d:%02d ly=%d ls=%d dst=%d\n",
t.year + 2000, t.yday, t.hour, t.minute, t.second, t.ls, t.ly, t.dst);
exit(0);
}
void next_second() {}
int main() {
while(1) {
int c = getchar();
if(c == EOF) break;
wwvb_receive_loop(c == '1');
}
printf("failed to set time\n");
}
#else
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#define LED (5)
#define WWVB (0)
#define CYCLES_HIGH 16000
int uptime;
char buf[80];
void set_time(const wwvb_t &t) {
snprintf(buf, sizeof(buf),
"set time %d/%03d %d:%02d:%02d ly=%d ls=%d dst=%d uptime=%d pol=%d\n",
t.year + 2000, t.yday, t.hour, t.minute, t.second, t.ls, t.ly, t.dst,
uptime, wwvb_polarity);
eeprom_write_block(buf, 0, strlen(buf)+1);
// set LED on solid
PORTB |= (1<<LED);
// and freeze here
while(1) {}
}
ISR(TIMER1_CAPT_vect) {
bool wwvb_raw = !!(PINB & (1<<WWVB));
wwvb_receive_loop(wwvb_raw);
int val;
if(wwvb_state == STATE_FIND_POLARITY) {
val = pps_good;
} else {
val = (wwvb_denoised ^ wwvb_polarity ) ? 1 : 16;
}
if((free_running_ms & 15) < val)
PORTB |= (1<<LED);
else
PORTB &= ~(1<<LED);
}
void eeprom_to_serial() {
uint8_t *i = 0;
uint8_t c;
while(i < (uint8_t*)256 && (c = eeprom_read_byte(i++))) {
while(!(UCSR0A & (1<<UDRE0)))
{}
UDR0 = c;
}
}
void next_second() {
uptime++;
}
void main() {
// Set up TIMER1 in CTC mode with ICR1 as top
TCCR1A = (1<WGM11);
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10);
ICR1 = CYCLES_HIGH;
TIMSK1 = (1<<ICIE1);
// Set the UART to 19.2kb/s.
#define F_CPU 16000000
#define BAUD 19200
#include <util/setbaud.h>
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X
UCSR0A |= (1 << U2X0);
#else
UCSR0A &= ~(1 << U2X0);
#endif
DDRD = (1<<1); // enable TXD as output
// Set up the LED as output
DDRB = (1<<LED);
PORTB |= (1<<WWVB); // turn on pull-up on wwvb
// enable interrupts
sei();
// perhaps our predecessor left a message
eeprom_to_serial();
// and sleep forever, since things happen in the interrupt only
#ifdef SLEEP_MODE_IDLE
set_sleep_mode(SLEEP_MODE_IDLE);
while(1) sleep_mode();
#else
while(1) {}
#endif
}
#endif