950 lines
31 KiB
C++
950 lines
31 KiB
C++
|
|
#include <Wire.h> //for RTC comms over I2C
|
|
#include <SPI.h> //for LED output to Max7219 module
|
|
#include <SoftwareSerial.h>
|
|
#include <LiquidCrystal_Software_I2C.h>
|
|
|
|
//VERSION 7: NANO PIN ASSIGNMENTS, adds simple count up timer, LED dimmer, shows GPS PPS status.
|
|
// Commit Date: 23-June-2020
|
|
// RTC always has UTC time and date
|
|
|
|
//Nano pins: D10-D13, SPI for Max; A3/D17(SQW), A4(SDA) and A5(SCL) for I2C to DS3231 RTC.
|
|
// D0,D1(serial) D2(PPS) for GPS. D3 for IR receiver interrupt.
|
|
|
|
// 135
|
|
LiquidCrystal_I2C lcd(0x27, 16, 2, 6, 5);
|
|
SoftwareSerial GPS(3, 4);
|
|
|
|
//STATE MACHINE SETUP//
|
|
enum { DEBUG, BOOTUP, REG_OPS, TOGGLE_DISPLAY, COUNTER, CHECK_PPS, GPS_INIT, GPS_PPS_SYNC, GPS_NMEA_SYNC } StateMachine;
|
|
|
|
//PIN DEFS & ADDRESSES//
|
|
const byte RTC_SQW_Pin = 17; //same as A3 pin (using analog pin as digital for RTC SQW)
|
|
const byte GPS_PPS_Pin = 2; // for receiving the GPS PPS signal
|
|
const byte ir_pin = 3; //Interrupt pin for IR receiver data pin.
|
|
const byte ChipSelectPin = 10; // For Max7219. We set the SPI Chip Select/Slave Select Pin here. 10 for uno/nano. 53 for mega
|
|
|
|
const int RTC_I2C_ADDRESS = 0x68; // Must be data type [int] to keep wire.h happy. Sets RTC DS3231 i2C address.
|
|
|
|
|
|
//TIMERS AND EDGE DETECTORS//
|
|
unsigned long GPS_INIT_t0, t0, t1, t2; //for timers
|
|
|
|
byte RTC_SQW_Current = LOW;
|
|
byte RTC_SQW_Prev = LOW;
|
|
byte GPS_PPS_Current = HIGH;
|
|
byte GPS_PPS_Prev = HIGH;
|
|
|
|
//ISR HANDLERS//
|
|
volatile unsigned int pulseChangeTime; //long or int?
|
|
volatile byte pulseFlag = 0;
|
|
|
|
|
|
//UTC offset handlers// //ie, Time Zones and Daylight Savings (summer) Time //
|
|
bool UTC_offset_enable = true; // False for UTC time. True for local time.
|
|
|
|
const char offsetStandardHr = -5;
|
|
const char offsetStandardMin = 0;
|
|
const char offsetDSTHr = -4;
|
|
const char offsetDSTMin = 0;
|
|
|
|
const byte startDST[4] = {2,0,3,2}; // {nth,day of week,month,hh} use [0..6] for [Sunday...Saturday]
|
|
const byte startStandard[4] = {1,0,11,2}; // {nth,day of week,month,hh} [0..6] for [Sunday...Saturday]
|
|
// set n=5 for 5th or last. Assume DST starts/stops at 2am local time
|
|
|
|
const byte days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; // mapping days of the 12 months
|
|
|
|
const int currentCentury = 2000;
|
|
|
|
/* globals that store our offset values*/
|
|
int offYYYY;
|
|
byte offMO;
|
|
byte offDD;
|
|
byte offHH;
|
|
byte offMM;
|
|
byte offSS;
|
|
|
|
//RTC date+time holders//
|
|
byte ssRTC, mmRTC, hhRTC, dowRTC, ddRTC, moRTC, ctyRTC, yyRTC;
|
|
byte countSS, countMM, countHH;
|
|
|
|
//GPS UTC date+time handlers//
|
|
byte hhGPS, mmGPS, ssGPS, ddGPS, moGPS;
|
|
int yyyyGPS = 0;
|
|
|
|
bool newGPS_dateAvail = false;
|
|
bool newGPS_timeAvail = false;
|
|
bool NMEA_processFlag = false;
|
|
bool GGA_msg = false;
|
|
bool RMC_msg = false;
|
|
bool ZDA_msg = false;
|
|
bool msgStart = false;
|
|
|
|
bool GPS_sec_primed = false;
|
|
bool PPS_done = false;
|
|
|
|
byte byteIndex = 0;
|
|
|
|
bool counter_enable = true;
|
|
|
|
/* ----------------------- MAX STUFF ALL HERE ---------------------------vvvvvv
|
|
ICSP BLOCK PINOUT
|
|
5 3 1
|
|
6 4 2
|
|
|
|
===MAPPING SPI HARDWARE AND ARDUINO'S TO MAX7219 MODULES===
|
|
|
|
7219 MODULE: DIN LOAD(CS) CLK (*n/a*)
|
|
SPI: MOSI SS SCK MISO
|
|
UNO/NANO: 11/ICSP-4 10 13/ICSP-3 12/ICSP-1
|
|
MEGA: 51/ICSP-4 53 52/ICSP-3 50/ICSP-1
|
|
*
|
|
[CS(SS) can actually be any unused PIN on the uController, but these
|
|
are the typical conventions.]
|
|
*/
|
|
|
|
|
|
// Max7219 digit bitmaps for digit registers 0x01 to 0x08
|
|
|
|
const byte dp = 0b10000000; // Decimal Point. Do bitwise OR to combine with any digit to add decimal point
|
|
|
|
const byte blank = 0b00000000;
|
|
const byte hyphen = 0b00000001;
|
|
const byte excl = 0b10100000; //exclamation point!
|
|
|
|
// 0b0abcdefg
|
|
const byte A = 0b01110111;
|
|
const byte B = 0b00011111;
|
|
const byte C = 0b01001110;
|
|
const byte D = 0b00111101;
|
|
const byte E = 0b01001111;
|
|
const byte F = 0b01000111;
|
|
const byte G = 0b01111011;
|
|
const byte H = 0b00110111;
|
|
const byte I = 0b00110000;
|
|
const byte J = 0b00111100;
|
|
const byte L = 0b00001110;
|
|
const byte N = 0b00010101;
|
|
const byte O = 0b00011101;
|
|
const byte P = 0b01100111;
|
|
const byte Q = 0b01110011;
|
|
const byte R = 0b00000101;
|
|
const byte S = 0b01011011;
|
|
const byte T = 0b00001111;
|
|
const byte U = 0b00011100;
|
|
const byte M, K, V, W, X
|
|
= 0b00000000;
|
|
const byte Y = 0b00111011;
|
|
const byte Z = 0b01101101;
|
|
|
|
|
|
byte char_library[28] = {blank,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,hyphen};
|
|
|
|
//array of digits 0-9 with corresponding segment bit maps
|
|
byte digit[10] = {0b01111110, //0
|
|
0b00110000, //1
|
|
0b01101101, //2
|
|
0b01111001, //3
|
|
0b00110011, //4
|
|
0b01011011, //5
|
|
0b01011111, //6
|
|
0b01110000, //7
|
|
0b01111111, //8
|
|
0b01110011}; //9
|
|
|
|
// 0b0abcdefg
|
|
// -- a
|
|
// f | | b
|
|
// g --
|
|
// e | | c
|
|
// d --
|
|
//0b0abcdefg
|
|
|
|
// Max7219 Address Registers
|
|
const byte reg_nonop = 0x00; // generally not used
|
|
const byte reg_d1 = 0x01; // "Digit0" in the datasheet
|
|
const byte reg_d2 = 0x02; // "Digit1" in the datasheet
|
|
const byte reg_d3 = 0x03; // "Digit2" in the datasheet
|
|
const byte reg_d4 = 0x04; // "Digit3" in the datasheet
|
|
const byte reg_d5 = 0x05; // "Digit4" in the datasheet
|
|
const byte reg_d6 = 0x06; // "Digit5" in the datasheet
|
|
const byte reg_d7 = 0x07; // "Digit6" in the datasheet
|
|
const byte reg_d8 = 0x08; // "Digit7" in the datasheet
|
|
const byte reg_decode = 0x09; // 0x00 no decode, to 0xFF decode all; use a bit map to toggle, ie 0b00000010
|
|
const byte reg_intensity = 0x0A; // min 0x00, max 0x0F... 16 duty-cycle options. 0x07 middle.
|
|
const byte reg_scanlimit = 0x0B; // from 0x00 to 0x07 (sets number of digits being scanned)
|
|
const byte reg_shutdown = 0x0C; // 0x00 shutdown mode, 0x01 normal ops
|
|
const byte reg_displaytest = 0X0F; // 0x00 normal ops, 0x01 display test mode
|
|
|
|
void SPIwrite (byte reg_address, byte regdata) {
|
|
//Writes 2 bytes to SPI. This is optimized for Max7219 Comms.
|
|
|
|
digitalWrite(ChipSelectPin,LOW); // take the CS/SS pin low to select the chip
|
|
SPI.transfer(reg_address);
|
|
SPI.transfer(regdata);
|
|
digitalWrite(ChipSelectPin,HIGH); // take the CS/SS pin high to deselect the chip
|
|
|
|
} //end of SPIwrite
|
|
|
|
void setAllDigitsTo (byte set_digit) {
|
|
//this sets all the digits to set_digit
|
|
|
|
for (byte i=1;i<=8;i++) {
|
|
SPIwrite(i,set_digit);
|
|
} //end for
|
|
|
|
} //end of setAllDigitsTo()
|
|
|
|
|
|
void initializeMax7219() {
|
|
|
|
//reset the Max by activating shutdown mode
|
|
SPIwrite(reg_shutdown,0x00); // 0x00 shutdown, 0x01 normal ops
|
|
|
|
//initialize each digit with known values
|
|
setAllDigitsTo(hyphen);
|
|
|
|
//set intensity
|
|
SPIwrite(reg_intensity, 0x07); // min 0x00, max 0x0F... 16 duty-cycle options. 0x07 middle.
|
|
|
|
//set scan limit
|
|
SPIwrite(reg_scanlimit, 0x07); // from 0x00 to 0x07 (sets number of digits being scanned)
|
|
|
|
//set decode
|
|
SPIwrite(reg_decode, 0b00000000); // built-in decode, from 0x00 [all off] to 0xFF [all on] (bit map toggles digits 1-8).
|
|
|
|
//flash a display test
|
|
SPIwrite(reg_displaytest, 0x01); // 0x00 normal ops, 0x01 display test mode
|
|
delay(100);
|
|
|
|
//end the test
|
|
SPIwrite(reg_displaytest, 0x00); // 0x00 normal ops, 0x01 display test mode
|
|
delay(100);
|
|
|
|
//exit shutdown mode. resume normal ops
|
|
SPIwrite(reg_shutdown,0x01); // 0x00 shutdown, 0x01 normal ops
|
|
delay (100);
|
|
|
|
} //end of initializeMax7219
|
|
|
|
void maxDisplay(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h) {
|
|
SPIwrite(8,a);
|
|
SPIwrite(7,b);
|
|
SPIwrite(6,c);
|
|
SPIwrite(5,d);
|
|
SPIwrite(4,e);
|
|
SPIwrite(3,f);
|
|
SPIwrite(2,g);
|
|
SPIwrite(1,h);
|
|
}
|
|
|
|
|
|
//**** ISR ROUTINE ****//
|
|
|
|
void ISR_pulse_detected() {
|
|
pulseChangeTime = micros();
|
|
pulseFlag = 1;
|
|
} //end of ISR_pulse_detected()
|
|
|
|
//**** UNIVERSAL FUNCTIONS ****//
|
|
|
|
byte bcd2dec(byte n) {
|
|
// Converts binary coded decimal to normal decimal numbers
|
|
return ((n/16 * 10) + (n % 16));
|
|
} //end of bcd2dec()
|
|
|
|
byte dec2bcd(byte n) { //n must be in range [0..99] incl
|
|
// Converts normal decimal to binary coded decimal. Speed optimized.
|
|
// return ((n / 10 * 16) + (n % 10)); <----- slower method.
|
|
// see https://forum.arduino.cc/index.php?topic=185235.msg1372439#msg1372439
|
|
|
|
uint16_t a = n;
|
|
byte b = (a*103) >> 10;
|
|
return n + b*6;
|
|
|
|
} //end of dec2bcd()
|
|
|
|
|
|
void clearGPSInputBuffer() {
|
|
while (GPS.available() > 0) {
|
|
GPS.read();
|
|
}//end while
|
|
} //end of clearGPSInputBuffer()
|
|
|
|
|
|
//**** DISPLAY HANDLERS **** //
|
|
|
|
void displayRTC_timeOnMax(byte rtc_h, byte rtc_m, byte rtc_s) { //receiving [0-99] decimals from caller
|
|
lcd.setCursor(0,0); // Установка курсора в начало первой строки
|
|
if(rtc_h < 10)
|
|
lcd.print("0");
|
|
lcd.print(rtc_h);
|
|
lcd.print(":");
|
|
if(rtc_m < 10)
|
|
lcd.print("0");
|
|
lcd.print(rtc_m);
|
|
lcd.print(":");
|
|
if(rtc_s < 10)
|
|
lcd.print("0");
|
|
lcd.print(rtc_s);
|
|
//Serial.print(rtc_h); Serial.print(":"); Serial.print(rtc_m); Serial.print(":"); Serial.println(rtc_s);
|
|
//maxDisplay(digit[(rtc_h/10)%10],digit[rtc_h%10],hyphen,
|
|
// digit[(rtc_m/10)%10],digit[rtc_m%10],hyphen,
|
|
// digit[(rtc_s/10)%10],digit[rtc_s%10]);
|
|
|
|
} //end of displayRTC_timeOnMax()
|
|
|
|
void serialRTC_timeOnMax(byte rtc_h, byte rtc_m, byte rtc_s) { //receiving [0-99] decimals from caller
|
|
if(rtc_h < 10)
|
|
Serial.print("0");
|
|
Serial.print(rtc_h);
|
|
Serial.print(":");
|
|
if(rtc_m < 10)
|
|
Serial.print("0");
|
|
Serial.print(rtc_m);
|
|
Serial.print(":");
|
|
if(rtc_s < 10)
|
|
Serial.print("0");
|
|
Serial.println(rtc_s);
|
|
} //end of serialRTC_timeOnMax()
|
|
|
|
//**** GPS HANDLERS **** //
|
|
|
|
bool PPS_detect() { // top of the second for ublox 6 GPS (default setting) is rising edge of PPS time pulse
|
|
GPS_PPS_Prev = GPS_PPS_Current;
|
|
GPS_PPS_Current = digitalRead(GPS_PPS_Pin);
|
|
return (GPS_PPS_Prev == LOW && GPS_PPS_Current == HIGH); //returns true if PPS has gone high!
|
|
} // end of PPS_detect()
|
|
|
|
void processNMEA() {
|
|
byte inByte;
|
|
if (GPS.available() > 0) { //do all of this only if byte ready to read
|
|
inByte = GPS.read();
|
|
byteIndex++; // we only increment index if we read a byte
|
|
|
|
switch (byteIndex) {
|
|
case 1 ... 3:
|
|
break;
|
|
case 4:
|
|
GGA_msg = (inByte == 'G');
|
|
RMC_msg = (inByte == 'R');
|
|
ZDA_msg = (inByte == 'Z');
|
|
break;
|
|
case 5:
|
|
GGA_msg = (GGA_msg && inByte == 'G');
|
|
RMC_msg = (RMC_msg && inByte == 'M');
|
|
ZDA_msg = (ZDA_msg && inByte == 'D');
|
|
break;
|
|
case 6:
|
|
GGA_msg = (GGA_msg && inByte == 'A');
|
|
RMC_msg = (RMC_msg && inByte == 'C');
|
|
ZDA_msg = (ZDA_msg && inByte == 'A');
|
|
NMEA_processFlag = (GGA_msg || RMC_msg || ZDA_msg); // if we have any of these, we keep processing
|
|
break;
|
|
case 7:
|
|
break;
|
|
case 8: // hour tens
|
|
hhGPS = (inByte - '0')*10;
|
|
break;
|
|
case 9: //hour units
|
|
hhGPS += (inByte - '0');
|
|
break;
|
|
case 10: // min tens
|
|
mmGPS = (inByte - '0')*10;
|
|
break;
|
|
case 11: //min units
|
|
mmGPS += (inByte - '0');
|
|
break;
|
|
case 12: // sec tens
|
|
ssGPS = (inByte - '0')*10;
|
|
break;
|
|
case 13: //sec units
|
|
ssGPS += (inByte - '0');
|
|
//**AT THIS POINT WE HAVE GPS TIME**// CAN ACTUALLY UPDATE RTC!
|
|
newGPS_timeAvail = true;
|
|
NMEA_processFlag = ZDA_msg; //if we have ZDA, this is true and we keep processing.
|
|
break;
|
|
case 14 ... 17: //** WILL NEED TO TUNE THIS TO ACTUAL OUTPUT OF NEO 6M
|
|
break;
|
|
case 18: //day tens
|
|
ddGPS = (inByte - '0')*10;
|
|
break;
|
|
case 19: //day units
|
|
ddGPS += (inByte - '0');
|
|
break;
|
|
case 20:
|
|
break;
|
|
case 21: //mo tens
|
|
moGPS = (inByte - '0')*10;
|
|
break;
|
|
case 22: //mo units
|
|
moGPS += (inByte - '0');
|
|
break;
|
|
case 23:
|
|
break;
|
|
case 24:
|
|
yyyyGPS = (inByte - '0')*1000;
|
|
break;
|
|
case 25:
|
|
yyyyGPS += (inByte - '0')*100;
|
|
break;
|
|
case 26:
|
|
yyyyGPS += (inByte - '0')*10;
|
|
break;
|
|
case 27:
|
|
yyyyGPS += (inByte - '0');
|
|
newGPS_dateAvail = true; //all date fields now read
|
|
NMEA_processFlag = false; //and no need to process further
|
|
break;
|
|
default:
|
|
NMEA_processFlag = false;
|
|
|
|
} //end switch byteIndex
|
|
} //end if GPS available
|
|
} //end of processNMEA
|
|
|
|
/* // 123456789012345678901234567||| [27] GPS unit gives 2 decimals on time
|
|
// $GPGGA,hhmmss.tt,...
|
|
// /* $GPGGA, $GPRMC: $GPRMC,hhmmss.tt,...
|
|
// $GPZDA: $GPZDA,hhmmss.tt,dd,mm,yyyy,...
|
|
*/
|
|
|
|
//**** UTC TIMEZONE OFFSET AND DST HANDLERS ****//
|
|
|
|
void offsetAdj(int y, byte mo, byte d, byte h, byte m, char offsetHr, char offsetMin) {
|
|
offMM = (m + offsetMin + 120) % 60;
|
|
if (m + offsetMin > 59) {
|
|
offsetHr ++;
|
|
}
|
|
if (m + offsetMin < 0) {
|
|
offsetHr --;
|
|
}
|
|
offHH = (h + offsetHr + 24) % 24;
|
|
offDD = d;
|
|
offYYYY = y;
|
|
offMO = mo;
|
|
if (offsetHr + h < 0) {
|
|
//Do a decrement
|
|
if (d > 1) {
|
|
offDD--;
|
|
}
|
|
else { //rollover
|
|
offDD = days[((mo+11-1)%12+1)] + (mo==2 && isLeap(y));
|
|
offMO--;
|
|
if (mo == 1) {
|
|
offMO = 12;
|
|
offYYYY = y - 1;
|
|
}//end if
|
|
}
|
|
} //end if for decrement day
|
|
if (offsetHr + h >= 24) {
|
|
// Do an increment
|
|
if (d == (days[mo] + (mo == 2 && isLeap(y)))) { //ie, if d is last day of month
|
|
offDD = 1;
|
|
offMO++;
|
|
if (mo == 12) {
|
|
offMO = 1;
|
|
offYYYY = y + 1;
|
|
}
|
|
}//end if
|
|
else {
|
|
offDD = d+1;
|
|
}
|
|
} //end if increment day
|
|
} //end offsetAdj
|
|
|
|
bool isLeap (byte y) {
|
|
return ((y%4==0) || (!(y%100==0) && (y%400==0)));
|
|
} //end isLeap()
|
|
|
|
byte dowDate(int y, byte n, byte dow_target, byte m) { // returns date number in month of nth day of week in month
|
|
char temp = 1; // char because could be negative. Set at 1 for 1st of month.
|
|
byte maxDays = days[m]; // number of days in given month
|
|
if (m==2) {
|
|
maxDays = days[m] + isLeap(y);
|
|
}
|
|
byte startDOW = dow(y,m,1); //gets dow of 1st of given month
|
|
temp += dow_target - dow(y,m,1);
|
|
if (temp < 0) {
|
|
temp += 7;
|
|
} //end if
|
|
temp += 7*(n-1);
|
|
if (temp > maxDays) {
|
|
temp -= 7;
|
|
}
|
|
return temp;
|
|
} //end dowDate()
|
|
|
|
byte dow(int y, byte m, byte d) { //pass non-leading zero values
|
|
static byte t[] = {0,3,2,5,0,3,5,1,4,6,2,4};
|
|
y -= m < 3;
|
|
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
|
|
// returns [0..6] for [Sunday...Saturday]
|
|
} //end of dow()
|
|
|
|
void getLocalTime(int y, byte mo, byte d, byte h, byte m) {
|
|
// **this function sets offMO, offDD, offYYYY, offHH, offMM to local time**
|
|
// we first get a baseline offset
|
|
// NOTE: int y is being passed as RTC's 2-digit date.
|
|
|
|
if (yyyyGPS == 0) { // Converting RTC 2 digit date to 4 digit:
|
|
y += currentCentury; // takes our 2-digit year and converts to 4 digit
|
|
}
|
|
else { // or if GPS signal available, we just use that
|
|
y = yyyyGPS; // uses GPS 4-digit year if available
|
|
}
|
|
|
|
offsetAdj(y,mo,d,h,m,offsetStandardHr,offsetStandardMin);
|
|
|
|
// next we do our DST checks, and recalc offset if DST is in effect
|
|
|
|
if (offMO > startDST[2] && offMO < startStandard[2]) {
|
|
offsetAdj(y,mo,d,h,m,offsetDSTHr,offsetDSTMin);
|
|
} //end if
|
|
else if (offMO == startDST[2]) {
|
|
byte dowDateDST = dowDate(y,startDST[0],startDST[1],startDST[2]);
|
|
if (offDD > dowDateDST) {
|
|
offsetAdj(y,mo,d,h,m,offsetDSTHr,offsetDSTMin);
|
|
} //end if
|
|
else if (offDD == dowDateDST && offHH >= startDST[3]) {
|
|
offsetAdj(y,mo,d,h,m,offsetDSTHr,offsetDSTMin);
|
|
} //end else if
|
|
} //end else if
|
|
else if (offMO == startStandard[2]) {
|
|
byte dowDateStd = dowDate(y,startStandard[0],startStandard[1],startStandard[2]);
|
|
if (offDD < dowDateStd) {
|
|
offsetAdj(y,mo,d,h,m,offsetDSTHr,offsetDSTMin);
|
|
}// end if
|
|
else if (offDD == dowDateStd && offHH < (startStandard[3]-1)) {
|
|
offsetAdj(y,mo,d,h,m,offsetDSTHr,offsetDSTMin);
|
|
}// end else if
|
|
} //end else if
|
|
} //end getLocalTime()
|
|
|
|
|
|
//**** RTC HANDLERS ****//
|
|
|
|
void getRTC() { //reads all current time/date data from DS3231 chip via I2C
|
|
// ssRTC, mmRTC, hhRTC, dowRTC, ddRTC, moRTC, ctyRTC, yyRTC;
|
|
|
|
byte temp_buffer;
|
|
|
|
Wire.beginTransmission(RTC_I2C_ADDRESS);
|
|
Wire.write(0x00); //set register points to address 00h on DS3231
|
|
Wire.endTransmission();
|
|
Wire.requestFrom(RTC_I2C_ADDRESS, 7); // need 7 reads to clear this.
|
|
ssRTC = bcd2dec(Wire.read()); // read reg 0 [range 00-59]
|
|
mmRTC = bcd2dec(Wire.read()); // read reg 1 [range 00-59]
|
|
hhRTC = bcd2dec(Wire.read() & 0b00111111); // read reg 2 and mask out BITS 7-8
|
|
dowRTC = bcd2dec(Wire.read()); // read reg 3 [range 1-7]
|
|
ddRTC = bcd2dec(Wire.read()); // read reg 4 [range 01-31]
|
|
temp_buffer = bcd2dec(Wire.read()); // read reg 5
|
|
moRTC = bcd2dec(temp_buffer & 0b00011111);
|
|
ctyRTC = temp_buffer >> 7;
|
|
yyRTC = bcd2dec(Wire.read()); //read reg 6 [range 00-99]
|
|
|
|
} //end of getRTC()
|
|
|
|
bool RTC_detect() { //Detects DS3231 SQW falling edge
|
|
RTC_SQW_Prev = RTC_SQW_Current;
|
|
RTC_SQW_Current = digitalRead(RTC_SQW_Pin);
|
|
return (RTC_SQW_Prev == HIGH && RTC_SQW_Current == LOW);
|
|
} //end of detect_RTC
|
|
|
|
void displayRTCDate() { // adjusts to local date if flag set
|
|
getRTC(); // updates ssRTC, mmRTC, hhRTC, dowRTC, ddRTC, moRTC, ctyRTC, yyRTC;
|
|
if (UTC_offset_enable == false) { // if flag false, we do UTC time and date
|
|
displayRTC_timeOnMax(ddRTC,moRTC,yyRTC); // displays UTC date
|
|
} //end if
|
|
else { // else requests local offset time
|
|
getLocalTime(yyRTC,moRTC,ddRTC,hhRTC,mmRTC); // updates offDD,offMO,offYYYY
|
|
displayRTC_timeOnMax(offDD,offMO,offYYYY % 100);
|
|
}
|
|
} //end displayRTCDate
|
|
|
|
|
|
void countUp() {
|
|
|
|
RTC_SQW_Prev = RTC_SQW_Current;
|
|
RTC_SQW_Current = digitalRead(RTC_SQW_Pin);
|
|
if (counter_enable && RTC_SQW_Prev == HIGH && RTC_SQW_Current == LOW) { //tests for falling edge
|
|
|
|
countSS++;
|
|
|
|
if (countSS == 60) {
|
|
countSS = 0;
|
|
countMM++;
|
|
} //end if
|
|
if (countMM == 60) {
|
|
countMM = 0;
|
|
countHH++;
|
|
} //end if
|
|
if (countHH == 100) {
|
|
countHH = 0;
|
|
}
|
|
|
|
displayRTC_timeOnMax(countHH,countMM,countSS);
|
|
|
|
} //end if
|
|
} //end of countUp
|
|
|
|
void displayRTC() { //updates display if new RTC time. Detects DS3231 SQW falling edge then trigger display of time update
|
|
|
|
RTC_SQW_Prev = RTC_SQW_Current;
|
|
RTC_SQW_Current = digitalRead(RTC_SQW_Pin);
|
|
|
|
if (RTC_SQW_Prev == HIGH && RTC_SQW_Current == LOW) { //test for falling edge
|
|
//Serial.print("RTC_SQW_Prev vs RTC_SQW_Current:"); Serial.print(RTC_SQW_Prev); Serial.print("-"); Serial.println(RTC_SQW_Current);
|
|
//lcd.setCursor(0, 1);
|
|
//lcd.print("SQW: ");
|
|
//lcd.print(RTC_SQW_Prev);
|
|
//lcd.print(" => ");
|
|
//lcd.print(RTC_SQW_Current);
|
|
getRTC(); // updates ssRTC, mmRTC, hhRTC, dowRTC, ddRTC, moRTC, ctyRTC, yyRTC;
|
|
|
|
if (UTC_offset_enable == false) { // flag requests UTC time
|
|
displayRTC_timeOnMax(hhRTC,mmRTC,ssRTC);
|
|
} //end if
|
|
else { // flag requests local offset time
|
|
getLocalTime(yyRTC,moRTC,ddRTC,hhRTC,mmRTC);
|
|
displayRTC_timeOnMax(offHH,offMM,ssRTC);
|
|
} //end else
|
|
} //end if
|
|
} // end of displayRTC()
|
|
|
|
void displayRTC_now() { //immediate retrieve and display of RTC time registers
|
|
getRTC(); // updates ssRTC, mmRTC, hhRTC, dowRTC, ddRTC, moRTC, ctyRTC, yyRTC;
|
|
displayRTC_timeOnMax(hhRTC,mmRTC,ssRTC);
|
|
} // end of displayRTC()
|
|
|
|
void sendRTC(byte reg_addr, byte byte_data) {
|
|
Wire.beginTransmission(RTC_I2C_ADDRESS);
|
|
Wire.write(reg_addr); //set register pointer to address on DS3231
|
|
Wire.write(byte_data);
|
|
Wire.endTransmission();
|
|
} //end of sendRTC()
|
|
|
|
byte getSingleRTC(byte reg_addr) { //returns a single raw byte from the provided address register
|
|
Wire.beginTransmission(RTC_I2C_ADDRESS);
|
|
Wire.write(reg_addr); //set to reg address on DS3231
|
|
Wire.endTransmission();
|
|
Wire.requestFrom(RTC_I2C_ADDRESS, 1);
|
|
return (Wire.read());
|
|
} //end of getRTC_BCD()
|
|
|
|
void setRTC_Time(byte hh, byte mm, byte ss) { //must be [0-99]
|
|
// example use: setRTC_Time(23,49,50); //hh,mm,ss
|
|
|
|
Wire.beginTransmission(RTC_I2C_ADDRESS);
|
|
Wire.write(0x00); //set register pointer to address on DS3231
|
|
Wire.write(dec2bcd(ss)); //set seconds
|
|
Wire.write(dec2bcd(mm)); //set minutes
|
|
Wire.write(dec2bcd(hh)); //set hours. Bit 6 low keeps at 24hr mode. So can leave as is.
|
|
Wire.endTransmission();
|
|
} //end of setRTC_Time()
|
|
|
|
void setRTC_Date(int yyyy, byte mo, byte dd) {
|
|
Wire.beginTransmission(RTC_I2C_ADDRESS);
|
|
Wire.write(0x04); //set register pointer to address on DS3231
|
|
Wire.write(dec2bcd(dd)); //set date. sending the last 2 digits only.
|
|
Wire.write(dec2bcd(mo)); //set month. ignore century as don't have any use for that.
|
|
Wire.write(dec2bcd(yyyy % 100)); //set year. sending the 2 LS digits only.
|
|
Wire.endTransmission();
|
|
} //end of setRTC_Time()
|
|
|
|
//**** STATE MACHINE **** //
|
|
|
|
void RunStateMachine() {
|
|
byte temp_buffer;
|
|
//135Serial.println("R-SM");
|
|
|
|
switch (StateMachine) {
|
|
|
|
case DEBUG: //remember, it's looping!
|
|
Serial.println("DEBUG");
|
|
|
|
if (RTC_detect()) {
|
|
t1=micros();
|
|
} else {
|
|
Serial.println("No RTC detected.");
|
|
}
|
|
if (PPS_detect()) {
|
|
t2=micros();
|
|
} else {
|
|
Serial.println("No PPS detected.");
|
|
}
|
|
break; //end DEBUG case
|
|
|
|
|
|
// ------------------------------
|
|
|
|
case BOOTUP:
|
|
Serial.println("BOOTUP");
|
|
sendRTC(0x0E,0x00); // enables the 1Hz pulse on RTC DS3231's SQW pin
|
|
delay(50);
|
|
temp_buffer = getSingleRTC(0x02); //get hour byte from addr 0x02. Bit 6: LOW (0) = 24 hr mode. HIGH (1) = 12 hr.
|
|
if ((temp_buffer & 0b01000000) != 0) { //if Bit 6 is HIGH, i.e., if 24 hour time is *not* set, then...
|
|
getRTC(); // grab time
|
|
if (mmRTC > 58 && ssRTC > 58) { // checking to make sure not near an hours rollover.
|
|
break; //keep breaking until we roll over the seconds
|
|
} //end if
|
|
else {
|
|
sendRTC(0x02,temp_buffer ^ 0b01000000); //set BIT 6 low to enable 24 hr time
|
|
} //end else
|
|
} //end if
|
|
//StateMachine = DEBUG; // >>>> State Change! <<<<//
|
|
StateMachine = REG_OPS; // >>>> State Change! <<<<//
|
|
t0=micros();
|
|
break;
|
|
//end BOOTUP case
|
|
|
|
// ------------------------------
|
|
|
|
case REG_OPS:
|
|
//135Serial.println("REG_OPS");
|
|
|
|
displayRTC(); //keep the trains running
|
|
|
|
//if (mmRTC == 40 && ssRTC == 0) { //every hour at minute 15, do a GPS_INIT check to prep for GPS-to-RTC time update
|
|
if (mmRTC != 0 && ssRTC == 0) {
|
|
GPS_INIT_t0 = millis(); //sets our timer to allow a timeout in the next state
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">GPS_INIT");
|
|
StateMachine = GPS_INIT; // >>>> State Change! <<<<//
|
|
break;
|
|
} //end if
|
|
//if (pulseFlag == 1) {
|
|
// detachInterrupt(digitalPinToInterrupt(ir_pin)); //stop interrupt while we process
|
|
//135SonyIR_analyzer();
|
|
// attachInterrupt(digitalPinToInterrupt(ir_pin), ISR_pulse_detected, CHANGE);
|
|
//} // end if
|
|
break;
|
|
//end REG_OPS case
|
|
|
|
// ------------------------------
|
|
|
|
case TOGGLE_DISPLAY: //holds the display before resuming regular ops
|
|
Serial.println("TOGGLE_DISPLAY");
|
|
if (millis() - t1 < 3500) {
|
|
break;
|
|
}
|
|
else {
|
|
StateMachine = REG_OPS;
|
|
break;
|
|
}
|
|
|
|
//end TOGGLE_DISPLAY case
|
|
|
|
// ------------------------------
|
|
|
|
case COUNTER: //runs the counter
|
|
Serial.println("COUNTER");
|
|
countUp();
|
|
if (pulseFlag == 1) {
|
|
detachInterrupt(digitalPinToInterrupt(ir_pin)); //stop interrupt while we process
|
|
//135SonyIR_analyzer();
|
|
attachInterrupt(digitalPinToInterrupt(ir_pin), ISR_pulse_detected, CHANGE);
|
|
} // end if
|
|
break;
|
|
|
|
// ------------------------------
|
|
|
|
case CHECK_PPS: //displays whether a PPS pulse is coming from GPS
|
|
Serial.println("CHECK_PPS");
|
|
if (millis() - t1 < 4000) {
|
|
if (PPS_detect()) {
|
|
maxDisplay(P,P,S,blank,O,N,blank,blank);
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
StateMachine = REG_OPS;
|
|
break;
|
|
}
|
|
|
|
//end TOGGLE_DISPLAY case
|
|
|
|
// ------------------------------
|
|
|
|
case GPS_INIT:
|
|
Serial.println("GPS_INIT");
|
|
displayRTC(); // keeps the trains running on the display!
|
|
|
|
if (PPS_detect()) { //means PPS is detected on the GPS unit
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">GPS_PPS_SYNC");
|
|
StateMachine = GPS_PPS_SYNC; // >>>> State Change! <<<<//
|
|
clearGPSInputBuffer(); //purges old GPS data in GPS UART buffer
|
|
break;
|
|
} //end if
|
|
|
|
if (millis() - GPS_INIT_t0 > 4000) { //timeout this state after 4 secs if no PPS detected
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">GPS_NMEA_SYNC");
|
|
StateMachine = GPS_NMEA_SYNC; // >>>> State Change! <<<<//
|
|
clearGPSInputBuffer(); //purges old GPS data in GPS UART buffer
|
|
break;
|
|
} //end if
|
|
|
|
break;
|
|
//end GPS_INIT case
|
|
|
|
// ------------------------------
|
|
case GPS_PPS_SYNC:
|
|
byte ssGPS_incr;
|
|
|
|
displayRTC(); //keep the trains running while reading serial input!
|
|
|
|
if (newGPS_timeAvail && !GPS_sec_primed) { //here we prime the GPS seconds by incrementing 1s as we wait for a PPS signal
|
|
ssGPS_incr = (ssGPS + 1) % 60;
|
|
GPS_sec_primed = true;
|
|
newGPS_timeAvail = false; // set this false to force another read post PPS.
|
|
} //end if
|
|
|
|
if (PPS_detect() && GPS_sec_primed) { // if PPS detected send only the primed second to RTC
|
|
sendRTC(0x00,dec2bcd(ssGPS_incr)); // here is where we would add delay for insanity mode
|
|
PPS_done = true;
|
|
} //end if
|
|
|
|
if (PPS_done && GPS_sec_primed && newGPS_timeAvail) { // send minutes and hours of next NMEA time read after PPS
|
|
sendRTC(0x01,dec2bcd(mmGPS));
|
|
sendRTC(0x02,dec2bcd(hhGPS));
|
|
GPS_sec_primed = false;
|
|
newGPS_timeAvail = false;
|
|
} //end if
|
|
|
|
if (newGPS_dateAvail && PPS_done) {
|
|
setRTC_Date(yyyyGPS,moGPS,ddGPS);
|
|
newGPS_dateAvail = false; //this completed the date and time update.
|
|
PPS_done = false;
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">REG_OPS");
|
|
StateMachine = REG_OPS; // >>>> State Change! <<<<//
|
|
break;
|
|
} //end if
|
|
|
|
//need a timeout where after 30 seconds of no updates it goes back to REG_OPS
|
|
|
|
if (NMEA_processFlag) {
|
|
processNMEA();
|
|
} //end if
|
|
else {
|
|
if (GPS.available() > 0) {
|
|
if (GPS.read() == '$') { //start of NMEA message
|
|
NMEA_processFlag = true; //we only want to start processing at beginning of message
|
|
GGA_msg = false;
|
|
RMC_msg = false;
|
|
ZDA_msg = false;
|
|
byteIndex = 1;
|
|
} //end if
|
|
} //end if
|
|
} //end else
|
|
|
|
if (millis() - GPS_INIT_t0 > 14000) { //timeout this state after 10 secs if no PPS detected
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">REG_OPS");
|
|
StateMachine = REG_OPS; // >>>> State Change! <<<<//
|
|
}
|
|
|
|
break;
|
|
|
|
//end GPS_PPS_SYNC case
|
|
|
|
// ------------------------------
|
|
|
|
case GPS_NMEA_SYNC:
|
|
displayRTC(); //keep the trains running while reading serial input!
|
|
|
|
if (newGPS_timeAvail) {
|
|
setRTC_Time(hhGPS,mmGPS,ssGPS);
|
|
newGPS_timeAvail = false;
|
|
} //end if
|
|
|
|
if (newGPS_dateAvail) {
|
|
setRTC_Date(yyyyGPS,moGPS,ddGPS);
|
|
newGPS_dateAvail = false; //this completed the date and time update.
|
|
StateMachine = REG_OPS; // >>>> State Change! <<<<//
|
|
break;
|
|
} //end if
|
|
|
|
if (NMEA_processFlag) {
|
|
processNMEA();
|
|
} //end if
|
|
else {
|
|
if (GPS.available() > 0) {
|
|
if (GPS.read() == '$') { //start of NMEA message
|
|
NMEA_processFlag = true;
|
|
GGA_msg = false;
|
|
RMC_msg = false;
|
|
ZDA_msg = false;
|
|
byteIndex = 1;
|
|
} //end if
|
|
} //end if
|
|
} //end else
|
|
|
|
if (millis() - GPS_INIT_t0 > 14000) { //timeout this state after 10 secs if no PPS detected
|
|
getRTC();
|
|
serialRTC_timeOnMax(ddRTC,moRTC,yyRTC);
|
|
Serial.println(">REG_OPS");
|
|
StateMachine = REG_OPS; // >>>> State Change! <<<<//
|
|
}
|
|
|
|
break;
|
|
//end GPS_NMEA_SYNC case
|
|
|
|
} //end switch StateMachine
|
|
} //end of RunStateMachine()
|
|
|
|
|
|
void setup() {
|
|
// put your setup code here, to run once:
|
|
// 135
|
|
lcd.init(); // Инициализация дисплея
|
|
lcd.clear();
|
|
lcd.backlight();
|
|
|
|
delay(1000); //give system time to stabilize
|
|
|
|
//start up SPI for Max7219
|
|
//135SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0)); //per Max7219 Datasheet, 10MHz, MSB
|
|
//pinMode(ChipSelectPin, OUTPUT); //sets the output pin for SS/CS
|
|
//digitalWrite(ChipSelectPin,HIGH); // take the CS/SS pin high to deselect the chip
|
|
//SPI.begin(); //initialize the SPI bus using our settings from above
|
|
|
|
//initializeMax7219(); //sets all startup parameters for Max7219 chip
|
|
|
|
delay(100);
|
|
|
|
pinMode(RTC_SQW_Pin, INPUT_PULLUP); // SQW is open drain on DS3231, so need internal pullup enabled.
|
|
pinMode(GPS_PPS_Pin, INPUT);
|
|
pinMode(ir_pin, INPUT_PULLUP);
|
|
|
|
Wire.setClock(400000); //i2C 100kHz typical. 400kHz fast mode. DS3231 RTC supports fast mode.
|
|
delay(100); //more stabilizing
|
|
|
|
Serial.begin(9600); // for the GPS unit. Later set to Serial when debugging done.
|
|
GPS.begin(9600);
|
|
delay(100); //more stabilizing
|
|
Serial.println("setup().");
|
|
|
|
attachInterrupt (digitalPinToInterrupt(ir_pin), ISR_pulse_detected, CHANGE);
|
|
|
|
//clearGPSInputBuffer();
|
|
|
|
|
|
StateMachine = BOOTUP; //set the initial state
|
|
|
|
}
|
|
|
|
void loop() {
|
|
// put your main code here, to run repeatedly:
|
|
|
|
RunStateMachine();
|
|
|
|
}
|