├── .gitignore ├── GPS_Clock.c ├── GPS_Clock_v4.c ├── GPS_Clock_v5.c ├── LICENSE └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /GPS_Clock.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | GPS Clock 4 | Copyright (C) 2016 Nicholas W. Sayer 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along 17 | with this program; if not, write to the Free Software Foundation, Inc., 18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | */ 21 | 22 | // Fuse settings: lfuse=0xe2, hfuse=0xdf, efuse=0xff 23 | 24 | // Hardware options: 25 | // --- 26 | // V2 hardware has the PPS going into the ICP pin instead of INT0 27 | #define V2 28 | 29 | // V3 has the PPS line going into ICP2 instead of ICP1 so !SS can be !D_CS 30 | #define V3 31 | 32 | // V3.1 has the two buttons moved to port A to make room for the crystal 33 | // on pins B0 & B1. 34 | #define V31 35 | 36 | // Define this if your board uses an 12 MHz crystal instead of the RC oscillator 37 | //#define XTAL 38 | 39 | // !NEW_AMPM hardware has AM on the digit 7 DP and PM on the digit 6 DP 40 | #define NEW_AMPM 41 | 42 | // Older hardware doesn't have a tenth of a second digit 43 | #define TENTH_DIGIT 44 | 45 | // Older hardware doesn't have colons. 46 | #define COLONS 47 | // --- 48 | 49 | #if (defined(V31) && !defined(V3)) 50 | #error V31 requires V3 51 | #endif 52 | 53 | #if (defined(XTAL) && !defined(V31)) 54 | #error XTAL requires V31 55 | #endif 56 | 57 | #if (defined(V3) && !defined(V2)) 58 | #error V3 requires V2 59 | #endif 60 | 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | #ifdef XTAL 74 | // 12 MHz. 75 | #define F_CPU (12000000UL) 76 | #else 77 | // 8 MHz. 78 | #define F_CPU (8000000UL) 79 | #endif 80 | 81 | #include 82 | 83 | // UBRR?_VALUE macros defined here are used below in serial initialization in main() 84 | #define BAUD 9600 85 | #include 86 | 87 | // Port A is used for the display SPI interface and serial. 88 | // We don't need to config the serial pins - it's enough to 89 | // just turn on the USART. 90 | #define PORT_MAX PORTA 91 | #ifdef V3 92 | #define BIT_MAX_CS _BV(PORTA7) 93 | #else 94 | #define BIT_MAX_CS _BV(PORTA3) 95 | #endif 96 | // MOSI and SCK are taken care of by the SPI stuff, but must still 97 | // have the DDR bits set to make them outputs. 98 | #ifdef V3 99 | #define DDR_BITS_A _BV(DDA7) | _BV(DDA4) | _BV(DDA6) 100 | #else 101 | #define DDR_BITS_A _BV(DDA3) | _BV(DDA4) | _BV(DDA6) 102 | #endif 103 | 104 | // The MAX6951 registers and their bits 105 | #define MAX_REG_DEC_MODE 0x01 106 | #define MAX_REG_INTENSITY 0x02 107 | #define MAX_REG_SCAN_LIMIT 0x03 108 | #define MAX_REG_CONFIG 0x04 109 | #define MAX_REG_CONFIG_R _BV(5) 110 | #define MAX_REG_CONFIG_T _BV(4) 111 | #define MAX_REG_CONFIG_E _BV(3) 112 | #define MAX_REG_CONFIG_B _BV(2) 113 | #define MAX_REG_CONFIG_S _BV(0) 114 | #define MAX_REG_TEST 0x07 115 | // P0 and P1 are planes - used when blinking is turned on 116 | // or the mask with the digit number 0-7. On the hardware, 0-6 117 | // are the digits from left to right (D6 is tenths of seconds, D0 118 | // is tens of hours). D7 is AM, PM and the four LEDs for the colons. 119 | // To blink, you write different stuff to P1 and P0 and turn on 120 | // blinking in the config register (bit E to turn on, bit B for speed). 121 | #define MAX_REG_MASK_P0 0x20 122 | #define MAX_REG_MASK_P1 0x40 123 | #define MAX_REG_MASK_BOTH (MAX_REG_MASK_P0 | MAX_REG_MASK_P1) 124 | // When decoding is turned off, this is the bit mapping. 125 | // Segment A is at the top, the rest proceed clockwise around, and 126 | // G is in the middle. DP is the decimal point. 127 | // When decoding is turned on, bits 0-3 are a hex value, 4-6 are ignored, 128 | // and DP is as before. 129 | #define MASK_DP _BV(7) 130 | #define MASK_A _BV(6) 131 | #define MASK_B _BV(5) 132 | #define MASK_C _BV(4) 133 | #define MASK_D _BV(3) 134 | #define MASK_E _BV(2) 135 | #define MASK_F _BV(1) 136 | #define MASK_G _BV(0) 137 | 138 | // Digit 7 has the two colons and the AM & PM lights 139 | #ifdef COLONS 140 | #define MASK_COLON_HM (MASK_E | MASK_F) 141 | #define MASK_COLON_MS (MASK_B | MASK_C) 142 | #endif 143 | #define MASK_AM (MASK_A) 144 | #define MASK_PM (MASK_D) 145 | 146 | // Digit map 147 | #define DIGIT_10_HR (0) 148 | #define DIGIT_1_HR (1) 149 | #define DIGIT_10_MIN (2) 150 | #define DIGIT_1_MIN (3) 151 | #define DIGIT_10_SEC (4) 152 | #define DIGIT_1_SEC (5) 153 | #define DIGIT_100_MSEC (6) 154 | #define DIGIT_MISC (7) 155 | 156 | #define RX_BUF_LEN (96) 157 | #define TX_BUF_LEN (24) 158 | 159 | // Note that some versions of the AVR LIBC forgot to 160 | // define the individual PUExn bit numbers. 161 | #ifndef PUEA0 162 | #define PUEA0 0 163 | #define PUEA1 1 164 | #define PUEA2 2 165 | #define PUEA3 3 166 | #define PUEA4 4 167 | #define PUEA5 5 168 | #define PUEA6 6 169 | #define PUEA7 7 170 | #define PUEB0 0 171 | #define PUEB1 1 172 | #define PUEB2 2 173 | #define PUEB3 3 174 | #endif 175 | 176 | #ifdef V31 177 | #define PORT_SW PINA 178 | #define SW_0_BIT _BV(PINA0) 179 | #define SW_1_BIT _BV(PINA3) 180 | // DDR_BITS_A doesn't need to be adjusted. The correct bits are already inputs. 181 | // Port B only has the PPS input, so it's zero. 182 | #define DDR_BITS_B (0) 183 | #define PULLUP_BITS_A _BV(PUEA0) | _BV(PUEA3) 184 | #else // !V31 185 | // Port B is the switches and the PPS GPS input 186 | #define PORT_SW PINB 187 | #define DDR_BITS_B (0) 188 | #define SW_0_BIT _BV(PINB0) 189 | #ifdef V3 190 | #define SW_1_BIT _BV(PINB1) 191 | #else // !V3 192 | #define SW_1_BIT _BV(PINB2) 193 | #endif 194 | #ifdef V3 195 | #define PULLUP_BITS_B _BV(PUEB0) | _BV(PUEB1) 196 | #else // !V3 197 | #define PULLUP_BITS_B _BV(PUEB0) | _BV(PUEB2) 198 | #endif 199 | #endif 200 | 201 | // These are return values from the DST detector routine. 202 | // DST is not in effect all day 203 | #define DST_NO 0 204 | // DST is in effect all day 205 | #define DST_YES 1 206 | // DST begins at 0200 207 | #define DST_BEGINS 2 208 | // DST ends 0300 - that is, at 0200 pre-correction. 209 | #define DST_ENDS 3 210 | 211 | // The possible values for dst_mode 212 | #define DST_OFF 0 213 | #define DST_US 1 214 | #define DST_EU 2 215 | #define DST_AU 3 216 | #define DST_NZ 4 217 | #define DST_MODE_MAX DST_NZ 218 | 219 | #ifdef COLONS 220 | #define COLON_OFF 0 221 | #define COLON_ON 1 222 | #define COLON_BLINK 2 223 | #define COLON_STATE_MAX COLON_BLINK 224 | #endif 225 | 226 | // EEPROM locations to store the configuration. 227 | #define EE_TIMEZONE ((uint8_t*)0) 228 | #define EE_DST_MODE ((uint8_t*)1) 229 | #define EE_AM_PM ((uint8_t*)2) 230 | #define EE_BRIGHTNESS ((uint8_t*)3) 231 | #ifdef TENTH_DIGIT 232 | #define EE_TENTHS ((uint8_t*)4) 233 | #endif 234 | #ifdef COLONS 235 | #define EE_COLONS ((uint8_t*)5) 236 | #endif 237 | 238 | // This is the timer frequency - it's the system clock prescaled by 8 239 | #define F_TICK (F_CPU / 8) 240 | 241 | // We want something like 50 ms. 242 | #define DEBOUNCE_TICKS (F_TICK / 20) 243 | // The buttons 244 | #define SELECT 1 245 | #define SET 2 246 | 247 | // If we don't get a PPS at least this often, then we've lost it. 248 | // This is F_TICK*1.25 - a quarter second late. 249 | #define LOST_PPS_TICKS (F_TICK + F_TICK / 4) 250 | 251 | // For unknown reasons, we sometimes get a first PPS tick that's way, way 252 | // too fast. Rather than have the display look weird, we'll just skip 253 | // showing tenths anytime GPS tells us a tenth of a second is less than 254 | // 50 ms worth of system clock. 255 | #define FAST_PPS_TICKS (F_TICK / 20) 256 | 257 | volatile unsigned char disp_buf[8]; 258 | volatile unsigned char rx_buf[RX_BUF_LEN]; 259 | volatile unsigned char rx_str_len; 260 | volatile unsigned char tx_buf[TX_BUF_LEN]; 261 | volatile unsigned int tx_buf_head, tx_buf_tail; 262 | volatile unsigned char nmea_ready; 263 | volatile unsigned long last_pps_tick; 264 | volatile unsigned long tenth_ticks; 265 | volatile unsigned char gps_locked; 266 | volatile unsigned char dst_mode; 267 | volatile unsigned char ampm; 268 | volatile unsigned char menu_pos; 269 | volatile char tz_hour; 270 | volatile unsigned int timer_hibits; 271 | #ifdef TENTH_DIGIT 272 | volatile unsigned char tenth_enable; 273 | volatile unsigned char disp_tenth; 274 | volatile unsigned char tenth_dp; 275 | #endif 276 | #ifdef COLONS 277 | unsigned char colon_state; 278 | #endif 279 | unsigned long debounce_time; 280 | unsigned char button_down; 281 | unsigned char brightness; 282 | unsigned int fw_version_year, utc_ref_year; 283 | unsigned char fw_version_mon, utc_ref_mon; 284 | unsigned char fw_version_day, utc_ref_day; 285 | 286 | // Delay, but pet the watchdog while doing it. 287 | static void Delay(unsigned long ms) { 288 | while(ms > 100) { 289 | _delay_ms(100); 290 | wdt_reset(); 291 | ms -= 100; 292 | } 293 | _delay_ms(ms); 294 | wdt_reset(); 295 | } 296 | 297 | void write_reg(const unsigned char addr, const unsigned char val) { 298 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 299 | #ifndef V3 300 | // We have to make !SS an output for the duration of SPI master mode. 301 | // If it's an input and becomes low, the SPI controller get screwed up. 302 | // Trouble is, it's connected to another device (the GPS module) that's 303 | // actively sending a signal. So to avoid making a short circuit, we 304 | // copy the current input state to the output port and just hope that 305 | // the signal doesn't change for the duration of the SPI transaction. 306 | if (PINA & _BV(PINA7)) { 307 | PORTA |= _BV(PORTA7); 308 | } else { 309 | PORTA &= ~_BV(PORTA7); 310 | } 311 | DDRA |= _BV(DDA7); // Make !SS an output 312 | SPCR = _BV(SPE) | _BV(MSTR); // And turn on SPI 313 | #endif 314 | 315 | // Now assert !CS 316 | PORT_MAX &= ~BIT_MAX_CS; 317 | 318 | SPDR = addr; 319 | while(!(SPSR & _BV(SPIF))) ; 320 | 321 | SPDR = val; 322 | while(!(SPSR & _BV(SPIF))) ; 323 | 324 | // And finally, release !CS. 325 | PORT_MAX |= BIT_MAX_CS; 326 | 327 | #ifndef V3 328 | SPCR = 0; // Turn off SPI 329 | DDRA &= ~_BV(DDA7); // and revert the !SS pin back to an input. 330 | //PORTA &= ~_BV(PORTA7); // This doesn't really matter. Pull-ups are via PUEx. 331 | #endif 332 | } 333 | } 334 | 335 | const unsigned char month_tweak[] PROGMEM = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; 336 | 337 | static inline unsigned char first_sunday(unsigned char m, unsigned int y) { 338 | // first, what's the day-of-week for the first day of whatever month? 339 | // From http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week 340 | y -= m < 3; 341 | unsigned char month_tweak_val = pgm_read_byte(&(month_tweak[m - 1])); 342 | unsigned char dow = (y + y/4 - y/100 + y/400 + month_tweak_val + 1) % 7; 343 | 344 | // If the 1st is a Sunday, then the answer is 1. Otherwise, we count 345 | // up until we find a Sunday. 346 | return (dow == 0)?1:(8 - dow); 347 | } 348 | 349 | static inline unsigned char calculateDSTAU(const unsigned char d, const unsigned char m, const unsigned int y) { 350 | // DST is in effect between the first Sunday in October and the first Sunday in April 351 | unsigned char change_day; 352 | switch(m) { 353 | case 1: // November through March 354 | case 2: 355 | case 3: 356 | case 11: 357 | case 12: 358 | return DST_YES; 359 | case 4: // April 360 | change_day = first_sunday(m, y); 361 | if (d < change_day) return DST_YES; 362 | else if (d == change_day) return DST_ENDS; 363 | else return DST_NO; 364 | break; 365 | case 5: // April through September 366 | case 6: 367 | case 7: 368 | case 8: 369 | case 9: 370 | return DST_NO; 371 | case 10: // October 372 | change_day = first_sunday(m, y); 373 | if (d < change_day) return DST_NO; 374 | else if (d == change_day) return DST_BEGINS; 375 | else return DST_YES; 376 | break; 377 | default: // This is impossible, since m can only be between 1 and 12. 378 | return 255; 379 | } 380 | } 381 | static inline unsigned char calculateDSTNZ(const unsigned char d, const unsigned char m, const unsigned int y) { 382 | // DST is in effect between the last Sunday in September and the first Sunday in April 383 | unsigned char change_day; 384 | switch(m) { 385 | case 1: // October through March 386 | case 2: 387 | case 3: 388 | case 10: 389 | case 11: 390 | case 12: 391 | return DST_YES; 392 | case 4: // April 393 | change_day = first_sunday(m, y); 394 | if (d < change_day) return DST_YES; 395 | else if (d == change_day) return DST_ENDS; 396 | else return DST_NO; 397 | break; 398 | case 5: // April through August 399 | case 6: 400 | case 7: 401 | case 8: 402 | return DST_NO; 403 | case 9: // September 404 | change_day = first_sunday(m, y); 405 | while(change_day + 7 <= 30) change_day += 7; // last Sunday 406 | if (d < change_day) return DST_NO; 407 | else if (d == change_day) return DST_BEGINS; 408 | else return DST_YES; 409 | break; 410 | default: // This is impossible, since m can only be between 1 and 12. 411 | return 255; 412 | } 413 | } 414 | static inline unsigned char calculateDSTEU(const unsigned char d, const unsigned char m, const unsigned int y) { 415 | // DST is in effect between the last Sunday in March and the last Sunday in October 416 | unsigned char change_day; 417 | switch(m) { 418 | case 1: // November through February 419 | case 2: 420 | case 11: 421 | case 12: 422 | return DST_NO; 423 | case 3: // March 424 | change_day = first_sunday(m, y); 425 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 426 | if (d < change_day) return DST_NO; 427 | else if (d == change_day) return DST_BEGINS; 428 | else return DST_YES; 429 | break; 430 | case 4: // April through September 431 | case 5: 432 | case 6: 433 | case 7: 434 | case 8: 435 | case 9: 436 | return DST_YES; 437 | case 10: // October 438 | change_day = first_sunday(m, y); 439 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 440 | if (d < change_day) return DST_YES; 441 | else if (d == change_day) return DST_ENDS; 442 | else return DST_NO; 443 | break; 444 | default: // This is impossible, since m can only be between 1 and 12. 445 | return 255; 446 | } 447 | } 448 | static inline unsigned char calculateDSTUS(const unsigned char d, const unsigned char m, const unsigned int y) { 449 | // DST is in effect between the 2nd Sunday in March and the first Sunday in November 450 | // The return values here are that DST is in effect, or it isn't, or it's beginning 451 | // for the year today or it's ending today. 452 | unsigned char change_day; 453 | switch(m) { 454 | case 1: // December through February 455 | case 2: 456 | case 12: 457 | return DST_NO; 458 | case 3: // March 459 | change_day = first_sunday(m, y) + 7; // second Sunday. 460 | if (d < change_day) return DST_NO; 461 | else if (d == change_day) return DST_BEGINS; 462 | else return DST_YES; 463 | break; 464 | case 4: // April through October 465 | case 5: 466 | case 6: 467 | case 7: 468 | case 8: 469 | case 9: 470 | case 10: 471 | return DST_YES; 472 | case 11: // November 473 | change_day = first_sunday(m, y); 474 | if (d < change_day) return DST_YES; 475 | else if (d == change_day) return DST_ENDS; 476 | else return DST_NO; 477 | break; 478 | default: // This is impossible, since m can only be between 1 and 12. 479 | return 255; 480 | } 481 | } 482 | static inline unsigned char calculateDST(const unsigned char d, const unsigned char m, const unsigned int y) { 483 | switch(dst_mode) { 484 | case DST_US: 485 | return calculateDSTUS(d, m, y); 486 | case DST_EU: 487 | return calculateDSTEU(d, m, y); 488 | case DST_AU: 489 | return calculateDSTAU(d, m, y); 490 | case DST_NZ: 491 | return calculateDSTNZ(d, m, y); 492 | default: // off - should never happen 493 | return DST_NO; 494 | } 495 | } 496 | 497 | static inline void startLeapCheck(); 498 | 499 | static inline void handle_time(char h, unsigned char m, unsigned char s, unsigned char dst_flags) { 500 | // What we get is the current second. We have to increment it 501 | // to represent the *next* second. 502 | s++; 503 | // Note that this also handles leap-seconds. We wind up pinning to 0 504 | // twice. 505 | if (s >= 60) { s = 0; m++; } 506 | if (m >= 60) { m = 0; h++; } 507 | if (h >= 24) { h = 0; } 508 | 509 | // Move to local standard time. 510 | h += tz_hour; 511 | while (h >= 24) h -= 24; 512 | while (h < 0) h += 24; 513 | 514 | if (dst_mode != DST_OFF) { 515 | unsigned char dst_offset = 0; 516 | // For Europe, decisions are at 0100. Everywhere else it's 0200. 517 | unsigned char decision_hour = (dst_mode == DST_EU)?1:2; 518 | switch(dst_flags) { 519 | case DST_NO: dst_offset = 0; break; // do nothing 520 | case DST_YES: dst_offset = 1; break; // add one hour 521 | case DST_BEGINS: 522 | dst_offset = (h >= decision_hour)?1:0; // offset becomes 1 at 0200 (0100 EU) 523 | break; 524 | case DST_ENDS: 525 | // The *summer time* hour has to be the decision hour, 526 | // and we haven't yet made 'h' the summer time hour, 527 | // so compare it to one less than the decision hour. 528 | dst_offset = (h >= (decision_hour - 1))?0:1; // offset becomes 0 at 0200 (daylight) (0100 EU) 529 | break; 530 | } 531 | h += dst_offset; 532 | if (h >= 24) h -= 24; 533 | } 534 | 535 | // Every hour, check to see if the leap second value in the receiver is out-of-date 536 | unsigned char doLeapCheck = (m == 30 && s == 0); 537 | 538 | unsigned char am = 0; 539 | if (ampm) { 540 | // Create AM or PM 541 | if (h == 0) { h = 12; am = 1; } 542 | else if (h < 12) { am = 1; } 543 | else if (h > 12) h -= 12; 544 | } 545 | 546 | disp_buf[DIGIT_1_SEC] = (s % 10); 547 | disp_buf[DIGIT_10_SEC] = s / 10; 548 | disp_buf[DIGIT_1_MIN] = (m % 10); 549 | disp_buf[DIGIT_10_MIN] = m / 10; 550 | disp_buf[DIGIT_1_HR] = (h % 10); 551 | disp_buf[DIGIT_10_HR] = h / 10; 552 | disp_buf[DIGIT_100_MSEC] = disp_buf[DIGIT_MISC] = 0; 553 | #ifndef COLONS 554 | // If we don't have colons, add decimal points as separators 555 | disp_buf[DIGIT_1_HR] |= MASK_DP; 556 | disp_buf[DIGIT_1_MIN] |= MASK_DP; 557 | #endif 558 | if (ampm) { 559 | #ifdef NEW_AMPM 560 | disp_buf[DIGIT_MISC] |= am ? MASK_AM : MASK_PM; 561 | #else 562 | // Early hardware used the digit 6 and 7 DPs for AM/PM. 563 | if (am) 564 | disp_buf[DIGIT_MISC] |= MASK_DP; 565 | else 566 | disp_buf[DIGIT_100_MSEC] |= MASK_DP; 567 | #endif 568 | } 569 | #ifdef COLONS 570 | if (colon_state == COLON_ON || ((colon_state == COLON_BLINK) && (s % 2 == 0))) { 571 | disp_buf[DIGIT_MISC] |= MASK_COLON_HM | MASK_COLON_MS; 572 | } 573 | #endif 574 | 575 | if (doLeapCheck) startLeapCheck(); 576 | } 577 | 578 | static inline void tx_char(const unsigned char c); 579 | static inline void write_msg(const unsigned char *msg, const size_t length) { 580 | for(int i = 0; i < length; i++) { 581 | tx_char(msg[i]); 582 | } 583 | } 584 | 585 | const unsigned char PROGMEM version_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x02, 0x01, 0x03, 0x0d, 0x0a }; 586 | static inline void startVersionCheck(void) { 587 | // Ask for the firnware version. We expect a 0x80 in response 588 | unsigned char msg[sizeof(version_msg)]; 589 | memcpy_P(msg, version_msg, sizeof(version_msg)); 590 | write_msg(msg, sizeof(msg)); 591 | } 592 | 593 | const unsigned char PROGMEM leap_check_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x64, 0x20, 0x44, 0x0d, 0x0a }; 594 | static inline void startLeapCheck(void) { 595 | // Ask for the time message. We expect a 0x64-0x8e in response 596 | unsigned char msg[sizeof(leap_check_msg)]; 597 | memcpy_P(msg, leap_check_msg, sizeof(leap_check_msg)); 598 | write_msg(msg, sizeof(msg)); 599 | } 600 | 601 | const unsigned char PROGMEM leap_update_msg[] = { 0xa0, 0xa1, 0x00, 0x04, 0x64, 0x1f, 0x00, 0x01, 0x7a, 0x0d, 0x0a }; 602 | static inline void updateLeapDefault(const unsigned char leap_offset) { 603 | // This is a set leap-second default message. It will write the given 604 | // offset to flash. 605 | unsigned char msg[sizeof(leap_update_msg)]; 606 | memcpy_P(msg, leap_update_msg, sizeof(leap_update_msg)); 607 | msg[6] = leap_offset; 608 | msg[8] ^= leap_offset; // fix the checksum 609 | write_msg(msg, sizeof(msg)); 610 | } 611 | 612 | const unsigned char PROGMEM get_utc_ref_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x64, 0x16, 0x72, 0x0d, 0x0a }; 613 | static inline void startUTCReferenceFetch() { 614 | // This is a request for UTC reference date message. We expect a 0x64-0x8a in response 615 | unsigned char msg[sizeof(get_utc_ref_msg)]; 616 | memcpy_P(msg, get_utc_ref_msg, sizeof(get_utc_ref_msg)); 617 | write_msg(msg, sizeof(msg)); 618 | } 619 | 620 | const unsigned char PROGMEM utc_ref_msg[] = { 0xa0, 0xa1, 0x00, 0x08, 0x64, 0x15, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x71, 0x0d, 0x0a }; 621 | static inline void updateUTCReference(const unsigned int y, const unsigned char mon, const unsigned char d) { 622 | // This sets the UTC reference date, which controls the boundaries of the GPS week window 623 | unsigned char msg[sizeof(utc_ref_msg)]; 624 | memcpy_P(msg, utc_ref_msg, sizeof(utc_ref_msg)); 625 | msg[7] = (unsigned char)(y >> 8); 626 | msg[8] = (unsigned char)y; 627 | msg[9] = mon; 628 | msg[10] = d; 629 | for(int i = 7; i <= 10; i++) msg[12] ^= msg[i]; // fix checksum 630 | write_msg(msg, sizeof(msg)); 631 | } 632 | 633 | static const char *skip_commas(const char *ptr, const int num) { 634 | for(int i = 0; i < num; i++) { 635 | ptr = strchr(ptr, ','); 636 | if (ptr == NULL) return NULL; // not enough commas 637 | ptr++; // skip over it 638 | } 639 | return ptr; 640 | } 641 | 642 | const char hexes[] PROGMEM = "0123456789abcdef"; 643 | 644 | static unsigned char hexChar(unsigned char c) { 645 | if (c >= 'A' && c <= 'F') c += ('a' - 'A'); // make lower case 646 | const char* outP = strchr_P(hexes, c); 647 | if (outP == NULL) return 0; 648 | return (unsigned char)(outP - hexes); 649 | } 650 | 651 | static inline void handleGPS(unsigned char binaryOnly) { 652 | unsigned int str_len = rx_str_len; // rx_str_len is where the \0 was written. 653 | 654 | if (str_len >= 3 && rx_buf[0] == 0xa0 && rx_buf[1] == 0xa1) { // binary protocol message 655 | unsigned int payloadLength = (((unsigned int)rx_buf[2]) << 8) | rx_buf[3]; 656 | if (str_len != payloadLength + 7) return; // the A0, A1 bytes, length and checksum are added 657 | unsigned int checksum = 0; 658 | for(int i = 0; i < payloadLength; i++) checksum ^= rx_buf[i + 4]; 659 | if (checksum != rx_buf[payloadLength + 4]) return; // checksum mismatch 660 | if (rx_buf[4] == 0x80) { 661 | fw_version_year = rx_buf[12 + 3]; 662 | fw_version_mon = rx_buf[13 + 3]; 663 | fw_version_day = rx_buf[14 + 3]; 664 | } else if (rx_buf[4] == 0x64 && rx_buf[5] == 0x8a) { 665 | utc_ref_year = (rx_buf[3 + 4] << 8) | rx_buf[3 + 5]; 666 | utc_ref_mon = rx_buf[3 + 6]; 667 | utc_ref_day = rx_buf[3 + 7]; 668 | } else if (rx_buf[4] == 0x64 && rx_buf[5] == 0x8e) { 669 | if (!(rx_buf[15 + 3] & (1 << 2))) return; // GPS leap seconds invalid 670 | if (rx_buf[13 + 3] == rx_buf[14 + 3]) return; // Current and default agree 671 | updateLeapDefault(rx_buf[14 + 3]); 672 | } else { 673 | return; // unknown binary protocol message 674 | } 675 | } 676 | 677 | if (binaryOnly) return; // we're not handling text sentences. 678 | 679 | if (str_len < 9) return; // No sentence is shorter than $GPGGA*xx 680 | // First, check the checksum of the sentence 681 | unsigned char checksum = 0; 682 | int i; 683 | for(i = 1; i < str_len; i++) { 684 | if (rx_buf[i] == '*') break; 685 | checksum ^= rx_buf[i]; 686 | } 687 | if (i > str_len - 3) { 688 | return; // there has to be room for the "*" and checksum. 689 | } 690 | i++; // skip the * 691 | unsigned char sent_checksum = (hexChar(rx_buf[i]) << 4) | hexChar(rx_buf[i + 1]); 692 | if (sent_checksum != checksum) { 693 | return; // bad checksum. 694 | } 695 | 696 | const char *ptr = (char *)rx_buf; 697 | if (!strncmp_P(ptr, PSTR("$GPRMC"), 6)) { 698 | // $GPRMC,172313.000,A,xxxx.xxxx,N,xxxxx.xxxx,W,0.01,180.80,260516,,,D*74\x0d\x0a 699 | ptr = skip_commas(ptr, 1); 700 | if (ptr == NULL) return; // not enough commas 701 | char h = (ptr[0] - '0') * 10 + (ptr[1] - '0'); 702 | unsigned char min = (ptr[2] - '0') * 10 + (ptr[3] - '0'); 703 | unsigned char s = (ptr[4] - '0') * 10 + (ptr[5] - '0'); 704 | ptr = skip_commas(ptr, 1); 705 | if (ptr == NULL) return; // not enough commas 706 | gps_locked = *ptr == 'A'; // A = AOK 707 | ptr = skip_commas(ptr, 7); 708 | if (ptr == NULL) return; // not enough commas 709 | unsigned char d = (ptr[0] - '0') * 10 + (ptr[1] - '0'); 710 | unsigned char mon = (ptr[2] - '0') * 10 + (ptr[3] - '0'); 711 | unsigned int y = (ptr[4] - '0') * 10 + (ptr[5] - '0'); 712 | 713 | // We must turn the two digit year into the actual A.D. year number. 714 | // As time goes forward, we can keep a record of how far time has gotten, 715 | // and assume that time will always go forwards. If we see a date ostensibly 716 | // in the past, then it "must" mean that we've actually wrapped and need to 717 | // add 100 years. We keep this "reference" date in sync with the GPS receiver, 718 | // as it uses the reference date to control the GPS week rollover window. 719 | y += 2000; 720 | while (y < utc_ref_year) y += 100; // If it's in the "past," assume time wrapped on us. 721 | 722 | if (utc_ref_year != 0 && y != utc_ref_year) { 723 | // Once a year, we should update the refence date in the receiver. If we're running on New Years, 724 | // then that's probably when it will happen, but anytime is really ok. We just don't want to do 725 | // it a lot for fear of burning the flash out in the GPS receiver. 726 | updateUTCReference(y, mon, d); 727 | utc_ref_year = y; 728 | utc_ref_mon = mon; 729 | utc_ref_day = d; 730 | } 731 | 732 | // The problem is that our D/M/Y is UTC, but DST decisions are made in the local 733 | // timezone. We can adjust the day against standard time midnight, and 734 | // that will be good enough. Don't worry that this can result in d being either 0 735 | // or past the last day of the month. Those will still be more or less than the "decision day" 736 | // for DST, which is all that really matters. 737 | if (h + tz_hour < 0) d--; 738 | if (h + tz_hour > 23) d++; 739 | unsigned char dst_flags = calculateDST(d, mon, y); 740 | handle_time(h, min, s, dst_flags); 741 | } 742 | } 743 | 744 | ISR(USART0_RX_vect) { 745 | unsigned char rx_char = UDR0; 746 | 747 | if (nmea_ready) return; // ignore serial until current buffer handled 748 | if (rx_str_len == 0 && !(rx_char == '$' || rx_char == 0xa0)) return; // wait for a "$" or A0 to start the line. 749 | 750 | rx_buf[rx_str_len] = rx_char; 751 | 752 | if (++rx_str_len == RX_BUF_LEN) { 753 | // The string is too long. Start over. 754 | rx_str_len = 0; 755 | } 756 | 757 | // If it's an ASCII message, then it's ended with a CRLF. 758 | // If it's a binary message, then it's ended when it's the correct length 759 | if ( (rx_buf[0] == '$' && (rx_char == 0x0d || rx_char == 0x0a)) || 760 | (rx_buf[0] == 0xa0 && rx_str_len >= 4 && rx_str_len >= ((rx_buf[2] << 8) + rx_buf[3] + 7)) ) { 761 | rx_buf[rx_str_len] = 0; // null terminate 762 | nmea_ready = 1; // Mark it as ready 763 | return; 764 | } 765 | 766 | } 767 | 768 | ISR(USART0_UDRE_vect) { 769 | if (tx_buf_head == tx_buf_tail) { 770 | // the transmit queue is empty. 771 | UCSR0B &= ~_BV(UDRIE0); // disable the TX interrupt 772 | return; 773 | } 774 | UDR0 = tx_buf[tx_buf_tail]; 775 | if (++tx_buf_tail == TX_BUF_LEN) tx_buf_tail = 0; // point to the next char 776 | } 777 | 778 | // If the TX buffer fills up, then this method will block, which should be avoided. 779 | static inline void tx_char(const unsigned char c) { 780 | int buf_in_use; 781 | do { 782 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 783 | buf_in_use = tx_buf_head - tx_buf_tail; 784 | } 785 | if (buf_in_use < 0) buf_in_use += TX_BUF_LEN; 786 | wdt_reset(); // we might be waiting a while. 787 | } while (buf_in_use >= TX_BUF_LEN - 2) ; // wait for room in the transmit buffer 788 | 789 | tx_buf[tx_buf_head] = c; 790 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 791 | // this needs to be atomic, because an intermediate state is tx_buf_head 792 | // pointing *beyond* the end of the buffer. 793 | if (++tx_buf_head == TX_BUF_LEN) tx_buf_head = 0; // point to the next free spot in the tx buffer 794 | } 795 | UCSR0B |= _BV(UDRIE0); // enable the TX interrupt. If it was disabled, then it will trigger one now. 796 | } 797 | 798 | static void write_no_sig() { 799 | tenth_ticks = 0; 800 | // Clear out the digit data 801 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 802 | write_reg(MAX_REG_DEC_MODE, 0); 803 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_C | MASK_E | MASK_G); // n 804 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_D | MASK_E | MASK_G); // o 805 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // G 806 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_B | MASK_E | MASK_F | MASK_G); // P 807 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 808 | } 809 | 810 | static inline unsigned long timer_value() __attribute__ ((always_inline)); 811 | static inline unsigned long timer_value() { 812 | unsigned long now; 813 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 814 | // We have to be careful. It's possible that we capture TCNTn at the same 815 | // time as an overflow. If that happens, it means that the hibits value 816 | // is 1 too low. We can detect this by checking if the low bits are 817 | // "close" to zero and if the overflow interrupt is pending. If that's 818 | // true, then we can compensate locally for the missing interrupt (and 819 | // it will happen when we return anyway). If the low bits are high, 820 | // then the interrupt is pending because it came (shortly) after we sampled, so 821 | // we don't compensate. "close" can simply be testing the MSB for 0. 822 | #ifdef V3 823 | now = (((unsigned long)timer_hibits) << 16) | TCNT2; 824 | if ((TIFR2 & _BV(TOV2)) && !(now & 0x8000)) now += 0x10000L; 825 | #else 826 | now = (((unsigned long)timer_hibits) << 16) | TCNT1; 827 | if ((TIFR1 & _BV(TOV1)) && !(now & 0x8000)) now += 0x10000L; 828 | #endif 829 | } 830 | return now; 831 | } 832 | 833 | #ifdef V3 834 | ISR(TIMER2_OVF_vect) { 835 | #else 836 | ISR(TIMER1_OVF_vect) { 837 | #endif 838 | timer_hibits++; 839 | } 840 | 841 | #ifdef V2 842 | #ifdef V3 843 | ISR(TIMER2_CAPT_vect) { 844 | unsigned long this_tick = (((unsigned long)timer_hibits) << 16) | ICR2; 845 | #else 846 | ISR(TIMER1_CAPT_vect) { 847 | unsigned long this_tick = (((unsigned long)timer_hibits) << 16) | ICR1; 848 | #endif 849 | #else 850 | ISR(INT0_vect) { 851 | unsigned long this_tick = (((unsigned long)timer_hibits) << 16) | TCNT1; 852 | #endif 853 | 854 | #ifdef V3 855 | if ((TIFR2 & _BV(TOV2)) && !(this_tick & 0x8000)) this_tick += 0x10000L; 856 | #else 857 | if ((TIFR1 & _BV(TOV1)) && !(this_tick & 0x8000)) this_tick += 0x10000L; 858 | #endif 859 | 860 | #ifdef TENTH_DIGIT 861 | if (menu_pos == 0 && tenth_enable && last_pps_tick != 0) { 862 | tenth_ticks = (this_tick - last_pps_tick) / 10; 863 | // For unknown reasons we seemingly sometimes get spurious 864 | // PPS interrupts. If the calculus leads us to believe a 865 | // a tenth of a second is less than 50 ms worth of system clock, 866 | // then it's not right - just skip it. 867 | if (tenth_ticks < FAST_PPS_TICKS) tenth_ticks = 0; 868 | } else { 869 | tenth_ticks = 0; 870 | } 871 | #endif 872 | last_pps_tick = this_tick; 873 | if (last_pps_tick == 0) last_pps_tick++; // it can never be zero 874 | 875 | if (menu_pos) return; 876 | if (!gps_locked) { 877 | write_no_sig(); 878 | return; 879 | } 880 | 881 | unsigned char decode_mask = (unsigned char)~_BV(DIGIT_MISC); // assume decoding for all digits 882 | // If we are doing 12 hour display and if the 10 hours digit is 0, then blank it instead. 883 | // Its value will be zero, so simply disabling the hex decode will result in no segments. 884 | if (ampm && disp_buf[DIGIT_10_HR] == 0) { 885 | decode_mask &= ~_BV(DIGIT_10_HR); // No decode for tens-of-hours digit 886 | } 887 | #ifdef TENTH_DIGIT 888 | // If we're not going to show the tenths... 889 | if (tenth_ticks == 0) { 890 | decode_mask &= ~_BV(DIGIT_100_MSEC); // No decode for tenth digit 891 | } else { 892 | disp_buf[DIGIT_1_SEC] |= MASK_DP; // add a decimal point on seconds digit 893 | } 894 | 895 | disp_tenth = 0; // right now, 0 is showing. 896 | 897 | // Watch out, there's an old bug lurking here. disp_buf[] gets 898 | // updated with data for the *next* second early on during *this* 899 | // second. If the tenth DP is ever used for anything time related, 900 | // (it used to be used for PM), then it will wind up changing *early* 901 | // if you're not careful. 902 | tenth_dp = (disp_buf[DIGIT_100_MSEC] & MASK_DP) != 0; 903 | #else 904 | decode_mask &= ~_BV(DIGIT_100_MSEC); // No decode for tenth digit 905 | #endif 906 | write_reg(MAX_REG_DEC_MODE, decode_mask); 907 | 908 | // Copy the display buffer data into the display, but do the least 909 | // significant digits first, for great justice. 910 | for(int i = sizeof(disp_buf) - 1; i >= 0; i--) { 911 | write_reg(MAX_REG_MASK_BOTH | i, disp_buf[i]); 912 | } 913 | } 914 | 915 | static unsigned char check_buttons() { 916 | unsigned long now = timer_value(); 917 | if (debounce_time != 0 && now - debounce_time < DEBOUNCE_TICKS) { 918 | // We don't pay any attention to the buttons during debounce time. 919 | return 0; 920 | } else { 921 | debounce_time = 0; // debounce is over 922 | } 923 | unsigned char status = PORT_SW & (SW_0_BIT | SW_1_BIT); 924 | status ^= (SW_0_BIT | SW_1_BIT); // invert the buttons - 0 means down. 925 | if (!((button_down == 0) ^ (status == 0))) return 0; // either no button is down, or a button is still down 926 | 927 | // Something *changed*, which means we must now start a debounce interval. 928 | debounce_time = now; 929 | if (!debounce_time) debounce_time++; // it's not allowed to be zero 930 | 931 | if (!button_down && status) { 932 | button_down = 1; // a button has been pushed 933 | return (status & SW_1_BIT)?SELECT:SET; 934 | } 935 | if (button_down && !status) { 936 | button_down = 0; // a button has been released 937 | return 0; 938 | } 939 | __builtin_unreachable(); // we'll never get here. 940 | } 941 | 942 | static void menu_render() { 943 | // blank the display 944 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 945 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 946 | switch(menu_pos) { 947 | case 0: 948 | // we're returning to time mode. Either leave it blank or indicate no signal. 949 | if (!gps_locked) 950 | write_no_sig(); 951 | tenth_ticks = 0; 952 | break; 953 | case 1: // zone 954 | write_reg(MAX_REG_DEC_MODE, 0x30); // decoding for last two digits only 955 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_D | MASK_E | MASK_F | MASK_G); // t 956 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_E | MASK_F | MASK_G); // h 957 | if (tz_hour < 0) { 958 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_G); // - 959 | } 960 | write_reg(MAX_REG_MASK_BOTH | 4, abs(tz_hour) / 10); 961 | write_reg(MAX_REG_MASK_BOTH | 5, abs(tz_hour) % 10); 962 | break; 963 | case 2: // DST on/off 964 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 965 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_B | MASK_C | MASK_D | MASK_E | MASK_G); // d 966 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 967 | switch(dst_mode) { 968 | case DST_OFF: 969 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 970 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_E | MASK_F | MASK_G); // F 971 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_E | MASK_F | MASK_G); // F 972 | break; 973 | case DST_EU: 974 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_D | MASK_E | MASK_F | MASK_G); // E 975 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 976 | break; 977 | case DST_US: 978 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 979 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 980 | break; 981 | case DST_AU: 982 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_B | MASK_C | MASK_E | MASK_F | MASK_G); // A 983 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 984 | break; 985 | case DST_NZ: 986 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_E | MASK_G); // n 987 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_B | MASK_D | MASK_E | MASK_G); // Z 988 | break; 989 | } 990 | break; 991 | case 3: // 12/24 hour 992 | write_reg(MAX_REG_DEC_MODE, 0x6); // decoding for first two digits only (skipping one) 993 | write_reg(MAX_REG_MASK_BOTH | 1, ampm?1:2); 994 | write_reg(MAX_REG_MASK_BOTH | 2, ampm?2:4); 995 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_F | MASK_G); // h 996 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_E | MASK_G); // r 997 | break; 998 | #ifdef TENTH_DIGIT 999 | case 4: // tenths enabled 1000 | write_reg(MAX_REG_DEC_MODE, 3); // decode only first two digits 1001 | write_reg(MAX_REG_MASK_BOTH | 0, 1); 1002 | write_reg(MAX_REG_MASK_BOTH | 1, 0); 1003 | if (tenth_enable) { 1004 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 1005 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_G); // n 1006 | } else { 1007 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 1008 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_E | MASK_F | MASK_G); // F 1009 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_E | MASK_F | MASK_G); // F 1010 | } 1011 | break; 1012 | #endif 1013 | #ifdef COLONS 1014 | case 5: // colons enabled 1015 | write_reg(MAX_REG_DEC_MODE, 0); // decode only first two digits 1016 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_A | MASK_D | MASK_E | MASK_F); // C 1017 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_D | MASK_E | MASK_G); // o 1018 | write_reg(MAX_REG_MASK_BOTH | 2, MASK_D | MASK_E | MASK_F); // L 1019 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 1020 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_G); // n 1021 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 1022 | switch(colon_state) { 1023 | case COLON_OFF: // nothing 1024 | break; 1025 | case COLON_ON: // on solid 1026 | write_reg(MAX_REG_MASK_BOTH | 7, MASK_COLON_HM | MASK_COLON_MS); 1027 | break; 1028 | case COLON_BLINK: // blink - write to only P0. We don't actually blink the clock this way, though. 1029 | write_reg(MAX_REG_MASK_P0 | 7, MASK_COLON_HM | MASK_COLON_MS); 1030 | break; 1031 | } 1032 | break; 1033 | #endif 1034 | case 6: // brightness 1035 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 1036 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // b 1037 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_E | MASK_G); // r 1038 | write_reg(MAX_REG_MASK_BOTH | 2, MASK_B | MASK_C); // I 1039 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // G 1040 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_F | MASK_G); // h 1041 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_D | MASK_E | MASK_F | MASK_G); // t 1042 | write_reg(MAX_REG_INTENSITY, brightness); 1043 | break; 1044 | } 1045 | } 1046 | 1047 | static void menu_set() { 1048 | switch(menu_pos) { 1049 | case 0: 1050 | // we're entering the menu system. Disable the tenth digit. 1051 | tenth_ticks = 0; 1052 | break; 1053 | case 1: 1054 | eeprom_write_byte(EE_TIMEZONE, tz_hour + 12); 1055 | break; 1056 | case 2: 1057 | eeprom_write_byte(EE_DST_MODE, dst_mode); 1058 | break; 1059 | case 3: 1060 | eeprom_write_byte(EE_AM_PM, ampm); 1061 | break; 1062 | #ifdef TENTH_DIGIT 1063 | case 4: 1064 | eeprom_write_byte(EE_TENTHS, tenth_enable); 1065 | break; 1066 | #endif 1067 | #ifdef COLONS 1068 | case 5: 1069 | eeprom_write_byte(EE_COLONS, colon_state); 1070 | break; 1071 | #endif 1072 | case 6: 1073 | eeprom_write_byte(EE_BRIGHTNESS, brightness); 1074 | break; 1075 | } 1076 | if (++menu_pos > 6) menu_pos = 0; 1077 | #ifndef TENTH_DIGIT 1078 | // There is no tenth digit menu. Skip past it. 1079 | if (menu_pos == 4) menu_pos++; 1080 | #endif 1081 | #ifndef COLONS 1082 | // There is no colon menu. Skip past it. 1083 | if (menu_pos == 5) menu_pos++; 1084 | #endif 1085 | menu_render(); 1086 | } 1087 | 1088 | static void menu_select() { 1089 | switch(menu_pos) { 1090 | case 0: return; // ignore SET when just running 1091 | case 1: // timezone 1092 | if (++tz_hour >= 13) tz_hour = -12; 1093 | break; 1094 | case 2: // DST on/off 1095 | if (++dst_mode > DST_MODE_MAX) dst_mode = 0; 1096 | break; 1097 | case 3: // 12/24 hour 1098 | ampm = !ampm; 1099 | break; 1100 | #ifdef TENTH_DIGIT 1101 | case 4: // tenths enabled 1102 | tenth_enable = !tenth_enable; 1103 | break; 1104 | #endif 1105 | #ifdef COLONS 1106 | case 5: // colons 1107 | if (++colon_state > COLON_STATE_MAX) colon_state = 0; 1108 | break; 1109 | #endif 1110 | case 6: // brightness 1111 | brightness = ((brightness + 4) & 0xf) | 0x3; 1112 | break; 1113 | } 1114 | menu_render(); 1115 | } 1116 | 1117 | // main() never returns. 1118 | void __ATTR_NORETURN__ main(void) { 1119 | 1120 | wdt_enable(WDTO_250MS); 1121 | 1122 | // Leave on only the parts of the chip we use. 1123 | #ifdef V3 1124 | PRR = ~(_BV(PRSPI) | _BV(PRUSART0) | _BV(PRTIM2)); 1125 | #else 1126 | PRR = ~(_BV(PRSPI) | _BV(PRUSART0) | _BV(PRTIM1)); 1127 | #endif 1128 | 1129 | // Make sure the CS pin is high and everything else is low. 1130 | PORT_MAX = BIT_MAX_CS; 1131 | DDRA = DDR_BITS_A; 1132 | 1133 | DDRB = DDR_BITS_B; 1134 | #ifdef V31 1135 | PUEA = PULLUP_BITS_A; 1136 | #else 1137 | PUEB = PULLUP_BITS_B; 1138 | #endif 1139 | 1140 | UBRR0H = UBRRH_VALUE; 1141 | UBRR0L = UBRRL_VALUE; 1142 | #if USE_2X 1143 | UCSR0A = _BV(U2X0); 1144 | #else 1145 | UCSR0A = 0; 1146 | #endif 1147 | 1148 | UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0); // RX interrupt and TX+RX enable 1149 | 1150 | // 8N1 1151 | UCSR0C = _BV(UCSZ00) | _BV(UCSZ01); 1152 | 1153 | #ifndef V2 1154 | MCUCR = _BV(ISC01) | _BV(ISC00); // INT0 on rising edge 1155 | GIMSK = _BV(INT0); // enable INT0 1156 | #endif 1157 | 1158 | tx_buf_head = tx_buf_tail = 0; 1159 | rx_str_len = 0; 1160 | nmea_ready = 0; 1161 | 1162 | #ifdef V2 1163 | #ifdef V3 1164 | TCCR2B = _BV(ICES2) | _BV(CS21); // prescale by 8, capture on rising edge 1165 | TIMSK2 = _BV(TOIE2) | _BV(ICIE2); // interrupt on capture or overflow 1166 | #else 1167 | TCCR1B = _BV(ICES1) | _BV(CS11); // prescale by 8, capture on rising edge 1168 | TIMSK1 = _BV(TOIE1) | _BV(ICIE1); // interrupt on capture or overflow 1169 | #endif 1170 | #else 1171 | TCCR1B = _BV(CS11); // prescale by 8 1172 | TIMSK1 = _BV(TOIE1); // interrupt on overflow 1173 | #endif 1174 | 1175 | timer_hibits = 0; 1176 | 1177 | // For V2, we can't do this here. We can only turn on SPI 1178 | // while PA7 is an output. 1179 | #ifdef V3 1180 | SPCR = _BV(SPE) | _BV(MSTR); 1181 | #endif 1182 | // Go as fast as possible. 1183 | SPSR = _BV(SPI2X); 1184 | 1185 | unsigned char ee_rd = eeprom_read_byte(EE_TIMEZONE); 1186 | if (ee_rd == 0xff) 1187 | tz_hour = -8; 1188 | else 1189 | tz_hour = ee_rd - 12; 1190 | dst_mode = eeprom_read_byte(EE_DST_MODE); 1191 | if (dst_mode > DST_MODE_MAX) dst_mode = DST_US; 1192 | ampm = eeprom_read_byte(EE_AM_PM) != 0; 1193 | 1194 | #ifdef TENTH_DIGIT 1195 | tenth_enable = eeprom_read_byte(EE_TENTHS) != 0; 1196 | #endif 1197 | #ifdef COLONS 1198 | colon_state = eeprom_read_byte(EE_COLONS); 1199 | if (colon_state > COLON_STATE_MAX) colon_state = 1; // default to just on. 1200 | #endif 1201 | 1202 | gps_locked = 0; 1203 | menu_pos = 0; 1204 | debounce_time = 0; 1205 | button_down = 0; 1206 | last_pps_tick = 0; 1207 | #ifdef TENTH_DIGIT 1208 | tenth_ticks = 0; 1209 | disp_tenth = 0; 1210 | #endif 1211 | 1212 | // Turn off the shut-down register, clear the digit data 1213 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 1214 | write_reg(MAX_REG_SCAN_LIMIT, 7); // display all 8 digits 1215 | write_reg(MAX_REG_DEC_MODE, 0); 1216 | brightness = (eeprom_read_byte(EE_BRIGHTNESS) & 0xf) | 0x3; 1217 | write_reg(MAX_REG_INTENSITY, brightness); 1218 | 1219 | // Turn on the self-test for a second 1220 | write_reg(MAX_REG_TEST, 1); 1221 | Delay(1000); 1222 | write_reg(MAX_REG_TEST, 0); 1223 | 1224 | menu_pos = 99; // hack to disable PPS processing 1225 | 1226 | // Turn on interrupts 1227 | sei(); 1228 | 1229 | fw_version_year = fw_version_mon = fw_version_day = 0; 1230 | startVersionCheck(); 1231 | unsigned long start = timer_value(); 1232 | do { 1233 | wdt_reset(); 1234 | if (nmea_ready) { 1235 | handleGPS(1); 1236 | rx_str_len = 0; // clear the buffer 1237 | nmea_ready = 0; 1238 | if (fw_version_year != 0) { 1239 | write_reg(MAX_REG_DEC_MODE, 0x3f); // first 6 digits 1240 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_HR, fw_version_year / 10); 1241 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_HR, fw_version_year % 10); 1242 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_MIN, fw_version_mon / 10); 1243 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_MIN, fw_version_mon % 10); 1244 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_SEC, fw_version_day / 10); 1245 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_SEC, fw_version_day % 10); 1246 | fw_version_year = 0; // don't come back in here. 1247 | // we can't "stack" binary commands, so we need to do this after the first finished. 1248 | startUTCReferenceFetch(); 1249 | } 1250 | } 1251 | } while(timer_value() - start < 2 * F_TICK); // 2 seconds 1252 | menu_pos = 0; // end-of-hack 1253 | 1254 | write_no_sig(); 1255 | 1256 | while(1) { 1257 | wdt_reset(); 1258 | if (nmea_ready) { 1259 | // Do this out here so it's not in an interrupt-disabled context. 1260 | handleGPS(0); 1261 | rx_str_len = 0; // now clear the buffer 1262 | nmea_ready = 0; 1263 | continue; 1264 | } 1265 | unsigned long local_lpt, now; 1266 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 1267 | local_lpt = last_pps_tick; 1268 | now = timer_value(); 1269 | } 1270 | // If we've not seen a PPS pulse in a certain amount of time, then 1271 | // without doing something like this, the wrong time would just get stuck. 1272 | if (local_lpt != 0 && now - local_lpt > LOST_PPS_TICKS) { 1273 | write_no_sig(); 1274 | last_pps_tick = 0; 1275 | continue; 1276 | } 1277 | #ifdef TENTH_DIGIT 1278 | if (tenth_ticks != 0) { 1279 | unsigned long current_tick = now - local_lpt; 1280 | unsigned char ldt; 1281 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 1282 | ldt = disp_tenth; 1283 | } 1284 | unsigned int current_tenth = (unsigned int)((current_tick / tenth_ticks) % 10); 1285 | // if ldt is 0 and current_tenth is 9, then that means that 1286 | // the ISR changed disp_tenth out from under us. In that 1287 | // case, we don't really want to write the 9 on top of the 1288 | // zero that just happend. Next time we come through, though, 1289 | // current_tenth will be 0 since last_pps_tick will have changed. 1290 | if (ldt != current_tenth && !(ldt == 0 && current_tenth == 9)) { 1291 | // This is really only volatite during the 0 tenth ISR. 1292 | disp_tenth = current_tenth; 1293 | // Write the tenth-of-a-second digit, preserving the 1294 | // decimal point state (just in case) 1295 | // We don't do this on tenth zero, though, becuase SPI 1296 | // blocks interrupts and we don't want to delay the zero 1297 | // second interrupt, which in principle should happen simultaneously. 1298 | // We also don't need to write the tenth digit during tenth zero because 1299 | // the PPS handler already did it for us. 1300 | if (current_tenth != 0) 1301 | write_reg(MAX_REG_MASK_BOTH | DIGIT_100_MSEC, current_tenth | (tenth_dp ? MASK_DP : 0)); 1302 | } 1303 | } 1304 | #endif 1305 | unsigned char button = check_buttons(); 1306 | if (!button) continue; 1307 | 1308 | switch(button) { 1309 | case SELECT: 1310 | menu_select(); 1311 | break; 1312 | case SET: 1313 | menu_set(); 1314 | break; 1315 | } 1316 | } 1317 | __builtin_unreachable(); 1318 | } 1319 | -------------------------------------------------------------------------------- /GPS_Clock_v4.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | GPS Clock 4 | Copyright (C) 2016 Nicholas W. Sayer 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along 17 | with this program; if not, write to the Free Software Foundation, Inc., 18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | // 32 MHz 36 | #define F_CPU (32000000UL) 37 | 38 | // CLK2X = 0. For 9600 baud @ 32 MHz: 39 | #define BSEL (12) 40 | #define BSCALE (4) 41 | 42 | #include 43 | 44 | // Port C is used for the display SPI interface and serial. 45 | #define PORT_MAX PORTC 46 | #define BIT_MAX_CS _BV(4) 47 | 48 | // The MAX6951 registers and their bits 49 | #define MAX_REG_DEC_MODE 0x01 50 | #define MAX_REG_INTENSITY 0x02 51 | #define MAX_REG_SCAN_LIMIT 0x03 52 | #define MAX_REG_CONFIG 0x04 53 | #define MAX_REG_CONFIG_R _BV(5) 54 | #define MAX_REG_CONFIG_T _BV(4) 55 | #define MAX_REG_CONFIG_E _BV(3) 56 | #define MAX_REG_CONFIG_B _BV(2) 57 | #define MAX_REG_CONFIG_S _BV(0) 58 | #define MAX_REG_TEST 0x07 59 | // P0 and P1 are planes - used when blinking is turned on 60 | // or the mask with the digit number 0-7. On the hardware, 0-6 61 | // are the digits from left to right (D6 is tenths of seconds, D0 62 | // is tens of hours). D7 is AM, PM and the four LEDs for the colons. 63 | // To blink, you write different stuff to P1 and P0 and turn on 64 | // blinking in the config register (bit E to turn on, bit B for speed). 65 | #define MAX_REG_MASK_P0 0x20 66 | #define MAX_REG_MASK_P1 0x40 67 | #define MAX_REG_MASK_BOTH (MAX_REG_MASK_P0 | MAX_REG_MASK_P1) 68 | // When decoding is turned off, this is the bit mapping. 69 | // Segment A is at the top, the rest proceed clockwise around, and 70 | // G is in the middle. DP is the decimal point. 71 | // When decoding is turned on, bits 0-3 are a hex value, 4-6 are ignored, 72 | // and DP is as before. 73 | #define MASK_DP _BV(7) 74 | #define MASK_A _BV(6) 75 | #define MASK_B _BV(5) 76 | #define MASK_C _BV(4) 77 | #define MASK_D _BV(3) 78 | #define MASK_E _BV(2) 79 | #define MASK_F _BV(1) 80 | #define MASK_G _BV(0) 81 | 82 | // Digit 7 has the two colons and the AM & PM lights 83 | #define MASK_COLON_HM (MASK_E | MASK_F) 84 | #define MASK_COLON_MS (MASK_B | MASK_C) 85 | #define MASK_AM (MASK_A) 86 | #define MASK_PM (MASK_D) 87 | 88 | // Digit map 89 | #define DIGIT_10_HR (0) 90 | #define DIGIT_1_HR (1) 91 | #define DIGIT_10_MIN (2) 92 | #define DIGIT_1_MIN (3) 93 | #define DIGIT_10_SEC (4) 94 | #define DIGIT_1_SEC (5) 95 | #define DIGIT_100_MSEC (6) 96 | #define DIGIT_MISC (7) 97 | 98 | #define RX_BUF_LEN (96) 99 | 100 | #define PORT_SW PORTA.IN 101 | #define SW_0_BIT _BV(1) 102 | #define SW_1_BIT _BV(0) 103 | 104 | // These are return values from the DST detector routine. 105 | // DST is not in effect all day 106 | #define DST_NO 0 107 | // DST is in effect all day 108 | #define DST_YES 1 109 | // DST begins at 0200 110 | #define DST_BEGINS 2 111 | // DST ends 0200 - that is, at 0100 pre-correction. 112 | #define DST_ENDS 3 113 | 114 | // The possible values for dst_mode 115 | #define DST_OFF 0 116 | #define DST_US 1 117 | #define DST_EU 2 118 | #define DST_AU 3 119 | #define DST_NZ 4 120 | #define DST_MODE_MAX DST_NZ 121 | 122 | #define COLON_OFF 0 123 | #define COLON_ON 1 124 | #define COLON_BLINK 2 125 | #define COLON_STATE_MAX COLON_BLINK 126 | 127 | // EEPROM locations to store the configuration. 128 | #define EE_TIMEZONE ((uint8_t*)0) 129 | #define EE_DST_MODE ((uint8_t*)1) 130 | #define EE_AM_PM ((uint8_t*)2) 131 | #define EE_BRIGHTNESS ((uint8_t*)3) 132 | #define EE_TENTHS ((uint8_t*)4) 133 | #define EE_COLONS ((uint8_t*)5) 134 | 135 | // This is the timer frequency - it's the system clock prescaled by 1 136 | // Keep this synced with the configuration of Timer C4! 137 | #define F_TICK (F_CPU / 1) 138 | 139 | // We want something like 50 ms. - 1/20 sec 140 | #define DEBOUNCE_TICKS (F_TICK / 20) 141 | // The buttons 142 | #define SELECT 1 143 | #define SET 2 144 | 145 | // If we don't get a PPS at least this often, then we've lost it. 146 | // This is F_TICK*1.25 - a quarter second late. 147 | #define LOST_PPS_TICKS (F_TICK + F_TICK / 4) 148 | 149 | // For unknown reasons, we sometimes get a first PPS tick that's way, way 150 | // too fast. Rather than have the display look weird, we'll just skip 151 | // showing tenths anytime GPS tells us a tenth of a second is less than 152 | // 50 ms worth of system clock. 153 | #define FAST_PPS_TICKS (F_TICK / 20) 154 | 155 | volatile unsigned char disp_buf[8]; 156 | volatile unsigned char rx_buf[RX_BUF_LEN]; 157 | volatile unsigned char rx_str_len; 158 | volatile unsigned char nmea_ready; 159 | volatile unsigned long last_pps_tick; 160 | volatile unsigned char last_pps_tick_good; 161 | volatile unsigned long tenth_ticks; 162 | volatile unsigned char gps_locked; 163 | volatile unsigned char ampm; 164 | volatile unsigned char menu_pos; 165 | volatile unsigned char tenth_enable; 166 | volatile unsigned char disp_tenth; 167 | volatile unsigned char tenth_dp; 168 | unsigned char dst_mode; 169 | char tz_hour; 170 | unsigned char colon_state; 171 | unsigned long debounce_time; 172 | unsigned char button_down; 173 | unsigned char brightness; 174 | 175 | // Delay, but pet the watchdog while doing it. 176 | static void Delay(unsigned long ms) { 177 | while(ms > 100) { 178 | _delay_ms(100); 179 | wdt_reset(); 180 | ms -= 100; 181 | } 182 | _delay_ms(ms); 183 | wdt_reset(); 184 | } 185 | 186 | void write_reg(const unsigned char addr, const unsigned char val) { 187 | // Since we actually perform SPI operations in some interrupts, 188 | // we can't allow them to be interrupted. 189 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 190 | 191 | // Now assert !CS 192 | PORT_MAX.OUTCLR = BIT_MAX_CS; 193 | 194 | SPIC.DATA = addr; 195 | while(!(SPIC.STATUS & SPI_IF_bm)) ; 196 | 197 | SPIC.DATA = val; 198 | while(!(SPIC.STATUS & SPI_IF_bm)) ; 199 | 200 | // And finally, release !CS. 201 | PORT_MAX.OUTSET = BIT_MAX_CS; 202 | 203 | } 204 | } 205 | 206 | static const unsigned char month_tweak[] PROGMEM = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; 207 | 208 | static inline unsigned char first_sunday(unsigned char m, unsigned int y) { 209 | // first, what's the day-of-week for the first day of whatever month? 210 | // From http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week 211 | y -= m < 3; 212 | unsigned char month_tweak_val = pgm_read_byte(&(month_tweak[m - 1])); 213 | unsigned char dow = (y + y/4 - y/100 + y/400 + month_tweak_val + 1) % 7; 214 | 215 | // If the 1st is a Sunday, then the answer is 1. Otherwise, we count 216 | // up until we find a Sunday. 217 | return (dow == 0)?1:(8 - dow); 218 | } 219 | 220 | static inline unsigned char calculateDSTAU(const unsigned char d, const unsigned char m, const unsigned int y) { 221 | // DST is in effect between the first Sunday in October and the first Sunday in April 222 | unsigned char change_day; 223 | switch(m) { 224 | case 1: // November through March 225 | case 2: 226 | case 3: 227 | case 11: 228 | case 12: 229 | return DST_YES; 230 | case 4: // April 231 | change_day = first_sunday(m, y); 232 | if (d < change_day) return DST_YES; 233 | else if (d == change_day) return DST_ENDS; 234 | else return DST_NO; 235 | break; 236 | case 5: // April through September 237 | case 6: 238 | case 7: 239 | case 8: 240 | case 9: 241 | return DST_NO; 242 | case 10: // October 243 | change_day = first_sunday(m, y); 244 | if (d < change_day) return DST_NO; 245 | else if (d == change_day) return DST_BEGINS; 246 | else return DST_YES; 247 | break; 248 | default: // This is impossible, since m can only be between 1 and 12. 249 | return 255; 250 | } 251 | } 252 | static inline unsigned char calculateDSTNZ(const unsigned char d, const unsigned char m, const unsigned int y) { 253 | // DST is in effect between the last Sunday in September and the first Sunday in April 254 | unsigned char change_day; 255 | switch(m) { 256 | case 1: // October through March 257 | case 2: 258 | case 3: 259 | case 10: 260 | case 11: 261 | case 12: 262 | return DST_YES; 263 | case 4: // April 264 | change_day = first_sunday(m, y); 265 | if (d < change_day) return DST_YES; 266 | else if (d == change_day) return DST_ENDS; 267 | else return DST_NO; 268 | break; 269 | case 5: // April through August 270 | case 6: 271 | case 7: 272 | case 8: 273 | return DST_NO; 274 | case 9: // September 275 | change_day = first_sunday(m, y); 276 | while(change_day + 7 <= 30) change_day += 7; // last Sunday 277 | if (d < change_day) return DST_NO; 278 | else if (d == change_day) return DST_BEGINS; 279 | else return DST_YES; 280 | break; 281 | default: // This is impossible, since m can only be between 1 and 12. 282 | return 255; 283 | } 284 | } 285 | static inline unsigned char calculateDSTEU(const unsigned char d, const unsigned char m, const unsigned int y) { 286 | // DST is in effect between the last Sunday in March and the last Sunday in October 287 | unsigned char change_day; 288 | switch(m) { 289 | case 1: // November through February 290 | case 2: 291 | case 11: 292 | case 12: 293 | return DST_NO; 294 | case 3: // March 295 | change_day = first_sunday(m, y); 296 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 297 | if (d < change_day) return DST_NO; 298 | else if (d == change_day) return DST_BEGINS; 299 | else return DST_YES; 300 | break; 301 | case 4: // April through September 302 | case 5: 303 | case 6: 304 | case 7: 305 | case 8: 306 | case 9: 307 | return DST_YES; 308 | case 10: // October 309 | change_day = first_sunday(m, y); 310 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 311 | if (d < change_day) return DST_YES; 312 | else if (d == change_day) return DST_ENDS; 313 | else return DST_NO; 314 | break; 315 | default: // This is impossible, since m can only be between 1 and 12. 316 | return 255; 317 | } 318 | } 319 | static inline unsigned char calculateDSTUS(const unsigned char d, const unsigned char m, const unsigned int y) { 320 | // DST is in effect between the 2nd Sunday in March and the first Sunday in November 321 | // The return values here are that DST is in effect, or it isn't, or it's beginning 322 | // for the year today or it's ending today. 323 | unsigned char change_day; 324 | switch(m) { 325 | case 1: // December through February 326 | case 2: 327 | case 12: 328 | return DST_NO; 329 | case 3: // March 330 | change_day = first_sunday(m, y) + 7; // second Sunday. 331 | if (d < change_day) return DST_NO; 332 | else if (d == change_day) return DST_BEGINS; 333 | else return DST_YES; 334 | break; 335 | case 4: // April through October 336 | case 5: 337 | case 6: 338 | case 7: 339 | case 8: 340 | case 9: 341 | case 10: 342 | return DST_YES; 343 | case 11: // November 344 | change_day = first_sunday(m, y); 345 | if (d < change_day) return DST_YES; 346 | else if (d == change_day) return DST_ENDS; 347 | else return DST_NO; 348 | break; 349 | default: // This is impossible, since m can only be between 1 and 12. 350 | return 255; 351 | } 352 | } 353 | static inline unsigned char calculateDST(const unsigned char d, const unsigned char m, const unsigned int y) { 354 | switch(dst_mode) { 355 | case DST_US: 356 | return calculateDSTUS(d, m, y); 357 | case DST_EU: 358 | return calculateDSTEU(d, m, y); 359 | case DST_AU: 360 | return calculateDSTAU(d, m, y); 361 | case DST_NZ: 362 | return calculateDSTNZ(d, m, y); 363 | default: // off - should never happen 364 | return DST_NO; 365 | } 366 | } 367 | 368 | static inline void startLeapCheck(); 369 | 370 | static inline void handle_time(char h, unsigned char m, unsigned char s, unsigned char dst_flags) { 371 | // What we get is the current second. We have to increment it 372 | // to represent the *next* second. 373 | s++; 374 | // Note that this also handles leap-seconds. We wind up pinning to 0 375 | // twice. We can't do other than that because we'd need to know that 376 | // the second after 59 is 60 instead of 0, and we can't know that. 377 | if (s >= 60) { s = 0; m++; } 378 | if (m >= 60) { m = 0; h++; } 379 | if (h >= 24) { h = 0; } 380 | 381 | // Move to local standard time. 382 | h += tz_hour; 383 | while (h >= 24) h -= 24; 384 | while (h < 0) h += 24; 385 | 386 | if (dst_mode != DST_OFF) { 387 | unsigned char dst_offset = 0; 388 | // For Europe, decisions are at 0100. Everywhere else it's 0200. 389 | unsigned char decision_hour = (dst_mode == DST_EU)?1:2; 390 | switch(dst_flags) { 391 | case DST_NO: dst_offset = 0; break; // do nothing 392 | case DST_YES: dst_offset = 1; break; // add one hour 393 | case DST_BEGINS: 394 | dst_offset = (h >= decision_hour)?1:0; // offset becomes 1 at 0200 (0100 EU) 395 | break; 396 | case DST_ENDS: 397 | // The *summer time* hour has to be the decision hour, 398 | // and we haven't yet made 'h' the summer time hour, 399 | // so compare it to one less than the decision hour. 400 | dst_offset = (h >= (decision_hour - 1))?0:1; // offset becomes 0 at 0200 (daylight) (0100 EU) 401 | break; 402 | } 403 | h += dst_offset; 404 | if (h >= 24) h -= 24; 405 | } 406 | 407 | // Every hour, check to see if the leap second value in the receiver is out-of-date 408 | unsigned char doLeapCheck = (m == 30 && s == 0); 409 | 410 | unsigned char am = 0; 411 | if (ampm) { 412 | // Create AM or PM 413 | if (h == 0) { h = 12; am = 1; } 414 | else if (h < 12) { am = 1; } 415 | else if (h > 12) h -= 12; 416 | } 417 | 418 | disp_buf[DIGIT_1_SEC] = (s % 10); 419 | disp_buf[DIGIT_10_SEC] = s / 10; 420 | disp_buf[DIGIT_1_MIN] = (m % 10); 421 | disp_buf[DIGIT_10_MIN] = m / 10; 422 | disp_buf[DIGIT_1_HR] = (h % 10); 423 | disp_buf[DIGIT_10_HR] = h / 10; 424 | disp_buf[DIGIT_100_MSEC] = disp_buf[DIGIT_MISC] = 0; 425 | if (ampm) { 426 | disp_buf[DIGIT_MISC] |= am ? MASK_AM : MASK_PM; 427 | } 428 | if (colon_state == COLON_ON || ((colon_state == COLON_BLINK) && (s % 2 == 0))) { 429 | disp_buf[DIGIT_MISC] |= MASK_COLON_HM | MASK_COLON_MS; 430 | } 431 | 432 | if (doLeapCheck) startLeapCheck(); 433 | } 434 | 435 | static inline void write_msg(const unsigned char *msg, const size_t length) { 436 | for(int i = 0; i < length; i++) { 437 | while(!(USARTC0.STATUS & USART_DREIF_bm)) ; // wait for ready 438 | USARTC0.DATA = msg[i]; 439 | } 440 | } 441 | 442 | const unsigned char PROGMEM version_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x02, 0x01, 0x03, 0x0d, 0x0a }; 443 | static inline void startVersionCheck(void) { 444 | // Ask for the firnware version. We expect a 0x80 in response 445 | unsigned char msg[sizeof(version_msg)]; 446 | memcpy_P(msg, version_msg, sizeof(version_msg)); 447 | write_msg(msg, sizeof(msg)); 448 | } 449 | 450 | const unsigned char PROGMEM leap_check_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x64, 0x20, 0x44, 0x0d, 0x0a }; 451 | static inline void startLeapCheck(void) { 452 | // Ask for the time message. We expect a 0x64-0x8e in response 453 | unsigned char msg[sizeof(leap_check_msg)]; 454 | memcpy_P(msg, leap_check_msg, sizeof(leap_check_msg)); 455 | write_msg(msg, sizeof(msg)); 456 | } 457 | 458 | const unsigned char PROGMEM leap_update_msg[] = { 0xa0, 0xa1, 0x00, 0x04, 0x64, 0x1f, 0x00, 0x01, 0x7a, 0x0d, 0x0a }; 459 | static inline void updateLeapDefault(const unsigned char leap_offset) { 460 | // This is a set leap-second default message. It will write the given 461 | // offset to flash. 462 | unsigned char msg[sizeof(leap_update_msg)]; 463 | memcpy_P(msg, leap_update_msg, sizeof(leap_update_msg)); 464 | msg[6] = leap_offset; 465 | msg[8] ^= leap_offset; // fix the checksum 466 | write_msg(msg, sizeof(msg)); 467 | } 468 | 469 | const unsigned char PROGMEM utc_ref_msg[] = { 0xa0, 0xa1, 0x00, 0x08, 0x64, 0x15, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x71, 0x0d, 0x0a }; 470 | static inline void updateUTCReference(const unsigned int y, const unsigned char mon, const unsigned char d) { 471 | // This sets the UTC reference date, which controls the boundaries of the GPS week window 472 | unsigned char msg[sizeof(utc_ref_msg)]; 473 | memcpy_P(msg, utc_ref_msg, sizeof(utc_ref_msg)); 474 | msg[7] = (unsigned char)(y >> 8); 475 | msg[8] = (unsigned char)y; 476 | msg[9] = mon; 477 | msg[10] = d; 478 | for(int i = 7; i <= 10; i++) msg[12] ^= msg[i]; // fix checksum 479 | write_msg(msg, sizeof(msg)); 480 | } 481 | 482 | static const char *skip_commas(const char *ptr, const int num) { 483 | for(int i = 0; i < num; i++) { 484 | ptr = strchr(ptr, ','); 485 | if (ptr == NULL) return NULL; // not enough commas 486 | ptr++; // skip over it 487 | } 488 | return ptr; 489 | } 490 | 491 | static const char hexes[] PROGMEM = "0123456789abcdef"; 492 | 493 | static unsigned char hexChar(unsigned char c) { 494 | if (c >= 'A' && c <= 'F') c += ('a' - 'A'); // make lower case 495 | const char* outP = strchr_P(hexes, c); 496 | if (outP == NULL) return 0; 497 | return (unsigned char)(outP - hexes); 498 | } 499 | 500 | static inline void handleGPS() { 501 | unsigned int str_len = rx_str_len; // rx_str_len is where the \0 was written. 502 | 503 | if (str_len >= 3 && rx_buf[0] == 0xa0 && rx_buf[1] == 0xa1) { // binary protocol message 504 | unsigned int payloadLength = (((unsigned int)rx_buf[2]) << 8) | rx_buf[3]; 505 | if (str_len != payloadLength + 5) return; // the A0, A1 bytes, length and checksum are added 506 | unsigned int checksum = 0; 507 | for(int i = 0; i < payloadLength; i++) checksum ^= rx_buf[i + 4]; 508 | if (checksum != rx_buf[payloadLength + 4]) return; // checksum mismatch 509 | if (rx_buf[4] == 0x64 && rx_buf[5] == 0x8e) { 510 | if (!(rx_buf[15 + 3] & (1 << 2))) return; // GPS leap seconds invalid 511 | if (rx_buf[13 + 3] == rx_buf[14 + 3]) return; // Current and default agree 512 | updateLeapDefault(rx_buf[14 + 3]); 513 | } else { 514 | return; // unknown binary protocol message 515 | } 516 | } 517 | 518 | if (str_len < 9) return; // No sentence is shorter than $GPGGA*xx 519 | // First, check the checksum of the sentence 520 | unsigned char checksum = 0; 521 | int i; 522 | for(i = 1; i < str_len; i++) { 523 | if (rx_buf[i] == '*') break; 524 | checksum ^= rx_buf[i]; 525 | } 526 | if (i > str_len - 3) { 527 | return; // there has to be room for the "*" and checksum. 528 | } 529 | i++; // skip the * 530 | unsigned char sent_checksum = (hexChar(rx_buf[i]) << 4) | hexChar(rx_buf[i + 1]); 531 | if (sent_checksum != checksum) { 532 | return; // bad checksum. 533 | } 534 | 535 | const char *ptr = (char *)rx_buf; 536 | if (!strncmp_P(ptr, PSTR("$GPRMC"), 6)) { 537 | // $GPRMC,172313.000,A,xxxx.xxxx,N,xxxxx.xxxx,W,0.01,180.80,260516,,,D*74\x0d\x0a 538 | ptr = skip_commas(ptr, 1); 539 | if (ptr == NULL) return; // not enough commas 540 | char h = (ptr[0] - '0') * 10 + (ptr[1] - '0'); 541 | unsigned char min = (ptr[2] - '0') * 10 + (ptr[3] - '0'); 542 | unsigned char s = (ptr[4] - '0') * 10 + (ptr[5] - '0'); 543 | ptr = skip_commas(ptr, 1); 544 | if (ptr == NULL) return; // not enough commas 545 | gps_locked = *ptr == 'A'; // A = AOK. 546 | ptr = skip_commas(ptr, 7); 547 | if (ptr == NULL) return; // not enough commas 548 | unsigned char d = (ptr[0] - '0') * 10 + (ptr[1] - '0'); 549 | unsigned char mon = (ptr[2] - '0') * 10 + (ptr[3] - '0'); 550 | unsigned int y = (ptr[4] - '0') * 10 + (ptr[5] - '0'); 551 | 552 | // Y2.1K bug here... We must turn the two digit year into 553 | // the actual A.D. year number. As time goes forward, in 554 | // principle, we could start deciding that "low" values 555 | // get 2100 added instead of 2000. You'd think that 556 | // way before then GPS will be obsolete, though. 557 | y += 2000; 558 | if (y < 2017) y += 100; // As I type this, it's A.D. 2017 559 | 560 | // Every year, on april fool's day at 30 seconds past midnight, 561 | // update the UTC reference date in the receiver. 562 | if (h == 0 && min == 0 && s == 30 && mon == 4 && d == 1) { 563 | updateUTCReference(y, mon, d); 564 | } 565 | 566 | // The problem is that our D/M/Y is UTC, but DST decisions are made in the local 567 | // timezone. We can adjust the day against standard time midnight, and 568 | // that will be good enough. Don't worry that this can result in d being either 0 569 | // or past the last day of the month. Neither of those will match the "decision day" 570 | // for DST, which is the only day on which the day of the month is significant. 571 | if (h + tz_hour < 0) d--; 572 | if (h + tz_hour > 23) d++; 573 | unsigned char dst_flags = calculateDST(d, mon, y); 574 | handle_time(h, min, s, dst_flags); 575 | } 576 | } 577 | 578 | ISR(USARTC0_RXC_vect) { 579 | unsigned char rx_char = USARTC0.DATA; 580 | 581 | if (nmea_ready) return; // ignore serial until current buffer is handled 582 | if (rx_str_len == 0 && !(rx_char == '$' || rx_char == 0xa0)) return; // wait for a "$" or A0 to start the line. 583 | 584 | rx_buf[rx_str_len] = rx_char; 585 | if (rx_char == 0x0d || rx_char == 0x0a) { 586 | rx_buf[rx_str_len] = 0; // null terminate 587 | nmea_ready = 1; 588 | return; 589 | } 590 | if (++rx_str_len == RX_BUF_LEN) { 591 | // The string is too long. Start over. 592 | rx_str_len = 0; 593 | } 594 | } 595 | 596 | static void write_no_sig() { 597 | tenth_ticks = 0; 598 | last_pps_tick_good = 0; 599 | // Clear out the digit data 600 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 601 | write_reg(MAX_REG_DEC_MODE, 0); 602 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_C | MASK_E | MASK_G); // n 603 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_D | MASK_E | MASK_G); // o 604 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // G 605 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_B | MASK_E | MASK_F | MASK_G); // P 606 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 607 | } 608 | 609 | // Note that this function MUST be called from an atomic block. 610 | static inline unsigned long timer_value() __attribute__ ((always_inline)); 611 | static inline unsigned long timer_value() { 612 | // We've configured event block 0-3 for timer C 4/5 capture. 613 | // CCA causes an interrupt, but CCB doesn't, so use a 614 | // synthetic capture to grab the current value. This avoids 615 | // having to deal with overflow propagation issues. 616 | EVSYS.STROBE = _BV(1); // event channel 1 617 | while(!((TCC4.INTFLAGS & TC4_CCBIF_bm) && (TCC5.INTFLAGS & TC5_CCBIF_bm))) ; // wait for both words 618 | unsigned long out = (((unsigned long)TCC5.CCB) << 16) | TCC4.CCB; 619 | TCC4.INTFLAGS = TC4_CCBIF_bm; // XXX Why is this necessary? 620 | TCC5.INTFLAGS = TC5_CCBIF_bm; 621 | return out; 622 | } 623 | 624 | ISR(TCC5_CCA_vect) { 625 | unsigned long this_tick; 626 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 627 | while(!((TCC4.INTFLAGS & TC4_CCAIF_bm) && (TCC5.INTFLAGS & TC5_CCAIF_bm))) ; // wait for both words 628 | this_tick = (((unsigned long)TCC5.CCA) << 16) | TCC4.CCA; 629 | TCC4.INTFLAGS = TC4_CCAIF_bm; // XXX Why is this necessary? 630 | TCC5.INTFLAGS = TC5_CCAIF_bm; 631 | } 632 | if (last_pps_tick_good) { 633 | // DIY GPS driven FLL for the 32 MHz oscillator. 634 | unsigned long pps_tick_count = this_tick - last_pps_tick; 635 | if (pps_tick_count < F_CPU) DFLLRC32M.CALA++; // too slow 636 | else if (pps_tick_count > F_CPU) DFLLRC32M.CALA--; // too fast 637 | } 638 | 639 | // If we're in the menus, or if we've disabled 10ths or if this is 640 | // our first PPS (since good was set to 0), then we don't do the 641 | // tenth digit. 642 | if (menu_pos == 0 && tenth_enable && last_pps_tick_good) { 643 | tenth_ticks = (this_tick - last_pps_tick) / 10; 644 | // For unknown reasons we seemingly sometimes get spurious 645 | // PPS interrupts. If the math leads us to believe a 646 | // a tenth of a second is less than 50 ms worth of system clock, 647 | // then it's not right - just skip it. 648 | if (tenth_ticks < FAST_PPS_TICKS) tenth_ticks = 0; 649 | } else { 650 | tenth_ticks = 0; 651 | } 652 | last_pps_tick_good = 1; 653 | last_pps_tick = this_tick; 654 | 655 | if (menu_pos) return; 656 | if (!gps_locked) { 657 | write_no_sig(); 658 | return; 659 | } 660 | 661 | unsigned char decode_mask = (unsigned char)~_BV(DIGIT_MISC); // assume decoding for all digits 662 | // If we are doing 12 hour display and if the 10 hours digit is 0, then blank it instead. 663 | // Its value will be zero, so simply disabling the hex decode will result in no segments. 664 | if (ampm && disp_buf[DIGIT_10_HR] == 0) { 665 | decode_mask &= ~_BV(DIGIT_10_HR); // No decode for tens-of-hours digit 666 | } 667 | // If we're not going to show the tenths... 668 | if (tenth_ticks == 0) { 669 | decode_mask &= ~_BV(DIGIT_100_MSEC); // No decode for tenth digit 670 | } else { 671 | disp_buf[DIGIT_1_SEC] |= MASK_DP; // add a decimal point on seconds digit 672 | } 673 | 674 | disp_tenth = 0; // right now, 0 is showing. 675 | 676 | // Watch out, there's an old bug lurking here. disp_buf[] gets 677 | // updated with data for the *next* second early on during *this* 678 | // second. If the tenth DP is ever used for anything time related, 679 | // (it used to be used for PM), then it will wind up changing *early* 680 | // if you're not careful. 681 | tenth_dp = (disp_buf[DIGIT_100_MSEC] & MASK_DP) != 0; 682 | 683 | write_reg(MAX_REG_DEC_MODE, decode_mask); 684 | // Copy the display buffer data into the display, but do the least 685 | // significant digits first, for great justice. 686 | for(int i = sizeof(disp_buf) - 1; i >= 0; i--) { 687 | write_reg(MAX_REG_MASK_BOTH | i, disp_buf[i]); 688 | } 689 | } 690 | 691 | static unsigned char check_buttons() { 692 | unsigned long now; 693 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 694 | now = timer_value(); 695 | } 696 | if (debounce_time != 0 && now - debounce_time < DEBOUNCE_TICKS) { 697 | // We don't pay any attention to the buttons during debounce time. 698 | return 0; 699 | } else { 700 | debounce_time = 0; // debounce is over 701 | } 702 | unsigned char status = PORT_SW & (SW_0_BIT | SW_1_BIT); 703 | status ^= (SW_0_BIT | SW_1_BIT); // invert the buttons - 0 means down. 704 | if (!((button_down == 0) ^ (status == 0))) return 0; // either no button is down, or a button is still down 705 | 706 | // Something *changed*, which means we must now start a debounce interval. 707 | debounce_time = now; 708 | if (!debounce_time) debounce_time++; // it's not allowed to be zero 709 | 710 | if (!button_down && status) { 711 | button_down = 1; // a button has been pushed 712 | return (status & SW_1_BIT)?SELECT:SET; 713 | } 714 | if (button_down && !status) { 715 | button_down = 0; // a button has been released 716 | return 0; 717 | } 718 | __builtin_unreachable(); // we'll never get here. 719 | } 720 | 721 | static void menu_render() { 722 | // blank the display 723 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 724 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 725 | switch(menu_pos) { 726 | case 0: 727 | // we're returning to time mode. Either leave it blank or indicate no signal. 728 | if (!gps_locked) 729 | write_no_sig(); 730 | tenth_ticks = 0; 731 | break; 732 | case 1: // zone 733 | write_reg(MAX_REG_DEC_MODE, 0x30); // decoding for last two digits only 734 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_D | MASK_E | MASK_F | MASK_G); // t 735 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_E | MASK_F | MASK_G); // h 736 | if (tz_hour < 0) { 737 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_G); // - 738 | } 739 | write_reg(MAX_REG_MASK_BOTH | 4, abs(tz_hour) / 10); 740 | write_reg(MAX_REG_MASK_BOTH | 5, abs(tz_hour) % 10); 741 | break; 742 | case 2: // DST on/off 743 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 744 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_B | MASK_C | MASK_D | MASK_E | MASK_G); // d 745 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 746 | switch(dst_mode) { 747 | case DST_OFF: 748 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 749 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_E | MASK_F | MASK_G); // F 750 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_E | MASK_F | MASK_G); // F 751 | break; 752 | case DST_EU: 753 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_D | MASK_E | MASK_F | MASK_G); // E 754 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 755 | break; 756 | case DST_US: 757 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 758 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 759 | break; 760 | case DST_AU: 761 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_B | MASK_C | MASK_E | MASK_F | MASK_G); // A 762 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_B | MASK_C | MASK_D | MASK_E | MASK_F); // U 763 | break; 764 | case DST_NZ: 765 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_E | MASK_G); // n 766 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_B | MASK_D | MASK_E | MASK_G); // Z 767 | break; 768 | } 769 | break; 770 | case 3: // 12/24 hour 771 | write_reg(MAX_REG_DEC_MODE, 0x6); // decoding for first two digits only (skipping one) 772 | write_reg(MAX_REG_MASK_BOTH | 1, ampm?1:2); 773 | write_reg(MAX_REG_MASK_BOTH | 2, ampm?2:4); 774 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_F | MASK_G); // h 775 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_E | MASK_G); // r 776 | break; 777 | case 4: // tenths enabled 778 | write_reg(MAX_REG_DEC_MODE, 3); // decode only first two digits 779 | write_reg(MAX_REG_MASK_BOTH | 0, 1); 780 | write_reg(MAX_REG_MASK_BOTH | 1, 0); 781 | if (tenth_enable) { 782 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 783 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_G); // n 784 | } else { 785 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 786 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_A | MASK_E | MASK_F | MASK_G); // F 787 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_E | MASK_F | MASK_G); // F 788 | } 789 | break; 790 | case 5: // colons enabled 791 | write_reg(MAX_REG_DEC_MODE, 0); // decode only first two digits 792 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_A | MASK_D | MASK_E | MASK_F); // C 793 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_C | MASK_D | MASK_E | MASK_G); // o 794 | write_reg(MAX_REG_MASK_BOTH | 2, MASK_D | MASK_E | MASK_F); // L 795 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_C | MASK_D | MASK_E | MASK_G); // o 796 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_G); // n 797 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_A | MASK_C | MASK_D | MASK_F | MASK_G); // S 798 | switch(colon_state) { 799 | case COLON_OFF: // nothing 800 | break; 801 | case COLON_ON: // on solid 802 | write_reg(MAX_REG_MASK_BOTH | 7, MASK_COLON_HM | MASK_COLON_MS); 803 | break; 804 | case COLON_BLINK: // blink - write to only P0. We don't actually blink the clock this way, though. 805 | write_reg(MAX_REG_MASK_P0 | 7, MASK_COLON_HM | MASK_COLON_MS); 806 | break; 807 | } 808 | break; 809 | case 6: // brightness 810 | write_reg(MAX_REG_DEC_MODE, 0); // no decoding 811 | write_reg(MAX_REG_MASK_BOTH | 0, MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // b 812 | write_reg(MAX_REG_MASK_BOTH | 1, MASK_E | MASK_G); // r 813 | write_reg(MAX_REG_MASK_BOTH | 2, MASK_B | MASK_C); // I 814 | write_reg(MAX_REG_MASK_BOTH | 3, MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G); // G 815 | write_reg(MAX_REG_MASK_BOTH | 4, MASK_C | MASK_E | MASK_F | MASK_G); // h 816 | write_reg(MAX_REG_MASK_BOTH | 5, MASK_D | MASK_E | MASK_F | MASK_G); // t 817 | write_reg(MAX_REG_INTENSITY, brightness); 818 | break; 819 | } 820 | } 821 | 822 | static void menu_set() { 823 | switch(menu_pos) { 824 | case 0: 825 | // we're entering the menu system. Disable the tenth digit. 826 | tenth_ticks = 0; 827 | break; 828 | case 1: 829 | eeprom_write_byte(EE_TIMEZONE, tz_hour + 12); 830 | break; 831 | case 2: 832 | eeprom_write_byte(EE_DST_MODE, dst_mode); 833 | break; 834 | case 3: 835 | eeprom_write_byte(EE_AM_PM, ampm); 836 | break; 837 | case 4: 838 | eeprom_write_byte(EE_TENTHS, tenth_enable); 839 | break; 840 | case 5: 841 | eeprom_write_byte(EE_COLONS, colon_state); 842 | break; 843 | case 6: 844 | eeprom_write_byte(EE_BRIGHTNESS, brightness); 845 | break; 846 | } 847 | if (++menu_pos > 6) menu_pos = 0; 848 | menu_render(); 849 | } 850 | 851 | static void menu_select() { 852 | switch(menu_pos) { 853 | case 0: return; // ignore SET when just running 854 | case 1: // timezone 855 | if (++tz_hour >= 13) tz_hour = -12; 856 | break; 857 | case 2: // DST on/off 858 | if (++dst_mode > DST_MODE_MAX) dst_mode = 0; 859 | break; 860 | case 3: // 12/24 hour 861 | ampm = !ampm; 862 | break; 863 | case 4: // tenths enabled 864 | tenth_enable = !tenth_enable; 865 | break; 866 | case 5: // colons 867 | if (++colon_state > COLON_STATE_MAX) colon_state = 0; 868 | break; 869 | case 6: // brightness 870 | brightness = ((brightness + 4) & 0xf) | 0x3; 871 | break; 872 | } 873 | menu_render(); 874 | } 875 | 876 | // main() never returns. 877 | void __ATTR_NORETURN__ main(void) { 878 | 879 | // Run the CPU at 32 MHz. 880 | OSC.CTRL = OSC_RC32MEN_bm; 881 | while(!(OSC.STATUS & OSC_RC32MRDY_bm)) ; // wait for it. 882 | 883 | _PROTECTED_WRITE(CLK.CTRL, CLK_SCLKSEL_RC32M_gc); // switch to it 884 | OSC.CTRL &= ~(OSC_RC2MEN_bm); // we're done with the 2 MHz osc. 885 | 886 | //wdt_enable(WDTO_1S); // This is broken on XMegas. 887 | // This replacement code doesn't disable interrupts (but they're not on now anyway) 888 | _PROTECTED_WRITE(WDT.CTRL, WDT_PER_256CLK_gc | WDT_ENABLE_bm | WDT_CEN_bm); 889 | while(WDT.STATUS & WDT_SYNCBUSY_bm) ; // wait for it to take 890 | // We don't want a windowed watchdog. 891 | _PROTECTED_WRITE(WDT.WINCTRL, WDT_WCEN_bm); 892 | while(WDT.STATUS & WDT_SYNCBUSY_bm) ; // wait for it to take 893 | 894 | // Leave on only the parts of the chip we use. 895 | PR.PRGEN = PR_XCL_bm | PR_RTC_bm | PR_EDMA_bm; 896 | PR.PRPA = PR_DAC_bm | PR_ADC_bm | PR_AC_bm; 897 | PR.PRPC = PR_TWI_bm | PR_HIRES_bm; 898 | PR.PRPD = PR_USART0_bm | PR_TC5_bm; 899 | 900 | // Event 0 is PPS - it causes a timer capture. 901 | EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc; 902 | EVSYS.CH0CTRL = 0; 903 | // Event 4 is a carry from timer 4 to timer 5 904 | EVSYS.CH4MUX = EVSYS_CHMUX_TCC4_OVF_gc; 905 | EVSYS.CH4CTRL = 0; 906 | 907 | PORTC.OUTSET = _BV(3) | _BV(4); // TXD and !D_CS default to high 908 | PORTC.DIRSET = _BV(3) | _BV(4) | _BV(5) | _BV(7); 909 | 910 | // Send an event on the rising edge of PPS. 911 | PORTC.PIN0CTRL = PORT_ISC_RISING_gc; 912 | 913 | // Switches get pull-ups. 914 | PORTA.PIN0CTRL = PORT_OPC_PULLUP_gc; 915 | PORTA.PIN1CTRL = PORT_OPC_PULLUP_gc; 916 | 917 | rx_str_len = 0; 918 | nmea_ready = 0; 919 | 920 | USARTC0.CTRLA = USART_DRIE_bm | USART_RXCINTLVL_LO_gc; 921 | USARTC0.CTRLB = USART_RXEN_bm | USART_TXEN_bm; 922 | USARTC0.CTRLC = USART_CHSIZE_8BIT_gc; 923 | USARTC0.CTRLD = 0; 924 | USARTC0.BAUDCTRLA = BSEL & 0xff; 925 | USARTC0.BAUDCTRLB = (BSEL >> 8) | (BSCALE << USART_BSCALE_gp); 926 | 927 | SPIC.CTRL = SPI_CLK2X_bm | SPI_ENABLE_bm | SPI_MASTER_bm; // As fast as possible, master mode. 928 | SPIC.INTCTRL = 0; 929 | SPIC.CTRLB = 0; 930 | 931 | TCC4.CTRLA = TC45_CLKSEL_DIV1_gc; // 32 MHz timer clocking - 31.25 ns granularity 932 | TCC4.CTRLB = 0; 933 | TCC4.CTRLC = 0; 934 | TCC4.CTRLD = TC45_EVSEL_CH0_gc; // capture on event A:0 B:1 935 | TCC4.CTRLE = TC45_CCBMODE_CAPT_gc | TC45_CCAMODE_CAPT_gc; 936 | TCC4.INTCTRLA = 0; 937 | TCC4.INTCTRLB = 0; // we'll use TCC5 for this 938 | 939 | TCC5.CTRLA = TC45_CLKSEL_EVCH4_gc; // Clock from timer 4's overflow 940 | TCC5.CTRLB = 0; 941 | TCC5.CTRLC = 0; 942 | TCC5.CTRLD = TC5_EVDLY_bm | TC45_EVSEL_CH0_gc; // We're cascading 32 bits - we must delay capture events 1 cycle 943 | TCC5.CTRLE = TC45_CCBMODE_CAPT_gc | TC45_CCAMODE_CAPT_gc; 944 | TCC5.INTCTRLA = 0; 945 | TCC5.INTCTRLB = TC45_CCAINTLVL_MED_gc; 946 | 947 | unsigned char ee_rd = eeprom_read_byte(EE_TIMEZONE); 948 | if (ee_rd == 0xff) 949 | tz_hour = -8; 950 | else 951 | tz_hour = ee_rd - 12; 952 | dst_mode = eeprom_read_byte(EE_DST_MODE); 953 | if (dst_mode > DST_MODE_MAX) dst_mode = DST_US; 954 | ampm = eeprom_read_byte(EE_AM_PM) != 0; 955 | 956 | tenth_enable = eeprom_read_byte(EE_TENTHS) != 0; 957 | colon_state = eeprom_read_byte(EE_COLONS); 958 | if (colon_state > COLON_STATE_MAX) colon_state = 1; // default to just on. 959 | 960 | gps_locked = 0; 961 | menu_pos = 0; 962 | debounce_time = 0; 963 | button_down = 0; 964 | last_pps_tick_good = 0; 965 | tenth_ticks = 0; 966 | disp_tenth = 0; 967 | 968 | // Enable high level of the interrupt controller 969 | PMIC.CTRL = PMIC_HILVLEN_bm; 970 | sei(); // turn interrupts on 971 | 972 | // Turn off the shut-down register, clear the digit data 973 | write_reg(MAX_REG_CONFIG, MAX_REG_CONFIG_R | MAX_REG_CONFIG_B | MAX_REG_CONFIG_S | MAX_REG_CONFIG_E); 974 | write_reg(MAX_REG_SCAN_LIMIT, 7); // display all 8 digits 975 | brightness = (eeprom_read_byte(EE_BRIGHTNESS) & 0xf) | 0x3; 976 | write_reg(MAX_REG_INTENSITY, brightness); 977 | 978 | // Turn on the self-test for a second 979 | write_reg(MAX_REG_TEST, 1); 980 | Delay(1000); 981 | write_reg(MAX_REG_TEST, 0); 982 | 983 | // Now enable the serial interrupt 984 | PMIC.CTRL |= PMIC_LOLVLEN_bm; 985 | 986 | startVersionCheck(); 987 | unsigned long start = timer_value(); 988 | do { 989 | wdt_reset(); 990 | if (nmea_ready) { 991 | // This do block only exists so we can conveniently break out early. 992 | // It's an alternative to a goto. 993 | do { 994 | unsigned int str_len = rx_str_len; // rx_str_len is where the \0 was written. 995 | if (str_len < 5) break; // too short 996 | if (rx_buf[0] != 0xa0 || rx_buf[1] != 0xa1) break; // Not a binary msg 997 | unsigned int payloadLength = (((unsigned int)rx_buf[2]) << 8) | rx_buf[3]; 998 | if (str_len != payloadLength + 5) break; // the A0, A1 bytes, length and checksum are added 999 | unsigned int checksum = 0; 1000 | for(int i = 0; i < payloadLength; i++) checksum ^= rx_buf[i + 4]; 1001 | if (checksum != rx_buf[payloadLength + 4]) break; // checksum mismatch 1002 | if (payloadLength < 14 || rx_buf[4] != 0x80) break; // wrong message 1003 | write_reg(MAX_REG_DEC_MODE, 0x3f); 1004 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_HR, rx_buf[12 + 3] / 10); 1005 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_HR, rx_buf[12 + 3] % 10); 1006 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_MIN, rx_buf[13 + 3] / 10); 1007 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_MIN, rx_buf[13 + 3] % 10); 1008 | write_reg(MAX_REG_MASK_BOTH | DIGIT_10_SEC, rx_buf[14 + 3] / 10); 1009 | write_reg(MAX_REG_MASK_BOTH | DIGIT_1_SEC, rx_buf[14 + 3] % 10); 1010 | } while(0); 1011 | rx_str_len = 0; // clear the buffer 1012 | nmea_ready = 0; 1013 | } 1014 | } while(timer_value() - start < 2 * F_TICK); 1015 | 1016 | write_no_sig(); 1017 | 1018 | // Now enable the pps interrupt 1019 | PMIC.CTRL |= PMIC_MEDLVLEN_bm; 1020 | 1021 | while(1) { 1022 | wdt_reset(); 1023 | if (nmea_ready) { 1024 | handleGPS(); 1025 | rx_str_len = 0; // now clear the buffer 1026 | nmea_ready = 0; 1027 | continue; 1028 | } 1029 | unsigned long local_lpt, local_tt; 1030 | unsigned char local_dt, local_lptg; 1031 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 1032 | local_lpt = last_pps_tick; 1033 | local_lptg = last_pps_tick_good; 1034 | local_dt = disp_tenth; 1035 | local_tt = tenth_ticks; 1036 | } 1037 | unsigned long now = timer_value(); 1038 | unsigned long current_tick = now - local_lpt; 1039 | // If we've not seen a PPS pulse in a certain amount of time, then 1040 | // without doing something like this, the wrong time would just get stuck. 1041 | if (local_lptg && current_tick > LOST_PPS_TICKS) { 1042 | write_no_sig(); 1043 | continue; 1044 | } 1045 | if (local_tt != 0) { 1046 | unsigned int current_tenth = (unsigned int)((current_tick / local_tt) % 10); 1047 | // if local_dt is 0 and current_tenth is 9, then that means that 1048 | // the ISR changed disp_tenth out from under us. In that 1049 | // case, we don't really want to write the 9 on top of the 1050 | // zero that just happend. Next time we come through, though, 1051 | // current_tenth will be 0 since last_pps_tick will have changed. 1052 | if (local_dt != current_tenth && local_dt != 9) { 1053 | // This is really only volatite during the 0 tenth ISR. 1054 | disp_tenth = current_tenth; 1055 | // Write the tenth-of-a-second digit, preserving the 1056 | // decimal point state (just in case) 1057 | // We don't do this on tenth zero, though, becuase SPI 1058 | // blocks interrupts and we don't want to delay the zero 1059 | // second interrupt, which in principle should happen simultaneously. 1060 | // We also don't need to write the tenth digit during tenth zero because 1061 | // the PPS handler already did it for us. 1062 | if (current_tenth != 0) 1063 | write_reg(MAX_REG_MASK_BOTH | DIGIT_100_MSEC, current_tenth | (tenth_dp ? MASK_DP : 0)); 1064 | } 1065 | } 1066 | unsigned char button = check_buttons(); 1067 | if (!button) continue; 1068 | 1069 | switch(button) { 1070 | case SELECT: 1071 | menu_select(); 1072 | break; 1073 | case SET: 1074 | menu_set(); 1075 | break; 1076 | } 1077 | } 1078 | __builtin_unreachable(); 1079 | } 1080 | -------------------------------------------------------------------------------- /GPS_Clock_v5.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | GPS Clock 4 | Copyright (C) 2016 Nicholas W. Sayer 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along 17 | with this program; if not, write to the Free Software Foundation, Inc., 18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | // GPS module is the PX1100T GPS receiver. Default is the Venus838LPx-T 36 | #define PX1100T 37 | 38 | // If you have a 16 MHz crystal connected up to port R, define this. 39 | #define XTAL 40 | 41 | // If you want to use C7 to measure the PPS ISR latency, define this. 42 | // C7 will turn high when the PPS ISR completes, and turn low sometime 43 | // around 500 ms past the second. Use a scope and compare the delta between 44 | // the rising edge of PPS and the rising edge of C7. 45 | //#define LATENCY 46 | 47 | // For the GPS FLL, how much does the frequency have to be off 48 | // before we fix it? The hardware FLL uses half the calibration 49 | // step size, which we can guess at. 50 | #define GPS_FLL_HYST (30000L) 51 | 52 | // 32 MHz 53 | #define F_CPU (32000000UL) 54 | 55 | #ifdef PX1100T 56 | // CLK2X = 0. For 115200 baud @ 32 MHz: 57 | #define BSEL (131) 58 | #define BSCALE (-3) 59 | #else 60 | // CLK2X = 0. For 9600 baud @ 32 MHz: 61 | #define BSEL (12) 62 | #define BSCALE (4) 63 | #endif 64 | 65 | // Port A is the anodes - so segments A through G + DP. 66 | // Port D is the cathodes - so the digits from 0-7 67 | #define DIGIT_VAL_REG PORTA.OUT 68 | #define DIGIT_SEL_REG PORTD.OUT 69 | 70 | #define MASK_A _BV(0) 71 | #define MASK_B _BV(1) 72 | #define MASK_C _BV(2) 73 | #define MASK_D _BV(3) 74 | #define MASK_E _BV(4) 75 | #define MASK_F _BV(5) 76 | #define MASK_G _BV(6) 77 | #define MASK_DP _BV(7) 78 | 79 | // Digit 7 has the two colons and the AM & PM lights 80 | #define MASK_COLON_HM (MASK_B | MASK_C) 81 | #define MASK_COLON_MS (MASK_E | MASK_F) 82 | #define MASK_AM (MASK_A) 83 | #define MASK_PM (MASK_D) 84 | 85 | // Digit map 86 | #define DIGIT_10_HR (0) 87 | #define DIGIT_1_HR (1) 88 | #define DIGIT_10_MIN (2) 89 | #define DIGIT_1_MIN (3) 90 | #define DIGIT_10_SEC (4) 91 | #define DIGIT_1_SEC (5) 92 | #define DIGIT_100_MSEC (6) 93 | #define DIGIT_MISC (7) 94 | 95 | // Port C is the serial port, switches, PPS and FIX LED. 96 | 97 | // The buttons 98 | #define PORT_SW PORTC.IN 99 | #define SW_0_BIT _BV(5) 100 | #define SW_1_BIT _BV(4) 101 | 102 | // The buttons, from a software perspective 103 | #define SELECT 1 104 | #define SET 2 105 | 106 | // The serial buffer length 107 | #define RX_BUF_LEN (96) 108 | #define TX_BUF_LEN (96) 109 | 110 | // The menu list 111 | #define MENU_OFF (0) 112 | #define MENU_SNR (1) 113 | #define MENU_ZONE (2) 114 | #define MENU_DST (3) 115 | #define MENU_AMPM (4) 116 | #define MENU_10TH (5) 117 | #define MENU_COLON (6) 118 | #define MENU_BRIGHT (7) 119 | 120 | // These are return values from the DST detector routine. 121 | // DST is not in effect all day 122 | #define DST_NO 0 123 | // DST is in effect all day 124 | #define DST_YES 1 125 | // DST begins at 0200 126 | #define DST_BEGINS 2 127 | // DST ends 0300 - that is, at 0200 pre-correction. 128 | #define DST_ENDS 3 129 | 130 | // The possible values for dst_mode 131 | #define DST_OFF 0 132 | #define DST_US 1 133 | #define DST_EU 2 134 | #define DST_AU 3 135 | #define DST_NZ 4 136 | #define DST_MODE_MAX DST_NZ 137 | 138 | // The possible colon states 139 | #define COLON_OFF 0 140 | #define COLON_ON 1 141 | #define COLON_BLINK 2 142 | #define COLON_STATE_MAX COLON_BLINK 143 | 144 | // EEPROM locations to store the configuration. 145 | #define EE_TIMEZONE ((uint8_t*)0) 146 | #define EE_DST_MODE ((uint8_t*)1) 147 | #define EE_AM_PM ((uint8_t*)2) 148 | #define EE_BRIGHTNESS ((uint8_t*)3) 149 | #define EE_TENTHS ((uint8_t*)4) 150 | #define EE_COLONS ((uint8_t*)5) 151 | 152 | // The refresh rate is F_CPU / (digit_count * REFRESH_PERIOD). Different levels 153 | // of brightness are achieved by lighting n out of every m (BRIGHTNESS_LEVELS) 154 | // raster intervals. 155 | // 156 | // We maintain the clock's display accuracy by resetting the raster cycle to 157 | // the beginning after "important" display updates (specifically the time). 158 | // This means that worst case display latency for the time is one raster cycle, 159 | // or (for 10 kHz) 100 microseconds. The good news, however, is that since we 160 | // reset the raster cycle, we wind up displaying the least significant digits 161 | // immediately, which means the *real* accuracy is much better than worst-case. 162 | // 163 | // The high side switch requires 2 microseconds of turn-off time. To achieve 164 | // this, we alternate between long and short interrupt intervals. The short 165 | // intervals are chosen to be the turn-off time and the long intervals 166 | // is a tradeoff between refresh rate (which is also directly tied to the display 167 | // accuracy) and the duty cycle of the display (which impacts the resulting brightness). 168 | // 169 | // 2 microseconds is actually 64 counts, but that's quicker than the ISR itself, 170 | // so we have to stretch it out a bit. This has implications for how short the 171 | // REFRESH_PERIOD can be, because - again - it impacts the brightness. 172 | // 173 | // 10 kHz refresh rate - 8 digits, 32 MHz freq, 400 counts per digit. 174 | #define REFRESH_PERIOD (400) 175 | #define REFRESH_PERIOD_SHORT (100) 176 | #define REFRESH_PERIOD_LONG (REFRESH_PERIOD-REFRESH_PERIOD_SHORT) 177 | 178 | // How many brightness levels do we support? This is a tradeoff 179 | // between refresh frequency and brightness granularity. 180 | #define BRIGHTNESS_LEVELS 4 181 | 182 | // This is the timer frequency - it's the system clock prescaled by 1 183 | // Keep this synced with the configuration of Timer C4! 184 | #define F_TICK (F_CPU / 1) 185 | 186 | // We want something like 50 ms. 187 | #define DEBOUNCE_TICKS (F_TICK / 20) 188 | 189 | // If we don't get a PPS at least this often, then we've lost it. 190 | // This is F_TICK*1.25 - a quarter second late. 191 | #define LOST_PPS_TICKS (F_TICK + F_TICK / 4) 192 | 193 | // For unknown reasons, we sometimes get a first PPS tick that's way, way 194 | // too fast. Rather than have the display look weird, we'll just skip 195 | // showing tenths anytime GPS tells us a tenth of a second is less than 196 | // 50 ms worth of system clock. 197 | #define FAST_PPS_TICKS (F_TICK / 20) 198 | 199 | // There can be up to 5 $GxGSV messages per constellation 200 | #define MAX_GSV_MSGS (5) 201 | // There are 4 satellites in each $GxGSV message 202 | #define SATS_PER_GSV (4) 203 | 204 | // How many satellite SNRs are we willing to track (from GPGSV)? 205 | #ifdef PX1100T 206 | // 20 satellites per system, GPS, GLONASS, Galileo & Beidou 207 | #define MAX_SAT (SATS_PER_GSV * MAX_GSV_MSGS * 4) 208 | #else 209 | // 20 GPS satellites 210 | #define MAX_SAT (SATS_PER_GSV * MAX_GSV_MSGS) 211 | #endif 212 | 213 | // disp_reg is the "registers" for the display. It's what's actively being 214 | // displayed right now by the rastering system. 215 | volatile unsigned char disp_reg[8]; 216 | // disp_buf is the buffer where data is prepped for display during the next second. 217 | // It's copied into disp_reg by the PPS ISR. 218 | volatile unsigned char disp_buf[8]; 219 | volatile unsigned char brightness; 220 | volatile unsigned char current_slot; 221 | volatile unsigned char bright_step; 222 | 223 | volatile unsigned char rx_buf[RX_BUF_LEN]; 224 | volatile unsigned char tx_buf[TX_BUF_LEN]; 225 | volatile unsigned int tx_buf_head, tx_buf_tail; 226 | volatile unsigned char rx_str_len; 227 | volatile unsigned char nmea_ready; 228 | 229 | volatile unsigned long last_pps_tick; 230 | volatile unsigned char last_pps_tick_good; 231 | volatile unsigned long tenth_ticks; 232 | volatile unsigned char gps_locked; 233 | volatile unsigned char gps_mode; 234 | volatile unsigned char sat_snr[MAX_SAT]; 235 | volatile unsigned char total_sat_count; 236 | volatile unsigned char total_sat_fix_count; 237 | volatile unsigned char menu_pos; 238 | volatile unsigned char tenth_enable; 239 | volatile unsigned char disp_tenth; 240 | unsigned char dst_mode; 241 | unsigned char ampm; 242 | char tz_hour; 243 | unsigned char colon_state; 244 | unsigned long debounce_time; 245 | unsigned char button_down; 246 | unsigned long fake_blink; 247 | unsigned int fw_version_year, utc_ref_year; 248 | unsigned char fw_version_mon, utc_ref_mon; 249 | unsigned char fw_version_day, utc_ref_day; 250 | 251 | // digit to 7 segment table. 252 | static const unsigned char character_set[] PROGMEM = { 253 | MASK_A | MASK_B | MASK_C | MASK_D | MASK_E | MASK_F, // 0 254 | MASK_B | MASK_C, // 1 255 | MASK_A | MASK_B | MASK_D | MASK_E | MASK_G, // 2 256 | MASK_A | MASK_B | MASK_C | MASK_D | MASK_G, // 3 257 | MASK_B | MASK_C | MASK_F | MASK_G, // 4 258 | MASK_A | MASK_C | MASK_D | MASK_F | MASK_G, // 5 259 | MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G, // 6 260 | MASK_A | MASK_B | MASK_C, // 7 261 | MASK_A | MASK_B | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G, // 8 262 | MASK_A | MASK_B | MASK_C | MASK_D | MASK_F | MASK_G, // 9 263 | // The clock doesn't need A-F, but we include it for potential 264 | // debugging use. 265 | MASK_A | MASK_B | MASK_C | MASK_E | MASK_F | MASK_G, // A 266 | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G, // b 267 | MASK_A | MASK_D | MASK_E | MASK_F, // C 268 | MASK_B | MASK_C | MASK_D | MASK_E | MASK_G, // d 269 | MASK_A | MASK_D | MASK_E | MASK_F | MASK_G, // E 270 | MASK_A | MASK_E | MASK_F | MASK_G // F 271 | }; 272 | 273 | static inline unsigned char convert_digit(unsigned char val) __attribute__ ((always_inline)); 274 | static inline unsigned char convert_digit(unsigned char val) { 275 | if (val >= sizeof(character_set)) return 0; 276 | return pgm_read_byte(&(character_set[val])); 277 | } 278 | 279 | static const unsigned char month_tweak[] PROGMEM = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; 280 | 281 | static inline unsigned char first_sunday(unsigned char m, unsigned int y) { 282 | // first, what's the day-of-week for the first day of whatever month? 283 | // From http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week 284 | y -= m < 3; 285 | unsigned char month_tweak_val = pgm_read_byte(&(month_tweak[m - 1])); 286 | unsigned char dow = (y + y/4 - y/100 + y/400 + month_tweak_val + 1) % 7; 287 | 288 | // If the 1st is a Sunday, then the answer is 1. Otherwise, we count 289 | // up until we find a Sunday. 290 | return (dow == 0)?1:(8 - dow); 291 | } 292 | 293 | static inline unsigned char calculateDSTAU(const unsigned char d, const unsigned char m, const unsigned int y) { 294 | // DST is in effect between the first Sunday in October and the first Sunday in April 295 | unsigned char change_day; 296 | switch(m) { 297 | case 1: // November through March 298 | case 2: 299 | case 3: 300 | case 11: 301 | case 12: 302 | return DST_YES; 303 | case 4: // April 304 | change_day = first_sunday(m, y); 305 | if (d < change_day) return DST_YES; 306 | else if (d == change_day) return DST_ENDS; 307 | else return DST_NO; 308 | break; 309 | case 5: // April through September 310 | case 6: 311 | case 7: 312 | case 8: 313 | case 9: 314 | return DST_NO; 315 | case 10: // October 316 | change_day = first_sunday(m, y); 317 | if (d < change_day) return DST_NO; 318 | else if (d == change_day) return DST_BEGINS; 319 | else return DST_YES; 320 | break; 321 | default: // This is impossible, since m can only be between 1 and 12. 322 | return 255; 323 | } 324 | } 325 | static inline unsigned char calculateDSTNZ(const unsigned char d, const unsigned char m, const unsigned int y) { 326 | // DST is in effect between the last Sunday in September and the first Sunday in April 327 | unsigned char change_day; 328 | switch(m) { 329 | case 1: // October through March 330 | case 2: 331 | case 3: 332 | case 10: 333 | case 11: 334 | case 12: 335 | return DST_YES; 336 | case 4: // April 337 | change_day = first_sunday(m, y); 338 | if (d < change_day) return DST_YES; 339 | else if (d == change_day) return DST_ENDS; 340 | else return DST_NO; 341 | break; 342 | case 5: // April through August 343 | case 6: 344 | case 7: 345 | case 8: 346 | return DST_NO; 347 | case 9: // September 348 | change_day = first_sunday(m, y); 349 | while(change_day + 7 <= 30) change_day += 7; // last Sunday 350 | if (d < change_day) return DST_NO; 351 | else if (d == change_day) return DST_BEGINS; 352 | else return DST_YES; 353 | break; 354 | default: // This is impossible, since m can only be between 1 and 12. 355 | return 255; 356 | } 357 | } 358 | static inline unsigned char calculateDSTEU(const unsigned char d, const unsigned char m, const unsigned int y) { 359 | // DST is in effect between the last Sunday in March and the last Sunday in October 360 | unsigned char change_day; 361 | switch(m) { 362 | case 1: // November through February 363 | case 2: 364 | case 11: 365 | case 12: 366 | return DST_NO; 367 | case 3: // March 368 | change_day = first_sunday(m, y); 369 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 370 | if (d < change_day) return DST_NO; 371 | else if (d == change_day) return DST_BEGINS; 372 | else return DST_YES; 373 | break; 374 | case 4: // April through September 375 | case 5: 376 | case 6: 377 | case 7: 378 | case 8: 379 | case 9: 380 | return DST_YES; 381 | case 10: // October 382 | change_day = first_sunday(m, y); 383 | while(change_day + 7 <= 31) change_day += 7; // last Sunday 384 | if (d < change_day) return DST_YES; 385 | else if (d == change_day) return DST_ENDS; 386 | else return DST_NO; 387 | break; 388 | default: // This is impossible, since m can only be between 1 and 12. 389 | return 255; 390 | } 391 | } 392 | static inline unsigned char calculateDSTUS(const unsigned char d, const unsigned char m, const unsigned int y) { 393 | // DST is in effect between the 2nd Sunday in March and the first Sunday in November 394 | // The return values here are that DST is in effect, or it isn't, or it's beginning 395 | // for the year today or it's ending today. 396 | unsigned char change_day; 397 | switch(m) { 398 | case 1: // December through February 399 | case 2: 400 | case 12: 401 | return DST_NO; 402 | case 3: // March 403 | change_day = first_sunday(m, y) + 7; // second Sunday. 404 | if (d < change_day) return DST_NO; 405 | else if (d == change_day) return DST_BEGINS; 406 | else return DST_YES; 407 | break; 408 | case 4: // April through October 409 | case 5: 410 | case 6: 411 | case 7: 412 | case 8: 413 | case 9: 414 | case 10: 415 | return DST_YES; 416 | case 11: // November 417 | change_day = first_sunday(m, y); 418 | if (d < change_day) return DST_YES; 419 | else if (d == change_day) return DST_ENDS; 420 | else return DST_NO; 421 | break; 422 | default: // This is impossible, since m can only be between 1 and 12. 423 | return 255; 424 | } 425 | } 426 | static inline unsigned char calculateDST(const unsigned char d, const unsigned char m, const unsigned int y) { 427 | switch(dst_mode) { 428 | case DST_US: 429 | return calculateDSTUS(d, m, y); 430 | case DST_EU: 431 | return calculateDSTEU(d, m, y); 432 | case DST_AU: 433 | return calculateDSTAU(d, m, y); 434 | case DST_NZ: 435 | return calculateDSTNZ(d, m, y); 436 | default: // off - should never happen 437 | return DST_NO; 438 | } 439 | } 440 | 441 | static inline void startLeapCheck(); 442 | 443 | static inline void handle_time(char h, unsigned char m, unsigned char s, unsigned char dst_flags) { 444 | // What we get is the current second. We have to increment it 445 | // to represent the *next* second. 446 | s++; 447 | // Note that this also handles leap-seconds. We wind up pinning to 0 448 | // twice. We can't do other than that because we'd need to know that 449 | // the second after 59 is 60 instead of 0, and we can't know that. 450 | if (s >= 60) { s = 0; m++; } 451 | if (m >= 60) { m = 0; h++; } 452 | if (h >= 24) { h = 0; } 453 | 454 | // Move to local standard time. 455 | /* 456 | // To support half-hour zones, we may have to add 30 minutes 457 | if (0) m += 30; // we'd need to make a UI for this somehow 458 | while (m >= 60) h++, m -= 60; 459 | */ 460 | h += tz_hour; 461 | while (h >= 24) h -= 24; 462 | while (h < 0) h += 24; 463 | 464 | if (dst_mode != DST_OFF) { 465 | unsigned char dst_offset = 0; 466 | // For Europe, decisions are at 0100. Everywhere else it's 0200. 467 | unsigned char decision_hour = (dst_mode == DST_EU)?1:2; 468 | switch(dst_flags) { 469 | case DST_NO: dst_offset = 0; break; // do nothing 470 | case DST_YES: dst_offset = 1; break; // add one hour 471 | case DST_BEGINS: 472 | dst_offset = (h >= decision_hour)?1:0; // offset becomes 1 at 0200 (0100 EU) 473 | break; 474 | case DST_ENDS: 475 | // The *summer time* hour has to be the decision hour, 476 | // and we haven't yet made 'h' the summer time hour, 477 | // so compare it to one less than the decision hour. 478 | dst_offset = (h >= (decision_hour - 1))?0:1; // offset becomes 0 at 0200 (daylight) (0100 EU) 479 | break; 480 | } 481 | h += dst_offset; 482 | if (h >= 24) h -= 24; 483 | } 484 | 485 | // Every hour, check to see if the leap second value in the receiver is out-of-date 486 | unsigned char doLeapCheck = (m == 30 && s == 0); 487 | 488 | unsigned char am = 0; 489 | if (ampm) { 490 | // Create AM or PM 491 | if (h == 0) { h = 12; am = 1; } 492 | else if (h < 12) { am = 1; } 493 | else if (h > 12) h -= 12; 494 | } 495 | 496 | disp_buf[DIGIT_1_SEC] = convert_digit(s % 10); 497 | disp_buf[DIGIT_10_SEC] = convert_digit(s / 10); 498 | disp_buf[DIGIT_1_MIN] = convert_digit(m % 10); 499 | disp_buf[DIGIT_10_MIN] = convert_digit(m / 10); 500 | disp_buf[DIGIT_1_HR] = convert_digit(h % 10); 501 | disp_buf[DIGIT_10_HR] = convert_digit(h / 10); 502 | // no, we do this in the ISR 503 | //disp_buf[DIGIT_100_MSEC] = convert_digit(0); 504 | disp_buf[DIGIT_MISC] = 0; 505 | if (ampm) { 506 | // If we are doing 12 hour display and if the 10 hours digit is 0, then blank it instead. 507 | if (h / 10 == 0) { 508 | disp_buf[DIGIT_10_HR] = 0; // blank it 509 | } 510 | disp_buf[DIGIT_MISC] |= am ? MASK_AM : MASK_PM; 511 | } 512 | if (colon_state == COLON_ON || ((colon_state == COLON_BLINK) && (s % 2 == 0))) { 513 | disp_buf[DIGIT_MISC] |= MASK_COLON_HM | MASK_COLON_MS; 514 | } 515 | 516 | if (doLeapCheck) startLeapCheck(); 517 | } 518 | 519 | static inline void tx_char(const unsigned char c); 520 | static inline void write_msg(const unsigned char *msg, const size_t length) { 521 | for(int i = 0; i < length; i++) { 522 | tx_char(msg[i]); 523 | } 524 | } 525 | 526 | const unsigned char PROGMEM version_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x02, 0x01, 0x03, 0x0d, 0x0a }; 527 | static inline void startVersionCheck(void) { 528 | // Ask for the firnware version. We expect a 0x80 in response 529 | unsigned char msg[sizeof(version_msg)]; 530 | memcpy_P(msg, version_msg, sizeof(version_msg)); 531 | write_msg(msg, sizeof(msg)); 532 | } 533 | 534 | const unsigned char PROGMEM leap_check_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x64, 0x20, 0x44, 0x0d, 0x0a }; 535 | static inline void startLeapCheck(void) { 536 | // Ask for the time message. We expect a 0x64-0x8e in response 537 | unsigned char msg[sizeof(leap_check_msg)]; 538 | memcpy_P(msg, leap_check_msg, sizeof(leap_check_msg)); 539 | write_msg(msg, sizeof(msg)); 540 | } 541 | 542 | const unsigned char PROGMEM leap_update_msg[] = { 0xa0, 0xa1, 0x00, 0x04, 0x64, 0x1f, 0x00, 0x01, 0x7a, 0x0d, 0x0a }; 543 | static inline void updateLeapDefault(const unsigned char leap_offset) { 544 | // This is a set leap-second default message. It will write the given 545 | // offset to flash. 546 | unsigned char msg[sizeof(leap_update_msg)]; 547 | memcpy_P(msg, leap_update_msg, sizeof(leap_update_msg)); 548 | msg[6] = leap_offset; 549 | msg[8] ^= leap_offset; // fix the checksum 550 | write_msg(msg, sizeof(msg)); 551 | } 552 | 553 | const unsigned char PROGMEM get_utc_ref_msg[] = { 0xa0, 0xa1, 0x00, 0x02, 0x64, 0x16, 0x72, 0x0d, 0x0a }; 554 | static inline void startUTCReferenceFetch() { 555 | // This is a request for UTC reference date message. We expect a 0x64-0x8a in response 556 | unsigned char msg[sizeof(get_utc_ref_msg)]; 557 | memcpy_P(msg, get_utc_ref_msg, sizeof(get_utc_ref_msg)); 558 | write_msg(msg, sizeof(msg)); 559 | } 560 | 561 | const unsigned char PROGMEM utc_ref_msg[] = { 0xa0, 0xa1, 0x00, 0x08, 0x64, 0x15, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x71, 0x0d, 0x0a }; 562 | static inline void updateUTCReference(const unsigned int y, const unsigned char mon, const unsigned char d) { 563 | // This sets the UTC reference date, which controls the boundaries of the GPS week window 564 | unsigned char msg[sizeof(utc_ref_msg)]; 565 | memcpy_P(msg, utc_ref_msg, sizeof(utc_ref_msg)); 566 | msg[7] = (unsigned char)(y >> 8); 567 | msg[8] = (unsigned char)y; 568 | msg[9] = mon; 569 | msg[10] = d; 570 | for(int i = 7; i <= 10; i++) msg[12] ^= msg[i]; // fix checksum 571 | write_msg(msg, sizeof(msg)); 572 | } 573 | 574 | static const char *skip_commas(const char *ptr, const int num) { 575 | for(int i = 0; i < num; i++) { 576 | ptr = strchr(ptr, ','); 577 | if (ptr == NULL) return NULL; // not enough commas 578 | ptr++; // skip over it 579 | } 580 | return ptr; 581 | } 582 | 583 | static const char hexes[] PROGMEM = "0123456789abcdef"; 584 | 585 | static unsigned char hexChar(unsigned char c) { 586 | if (c >= 'A' && c <= 'F') c += ('a' - 'A'); // make lower case 587 | const char* outP = strchr_P(hexes, c); 588 | if (outP == NULL) return 0; 589 | return (unsigned char)(outP - hexes); 590 | } 591 | 592 | static inline void handleGPS(const unsigned char *rx_sentence, const unsigned int str_len, const unsigned char binaryOnly) { 593 | if (str_len >= 4 && rx_sentence[0] == 0xa0 && rx_sentence[1] == 0xa1) { // binary protocol message 594 | unsigned int payloadLength = (((unsigned int)rx_sentence[2]) << 8) + rx_sentence[3]; 595 | if (str_len != payloadLength + 7) { 596 | return; // the A0, A1 bytes, length and checksum are added 597 | } 598 | unsigned int checksum = 0; 599 | for(int i = 0; i < payloadLength; i++) checksum ^= rx_sentence[i + 4]; 600 | if (checksum != rx_sentence[payloadLength + 4]) { 601 | return; // checksum mismatch 602 | } 603 | if (rx_sentence[4] == 0x80) { 604 | fw_version_year = rx_sentence[12 + 3]; 605 | fw_version_mon = rx_sentence[13 + 3]; 606 | fw_version_day = rx_sentence[14 + 3]; 607 | } else if (rx_sentence[4] == 0x64 && rx_sentence[5] == 0x8a) { 608 | utc_ref_year = (rx_sentence[3 + 4] << 8) | rx_sentence[3 + 5]; 609 | utc_ref_mon = rx_sentence[3 + 6]; 610 | utc_ref_day = rx_sentence[3 + 7]; 611 | } else if (rx_sentence[4] == 0x64 && rx_sentence[5] == 0x8e) { 612 | if (!(rx_sentence[15 + 3] & (1 << 2))) return; // GPS leap seconds invalid 613 | if (rx_sentence[13 + 3] == rx_sentence[14 + 3]) return; // Current and default agree 614 | updateLeapDefault(rx_sentence[14 + 3]); 615 | } else { 616 | return; // unknown binary protocol message 617 | } 618 | } 619 | 620 | if (binaryOnly) return; // we're not handling text sentences. 621 | 622 | if (str_len < 9) return; // No sentence is shorter than $GPGGA*xx 623 | // First, check the checksum of the sentence 624 | unsigned char checksum = 0; 625 | int i; 626 | for(i = 1; i < str_len; i++) { 627 | if (rx_sentence[i] == '*') break; 628 | checksum ^= rx_sentence[i]; 629 | } 630 | if (i > str_len - 3) { 631 | return; // there has to be room for the "*" and checksum. 632 | } 633 | i++; // skip the * 634 | unsigned char sent_checksum = (hexChar(rx_sentence[i]) << 4) | hexChar(rx_sentence[i + 1]); 635 | if (sent_checksum != checksum) { 636 | return; // bad checksum. 637 | } 638 | 639 | const char *ptr = (char *)rx_sentence; 640 | if (!strncmp_P(ptr, PSTR("$GPGSA"), 6) 641 | #ifdef PX1100T 642 | || !strncmp_P(ptr, PSTR("$GLGSA"), 6) // GLONASS 643 | || !strncmp_P(ptr, PSTR("$GAGSA"), 6) // Galileo 644 | || !strncmp_P(ptr, PSTR("$GBGSA"), 6) // Beidou 645 | || !strncmp_P(ptr, PSTR("$GNGSA"), 6) // ?? This has to be a bug. 646 | #endif 647 | ) { 648 | ptr = skip_commas(ptr, 3); 649 | if (ptr == NULL) return; // not enough commas 650 | // count the number of satellites used for fix 651 | unsigned char sat_fix_count = 0; 652 | for(int i = 0; i < 12; i++) { 653 | if (ptr[0] != ',') sat_fix_count++; 654 | ptr = skip_commas(ptr, 1); 655 | if (ptr == NULL) return; // not enough commas 656 | } 657 | #ifdef PX1100T 658 | ptr = skip_commas(ptr, 3); 659 | if (ptr == NULL) return; // not enough commas 660 | unsigned char system_id = (unsigned char)atoi(ptr); 661 | if (system_id == 1) total_sat_fix_count = 0; 662 | total_sat_fix_count += sat_fix_count; 663 | #else 664 | total_sat_fix_count = sat_fix_count; 665 | #endif 666 | } else if (!strncmp_P(ptr, PSTR("$GPGSV"), 6) 667 | #ifdef PX1100T 668 | || !strncmp_P(ptr, PSTR("$GLGSV"), 6) // GLONASS 669 | || !strncmp_P(ptr, PSTR("$GAGSV"), 6) // Galileo 670 | || !strncmp_P(ptr, PSTR("$GBGSV"), 6) // Beidou 671 | #endif 672 | ) { 673 | unsigned char system_id = 0; // default to GPS 674 | switch(ptr[2]) { 675 | case 'L': system_id = 1; break; // GLONASS 676 | case 'A': system_id = 2; break; // Galileo 677 | case 'B': system_id = 3; break; // Beidou 678 | } 679 | ptr = skip_commas(ptr, 2); 680 | if (ptr == NULL) return; // not enough commas 681 | unsigned char msg_num = (unsigned char)atoi(ptr); 682 | if (msg_num > MAX_GSV_MSGS) return; // too many 683 | ptr = skip_commas(ptr, 1); 684 | if (ptr == NULL) return; // not enough commas 685 | unsigned char sat_count = (unsigned char)atoi(ptr); 686 | if (system_id == 0 && msg_num == 1) { 687 | memset((void*)sat_snr, 0, sizeof(sat_snr)); // on the first message, clear it all out 688 | total_sat_count = 0; 689 | } 690 | if (msg_num == 1) // it's the same satellite count for each msg 691 | total_sat_count += sat_count; 692 | // At this point we really are pointing to the right field... for the "-1" satellite. 693 | // So it's appropriate to first skip forward the number of fields (4) in each entry to land 694 | // on the last one, which is the SNR. 695 | for(int i = 0; i < SATS_PER_GSV; i++) { 696 | ptr = skip_commas(ptr, 4); 697 | if (ptr == NULL) return; // not enough commas 698 | if (*ptr == ',') continue; // no entry, keep looking 699 | sat_snr[i + ((msg_num - 1) * SATS_PER_GSV) + (system_id * (SATS_PER_GSV * MAX_GSV_MSGS))] = (unsigned char)atoi(ptr); 700 | } 701 | } else if (!strncmp_P(ptr, PSTR( 702 | #ifdef PX1100T 703 | "$GNZDA" 704 | #else 705 | "$GPZDA" 706 | #endif 707 | ), 6)) { 708 | // $GPZDA,124502.000,31,12,2020,00,00*44\x0d\x0a 709 | ptr = skip_commas(ptr, 1); 710 | if (ptr == NULL) return; // not enough commas 711 | char h = (ptr[0] - '0') * 10 + (ptr[1] - '0'); 712 | unsigned char min = (ptr[2] - '0') * 10 + (ptr[3] - '0'); 713 | unsigned char s = (ptr[4] - '0') * 10 + (ptr[5] - '0'); 714 | ptr = skip_commas(ptr, 1); 715 | if (ptr == NULL) return; // not enough commas 716 | unsigned char d = (unsigned char)atoi(ptr); 717 | ptr = skip_commas(ptr, 1); 718 | if (ptr == NULL) return; // not enough commas 719 | unsigned char mon = (unsigned char)atoi(ptr); 720 | ptr = skip_commas(ptr, 1); 721 | if (ptr == NULL) return; // not enough commas 722 | unsigned int y = atoi(ptr); 723 | 724 | if (utc_ref_year != 0 && y != utc_ref_year) { 725 | // Once a year, we should update the refence date in the receiver. If we're running on New Years, 726 | // then that's probably when it will happen, but anytime is really ok. We just don't want to do 727 | // it a lot for fear of burning the flash out in the GPS receiver. 728 | updateUTCReference(y, mon, d); 729 | utc_ref_year = y; 730 | utc_ref_mon = mon; 731 | utc_ref_day = d; 732 | } 733 | 734 | // The problem is that our D/M/Y is UTC, but DST decisions are made in the local 735 | // timezone. We can adjust the day against standard time midnight, and 736 | // that will be good enough. Don't worry that this can result in d being either 0 737 | // or past the last day of the month. Neither of those will match the "decision day" 738 | // for DST, which is the only day on which the day of the month is significant. 739 | if (h + tz_hour < 0) d--; 740 | if (h + tz_hour > 23) d++; 741 | unsigned char dst_flags = calculateDST(d, mon, y); 742 | handle_time(h, min, s, dst_flags); 743 | } else if (!strncmp_P(ptr, PSTR( 744 | #ifdef PX1100T 745 | "$GNRMC" 746 | #else 747 | "$GPRMC" 748 | #endif 749 | ), 6)) { 750 | // $GPRMC,172313.000,A,xxxx.xxxx,N,xxxxx.xxxx,W,0.01,180.80,260516,,,D*74\x0d\x0a 751 | ptr = skip_commas(ptr, 2); 752 | if (ptr == NULL) return; // not enough commas 753 | gps_locked = *ptr == 'A'; // A = AOK. 754 | ptr = skip_commas(ptr, 10); 755 | if (ptr == NULL) return; // not enough commas 756 | gps_mode = *ptr; 757 | } 758 | } 759 | 760 | // serial receive interrupt. 761 | ISR(USARTC0_RXC_vect) { 762 | unsigned char rx_char = USARTC0.DATA; 763 | 764 | if (nmea_ready) return; // ignore serial until the buffer is handled 765 | if (rx_str_len == 0 && !(rx_char == '$' || rx_char == 0xa0)) return; // wait for a "$" or A0 to start the line. 766 | 767 | rx_buf[rx_str_len++] = rx_char; 768 | 769 | if (rx_str_len == RX_BUF_LEN) { 770 | // The string is too long. Start over. 771 | rx_str_len = 0; 772 | return; 773 | } 774 | 775 | // If it's an ASCII message, then it's ended with a CRLF. 776 | // If it's a binary message, then it's ended when it's the correct length 777 | if ( (rx_buf[0] == '$' && (rx_char == 0x0d || rx_char == 0x0a)) || 778 | (rx_buf[0] == 0xa0 && rx_str_len >= 4 && rx_str_len >= ((rx_buf[2] << 8) + rx_buf[3] + 7)) ) { 779 | rx_buf[rx_str_len] = 0; // null terminate 780 | nmea_ready = 1; // Mark it as ready 781 | return; 782 | } 783 | } 784 | 785 | ISR(USARTC0_DRE_vect) { 786 | if (tx_buf_head == tx_buf_tail) { 787 | // the transmit queue is empty. 788 | USARTC0.CTRLA &= ~USART_DREINTLVL_gm; // disable the TX interrupt. 789 | //USARTC0.CTRLA |= USART_DREINTLVL_OFF_gc; // redundant - off is a zero value 790 | return; 791 | } 792 | USARTC0.DATA = tx_buf[tx_buf_tail]; 793 | if (++tx_buf_tail == TX_BUF_LEN) tx_buf_tail = 0; // point to the next char 794 | } 795 | 796 | // If the TX buffer fills up, then this method will block, which should be avoided. 797 | static inline void tx_char(const unsigned char c) { 798 | int buf_in_use; 799 | do { 800 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 801 | buf_in_use = tx_buf_head - tx_buf_tail; 802 | } 803 | if (buf_in_use < 0) buf_in_use += TX_BUF_LEN; 804 | wdt_reset(); // we might be waiting a while. 805 | } while (buf_in_use >= TX_BUF_LEN - 2) ; // wait for room in the transmit buffer 806 | 807 | tx_buf[tx_buf_head] = c; 808 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 809 | // this needs to be atomic, because an intermediate state is tx_buf_head 810 | // pointing *beyond* the end of the buffer. 811 | if (++tx_buf_head == TX_BUF_LEN) tx_buf_head = 0; // point to the next free spot in the tx buffer 812 | } 813 | //USARTC0.CTRLA &= ~USART_DREINTLVL_gm; // this is redundant - it was already 0 814 | USARTC0.CTRLA |= USART_DREINTLVL_LO_gc; // enable the TX interrupt. If it was disabled, then it will trigger one now. 815 | } 816 | 817 | static const unsigned char no_sig_data[] PROGMEM = { 818 | MASK_C | MASK_E | MASK_G, // n 819 | MASK_C | MASK_D | MASK_E | MASK_G, // o 820 | 0, 821 | MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G, // G 822 | MASK_A | MASK_B | MASK_E | MASK_F | MASK_G, // P 823 | MASK_A | MASK_C | MASK_D | MASK_F | MASK_G, // S 824 | 0, 825 | 0 826 | }; 827 | 828 | static void write_no_sig() { 829 | last_pps_tick_good = 0; 830 | tenth_ticks = 0; 831 | fake_blink = 0; 832 | PORTC.OUTSET = _BV(1); // turn FIX on 833 | memcpy_P((void*)disp_reg, no_sig_data, sizeof(no_sig_data)); 834 | } 835 | 836 | static inline unsigned long timer_value() __attribute__ ((always_inline)); 837 | static inline unsigned long timer_value() { 838 | // We've configured event block 0-3 for timer C 4/5 capture. 839 | // CCA causes an interrupt, but CCB doesn't, so use a 840 | // synthetic capture to grab the current value. This avoids 841 | // having to deal with overflow propagation issues. 842 | EVSYS.STROBE = _BV(1); // event channel 1 843 | while(!((TCC4.INTFLAGS & TC4_CCBIF_bm)) && ((TCC5.INTFLAGS & TC5_CCBIF_bm))) ; // wait for both words 844 | unsigned long out = (((unsigned long)TCC5.CCB) << 16) | TCC4.CCB; 845 | TCC4.INTFLAGS = TC4_CCBIF_bm; // XXX why is this necessary? 846 | TCC5.INTFLAGS = TC5_CCBIF_bm; 847 | return out; 848 | } 849 | 850 | static inline void reset_raster(void) { 851 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 852 | bright_step = current_slot = 0; 853 | } 854 | } 855 | 856 | // This is the PPS capture handler. That is, this happens when PPS rises. 857 | ISR(TCC5_CCA_vect) { 858 | while(!((TCC4.INTFLAGS & TC4_CCAIF_bm)) && ((TCC5.INTFLAGS & TC5_CCAIF_bm))) ; // wait for both words 859 | unsigned long this_tick = (((unsigned long)TCC5.CCA) << 16) | TCC4.CCA; 860 | TCC4.INTFLAGS = TC4_CCAIF_bm; // XXX why is this necessary? 861 | TCC5.INTFLAGS = TC5_CCAIF_bm; 862 | 863 | #ifndef XTAL 864 | // If we have no crystal, we will DFLL in software against GPS. 865 | if (last_pps_tick_good) { 866 | // DIY GPS driven FLL for the 32 MHz oscillator. 867 | unsigned long pps_tick_count = this_tick - last_pps_tick; 868 | long diff = ((long)F_CPU) - ((long)pps_tick_count); 869 | if (labs(diff) > GPS_FLL_HYST) { 870 | if (diff > 0) DFLLRC32M.CALA++; // too slow 871 | else if (diff < 0) DFLLRC32M.CALA--; // too fast 872 | } 873 | } 874 | #endif 875 | 876 | if (menu_pos == 0 && tenth_enable && last_pps_tick_good) { 877 | tenth_ticks = (this_tick - last_pps_tick) / 10; 878 | // For unknown reasons we seemingly sometimes get spurious 879 | // PPS interrupts. If the calculus leads us to believe a 880 | // a tenth of a second is less than 50 ms worth of system clock, 881 | // then it's not right - just skip it. 882 | if (tenth_ticks < FAST_PPS_TICKS) tenth_ticks = 0; 883 | } else { 884 | tenth_ticks = 0; 885 | } 886 | last_pps_tick_good = 1; 887 | last_pps_tick = this_tick; 888 | 889 | if (gps_locked) 890 | PORTC.OUTTGL = _BV(1); // toggle FIX 891 | 892 | if (menu_pos) return; 893 | if (!gps_locked) { 894 | write_no_sig(); 895 | return; 896 | } 897 | 898 | // If we're not going to show the tenths... 899 | if (tenth_ticks == 0) { 900 | disp_buf[DIGIT_100_MSEC] = 0; // blank 901 | } else { 902 | disp_buf[DIGIT_100_MSEC] = convert_digit(0); 903 | disp_buf[DIGIT_1_SEC] |= MASK_DP; // add a decimal point on seconds digit 904 | } 905 | 906 | disp_tenth = 0; // right now, 0 is showing. 907 | 908 | // Copy the display buffer data into the display. 909 | memcpy((void*)disp_reg, (const void *)disp_buf, sizeof(disp_reg)); 910 | reset_raster(); 911 | 912 | #ifdef LATENCY 913 | PORTC.OUTSET = _BV(7); 914 | #endif 915 | } 916 | 917 | // This is a precalculated array of 1 << n. The reason for it is that << (at runtime) results 918 | // in a loop, and this needs to be fast. 919 | static const unsigned char mask[] PROGMEM = { 1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7 }; 920 | 921 | // This is the display raster handler. We loop through four brightness levels and the 8 display 922 | // digits. For each digit, we use two interrupt slots - one to display that digit, and the other 923 | // as a dead-time immediately after to allow the high-side switch to turn off (not doing this 924 | // causes digit ghosting). When the display is dimmed, then we turn the display completely off 925 | // for some of the passes through the digits. 926 | ISR(TCD5_OVF_vect) { 927 | TCD5.INTFLAGS = TC5_OVFIF_bm; // ack the interrupt 928 | DIGIT_VAL_REG = 0; // turn off the value digits first. 929 | if (++current_slot >= (sizeof(mask) << 1)) { 930 | current_slot = 0; 931 | if (++bright_step >= BRIGHTNESS_LEVELS) 932 | bright_step = 0; 933 | } 934 | if (current_slot & 1) { 935 | TCD5.PER = REFRESH_PERIOD_SHORT; 936 | return; // this is the dead slot after the digit 937 | } 938 | TCD5.PER = REFRESH_PERIOD_LONG; 939 | if (bright_step > brightness) return; // This pass is full-off due to dimming. 940 | unsigned char current_digit = current_slot >> 1; 941 | DIGIT_SEL_REG = pgm_read_byte(&(mask[current_digit])); // This is (1 << current_digit) 942 | DIGIT_VAL_REG = disp_reg[current_digit]; // Light up the segments 943 | } 944 | 945 | static unsigned char check_buttons() { 946 | unsigned long now = timer_value(); 947 | if (debounce_time != 0 && now - debounce_time < DEBOUNCE_TICKS) { 948 | // We don't pay any attention to the buttons during debounce time. 949 | return 0; 950 | } else { 951 | debounce_time = 0; // debounce is over 952 | } 953 | unsigned char status = PORT_SW & (SW_0_BIT | SW_1_BIT); 954 | status ^= (SW_0_BIT | SW_1_BIT); // invert the buttons - 0 means down. 955 | if (!((button_down == 0) ^ (status == 0))) return 0; // either no button is down, or a button is still down 956 | 957 | // Something *changed*, which means we must now start a debounce interval. 958 | debounce_time = now; 959 | if (!debounce_time) debounce_time++; // it's not allowed to be zero 960 | 961 | if (!button_down && status) { 962 | button_down = 1; // a button has been pushed 963 | return (status & SW_1_BIT)?SELECT:SET; 964 | } 965 | if (button_down && !status) { 966 | button_down = 0; // a button has been released 967 | return 0; 968 | } 969 | __builtin_unreachable(); // we'll never get here. 970 | } 971 | 972 | static void menu_render() { 973 | // blank the display 974 | memset((void*)disp_reg, 0, sizeof(disp_reg)); 975 | switch(menu_pos) { 976 | case MENU_OFF: 977 | // we're returning to time mode. Either leave it blank or indicate no signal. 978 | if (!gps_locked) 979 | write_no_sig(); 980 | tenth_ticks = 0; 981 | break; 982 | case MENU_SNR: 983 | { 984 | unsigned char max_snr = 0; 985 | for (int i = 0; i < MAX_SAT; i++) 986 | if (max_snr < sat_snr[i]) max_snr = sat_snr[i]; 987 | disp_reg[0] = convert_digit(total_sat_count / 10); 988 | disp_reg[1] = convert_digit(total_sat_count % 10); 989 | disp_reg[2] = convert_digit(total_sat_fix_count / 10); 990 | disp_reg[3] = convert_digit(total_sat_fix_count % 10); 991 | disp_reg[4] = convert_digit(max_snr / 10); 992 | disp_reg[5] = convert_digit(max_snr % 10); 993 | switch(gps_mode) { 994 | case 'A': 995 | disp_reg[6] = MASK_A | MASK_B | MASK_C | MASK_E | MASK_F | MASK_G; // A 996 | break; 997 | case 'D': 998 | disp_reg[6] = MASK_B | MASK_C | MASK_D | MASK_E | MASK_G; // d 999 | break; 1000 | case 'E': 1001 | disp_reg[6] = MASK_A | MASK_D | MASK_E | MASK_F | MASK_G; // E 1002 | break; 1003 | case 'F': 1004 | disp_reg[6] = MASK_A | MASK_E | MASK_F | MASK_G; // F 1005 | break; 1006 | case 'M': 1007 | disp_reg[6] = MASK_A | MASK_B | MASK_C | MASK_E | MASK_F; // A without a crossbar 1008 | break; 1009 | case 'N': 1010 | disp_reg[6] = MASK_C | MASK_E | MASK_G; // n 1011 | break; 1012 | case 'P': 1013 | disp_reg[6] = MASK_A | MASK_B | MASK_E | MASK_G; // P 1014 | break; 1015 | case 'R': 1016 | disp_reg[6] = MASK_E | MASK_G; // r 1017 | break; 1018 | case 'S': 1019 | disp_reg[6] = MASK_A | MASK_C | MASK_D | MASK_F | MASK_G; // S 1020 | break; 1021 | default: 1022 | disp_reg[6] = 0; // blank 1023 | } 1024 | } 1025 | break; 1026 | case MENU_ZONE: 1027 | disp_reg[0] = MASK_D | MASK_E | MASK_F | MASK_G; // t 1028 | disp_reg[1] = MASK_C | MASK_E | MASK_F | MASK_G; // h 1029 | if (tz_hour < 0) { 1030 | disp_reg[3] = MASK_G; // - 1031 | } 1032 | disp_reg[4] = convert_digit(abs(tz_hour) / 10); 1033 | disp_reg[5] = convert_digit(abs(tz_hour) % 10); 1034 | break; 1035 | case MENU_DST: 1036 | disp_reg[0] = MASK_B | MASK_C | MASK_D | MASK_E | MASK_G; // d 1037 | disp_reg[1] = MASK_A | MASK_C | MASK_D | MASK_F | MASK_G; // S 1038 | switch(dst_mode) { 1039 | case DST_OFF: 1040 | disp_reg[3] = MASK_C | MASK_D | MASK_E | MASK_G; // o 1041 | disp_reg[4] = MASK_A | MASK_E | MASK_F | MASK_G; // F 1042 | disp_reg[5] = MASK_A | MASK_E | MASK_F | MASK_G; // F 1043 | break; 1044 | case DST_EU: 1045 | disp_reg[3] = MASK_A | MASK_D | MASK_E | MASK_F | MASK_G; // E 1046 | disp_reg[4] = MASK_B | MASK_C | MASK_D | MASK_E | MASK_F; // U 1047 | break; 1048 | case DST_US: 1049 | disp_reg[3] = MASK_B | MASK_C | MASK_D | MASK_E | MASK_F; // U 1050 | disp_reg[4] = MASK_A | MASK_C | MASK_D | MASK_F | MASK_G; // S 1051 | break; 1052 | case DST_AU: 1053 | disp_reg[3] = MASK_A | MASK_B | MASK_C | MASK_E | MASK_F | MASK_G; // A 1054 | disp_reg[4] = MASK_B | MASK_C | MASK_D | MASK_E | MASK_F; // U 1055 | break; 1056 | case DST_NZ: 1057 | disp_reg[3] = MASK_C | MASK_E | MASK_G; // n 1058 | disp_reg[4] = MASK_A | MASK_B | MASK_D | MASK_E | MASK_G; // Z 1059 | break; 1060 | } 1061 | break; 1062 | case MENU_AMPM: 1063 | disp_reg[1] = convert_digit(ampm?1:2); 1064 | disp_reg[2] = convert_digit(ampm?2:4); 1065 | disp_reg[4] = MASK_C | MASK_E | MASK_F | MASK_G; // h 1066 | disp_reg[5] = MASK_E | MASK_G; // r 1067 | break; 1068 | case MENU_10TH: 1069 | disp_reg[0] = convert_digit(1); 1070 | disp_reg[1] = convert_digit(0); 1071 | if (tenth_enable) { 1072 | disp_reg[3] = MASK_C | MASK_D | MASK_E | MASK_G; // o 1073 | disp_reg[4] = MASK_C | MASK_E | MASK_G; // n 1074 | } else { 1075 | disp_reg[3] = MASK_C | MASK_D | MASK_E | MASK_G; // o 1076 | disp_reg[4] = MASK_A | MASK_E | MASK_F | MASK_G; // F 1077 | disp_reg[5] = MASK_A | MASK_E | MASK_F | MASK_G; // F 1078 | } 1079 | break; 1080 | case MENU_COLON: 1081 | disp_reg[0] = MASK_A | MASK_D | MASK_E | MASK_F; // C 1082 | disp_reg[1] = MASK_C | MASK_D | MASK_E | MASK_G; // o 1083 | disp_reg[2] = MASK_D | MASK_E | MASK_F; // L 1084 | disp_reg[3] = MASK_C | MASK_D | MASK_E | MASK_G; // o 1085 | disp_reg[4] = MASK_C | MASK_E | MASK_G; // n 1086 | disp_reg[5] = MASK_A | MASK_C | MASK_D | MASK_F | MASK_G; // S 1087 | switch(colon_state) { 1088 | case COLON_OFF: // nothing 1089 | fake_blink = 0; 1090 | disp_reg[7] = 0; 1091 | break; 1092 | case COLON_ON: // on solid 1093 | fake_blink = 0; 1094 | disp_reg[7] = MASK_COLON_HM | MASK_COLON_MS; 1095 | break; 1096 | case COLON_BLINK: 1097 | // Don't bother setting disp_reg here. The fake blink handler will do it. 1098 | fake_blink = timer_value(); 1099 | if (fake_blink == 0) fake_blink++; 1100 | break; 1101 | } 1102 | break; 1103 | case MENU_BRIGHT: 1104 | disp_reg[0] = MASK_C | MASK_D | MASK_E | MASK_F | MASK_G; // b 1105 | disp_reg[1] = MASK_E | MASK_G; // r 1106 | disp_reg[2] = MASK_B | MASK_C; // I 1107 | disp_reg[3] = MASK_A | MASK_C | MASK_D | MASK_E | MASK_F | MASK_G; // G 1108 | disp_reg[4] = MASK_C | MASK_E | MASK_F | MASK_G; // h 1109 | disp_reg[5] = MASK_D | MASK_E | MASK_F | MASK_G; // t 1110 | break; 1111 | } 1112 | } 1113 | 1114 | static void menu_set() { 1115 | switch(menu_pos) { 1116 | case MENU_OFF: 1117 | // we're entering the menu system. Disable the tenth digit. 1118 | tenth_ticks = 0; 1119 | break; 1120 | case MENU_SNR: 1121 | break; 1122 | case MENU_ZONE: 1123 | eeprom_write_byte(EE_TIMEZONE, tz_hour + 12); 1124 | break; 1125 | case MENU_DST: 1126 | eeprom_write_byte(EE_DST_MODE, dst_mode); 1127 | break; 1128 | case MENU_AMPM: 1129 | eeprom_write_byte(EE_AM_PM, ampm); 1130 | break; 1131 | case MENU_10TH: 1132 | eeprom_write_byte(EE_TENTHS, tenth_enable); 1133 | break; 1134 | case MENU_COLON: 1135 | fake_blink = 0; // We're done with that nonsense 1136 | eeprom_write_byte(EE_COLONS, colon_state); 1137 | break; 1138 | case MENU_BRIGHT: 1139 | eeprom_write_byte(EE_BRIGHTNESS, brightness); 1140 | break; 1141 | } 1142 | if (++menu_pos > MENU_BRIGHT) menu_pos = 0; 1143 | menu_render(); 1144 | } 1145 | 1146 | static void menu_select() { 1147 | switch(menu_pos) { 1148 | case MENU_OFF: return; // ignore SELECT when just running 1149 | case MENU_SNR: return; // ignore SELECT when showing SNR 1150 | case MENU_ZONE: 1151 | if (++tz_hour >= 13) tz_hour = -12; 1152 | break; 1153 | case MENU_DST: 1154 | if (++dst_mode > DST_MODE_MAX) dst_mode = 0; 1155 | break; 1156 | case MENU_AMPM: 1157 | ampm = !ampm; 1158 | break; 1159 | case MENU_10TH: 1160 | tenth_enable = !tenth_enable; 1161 | break; 1162 | case MENU_COLON: 1163 | if (++colon_state > COLON_STATE_MAX) colon_state = 0; 1164 | break; 1165 | case MENU_BRIGHT: // brightness 1166 | if (++brightness >= BRIGHTNESS_LEVELS) brightness = 0; 1167 | break; 1168 | } 1169 | menu_render(); 1170 | } 1171 | 1172 | // main() never returns. 1173 | void __ATTR_NORETURN__ main(void) { 1174 | 1175 | #ifdef XTAL 1176 | // We have a 16 MHz crystal. Use the PLL to double that to 32 MHz. 1177 | 1178 | OSC.XOSCCTRL = OSC_FRQRANGE_12TO16_gc | OSC_XOSCSEL_XTAL_16KCLK_gc; 1179 | OSC.CTRL |= OSC_XOSCEN_bm; 1180 | while(!(OSC.STATUS & OSC_XOSCRDY_bm)) ; // wait for it. 1181 | 1182 | OSC.PLLCTRL = OSC_PLLSRC_XOSC_gc | (2 << OSC_PLLFAC_gp); // PLL from XOSC, mult by 2 1183 | OSC.CTRL |= OSC_PLLEN_bm; 1184 | while(!(OSC.STATUS & OSC_PLLRDY_bm)) ; // wait for it. 1185 | 1186 | _PROTECTED_WRITE(CLK.CTRL, CLK_SCLKSEL_PLL_gc); // switch to it 1187 | #else 1188 | // Run the CPU at 32 MHz using the RC osc. We'll DFLL against GPS later. 1189 | OSC.CTRL |= OSC_RC32MEN_bm; 1190 | while(!(OSC.STATUS & OSC_RC32MRDY_bm)) ; // wait for it. 1191 | 1192 | _PROTECTED_WRITE(CLK.CTRL, CLK_SCLKSEL_RC32M_gc); // switch to it 1193 | #endif 1194 | OSC.CTRL &= ~(OSC_RC2MEN_bm); // we're done with the 2 MHz osc. 1195 | 1196 | //wdt_enable(WDTO_1S); // This is broken on XMegas. 1197 | // This replacement code doesn't disable interrupts (but they're not on now anyway) 1198 | _PROTECTED_WRITE(WDT.CTRL, WDT_PER_256CLK_gc | WDT_ENABLE_bm | WDT_CEN_bm); 1199 | while(WDT.STATUS & WDT_SYNCBUSY_bm) ; // wait for it to take 1200 | // We don't want a windowed watchdog. 1201 | _PROTECTED_WRITE(WDT.WINCTRL, WDT_WCEN_bm); 1202 | while(WDT.STATUS & WDT_SYNCBUSY_bm) ; // wait for it to take 1203 | 1204 | // Leave on only the parts of the chip we use. 1205 | PR.PRGEN = PR_XCL_bm | PR_RTC_bm | PR_EDMA_bm; 1206 | PR.PRPA = PR_DAC_bm | PR_ADC_bm | PR_AC_bm; 1207 | PR.PRPC = PR_TWI_bm | PR_SPI_bm | PR_HIRES_bm; 1208 | PR.PRPD = PR_USART0_bm; 1209 | 1210 | // Event 0 is PPS - it causes a timer capture. 1211 | EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc; 1212 | EVSYS.CH0CTRL = 0; 1213 | // Event 4 is a carry from timer 4 to timer 5 1214 | EVSYS.CH4MUX = EVSYS_CHMUX_TCC4_OVF_gc; 1215 | EVSYS.CH4CTRL = 0; 1216 | 1217 | // Ports A and D are the mux for the display. Initialize all of them 1218 | // output and low. 1219 | DIGIT_VAL_REG = 0; 1220 | DIGIT_SEL_REG = 0; 1221 | PORTA.DIRSET = 0xff; 1222 | PORTD.DIRSET = 0xff; 1223 | 1224 | PORTC.OUTSET = _BV(3) | _BV(1); // TXD defaults to high, but we really don't use it anyway. FIX LED starts on 1225 | PORTC.DIRSET = _BV(3) | _BV(1); // TXD and FIX are outputs. 1226 | #ifdef LATENCY 1227 | PORTC.OUTCLR = _BV(7); 1228 | PORTC.DIRSET = _BV(7); 1229 | #endif 1230 | 1231 | // Send an event on the rising edge of PPS. 1232 | PORTC.PIN0CTRL = PORT_ISC_RISING_gc; 1233 | 1234 | // Switches get pull-ups. 1235 | PORTC.PIN4CTRL = PORT_OPC_PULLUP_gc; 1236 | PORTC.PIN5CTRL = PORT_OPC_PULLUP_gc; 1237 | 1238 | rx_str_len = 0; 1239 | tx_buf_head = tx_buf_tail = 0; 1240 | nmea_ready = 0; 1241 | 1242 | // 9600 baud async serial, 8N1, low priority interrupt on receive 1243 | USARTC0.CTRLA = USART_DRIE_bm | USART_RXCINTLVL_LO_gc; 1244 | USARTC0.CTRLB = USART_RXEN_bm | USART_TXEN_bm; 1245 | USARTC0.CTRLC = USART_CHSIZE_8BIT_gc; 1246 | USARTC0.CTRLD = 0; 1247 | USARTC0.BAUDCTRLA = BSEL & 0xff; 1248 | USARTC0.BAUDCTRLB = (BSEL >> 8) | (BSCALE << USART_BSCALE_gp); 1249 | 1250 | // TCC4 and 5 are a 32 bit cascaded counter with cascaded capture (on PPS). 1251 | TCC4.CTRLA = TC45_CLKSEL_DIV1_gc; // 32 MHz timer clocking - 31.25 ns granularity 1252 | TCC4.CTRLB = 0; 1253 | TCC4.CTRLC = 0; 1254 | TCC4.CTRLD = TC45_EVSEL_CH0_gc; // capture on event 0 1255 | TCC4.CTRLE = TC45_CCBMODE_CAPT_gc | TC45_CCAMODE_CAPT_gc; 1256 | TCC4.INTCTRLA = 0; 1257 | TCC5.INTCTRLB = 0; // we're going to interrupt from TC5 1258 | 1259 | TCC5.CTRLA = TC45_CLKSEL_EVCH4_gc; // Clock from timer 4's overflow 1260 | TCC5.CTRLB = 0; 1261 | TCC5.CTRLC = 0; 1262 | TCC5.CTRLD = TC5_EVDLY_bm | TC45_EVSEL_CH0_gc; // We're cascading 32 bits - we must delay capture events 1 cycle 1263 | TCC5.CTRLE = TC45_CCBMODE_CAPT_gc | TC45_CCAMODE_CAPT_gc; 1264 | TCC5.INTCTRLA = 0; 1265 | TCC5.INTCTRLB = TC45_CCAINTLVL_MED_gc; 1266 | 1267 | // TCD5 is the timer for the display refresh. Its overflow triggers the rastering ISR. 1268 | // A rastering rate of 10 kHz means a display latency of 100 microseconds, but it also 1269 | // means that we're spending about half of the CPU doing it (~50 clocks spent in the ISR). 1270 | TCD5.CTRLA = TC45_CLKSEL_DIV1_gc; // full speed clocking 1271 | TCD5.CTRLB = 0; 1272 | TCD5.CTRLC = 0; 1273 | TCD5.CTRLD = 0; 1274 | TCD5.CTRLE = 0; 1275 | TCD5.INTCTRLA = TC45_OVFINTLVL_HI_gc; 1276 | TCD5.INTCTRLB = 0; 1277 | TCD5.PER = REFRESH_PERIOD_LONG; 1278 | 1279 | unsigned char ee_rd = eeprom_read_byte(EE_TIMEZONE); 1280 | if (ee_rd == 0xff) 1281 | tz_hour = -8; 1282 | else 1283 | tz_hour = ee_rd - 12; 1284 | dst_mode = eeprom_read_byte(EE_DST_MODE); 1285 | if (dst_mode > DST_MODE_MAX) dst_mode = DST_US; 1286 | ampm = eeprom_read_byte(EE_AM_PM) != 0; 1287 | 1288 | tenth_enable = eeprom_read_byte(EE_TENTHS) != 0; 1289 | colon_state = eeprom_read_byte(EE_COLONS); 1290 | if (colon_state > COLON_STATE_MAX) colon_state = 1; // default to just on. 1291 | 1292 | gps_locked = 0; 1293 | total_sat_count = 0; 1294 | total_sat_fix_count = 0; 1295 | gps_mode = 0; // none of the above 1296 | menu_pos = 0; 1297 | debounce_time = 0; 1298 | button_down = 0; 1299 | last_pps_tick_good = 0; 1300 | tenth_ticks = 0; 1301 | disp_tenth = 0; 1302 | fake_blink = 0; 1303 | 1304 | // Turn on just the display refresh interrupt to start with 1305 | reset_raster(); 1306 | PMIC.CTRL = PMIC_HILVLEN_bm; 1307 | sei(); 1308 | 1309 | // Turn on the self-test for a second 1310 | brightness = BRIGHTNESS_LEVELS - 1; // max 1311 | memset((void*)disp_reg, 0xff, sizeof(disp_reg)); 1312 | 1313 | // We want to wait for 1 second, but _delay_ms() assumes we get the whole 1314 | // CPU. So we'll use the timer instead. 1315 | unsigned long start = timer_value(); 1316 | while(timer_value() - start < F_TICK) wdt_reset(); 1317 | 1318 | unsigned char b = eeprom_read_byte(EE_BRIGHTNESS); 1319 | if (b >= BRIGHTNESS_LEVELS) b = BRIGHTNESS_LEVELS - 1; // default to max 1320 | brightness = b; 1321 | 1322 | memset((void*)disp_reg, 0, sizeof(disp_reg)); 1323 | 1324 | // turn on the serial interrupt 1325 | PMIC.CTRL |= PMIC_LOLVLEN_bm; 1326 | 1327 | fw_version_year = fw_version_mon = fw_version_day = 0; 1328 | startVersionCheck(); 1329 | start = timer_value(); 1330 | do { 1331 | wdt_reset(); 1332 | if (nmea_ready) { 1333 | unsigned char temp_buf[RX_BUF_LEN]; 1334 | unsigned int temp_len = rx_str_len; 1335 | memcpy(temp_buf, (const char *)rx_buf, temp_len); 1336 | rx_str_len = 0; // clear the buffer 1337 | nmea_ready = 0; 1338 | handleGPS(temp_buf, temp_len, 1); // binary only handling 1339 | if (fw_version_year != 0) { 1340 | disp_reg[DIGIT_10_HR] = convert_digit(fw_version_year / 10); 1341 | disp_reg[DIGIT_1_HR] = convert_digit(fw_version_year % 10); 1342 | disp_reg[DIGIT_10_MIN] = convert_digit(fw_version_mon / 10); 1343 | disp_reg[DIGIT_1_MIN] = convert_digit(fw_version_mon % 10); 1344 | disp_reg[DIGIT_10_SEC] = convert_digit(fw_version_day / 10); 1345 | disp_reg[DIGIT_1_SEC] = convert_digit(fw_version_day % 10); 1346 | disp_reg[DIGIT_100_MSEC] = 0; 1347 | fw_version_year = 0; // don't come back in here. 1348 | // we can't "stack" binary commands, so we need to do this after the first finished. 1349 | startUTCReferenceFetch(); 1350 | } 1351 | } 1352 | } while(timer_value() - start < 2 * F_TICK); 1353 | 1354 | // turn on the PPS interrupt 1355 | PMIC.CTRL |= PMIC_MEDLVLEN_bm; 1356 | 1357 | write_no_sig(); 1358 | 1359 | while(1) { 1360 | wdt_reset(); 1361 | if (nmea_ready) { 1362 | // We're doing this here to get the heavy processing 1363 | // out of an ISR. It *is* a lot of work, after all. 1364 | // But this may take a long time, so we need to let 1365 | // the serial ISR keep working. 1366 | unsigned char temp_buf[RX_BUF_LEN]; 1367 | unsigned int temp_len = rx_str_len; 1368 | memcpy(temp_buf, (const char *)rx_buf, temp_len + 1); // include null terminator 1369 | rx_str_len = 0; // clear the buffer 1370 | nmea_ready = 0; 1371 | handleGPS(temp_buf, temp_len, 0); 1372 | if (menu_pos == MENU_SNR) menu_render(); // refresh the SNR display 1373 | continue; 1374 | } 1375 | 1376 | unsigned long local_lpt, local_tt; 1377 | unsigned char local_dt, local_lptg; 1378 | // capture these values atomically so they can't change independently of each other. 1379 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 1380 | local_lptg = last_pps_tick_good; 1381 | local_lpt = last_pps_tick; 1382 | local_dt = disp_tenth; 1383 | local_tt = tenth_ticks; 1384 | } 1385 | unsigned long now = timer_value(); 1386 | unsigned long current_tick = now - local_lpt; 1387 | // If we've not seen a PPS pulse in a certain amount of time, then 1388 | // without doing something like this, the wrong time would just get stuck. 1389 | if (local_lptg && current_tick > LOST_PPS_TICKS) { 1390 | write_no_sig(); 1391 | continue; 1392 | } 1393 | if (local_tt != 0) { 1394 | unsigned int current_tenth = (unsigned int)((current_tick / local_tt) % 10); 1395 | // We don't want to do the 9 -> 0 transition here. The PPS interrupt is absolutely 1396 | // accurate. We don't need to worry about this code going 0->9 because if local_dt 1397 | // was set to 0 by the capture ISR, then it HAD to also change local_lpt so that 1398 | // current_tenth is now 0. 1399 | if (local_dt != current_tenth && local_dt != 9) { 1400 | // This is really only volatite during the 0 tenth ISR. 1401 | disp_tenth = current_tenth; 1402 | // Write the tenth-of-a-second digit, preserving the 1403 | // decimal point state (just in case) 1404 | // Do this atomically so the PPS ISR can't change it in the middle. 1405 | // (should never happen because local_dt would be 9). 1406 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 1407 | disp_reg[DIGIT_100_MSEC] &= MASK_DP; 1408 | disp_reg[DIGIT_100_MSEC] |= convert_digit(current_tenth); 1409 | reset_raster(); 1410 | } 1411 | #ifdef LATENCY 1412 | if (current_tenth == 5) PORTC.OUTCLR = _BV(7); 1413 | #endif 1414 | } 1415 | } 1416 | if (fake_blink) { 1417 | // This is necessary for the colon menu. We want to blink at 1 Hz (never mind that 1418 | // in the actual clock, we blink at 1/2 Hz), and we have to do it whether GPS is 1419 | // working or not (but it doesn't have to be accurate). We want it to be an even 1420 | // multiple of the timer range so we don't have any discontinuities. 2^24 is 5% 1421 | // more than 16E6 - close enough. We also want to start at the beginning of a 1422 | // blink, so we'll offset from when we began fake_blinking. We also want to start 1423 | // off (since it's menu position immediately follows 'full on'). 1424 | if (((now - fake_blink) >> 24) % 2) { 1425 | disp_reg[DIGIT_MISC] |= MASK_COLON_HM | MASK_COLON_MS; 1426 | } else { 1427 | disp_reg[DIGIT_MISC] &= ~(MASK_COLON_HM | MASK_COLON_MS); 1428 | } 1429 | } 1430 | unsigned char button = check_buttons(); 1431 | switch(button) { 1432 | case SELECT: 1433 | menu_select(); 1434 | break; 1435 | case SET: 1436 | menu_set(); 1437 | break; 1438 | } 1439 | } 1440 | __builtin_unreachable(); 1441 | } 1442 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | # For v3 or previous clocks 4 | #OUT=GPS_Clock 5 | #CHIP = attiny841 6 | #PROGRAMMER = usbtiny -B 5 7 | # For v5 clocks 8 | OUT=GPS_Clock_v5 9 | CHIP = atxmega32e5 10 | PROGRAMMER = atmelice_pdi 11 | 12 | CC = avr-gcc 13 | OBJCPY = avr-objcopy 14 | AVRDUDE = avrdude 15 | OPTS = -Os -g -std=c11 -Wall -Wno-main -fno-tree-switch-conversion 16 | 17 | CFLAGS = -mmcu=$(CHIP) $(OPTS) 18 | 19 | %.o: %.c Makefile 20 | $(CC) $(CFLAGS) -c -o $@ $< 21 | 22 | %.hex: %.elf 23 | $(OBJCPY) -j .text -j .data -O ihex $^ $@ 24 | 25 | %.elf: %.o 26 | $(CC) $(CFLAGS) -o $@ $^ 27 | 28 | all: $(OUT).hex 29 | 30 | clean: 31 | rm -f *.hex *.elf *.o 32 | 33 | flash: $(OUT).hex 34 | $(AVRDUDE) -c $(PROGRAMMER) -p $(CHIP) -U flash:w:$(OUT).hex 35 | 36 | # note that the fuse target is for ATTiny841 (version < 4) only. 37 | fuse: 38 | $(AVRDUDE) -c $(PROGRAMMER) -p $(CHIP) -U hfuse:w:0xd5:m -U lfuse:w:0xe2:m -U efuse:w:0xff:m 39 | 40 | init: fuse flash 41 | --------------------------------------------------------------------------------