commit
2d9aa994a8
@ -0,0 +1,131 @@ |
|||||||
|
#define SSD1306_NO_SPLASH |
||||||
|
|
||||||
|
// OLED display width and height, in pixels
|
||||||
|
#define SCREEN_WIDTH 128 |
||||||
|
#define SCREEN_HEIGHT 32 |
||||||
|
#define OLED_RESET -1 |
||||||
|
// i2c address for oled
|
||||||
|
///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
|
||||||
|
#define SCREEN_ADDRESS 0x3C |
||||||
|
|
||||||
|
#include <Adafruit_SSD1306.h> |
||||||
|
#include <splash.h> |
||||||
|
#include <Adafruit_GrayOLED.h> |
||||||
|
#include <gfxfont.h> |
||||||
|
#include <Adafruit_GFX.h> |
||||||
|
#include <Adafruit_SPITFT.h> |
||||||
|
#include <Adafruit_SPITFT_Macros.h> |
||||||
|
#include <LiquidCrystal_I2C.h> |
||||||
|
|
||||||
|
// set the LCD address to 0x27 for a 16 chars and 2 line display
|
||||||
|
LiquidCrystal_I2C lcd(0x27, 16, 2); |
||||||
|
|
||||||
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); |
||||||
|
|
||||||
|
void setup() { |
||||||
|
lcd.init(); |
||||||
|
lcd.backlight(); |
||||||
|
lcd.setCursor(0, 0); |
||||||
|
lcd.print("0x123456789abcde"); |
||||||
|
lcd.setCursor(0, 1); |
||||||
|
lcd.print("fghijklmnopqrstu"); |
||||||
|
|
||||||
|
Serial.begin(115200); |
||||||
|
|
||||||
|
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
|
||||||
|
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { |
||||||
|
Serial.println(F("SSD1306 allocation failed")); |
||||||
|
for (;;); // Don't proceed, loop forever
|
||||||
|
} |
||||||
|
|
||||||
|
display.display(); |
||||||
|
delay(50); // Pause for 2 seconds
|
||||||
|
|
||||||
|
// Clear the buffer
|
||||||
|
display.clearDisplay(); |
||||||
|
|
||||||
|
testdrawstyles(); // Draw 'stylized' characters
|
||||||
|
|
||||||
|
panic(); |
||||||
|
//
|
||||||
|
// // Invert and restore display, pausing in-between
|
||||||
|
// display.invertDisplay(true);
|
||||||
|
// delay(1000);
|
||||||
|
// display.invertDisplay(false);
|
||||||
|
// delay(1000);
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void panic() { |
||||||
|
for (;;) { |
||||||
|
display.invertDisplay(true); |
||||||
|
delay(1000); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void loop() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
#define LINE_HEIGHT_PX 9 |
||||||
|
|
||||||
|
void testdrawstyles(void) { |
||||||
|
display.clearDisplay(); |
||||||
|
display.setTextSize(1); // Normal 1:1 pixel scale
|
||||||
|
display.setTextColor(SSD1306_WHITE); // Draw white text
|
||||||
|
display.setCursor(0, 0 * LINE_HEIGHT_PX); |
||||||
|
display.println(F("123456789112345678921")); |
||||||
|
display.setCursor(0, 1 * LINE_HEIGHT_PX); |
||||||
|
display.println(F("234567893123456789412")); |
||||||
|
display.setCursor(0, 2 * LINE_HEIGHT_PX); |
||||||
|
display.println(F("345678951234567896123")); |
||||||
|
display.display(); |
||||||
|
|
||||||
|
display.startscrollright(0x00, 0xFF); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
|
||||||
|
display.println(3.141592); |
||||||
|
|
||||||
|
display.setTextSize(2); // Draw 2X-scale text
|
||||||
|
display.setTextColor(SSD1306_WHITE); |
||||||
|
display.print(F("0x")); display.println(0xDEADBEEF, HEX); |
||||||
|
|
||||||
|
display.display(); |
||||||
|
delay(2000); |
||||||
|
*/ |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
void testscrolltext(void) { |
||||||
|
display.clearDisplay(); |
||||||
|
|
||||||
|
display.setTextSize(1.5); // Draw 2X-scale text
|
||||||
|
display.setTextColor(SSD1306_WHITE); |
||||||
|
display.setCursor(10, 0); |
||||||
|
display.println(F("1337CAFE")); |
||||||
|
display.display(); // Show initial text
|
||||||
|
delay(70); |
||||||
|
|
||||||
|
// Scroll in various directions, pausing in-between:
|
||||||
|
display.startscrollright(0x00, 0x0F); |
||||||
|
delay(2000); |
||||||
|
display.stopscroll(); |
||||||
|
delay(1000); |
||||||
|
display.startscrollleft(0x00, 0x0F); |
||||||
|
delay(2000); |
||||||
|
display.stopscroll(); |
||||||
|
delay(1000); |
||||||
|
display.startscrolldiagright(0x00, 0x07); |
||||||
|
delay(2000); |
||||||
|
display.startscrolldiagleft(0x00, 0x07); |
||||||
|
delay(2000); |
||||||
|
display.stopscroll(); |
||||||
|
delay(1000); |
||||||
|
} |
||||||
|
|
||||||
|
*/ |
@ -0,0 +1,51 @@ |
|||||||
|
/*
|
||||||
|
ESP8266 BlinkWithoutDelay by Simon Peter |
||||||
|
Blink the blue LED on the ESP-01 module |
||||||
|
Based on the Arduino Blink without Delay example |
||||||
|
This example code is in the public domain |
||||||
|
|
||||||
|
The blue LED on the ESP-01 module is connected to GPIO1 |
||||||
|
(which is also the TXD pin; so we cannot use Serial.print() at the same time) |
||||||
|
|
||||||
|
Note that this sketch uses LED_BUILTIN to find the pin with the internal LED |
||||||
|
*/ |
||||||
|
#define IR_RX_PIN 14 |
||||||
|
#define IR_TX_PIN 12 |
||||||
|
#define SERIAL_BPS 115200 |
||||||
|
|
||||||
|
#define POWER_BUTTON 0xFFEA15 |
||||||
|
#define EDIT_BUTTON 0xFF7887 |
||||||
|
#define EXIT_BUTTON 0xFF38C7 |
||||||
|
#define ONE_BUTTON 0xFF08F7 |
||||||
|
#define TWO_BUTTON 0xFF8877 |
||||||
|
#define THREE_BUTTON 0xFF48B7 |
||||||
|
#define FOUR_BUTTON 0xFFC837 |
||||||
|
#define FIVE_BUTTON 0xFF28D7 |
||||||
|
#define SIX_BUTTON 0xFFA857 |
||||||
|
#define SEVEN_BUTTON 0xFFE817 |
||||||
|
#define EIGHT_BUTTON 0xFF18E7 |
||||||
|
#define NINE_BUTTON 0xFF9867 |
||||||
|
#define ZERO_BUTTON 0xFFB847 |
||||||
|
|
||||||
|
#include <IRremote.h> |
||||||
|
|
||||||
|
IRrecv irrecv(IR_RX_PIN); |
||||||
|
decode_results results; |
||||||
|
|
||||||
|
int ledState = LOW; |
||||||
|
volatile byte IRInputState; |
||||||
|
|
||||||
|
void setup() { |
||||||
|
IrReceiver.begin(IR_RX_PIN, ENABLE_LED_FEEDBACK); // Start the receiver
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT); |
||||||
|
Serial.begin(SERIAL_BPS); |
||||||
|
Serial.print("herro booted\n"); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
if (IrReceiver.decode()) { |
||||||
|
Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX); |
||||||
|
IrReceiver.printIRResultShort(&Serial); // optional use new print version
|
||||||
|
IrReceiver.resume(); // Enable receiving of the next value
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
// Demo the quad alphanumeric display LED backpack kit
|
||||||
|
// scrolls through every character, then scrolls Serial
|
||||||
|
// input onto the display
|
||||||
|
|
||||||
|
#include <Wire.h> |
||||||
|
#include <Adafruit_GFX.h> |
||||||
|
#include "Adafruit_LEDBackpack.h" |
||||||
|
|
||||||
|
Adafruit_AlphaNum4 one = Adafruit_AlphaNum4(); |
||||||
|
Adafruit_AlphaNum4 two = Adafruit_AlphaNum4(); |
||||||
|
|
||||||
|
unsigned long ticks; |
||||||
|
|
||||||
|
|
||||||
|
void setup() { |
||||||
|
Serial.begin(9600); |
||||||
|
|
||||||
|
one.begin(0x71); // pass in the address
|
||||||
|
two.begin(0x70); |
||||||
|
|
||||||
|
one.writeDigitRaw(3, 0x0); |
||||||
|
one.writeDigitRaw(0, 0xFFFF); |
||||||
|
one.writeDisplay(); |
||||||
|
delay(200); |
||||||
|
one.writeDigitRaw(0, 0x0); |
||||||
|
one.writeDigitRaw(1, 0xFFFF); |
||||||
|
one.writeDisplay(); |
||||||
|
delay(200); |
||||||
|
one.writeDigitRaw(1, 0x0); |
||||||
|
one.writeDigitRaw(2, 0xFFFF); |
||||||
|
one.writeDisplay(); |
||||||
|
delay(200); |
||||||
|
one.writeDigitRaw(2, 0x0); |
||||||
|
one.writeDigitRaw(3, 0xFFFF); |
||||||
|
one.writeDisplay(); |
||||||
|
delay(200); |
||||||
|
|
||||||
|
one.clear(); |
||||||
|
one.writeDisplay(); |
||||||
|
|
||||||
|
two.clear(); |
||||||
|
two.writeDisplay(); |
||||||
|
|
||||||
|
one.writeDigitAscii(0, '1'); |
||||||
|
one.writeDigitAscii(1, '2'); |
||||||
|
one.writeDigitAscii(2, '0'); |
||||||
|
one.writeDigitAscii(3, '0'); |
||||||
|
two.writeDigitAscii(0, '0'); |
||||||
|
two.writeDigitAscii(1, '1'); |
||||||
|
two.writeDigitAscii(2, '2'); |
||||||
|
two.writeDigitAscii(3, '3'); |
||||||
|
one.writeDisplay(); |
||||||
|
two.writeDisplay(); |
||||||
|
|
||||||
|
delay(300); |
||||||
|
|
||||||
|
Serial.println("Start typing to display!"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
char displaybuffer[4] = {' ', ' ', ' ', ' '}; |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
ticks = millis(); |
||||||
|
|
||||||
|
|
||||||
|
sprintf(displaybuffer, "%02d", ticks/1000); |
||||||
|
sprintf(displaybuffer+2, "%02d", ticks-(ticks/1000)*1000); |
||||||
|
|
||||||
|
Serial.println(displaybuffer); |
||||||
|
|
||||||
|
// set every digit to the buffer
|
||||||
|
two.writeDigitAscii(0, displaybuffer[0]); |
||||||
|
two.writeDigitAscii(1, displaybuffer[1]); |
||||||
|
two.writeDigitAscii(2, displaybuffer[2]); |
||||||
|
two.writeDigitAscii(3, displaybuffer[3]); |
||||||
|
|
||||||
|
// write it out!
|
||||||
|
two.writeDisplay(); |
||||||
|
delay(10); |
||||||
|
} |
@ -0,0 +1,871 @@ |
|||||||
|
/* WWVB Clock v 1.0
|
||||||
|
*
|
||||||
|
* A WWVB (NIST Time Code Broadcast) receiving clock powered by an
|
||||||
|
* Arduino (ATMega328), a DS1307 Real Time Clock, and a C-Max
|
||||||
|
* C-Max CMMR-6P-60 WWVB receiver module. |
||||||
|
* |
||||||
|
* This code builds on Capt.Tagon's code at duinolab.blogspot.com |
||||||
|
* which was a great reference. Specifically, I used almost without change |
||||||
|
* his struct / unsigned long long overlay for the received WWVB Buffer,
|
||||||
|
* which apparently in turn draws on DCF77 decoding code by Rudi Niemeijer, |
||||||
|
* Mathias Dalheimer, and "Captain."
|
||||||
|
* |
||||||
|
* The RTC (DS1307) interface code builds in part on code by
|
||||||
|
* Jon McPhalen (www.jonmcphalen.com) |
||||||
|
* |
||||||
|
* There you have it. |
||||||
|
* |
||||||
|
* This code supports the "Atomic Clock" article in the April 2010 issue |
||||||
|
* of Popular Science. There is also a schematic for this project. There |
||||||
|
* is also WWVB signal simulator code, to facilitate debugging and
|
||||||
|
* hacking on this project when the reception of the WWVB signal
|
||||||
|
* itself is less than stellar. |
||||||
|
*
|
||||||
|
* The code for both the clock and the WWVB simulator, and the schematic |
||||||
|
* are available online at: |
||||||
|
* http://www.popsci.com/diy/article/2010-03/build-clock-uses-atomic-timekeeping
|
||||||
|
*
|
||||||
|
* and on GitHub at: http://github.com/vinmarshall/WWVB-Clock
|
||||||
|
*
|
||||||
|
* Version 1.0 |
||||||
|
* Notes:
|
||||||
|
* - No timezone support in this version. UTC only. |
||||||
|
* - No explicit leapsecond (3 frame marker bits) support in this version. |
||||||
|
* - 24 hour mode only |
||||||
|
*
|
||||||
|
* |
||||||
|
* Copyright (c) 2010 Vin Marshall (vlm@2552.com, www.2552.com) |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person |
||||||
|
* obtaining a copy of this software and associated documentation |
||||||
|
* files (the "Software"), to deal in the Software without |
||||||
|
* restriction, including without limitation the rights to use, |
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the |
||||||
|
* Software is furnished to do so, subject to the following |
||||||
|
* conditions: |
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be |
||||||
|
* included in all copies or substantial portions of the Software. |
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/ |
||||||
|
|
||||||
|
#define SERIAL_RATE 115200 |
||||||
|
#define WWV_SIGNAL_PIN 14 |
||||||
|
#define PPS_OUTPUT_PIN 16 |
||||||
|
#define DEBUG1_OUTPUT_PIN 15 |
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <LiquidCrystal_I2C.h> |
||||||
|
LiquidCrystal_I2C lcd(0x27, 16, 2); |
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR wwvbChange(); |
||||||
|
|
||||||
|
//
|
||||||
|
// Initializations
|
||||||
|
|
||||||
|
// Debugging mode - prints debugging information on the Serial port.
|
||||||
|
boolean debug = true; |
||||||
|
|
||||||
|
// Front Panel Light
|
||||||
|
int lightPin = LED_BUILTIN; |
||||||
|
|
||||||
|
// WWVB Receiver Pin
|
||||||
|
int wwvbInPin = WWV_SIGNAL_PIN; |
||||||
|
|
||||||
|
// LCD init
|
||||||
|
//LiquidCrystal lcd(12, 11, 6, 5, 4, 3);
|
||||||
|
|
||||||
|
// RTC init
|
||||||
|
// RTC uses pins Analog4 = SDA, Analog5 = SCL
|
||||||
|
|
||||||
|
// RTC I2C Slave Address
|
||||||
|
#define DS1307 0xD0 >> 1 |
||||||
|
|
||||||
|
// RTC Memory Registers
|
||||||
|
#define RTC_SECS 0 |
||||||
|
#define RTC_MINS 1 |
||||||
|
#define RTC_HRS 2 |
||||||
|
#define RTC_DAY 3 |
||||||
|
#define RTC_DATE 4 |
||||||
|
#define RTC_MONTH 5 |
||||||
|
#define RTC_YEAR 6 |
||||||
|
#define RTC_SQW 7 |
||||||
|
|
||||||
|
// Month abbreviations
|
||||||
|
char *months[12] = { |
||||||
|
"Jan", |
||||||
|
"Feb", |
||||||
|
"Mar", |
||||||
|
"Apr", |
||||||
|
"May", |
||||||
|
"Jun", |
||||||
|
"Jul", |
||||||
|
"Aug", |
||||||
|
"Sep", |
||||||
|
"Oct", |
||||||
|
"Nov", |
||||||
|
"Dec" |
||||||
|
}; |
||||||
|
|
||||||
|
// Day of Year to month translation (thanks to Capt.Tagon)
|
||||||
|
// End of Month - to calculate Month and Day from Day of Year
|
||||||
|
int eomYear[14][2] = { |
||||||
|
{0,0}, // Begin
|
||||||
|
{31,31}, // Jan
|
||||||
|
{59,60}, // Feb
|
||||||
|
{90,91}, // Mar
|
||||||
|
{120,121}, // Apr
|
||||||
|
{151,152}, // May
|
||||||
|
{181,182}, // Jun
|
||||||
|
{212,213}, // Jul
|
||||||
|
{243,244}, // Aug
|
||||||
|
{273,274}, // Sep
|
||||||
|
{304,305}, // Oct
|
||||||
|
{334,335}, // Nov
|
||||||
|
{365,366}, // Dec
|
||||||
|
{366,367} // overflow
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Globals
|
||||||
|
|
||||||
|
// Timing and error recording
|
||||||
|
unsigned long pulseStartTime = 0; |
||||||
|
unsigned long pulseEndTime = 0; |
||||||
|
unsigned long frameEndTime = 0; |
||||||
|
unsigned long lastRtcUpdateTime = 0; |
||||||
|
boolean bitReceived = false; |
||||||
|
boolean wasMark = false; |
||||||
|
int framePosition = 0; |
||||||
|
int bitPosition = 1; |
||||||
|
char lastTimeUpdate[17]; |
||||||
|
char lastBit = ' '; |
||||||
|
int errors[10] = { 1,1,1,1,1,1,1,1,1,1 }; |
||||||
|
int errIdx = 0; |
||||||
|
int bitError = 0; |
||||||
|
boolean frameError = false; |
||||||
|
|
||||||
|
// RTC clock variables
|
||||||
|
byte second = 0x00; |
||||||
|
byte minute = 0x00; |
||||||
|
byte hour = 0x00; |
||||||
|
byte day = 0x00; |
||||||
|
byte date = 0x01; |
||||||
|
byte month = 0x01; |
||||||
|
byte year = 0x00; |
||||||
|
byte ctrl = 0x00; |
||||||
|
|
||||||
|
// WWVB time variables
|
||||||
|
byte wwvb_hour = 0; |
||||||
|
byte wwvb_minute = 0; |
||||||
|
byte wwvb_day = 0; |
||||||
|
byte wwvb_year = 0; |
||||||
|
|
||||||
|
|
||||||
|
/* WWVB time format struct - acts as an overlay on wwvbRxBuffer to extract time/date data.
|
||||||
|
* This points to a 64 bit buffer wwvbRxBuffer that the bits get inserted into as the |
||||||
|
* incoming data stream is received. (Thanks to Capt.Tagon @ duinolab.blogspot.com) |
||||||
|
*/ |
||||||
|
struct wwvbBuffer { |
||||||
|
unsigned long long U12 :4; // no value, empty four bits only 60 of 64 bits used
|
||||||
|
unsigned long long Frame :2; // framing
|
||||||
|
unsigned long long Dst :2; // dst flags
|
||||||
|
unsigned long long Leapsec :1; // leapsecond
|
||||||
|
unsigned long long Leapyear :1; // leapyear
|
||||||
|
unsigned long long U11 :1; // no value
|
||||||
|
unsigned long long YearOne :4; // year (5 -> 2005)
|
||||||
|
unsigned long long U10 :1; // no value
|
||||||
|
unsigned long long YearTen :4; // year (5 -> 2005)
|
||||||
|
unsigned long long U09 :1; // no value
|
||||||
|
unsigned long long OffVal :4; // offset value
|
||||||
|
unsigned long long U08 :1; // no value
|
||||||
|
unsigned long long OffSign :3; // offset sign
|
||||||
|
unsigned long long U07 :2; // no value
|
||||||
|
unsigned long long DayOne :4; // day ones
|
||||||
|
unsigned long long U06 :1; // no value
|
||||||
|
unsigned long long DayTen :4; // day tens
|
||||||
|
unsigned long long U05 :1; // no value
|
||||||
|
unsigned long long DayHun :2; // day hundreds
|
||||||
|
unsigned long long U04 :3; // no value
|
||||||
|
unsigned long long HourOne :4; // hours ones
|
||||||
|
unsigned long long U03 :1; // no value
|
||||||
|
unsigned long long HourTen :2; // hours tens
|
||||||
|
unsigned long long U02 :3; // no value
|
||||||
|
unsigned long long MinOne :4; // minutes ones
|
||||||
|
unsigned long long U01 :1; // no value
|
||||||
|
unsigned long long MinTen :3; // minutes tens
|
||||||
|
}; |
||||||
|
|
||||||
|
struct wwvbBuffer * wwvbFrame; |
||||||
|
unsigned long long receiveBuffer; |
||||||
|
unsigned long long lastFrameBuffer; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setup() |
||||||
|
* |
||||||
|
* uC Initialization |
||||||
|
*/ |
||||||
|
|
||||||
|
void setup() { |
||||||
|
Serial.begin(SERIAL_RATE); |
||||||
|
Serial.println("*****************************************"); |
||||||
|
Serial.println("*****************************************"); |
||||||
|
Serial.println("*****************************************"); |
||||||
|
Serial.print("*** ESP8266 Ready.\n"); |
||||||
|
// Initialize the I2C Two Wire Communication for the RTC
|
||||||
|
//Wire.begin();
|
||||||
|
|
||||||
|
lcd.init(); |
||||||
|
lcd.backlight(); |
||||||
|
lcd.setCursor(0, 0); |
||||||
|
lcd.print("booting"); |
||||||
|
|
||||||
|
|
||||||
|
// Setup the light pin
|
||||||
|
pinMode(lightPin, OUTPUT); |
||||||
|
digitalWrite(lightPin, LOW); |
||||||
|
pinMode(DEBUG1_OUTPUT_PIN, OUTPUT); |
||||||
|
|
||||||
|
// Print a message to the LCD.
|
||||||
|
lcd.clear(); |
||||||
|
lcd.setCursor(0, 0); |
||||||
|
lcd.print("WWVB Clock v 1.0"); |
||||||
|
lcd.setCursor(0, 1); |
||||||
|
lcd.print("PopSci Apr. 2010"); |
||||||
|
delay(5000); |
||||||
|
|
||||||
|
// Front panel light lights for 10 seconds on a successful frame rcpt.
|
||||||
|
digitalWrite(lightPin, HIGH); |
||||||
|
|
||||||
|
// Setup the WWVB Signal In Handling
|
||||||
|
pinMode(wwvbInPin, INPUT); |
||||||
|
attachInterrupt(0, wwvbChange, CHANGE); |
||||||
|
|
||||||
|
// Setup the WWVB Buffer
|
||||||
|
lastFrameBuffer = 0; |
||||||
|
receiveBuffer = 0; |
||||||
|
wwvbFrame = (struct wwvbBuffer *) &lastFrameBuffer; |
||||||
|
|
||||||
|
Serial.println("*** setup() complete"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* loop() |
||||||
|
* |
||||||
|
* Main program loop |
||||||
|
*/ |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
// If we've received another bit, process it
|
||||||
|
if (bitReceived == true) { |
||||||
|
Serial.println("*** bitReceived==true"); |
||||||
|
processBit(); |
||||||
|
} |
||||||
|
|
||||||
|
// Read from the RTC and update the display 4x per second
|
||||||
|
if (millis() - lastRtcUpdateTime > 250) { |
||||||
|
|
||||||
|
// Snag the RTC time and store it locally
|
||||||
|
//getRTC();
|
||||||
|
|
||||||
|
// And record the time of this last update.
|
||||||
|
lastRtcUpdateTime = millis(); |
||||||
|
|
||||||
|
// Update RTC if there has been a successfully received WWVB Frame
|
||||||
|
if (frameEndTime != 0) { |
||||||
|
//updateRTC();
|
||||||
|
frameEndTime = 0; |
||||||
|
} |
||||||
|
|
||||||
|
// Update the display
|
||||||
|
updateDisplay(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* updateDisplay() |
||||||
|
* |
||||||
|
* Update the 2 line x 16 char LCD display |
||||||
|
*/ |
||||||
|
|
||||||
|
void updateDisplay() { |
||||||
|
|
||||||
|
// Turn off the front panel light marking a successfully
|
||||||
|
// received frame after 10 seconds of being on.
|
||||||
|
if (bcd2dec(second) >= 10) { |
||||||
|
digitalWrite(lightPin, HIGH); |
||||||
|
} |
||||||
|
|
||||||
|
// Update the LCD
|
||||||
|
lcd.clear(); |
||||||
|
|
||||||
|
// Update the first row
|
||||||
|
lcd.setCursor(0,0); |
||||||
|
char *time = buildTimeString(); |
||||||
|
lcd.print(time); |
||||||
|
|
||||||
|
// Update the second row
|
||||||
|
// Cycle through our list of status messages
|
||||||
|
lcd.setCursor(0,1); |
||||||
|
int cycle = bcd2dec(second) / 10; // This gives us 6 slots for messages
|
||||||
|
char msg[17]; // 16 chars per line on display
|
||||||
|
|
||||||
|
switch (cycle) { |
||||||
|
|
||||||
|
// Show the Date
|
||||||
|
case 0: |
||||||
|
{ |
||||||
|
sprintf(msg, "%s %0.2i 20%0.2i", |
||||||
|
months[bcd2dec(month)-1], bcd2dec(date), bcd2dec(year)); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Show the WWVB signal strength based on the # of recent frame errors
|
||||||
|
case 1: |
||||||
|
{ |
||||||
|
int signal = (10 - sumFrameErrors()) / 2; |
||||||
|
sprintf(msg, "WWVB Signal: %i", signal); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Show LeapYear and LeapSecond Warning bits
|
||||||
|
case 2: |
||||||
|
{ |
||||||
|
const char *leapyear = ( ((byte) wwvbFrame->Leapyear) == 1)?"Yes":"No"; |
||||||
|
const char *leapsec = ( ((byte) wwvbFrame->Leapsec) == 1)?"Yes":"No"; |
||||||
|
sprintf(msg, "LY: %s LS: %s", leapyear, leapsec); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Show our Daylight Savings Time status
|
||||||
|
case 3: |
||||||
|
{ |
||||||
|
switch((byte)wwvbFrame->Dst) { |
||||||
|
case 0: |
||||||
|
sprintf(msg, "DST: No"); |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
sprintf(msg, "DST: Ending"); |
||||||
|
break; |
||||||
|
case 2: |
||||||
|
sprintf(msg, "DST: Starting"); |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
sprintf(msg, "DST: Yes"); |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Show the UT1 correction sign and amount
|
||||||
|
case 4: |
||||||
|
{ |
||||||
|
char sign; |
||||||
|
if ((byte)wwvbFrame->OffSign == 2) { |
||||||
|
sign = '-'; |
||||||
|
} else if ((byte)wwvbFrame->OffSign == 5) { |
||||||
|
sign = '+'; |
||||||
|
} else { |
||||||
|
sign = '?'; |
||||||
|
} |
||||||
|
sprintf(msg, "UT1 %c 0.%i", sign, (byte) wwvbFrame->OffVal); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Show the time and date of the last successfully received
|
||||||
|
// wwvb frame
|
||||||
|
case 5: |
||||||
|
{ |
||||||
|
sprintf(msg, "[%s]", lastTimeUpdate); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
lcd.print(msg); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* buildTimeString |
||||||
|
* |
||||||
|
* Prepare the string for displaying the time on line 1 of the LCD |
||||||
|
*/ |
||||||
|
|
||||||
|
char* buildTimeString() { |
||||||
|
char rv[255]; |
||||||
|
sprintf(rv,"%0.2i:%0.2i:%0.2i UTC %c", |
||||||
|
bcd2dec(hour), |
||||||
|
bcd2dec(minute), |
||||||
|
bcd2dec(second), |
||||||
|
lastBit); |
||||||
|
return rv; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getRTC |
||||||
|
*
|
||||||
|
* Read data from the DS1307 RTC over the I2C 2 wire interface. |
||||||
|
* Data is stored in the uC's global clock variables. |
||||||
|
*/ |
||||||
|
/*
|
||||||
|
void getRTC() { |
||||||
|
|
||||||
|
// Begin the Transmission
|
||||||
|
Wire.beginTransmission(DS1307); |
||||||
|
|
||||||
|
// Point the request at the first register (seconds)
|
||||||
|
Wire.write(RTC_SECS); |
||||||
|
|
||||||
|
// End the Transmission and Start Listening
|
||||||
|
Wire.endTransmission(); |
||||||
|
Wire.requestFrom(DS1307, 8); |
||||||
|
second = Wire.receive(); |
||||||
|
minute = Wire.receive(); |
||||||
|
hour = Wire.receive(); |
||||||
|
day = Wire.receive(); |
||||||
|
date = Wire.receive(); |
||||||
|
month = Wire.receive(); |
||||||
|
year = Wire.receive(); |
||||||
|
ctrl = Wire.receive(); |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setRTC |
||||||
|
*
|
||||||
|
* Set the DS1307 RTC over the I2C 2 wire interface. |
||||||
|
* Data is read from the uC's global clock variables. |
||||||
|
*/ |
||||||
|
/*
|
||||||
|
void setRTC() { |
||||||
|
|
||||||
|
// Begin the Transmission
|
||||||
|
Wire.beginTransmission(DS1307); |
||||||
|
|
||||||
|
// Start at the beginning
|
||||||
|
Wire.write(RTC_SECS); |
||||||
|
|
||||||
|
// Send data for each register in order
|
||||||
|
Wire.write(second); |
||||||
|
Wire.write(minute); |
||||||
|
Wire.write(hour); |
||||||
|
Wire.send(day); |
||||||
|
Wire.send(date); |
||||||
|
Wire.send(month); |
||||||
|
Wire.send(year); |
||||||
|
Wire.send(ctrl); |
||||||
|
|
||||||
|
// End the transmission
|
||||||
|
Wire.endTransmission(); |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
/*
|
||||||
|
* updateRTC |
||||||
|
*
|
||||||
|
* Update the time stored in the RTC to match the received WWVB frame. |
||||||
|
*/ |
||||||
|
/*
|
||||||
|
void updateRTC() { |
||||||
|
|
||||||
|
// Find out how long since the frame's On Time Marker (OTM)
|
||||||
|
// We'll need this adjustment when we set the time.
|
||||||
|
unsigned int timeSinceFrame = millis() - frameEndTime; |
||||||
|
byte secondsSinceFrame = timeSinceFrame / 1000; |
||||||
|
if (timeSinceFrame % 1000 > 500) { |
||||||
|
secondsSinceFrame++; |
||||||
|
} |
||||||
|
|
||||||
|
// The OTM for a minute comes at the beginning of the frame, meaning that
|
||||||
|
// the WWVB time we have is now 1 minute + `secondsSinceFrame` seconds old.
|
||||||
|
incrementWwvbMinute(); |
||||||
|
|
||||||
|
// Set up data for the RTC clock write
|
||||||
|
second = secondsSinceFrame; |
||||||
|
minute = ((byte) wwvbFrame->MinTen << 4) + (byte) wwvbFrame->MinOne; |
||||||
|
hour = ((byte) wwvbFrame->HourTen << 4) + (byte) wwvbFrame->HourOne; |
||||||
|
day = 0; // we're not using day of week at this time.
|
||||||
|
|
||||||
|
// Translate wwvb day of year into a month and a day of month
|
||||||
|
// This routine is courtesy of Capt.Tagon
|
||||||
|
int doy = ((byte) wwvbFrame->DayHun * 100) + |
||||||
|
((byte) wwvbFrame->DayTen * 10) + |
||||||
|
((byte) wwvbFrame->DayOne); |
||||||
|
|
||||||
|
int i = 0; |
||||||
|
byte isLeapyear = (byte) wwvbFrame->Leapyear; |
||||||
|
while ( (i < 14) && (eomYear[i][isLeapyear] < doy) ) { |
||||||
|
i++; |
||||||
|
} |
||||||
|
if (i>0) { |
||||||
|
date = dec2bcd(doy - eomYear[i-1][isLeapyear]); |
||||||
|
month = dec2bcd((i > 12)?1:i); |
||||||
|
} |
||||||
|
|
||||||
|
year = ((byte) wwvbFrame->YearTen << 4) + (byte) wwvbFrame->YearOne; |
||||||
|
|
||||||
|
// And write the update to the RTC
|
||||||
|
//setRTC();
|
||||||
|
|
||||||
|
// Store the time of update for the display status line
|
||||||
|
sprintf(lastTimeUpdate, "%0.2i:%0.2i %0.2i/%0.2i/%0.2i",
|
||||||
|
bcd2dec(hour), bcd2dec(minute), bcd2dec(month),
|
||||||
|
bcd2dec(date), bcd2dec(year)); |
||||||
|
|
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
/*
|
||||||
|
* processBit() |
||||||
|
*
|
||||||
|
* Decode a received pulse. Pulses are decoded according to the
|
||||||
|
* length of time the pulse was in the low state. |
||||||
|
*/ |
||||||
|
|
||||||
|
void processBit() { |
||||||
|
Serial.println("*** in processBit()"); |
||||||
|
char buf[255]; |
||||||
|
|
||||||
|
// Clear the bitReceived flag, as we're processing that bit.
|
||||||
|
bitReceived = false; |
||||||
|
|
||||||
|
// determine the width of the received pulse
|
||||||
|
unsigned int pulseWidth = pulseEndTime - pulseStartTime; |
||||||
|
|
||||||
|
sprintf(buf,"got bit with pulseWidth=%d\n",pulseWidth); |
||||||
|
Serial.print(buf); |
||||||
|
|
||||||
|
// Attempt to decode the pulse into an Unweighted bit (0),
|
||||||
|
// a Weighted bit (1), or a Frame marker.
|
||||||
|
|
||||||
|
// Pulses < 0.2 sec are an error in reception.
|
||||||
|
if (pulseWidth < 100) { |
||||||
|
buffer(-2); |
||||||
|
bitError++; |
||||||
|
wasMark = false; |
||||||
|
|
||||||
|
// 0.2 sec pulses are an Unweighted bit (0)
|
||||||
|
} else if (pulseWidth < 400) { |
||||||
|
buffer(0); |
||||||
|
wasMark = false; |
||||||
|
|
||||||
|
// 0.5 sec pulses are a Weighted bit (1)
|
||||||
|
} else if (pulseWidth < 700) { |
||||||
|
buffer(1); |
||||||
|
wasMark = false; |
||||||
|
|
||||||
|
// 0.8 sec pulses are a Frame Marker
|
||||||
|
} else if (pulseWidth < 900) { |
||||||
|
|
||||||
|
// Two Frame Position markers in a row indicate an
|
||||||
|
// end/beginning of frame marker
|
||||||
|
if (wasMark) { |
||||||
|
|
||||||
|
// For the display update
|
||||||
|
lastBit = '*'; |
||||||
|
Serial.println("*** Frame Start"); |
||||||
|
|
||||||
|
// Verify that our position data jives with this being
|
||||||
|
// a Frame start/end marker
|
||||||
|
if ( (framePosition == 6) && |
||||||
|
(bitPosition == 60) && |
||||||
|
(bitError == 0)) { |
||||||
|
|
||||||
|
// Process a received frame
|
||||||
|
frameEndTime = pulseStartTime; |
||||||
|
lastFrameBuffer = receiveBuffer; |
||||||
|
digitalWrite(lightPin, LOW); |
||||||
|
logFrameError(false); |
||||||
|
|
||||||
|
debugPrintFrame(); |
||||||
|
|
||||||
|
} else { |
||||||
|
frameError = true; |
||||||
|
} |
||||||
|
|
||||||
|
// Reset the position counters
|
||||||
|
framePosition = 0; |
||||||
|
bitPosition = 1; |
||||||
|
wasMark = false; |
||||||
|
bitError = 0; |
||||||
|
receiveBuffer = 0; |
||||||
|
|
||||||
|
// Otherwise, this was just a regular frame position marker
|
||||||
|
} else { |
||||||
|
buffer(-1); |
||||||
|
wasMark = true; |
||||||
|
} |
||||||
|
|
||||||
|
// Pulses > 0.8 sec are an error in reception
|
||||||
|
} else { |
||||||
|
buffer(-2); |
||||||
|
bitError++; |
||||||
|
wasMark = false; |
||||||
|
} |
||||||
|
|
||||||
|
// Reset everything if we have more than 60 bits in the frame. This means
|
||||||
|
// the frame markers went missing somewhere along the line
|
||||||
|
if (frameError == true || bitPosition > 60) { |
||||||
|
|
||||||
|
// Debugging
|
||||||
|
Serial.println("*** Frame Error"); |
||||||
|
|
||||||
|
// Reset all of the frame pointers and flags
|
||||||
|
frameError = false; |
||||||
|
logFrameError(true); |
||||||
|
framePosition = 0; |
||||||
|
bitPosition = 1; |
||||||
|
wasMark = false; |
||||||
|
bitError = 0; |
||||||
|
receiveBuffer = 0; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* logFrameError |
||||||
|
* |
||||||
|
* Log the error in the buffer that is a window on the past 10 frames.
|
||||||
|
*/ |
||||||
|
|
||||||
|
void logFrameError(boolean err) { |
||||||
|
|
||||||
|
// Add a 1 to log errors to the buffer
|
||||||
|
int add = err?1:0; |
||||||
|
errors[errIdx] = add; |
||||||
|
|
||||||
|
// and move the buffer loop-around pointer
|
||||||
|
if (++errIdx >= 10) { |
||||||
|
errIdx = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sumFrameErrors |
||||||
|
*
|
||||||
|
* Turn the errors in the frame error buffer into a number useful to display |
||||||
|
* the quality of reception of late in the status messages. Sums the errors |
||||||
|
* in the frame error buffer. |
||||||
|
*/ |
||||||
|
|
||||||
|
int sumFrameErrors() { |
||||||
|
|
||||||
|
// Sum all of the values in our error buffer
|
||||||
|
int i, rv; |
||||||
|
for (i=0; i< 10; i++) { |
||||||
|
rv += errors[i]; |
||||||
|
} |
||||||
|
|
||||||
|
return rv; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* debugPrintFrame |
||||||
|
*
|
||||||
|
* Print the decoded frame over the seriail port |
||||||
|
*/ |
||||||
|
|
||||||
|
void debugPrintFrame() { |
||||||
|
char time[255]; |
||||||
|
byte minTen = (byte) wwvbFrame->MinTen; |
||||||
|
byte minOne = (byte) wwvbFrame->MinOne; |
||||||
|
byte hourTen = (byte) wwvbFrame->HourTen; |
||||||
|
byte hourOne = (byte) wwvbFrame->HourOne; |
||||||
|
byte dayHun = (byte) wwvbFrame->DayHun; |
||||||
|
byte dayTen = (byte) wwvbFrame->DayTen; |
||||||
|
byte dayOne = (byte) wwvbFrame->DayOne; |
||||||
|
byte yearOne = (byte) wwvbFrame->YearOne; |
||||||
|
byte yearTen = (byte) wwvbFrame->YearTen; |
||||||
|
|
||||||
|
byte wwvb_minute = (10 * minTen) + minOne; |
||||||
|
byte wwvb_hour = (10 * hourTen) + hourOne; |
||||||
|
byte wwvb_day = (100 * dayHun) + (10 * dayTen) + dayOne; |
||||||
|
byte wwvb_year = (10 * yearTen) + yearOne; |
||||||
|
|
||||||
|
sprintf(time, "\nFrame Decoded: %0.2i:%0.2i %0.3i 20%0.2i\n", |
||||||
|
wwvb_hour, wwvb_minute, wwvb_day, wwvb_year); |
||||||
|
Serial.print(time); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* buffer |
||||||
|
* |
||||||
|
* Places the received bits in the receive buffer in the order they |
||||||
|
* were recived. The first received bit goes in the highest order
|
||||||
|
* bit of the receive buffer. |
||||||
|
*/ |
||||||
|
|
||||||
|
void buffer(int bit) { |
||||||
|
|
||||||
|
// Process our bits
|
||||||
|
if (bit == 0) { |
||||||
|
lastBit = '0'; |
||||||
|
if (debug) { Serial.println("0"); } |
||||||
|
|
||||||
|
} else if (bit == 1) { |
||||||
|
lastBit = '1'; |
||||||
|
if (debug) { Serial.println("1"); } |
||||||
|
|
||||||
|
} else if (bit == -1) { |
||||||
|
lastBit = 'M'; |
||||||
|
if (debug) { Serial.println("M"); } |
||||||
|
framePosition++; |
||||||
|
|
||||||
|
} else if (bit == -2) { |
||||||
|
lastBit = 'X'; |
||||||
|
if (debug) { Serial.println("X"); } |
||||||
|
} |
||||||
|
|
||||||
|
// Push the bit into the buffer. The 0s and 1s are the only
|
||||||
|
// ones we care about.
|
||||||
|
if (bit < 0) { bit = 0; } |
||||||
|
receiveBuffer = receiveBuffer | ( (unsigned long long) bit << (64 - bitPosition) ); |
||||||
|
|
||||||
|
// And increment the counters that keep track of where we are
|
||||||
|
// in the frame.
|
||||||
|
bitPosition++; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* incrementWwvbMinute |
||||||
|
* |
||||||
|
* The frame On Time Marker occurs at the beginning of the frame. This means |
||||||
|
* that the time in the frame is one minute old by the time it has been fully |
||||||
|
* received. Adding one to the minute can be somewhat complicated, in as much
|
||||||
|
* as it can roll over the successive hours, days, and years in just the right
|
||||||
|
* circumstances. This handles that. |
||||||
|
*/ |
||||||
|
|
||||||
|
void incrementWwvbMinute() { |
||||||
|
|
||||||
|
// Increment the Time and Date
|
||||||
|
if (++(wwvbFrame->MinOne) == 10) { |
||||||
|
wwvbFrame->MinOne = 0; |
||||||
|
wwvbFrame->MinTen++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->MinTen == 6) { |
||||||
|
wwvbFrame->MinTen = 0; |
||||||
|
wwvbFrame->HourOne++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->HourOne == 10) { |
||||||
|
wwvbFrame->HourOne = 0; |
||||||
|
wwvbFrame->HourTen++; |
||||||
|
} |
||||||
|
|
||||||
|
if ( (wwvbFrame->HourTen == 2) && (wwvbFrame->HourOne == 4) ) { |
||||||
|
wwvbFrame->HourTen = 0; |
||||||
|
wwvbFrame->HourOne = 0; |
||||||
|
wwvbFrame->DayOne++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->DayOne == 10) { |
||||||
|
wwvbFrame->DayOne = 0; |
||||||
|
wwvbFrame->DayTen++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->DayTen == 10) { |
||||||
|
wwvbFrame->DayTen = 0; |
||||||
|
wwvbFrame->DayHun++; |
||||||
|
} |
||||||
|
|
||||||
|
if ( (wwvbFrame->DayHun == 3) && |
||||||
|
(wwvbFrame->DayTen == 6) && |
||||||
|
(wwvbFrame->DayOne == (6 + (int) wwvbFrame->Leapyear)) ) { |
||||||
|
// Happy New Year.
|
||||||
|
wwvbFrame->DayHun = 0; |
||||||
|
wwvbFrame->DayTen = 0; |
||||||
|
wwvbFrame->DayOne = 1; |
||||||
|
wwvbFrame->YearOne++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->YearOne == 10) { |
||||||
|
wwvbFrame->YearOne = 0; |
||||||
|
wwvbFrame->YearTen++; |
||||||
|
} |
||||||
|
|
||||||
|
if (wwvbFrame->YearTen == 10) { |
||||||
|
wwvbFrame->YearTen = 0; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wwvbChange |
||||||
|
*
|
||||||
|
* This is the interrupt servicing routine. Changes in the level of the
|
||||||
|
* received WWVB pulse are recorded here to be processed in processBit(). |
||||||
|
*/ |
||||||
|
|
||||||
|
void wwvbChange() { |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, HIGH); |
||||||
|
|
||||||
|
int signalLevel = digitalRead(wwvbInPin); |
||||||
|
|
||||||
|
// Determine if this was triggered by a rising or a falling edge
|
||||||
|
// and record the pulse low period start and stop times
|
||||||
|
|
||||||
|
//FIXME this might need to be HIGH
|
||||||
|
if (signalLevel == HIGH) { |
||||||
|
pulseStartTime = millis(); |
||||||
|
} else { |
||||||
|
pulseEndTime = millis(); |
||||||
|
bitReceived = true; |
||||||
|
} |
||||||
|
|
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, LOW); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* bcd2dec |
||||||
|
*
|
||||||
|
* Utility function to convert 2 bcd coded hex digits into an integer |
||||||
|
*/ |
||||||
|
|
||||||
|
int bcd2dec(int bcd) { |
||||||
|
return ( (bcd>>4) * 10) + (bcd % 16); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dec2bcd |
||||||
|
*
|
||||||
|
* Utility function to convert an integer into 2 bcd coded hex digits |
||||||
|
*/ |
||||||
|
|
||||||
|
int dec2bcd(int dec) { |
||||||
|
return ( (dec/10) << 4) + (dec % 10); |
||||||
|
} |
||||||
|
|
||||||
|
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,482 @@ |
|||||||
|
#include <LiquidCrystal_I2C.h> |
||||||
|
LiquidCrystal_I2C lcd(0x27, 16, 2); |
||||||
|
|
||||||
|
#define SERIAL_RATE 115200 |
||||||
|
#define WWV_SIGNAL_PIN 14 |
||||||
|
#define PPS_OUTPUT_PIN 16 |
||||||
|
#define DEBUG1_OUTPUT_PIN 15 |
||||||
|
|
||||||
|
#define ONEBIT 1 |
||||||
|
#define ZEROBIT 2 |
||||||
|
#define MARKBIT 3 |
||||||
|
|
||||||
|
#define DEBUG false |
||||||
|
|
||||||
|
// we sample once per ms:
|
||||||
|
#define NUM_SAMPLES_PER_FRAME 700 |
||||||
|
|
||||||
|
#define CLOCKS_PER_MS 10 |
||||||
|
|
||||||
|
char statusString[25] = "LOS"; |
||||||
|
|
||||||
|
volatile unsigned int lowLatencyInState; |
||||||
|
volatile unsigned int wwvbInState; |
||||||
|
volatile unsigned int lastWWVBInState; |
||||||
|
|
||||||
|
volatile unsigned int stateStableCounter = 0; |
||||||
|
volatile unsigned int stateStableMillis = 0; |
||||||
|
volatile unsigned int lastStateStableMillis = 0; |
||||||
|
|
||||||
|
volatile unsigned int secondCounter = 0; |
||||||
|
volatile unsigned int milliCounter = 0; |
||||||
|
volatile unsigned int clockCounter = 0; |
||||||
|
|
||||||
|
volatile unsigned int timeToTick = 0; |
||||||
|
volatile unsigned int oldLastLowMillis = 0; |
||||||
|
volatile unsigned int lastLowMillis = 0; |
||||||
|
volatile unsigned int oldLastHighMillis = 0; |
||||||
|
volatile unsigned int lastHighMillis = 0; |
||||||
|
volatile unsigned int frameReadyToStart = 0; |
||||||
|
volatile unsigned int beginFrameSearch = 0; |
||||||
|
volatile unsigned int frameSamples = 0; |
||||||
|
volatile unsigned int lossOfSignal = 1; |
||||||
|
volatile unsigned int displayUpdateRequired = 1; |
||||||
|
volatile unsigned int frameSearch = 0; |
||||||
|
volatile unsigned int frameStart = 0; |
||||||
|
volatile unsigned int frameStartTime = 0; |
||||||
|
volatile unsigned int frameHigh = 0; |
||||||
|
volatile unsigned int frameHighReadOut = 0; |
||||||
|
volatile unsigned int frameLow = 0; |
||||||
|
volatile unsigned int frameLowReadOut = 0; |
||||||
|
volatile unsigned int frameCounter = 0; |
||||||
|
volatile unsigned int frameReadyForRead = 0; |
||||||
|
volatile unsigned int lastBitReceived = 0; |
||||||
|
volatile unsigned int millisSinceBoot = 0; |
||||||
|
volatile unsigned int ppsActivationTime; |
||||||
|
volatile unsigned int millisSinceSignalStateChange = 0; |
||||||
|
volatile unsigned int minuteSync = 0; |
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR WWVFallingEdge(); |
||||||
|
void ICACHE_RAM_ATTR OSCEdge(); |
||||||
|
void ICACHE_RAM_ATTR MilliEdge(); |
||||||
|
void ICACHE_RAM_ATTR SecondEdge(); |
||||||
|
|
||||||
|
|
||||||
|
#include "ESP8266TimerInterrupt.h" |
||||||
|
ESP8266Timer ITimer; |
||||||
|
|
||||||
|
void setup() { |
||||||
|
Serial.begin(SERIAL_RATE); |
||||||
|
while (!Serial); |
||||||
|
delay(200); |
||||||
|
Serial.print("\n\n\n"); |
||||||
|
Serial.println("************************************************"); |
||||||
|
Serial.println("************************************************"); |
||||||
|
Serial.println("*** ESP8266 Ready."); |
||||||
|
Serial.println("************************************************"); |
||||||
|
Serial.print(F("\nStarting WWVB on ")); |
||||||
|
Serial.println(ARDUINO_BOARD); |
||||||
|
Serial.println(ESP8266_TIMER_INTERRUPT_VERSION); |
||||||
|
Serial.print(F("CPU Frequency = ")); |
||||||
|
Serial.print(F_CPU / 1000000); |
||||||
|
Serial.println(F(" MHz")); |
||||||
|
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT); |
||||||
|
pinMode(PPS_OUTPUT_PIN, OUTPUT); |
||||||
|
pinMode(DEBUG1_OUTPUT_PIN, OUTPUT); |
||||||
|
pinMode(WWV_SIGNAL_PIN, INPUT); |
||||||
|
|
||||||
|
// 100us timer:
|
||||||
|
if (ITimer.attachInterruptInterval(100, TimerHandler)) { |
||||||
|
Serial.print(F("Starting ITimer OK, millis() = ")); Serial.println(millis()); |
||||||
|
} else { |
||||||
|
Serial.println(F("Can't set ITimer. Select another freq. or timer")); |
||||||
|
} |
||||||
|
|
||||||
|
//attachInterrupt(digitalPinToInterrupt(LOSC_INPUT_PIN), OSCEdge, RISING);
|
||||||
|
lcd.init(); |
||||||
|
lcd.backlight(); |
||||||
|
lcd.setCursor(0, 0); |
||||||
|
lcd.print("booting"); |
||||||
|
|
||||||
|
ppsActivationTime = millis(); |
||||||
|
} |
||||||
|
|
||||||
|
void TimerHandler() { |
||||||
|
//Serial.print(F("in timer OK, millis() = ")); Serial.println(millis());
|
||||||
|
clockCounter++; |
||||||
|
if(clockCounter > CLOCKS_PER_MS) { |
||||||
|
clockCounter -= CLOCKS_PER_MS; |
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// LOW LATENCY HACK to respond in 100us to a falling
|
||||||
|
// start-of-second edge
|
||||||
|
// respond really fast to a falling edge if in the frameReadyToStart
|
||||||
|
// state
|
||||||
|
lowLatencyInState = digitalRead(WWV_SIGNAL_PIN); |
||||||
|
if(!lowLatencyInState && frameReadyToStart && !lossOfSignal && !frameStart) { |
||||||
|
// TICK!
|
||||||
|
// falling edge, beginning of a new frame and second
|
||||||
|
digitalWrite(PPS_OUTPUT_PIN, 1); |
||||||
|
timeToTick = 1; |
||||||
|
frameStart = 1; |
||||||
|
frameStartTime = millisSinceBoot; |
||||||
|
frameReadyToStart = 0; |
||||||
|
frameSearch = 0; |
||||||
|
} |
||||||
|
// *****************************************************************************
|
||||||
|
MilliEdge(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void SecondEdge() { |
||||||
|
secondCounter++; |
||||||
|
displayUpdateRequired = 1; |
||||||
|
} |
||||||
|
|
||||||
|
void MilliEdge() { |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, 1); |
||||||
|
wwvbInState = digitalRead(WWV_SIGNAL_PIN); |
||||||
|
|
||||||
|
milliCounter++; |
||||||
|
millisSinceBoot++; |
||||||
|
|
||||||
|
if(milliCounter > 1000) { |
||||||
|
milliCounter -= 1000; |
||||||
|
SecondEdge(); |
||||||
|
} |
||||||
|
|
||||||
|
if(wwvbInState && (wwvbInState != lastWWVBInState)) { |
||||||
|
// we are on the rising edge, last low was 1ms ago
|
||||||
|
oldLastLowMillis = lastLowMillis; |
||||||
|
lastLowMillis = millisSinceBoot - 1; |
||||||
|
} |
||||||
|
|
||||||
|
if(!wwvbInState && (wwvbInState != lastWWVBInState)) { |
||||||
|
// we are on the falling edge, last high was 1ms ago
|
||||||
|
oldLastHighMillis = lastHighMillis; |
||||||
|
lastHighMillis = millisSinceBoot - 1; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if(wwvbInState != lastWWVBInState) { |
||||||
|
// we are on an edge
|
||||||
|
stateStableMillis = 0; |
||||||
|
} else { |
||||||
|
// nothing happening
|
||||||
|
stateStableMillis++; |
||||||
|
} |
||||||
|
|
||||||
|
if(wwvbInState && (stateStableMillis > 180) && (stateStableMillis < 2000)) { |
||||||
|
// if we are high but for less than 2s
|
||||||
|
// main screen turn on
|
||||||
|
lossOfSignal = 0; |
||||||
|
frameSearch = 1; |
||||||
|
} |
||||||
|
|
||||||
|
lastWWVBInState = wwvbInState; // copy/save for next loop
|
||||||
|
|
||||||
|
if((stateStableMillis > 2000) && !lossOfSignal) { |
||||||
|
// we have received nothing for 2 seconds, loss of signal:
|
||||||
|
lossOfSignal = 1; |
||||||
|
frameStart = 0; |
||||||
|
frameCounter = 0; |
||||||
|
minuteSync = 0; |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, 0); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if(!frameStart && frameSearch && wwvbInState) { |
||||||
|
// if we have been high for 180ms (frameSearch) we are ready to start a new frame on the mark
|
||||||
|
frameHigh = 0; |
||||||
|
frameLow = 0; |
||||||
|
frameReadyToStart = 1; |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, 0); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// frameReadyToStart -> frameStart (and PPS) happens in a 100us
|
||||||
|
// ISR above.
|
||||||
|
|
||||||
|
if (frameStart && (frameSamples < NUM_SAMPLES_PER_FRAME)) { |
||||||
|
frameSearch = 0; |
||||||
|
//begin sampling
|
||||||
|
if (wwvbInState) { |
||||||
|
frameHigh++; |
||||||
|
} else { |
||||||
|
frameLow++; |
||||||
|
} |
||||||
|
frameSamples++; |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, 0); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if(frameStart && (frameSamples >= NUM_SAMPLES_PER_FRAME)) { |
||||||
|
frameReadyForRead = 1; |
||||||
|
frameHighReadOut = frameHigh; |
||||||
|
frameLowReadOut = frameLow; |
||||||
|
frameStart = 0; |
||||||
|
frameHigh = 0; |
||||||
|
frameLow = 0; |
||||||
|
frameSamples = 0; |
||||||
|
} |
||||||
|
digitalWrite(DEBUG1_OUTPUT_PIN, 0); |
||||||
|
} |
||||||
|
|
||||||
|
char pb[255]; |
||||||
|
void loop() { |
||||||
|
digitalWrite(LED_BUILTIN, !wwvbInState); |
||||||
|
|
||||||
|
if(timeToTick) { |
||||||
|
timeToTick = 0; |
||||||
|
TickSecond(); |
||||||
|
} |
||||||
|
|
||||||
|
yield(); |
||||||
|
|
||||||
|
if(frameReadyForRead) { |
||||||
|
frameReadyForRead = 0; |
||||||
|
processFrame(); |
||||||
|
} |
||||||
|
|
||||||
|
yield(); |
||||||
|
|
||||||
|
PPSLowIfRequired(); |
||||||
|
|
||||||
|
yield(); |
||||||
|
|
||||||
|
if (displayUpdateRequired) { |
||||||
|
updateDisplay(); |
||||||
|
displayUpdateRequired = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void SetPPSHigh() { |
||||||
|
digitalWrite(PPS_OUTPUT_PIN, 1); |
||||||
|
} |
||||||
|
|
||||||
|
void SetPPSLow() { |
||||||
|
digitalWrite(PPS_OUTPUT_PIN, 0); |
||||||
|
} |
||||||
|
|
||||||
|
void SendPPS() { |
||||||
|
unsigned int tickInterval = millisSinceBoot - ppsActivationTime; |
||||||
|
ppsActivationTime = millisSinceBoot; |
||||||
|
SetPPSHigh(); |
||||||
|
} |
||||||
|
|
||||||
|
void PPSLowIfRequired() { |
||||||
|
if ((millisSinceBoot - ppsActivationTime) > 500) { |
||||||
|
SetPPSLow(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void TickSecond() { |
||||||
|
char buf[255]; |
||||||
|
sprintf(buf, "*** TICK(%d): WWVB going low after %d ms high (EDGE)\n", frameCounter, millisSinceBoot - lastLowMillis); |
||||||
|
SendPPS(); |
||||||
|
Serial.print(buf); |
||||||
|
} |
||||||
|
|
||||||
|
void processFrame() { |
||||||
|
char buf[255]; |
||||||
|
sprintf(buf, "end of frame summary: frameHigh: %d, frameLow: %d\n", frameHighReadOut, frameLowReadOut); |
||||||
|
Serial.print(buf); |
||||||
|
float rawVal = |
||||||
|
(float)frameHighReadOut |
||||||
|
/ |
||||||
|
( (float)frameHighReadOut + (float)frameLowReadOut ); |
||||||
|
|
||||||
|
rawVal *= 1000; |
||||||
|
unsigned int intRawVal = (int)rawVal; |
||||||
|
if (intRawVal > 100000) { |
||||||
|
intRawVal = 100000; |
||||||
|
} |
||||||
|
displayUpdateRequired++; |
||||||
|
registerBit(convertDutyCycleToBit(intRawVal)); |
||||||
|
} |
||||||
|
|
||||||
|
int convertDutyCycleToBit(unsigned int rawVal) { |
||||||
|
char buf[255]; |
||||||
|
/*
|
||||||
|
|
||||||
|
20% - marker |
||||||
|
50% - one bit |
||||||
|
80% - zero bit |
||||||
|
our cutoff points will be 50% and 80% |
||||||
|
*/ |
||||||
|
char bitbuf[20]; |
||||||
|
|
||||||
|
int output = 0; |
||||||
|
|
||||||
|
output = ZEROBIT; |
||||||
|
sprintf(bitbuf, "ZERO"); |
||||||
|
|
||||||
|
if (rawVal < 800) { |
||||||
|
output = ONEBIT; |
||||||
|
sprintf(bitbuf, "ONE"); |
||||||
|
} |
||||||
|
|
||||||
|
if (rawVal < 680) { |
||||||
|
output = MARKBIT; |
||||||
|
sprintf(bitbuf, "MARK"); |
||||||
|
} |
||||||
|
|
||||||
|
sprintf(buf, "frame rawVal=%d, bit=%s\n", rawVal, bitbuf); |
||||||
|
Serial.print(buf); |
||||||
|
return output; |
||||||
|
} |
||||||
|
|
||||||
|
void registerBit(int doot) { |
||||||
|
if (minuteSync) { |
||||||
|
frameCounter++; |
||||||
|
} |
||||||
|
|
||||||
|
if (doot == MARKBIT) { |
||||||
|
if (lastBitReceived == MARKBIT) { |
||||||
|
// two mark bits in a row means we are in the first second of the minute
|
||||||
|
frameCounter = 0; |
||||||
|
minuteSync = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!minuteSync) { |
||||||
|
frameCounter = 0; |
||||||
|
} |
||||||
|
|
||||||
|
sanityCheckFrame(doot); |
||||||
|
lastBitReceived = doot; |
||||||
|
logBit(doot); |
||||||
|
} |
||||||
|
|
||||||
|
void logBit(int doot) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void lossOfSync(int errorFrame) { |
||||||
|
char buf[255]; |
||||||
|
sprintf(buf, "ERROR: %d bit incorrect for framing, loss of sync!\n", errorFrame); |
||||||
|
Serial.print(buf); |
||||||
|
minuteSync = 0; |
||||||
|
displayUpdateRequired++; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void sanityCheckFrame(int doot) { |
||||||
|
if ( |
||||||
|
( |
||||||
|
(frameCounter == 9) |
||||||
|
|| |
||||||
|
(frameCounter == 19) |
||||||
|
|| |
||||||
|
(frameCounter == 29) |
||||||
|
|| |
||||||
|
(frameCounter == 39) |
||||||
|
|| |
||||||
|
(frameCounter == 49) |
||||||
|
) |
||||||
|
&& doot != MARKBIT) { |
||||||
|
lossOfSync(frameCounter); |
||||||
|
} |
||||||
|
|
||||||
|
if ( |
||||||
|
(doot == MARKBIT) |
||||||
|
&& |
||||||
|
( |
||||||
|
(frameCounter != 0) |
||||||
|
&& |
||||||
|
(frameCounter != 9) |
||||||
|
&& |
||||||
|
(frameCounter != 19) |
||||||
|
&& |
||||||
|
(frameCounter != 29) |
||||||
|
&& |
||||||
|
(frameCounter != 39) |
||||||
|
&& |
||||||
|
(frameCounter != 49) |
||||||
|
) |
||||||
|
) { |
||||||
|
lossOfSync(frameCounter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void updateDisplay() { |
||||||
|
//Serial.print("updateDisplay()\n");
|
||||||
|
|
||||||
|
if (lossOfSignal) { |
||||||
|
sprintf(statusString, "LOS"); |
||||||
|
} |
||||||
|
|
||||||
|
if (millisSinceBoot - frameStartTime < 10000) { |
||||||
|
sprintf(statusString, "RX(syncing)"); |
||||||
|
} |
||||||
|
if (minuteSync) { |
||||||
|
sprintf(statusString, "RX(bit %d)", frameCounter); |
||||||
|
} |
||||||
|
|
||||||
|
char d[20]; |
||||||
|
|
||||||
|
if (lastBitReceived == MARKBIT) { |
||||||
|
sprintf(d, "%d=MARK", frameCounter); |
||||||
|
} |
||||||
|
if (lastBitReceived == ONEBIT) { |
||||||
|
sprintf(d, "%d=ONE ", frameCounter); |
||||||
|
} |
||||||
|
if (lastBitReceived == ZEROBIT) { |
||||||
|
sprintf(d, "%d=ZERO", frameCounter); |
||||||
|
} |
||||||
|
|
||||||
|
lcd.clear(); |
||||||
|
lcd.setCursor(0, 0); |
||||||
|
char msg[20]; |
||||||
|
sprintf(msg, "up:%03d", secondCounter); |
||||||
|
lcd.print(msg); |
||||||
|
lcd.setCursor(0, 1); |
||||||
|
sprintf(msg, "s:%s", statusString); |
||||||
|
lcd.print(msg); |
||||||
|
|
||||||
|
if (minuteSync) { |
||||||
|
lcd.setCursor(10, 0); |
||||||
|
lcd.print(d); |
||||||
|
} |
||||||
|
|
||||||
|
if(DEBUG) { |
||||||
|
if(secondCounter % 10 == 0) { |
||||||
|
serialDebug(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void fastSerialDebug() { |
||||||
|
char buf[255]; |
||||||
|
sprintf(buf,"stateStableMillis=%d\n",stateStableMillis); |
||||||
|
Serial.print(buf); |
||||||
|
} |
||||||
|
|
||||||
|
void serialDebug() { |
||||||
|
char buf[255]; |
||||||
|
sprintf(buf,"lossOfSignal=%d\n",lossOfSignal); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameReadyToStart=%d\n",frameReadyToStart); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"beginFrameSearch=%d\n",beginFrameSearch); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameSearch=%d\n",frameSearch); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameStart=%d\n",frameStart); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameStartTime=%d\n",frameStartTime); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"millisSinceBoot=%d\n",millisSinceBoot); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameStartAge=%d\n",millisSinceBoot-frameStartTime); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"secondCounter=%d\n",secondCounter); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"timeToTick=%d\n",timeToTick); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameHigh=%d\n",frameHigh); |
||||||
|
Serial.print(buf); |
||||||
|
sprintf(buf,"frameLow=%d\n",frameLow); |
||||||
|
Serial.print(buf); |
||||||
|
} |
Loading…
Reference in new issue