#include <TimeLib.h>
#include <SPI.h>
#include <DS3232RTC.h> //http://github.com/JChristensen/DS3232RTC
#include <MsTimer2.h>
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <Bounce.h>
#include <OneWire.h>
#include <EEPROM.h>
#define defaultTimezone 3; // Сюда установить часовую зону по умолчанию.
// Часовую зону всегда можно оперативно откорректировать. Для вызова меню установки часовой зоны,
// часы надо включить при одновременно зажатых кнопках перевода Часов и Минут.
// Для выхода из режима установки можно просто выключить часы и включить их снова в штатном режие.
// Одновременное нажатие кнопок часов и минут также приводит к выходу из режима установки часовой зоны
float LATITUDE = 55.5, LONGITUDE = 37.5; // первоначальная позиция для Москвы, район аэропорта Остафьево.
int MaxTimeToSync=500; // Синхронизация каждые 500 секунд
#define MaxScreenTimer 114 // С какой полусекунды каждой минуты будет отображаться температура. Значение 114 -> с 57-й секунды до конца минуты.
// Подключение часов реальноговремени
// DS3231 -> Arduino
// D (SDA) -> A4
// C (SCL) -> A5
#define HourPin 4 // Это контакт кнопки перевода часов
#define MinutePin 8 // Это контакт кнопки перевода минут
//
// |----/.---- pin 4
// |
// +----/.---- pin 8
// |
// Gnd
Bounce HourButt = Bounce(HourPin,40 );
Bounce MinuteButt = Bounce(MinutePin,40 );
#define DotPin 5 // К этому пину подключены светодиоды в виде двоеточия между часами и минутами
byte DotNightBright = 9; // Это значение яркости зажжённого двоеточия в ночном режиме.
//
// pin 5 o------+-----+
// |,, |,,
// V V
// - -
// | |
// | | | |
// |R| |R| Значения R подбираются в зависимости от яркости светодиодов, от 100 ом до 6кОм.
// | | | |
// | |
// | |
// Gnd o----------+-----+
enum { REG_SELECT = 10 }; // пин, управляющий защёлкой (SS в терминах SPI)
// 0
// =======
// || ||
// 5 || || 1
// || 6 ||
// =======
// || ||
// 4 || || 2
// || 3 ||
// ======= o 7
//
// Module Arduino
// Vcc o----------- +5v
// Gnd o----------- Gnd
// Clk o----------- pin 13 SCK (52)
// Stb o----------- pin 10 SS (53)
// S_in o----------- pin 11 MOSI (51)
//
#define BrightnessPin 3
// 10k
// --| R |---*---------------*-- + 7.5 V
// | | | _______ Узел изменения яркости дисплея.
// | |----- S | | \ o \ Питание семисегментных матриц подаётся
// o---*----||--^ IRF9Z14 |=| 740 \ ____\__ на клеммные колодки DC8V на платах индикаторов
//pin3 G |----- D | | \/\ \
// | | \ \ \
// | | \ \______\
// ---------------*--> +DC8v \/______/
// \ \ \
// ------------------> -DC8V G D S
// |
// -
TinyGPS gps;
SoftwareSerial ss(9, 7); // К этим пинам подключается GPS-приёмник. Tx->9, Rx->7
// Module Arduino
// Vcc o----------- +5v
// Rx o----------- 7
// Tx o----------- 9
// Gnd o----------- Gnd
#define LightSensorPin 2
// Module Arduino Сенсор освещённости
// Vcc o----------- +5v
// Gnd o----------- Gnd
// D0 o----------- pin 2
#define DSdatapin 6 // К этому пину подключена линия данных датчика DS18B20
#define DSpowerpin 14 // С этого пина подаётся питание на датчик DS18B20
OneWire ds (DSdatapin);
int CurMins;
byte font[23] = {0b11000000, 0b11111001, 0b10100100, 0b10110000, 0b10011001, 0b10010010, 0b10000010, 0b11111000, 0b10000000, 0b10010000, // 0 - 9
0b01000000, 0b01111001, 0b00100100, 0b00110000, 0b00011001, 0b00010010, 0b00000010, 0b01111000, 0b00000000, 0b00010000, // 0. - 9.
0b10111111,0b10011100,0xff}; // -, °
char TIMEZONE = defaultTimezone;
byte Blinking = false;
byte TempPresent = false;
byte DSstatus,DSphase=0;
byte CRC;
byte tempdata[12];
unsigned int tempraw ;
float CurrentTemp, DSTemp;
byte DarkNight = 0;
int Synced;
int TimeToSync=0;
int t = 0;
int sc,mm,hh,dd,mn,ye;
byte bb,bo=0;
byte dot=true;
char GPSchar;
float flat, flon;
unsigned long age;
int GPSyear;
char GPSmonth, GPSday, GPShour, GPSminute, GPSsecond, GPShundredths;
unsigned long GPSage;
byte GPSpresent = false;
byte NeedToShowScreen = false;
byte ScreenPage=0;
int ScreenTimer;
unsigned long dotstart;
byte sm[9];
void TickEvery05s() // Эта процедура вызывается каждые полсекунды
{
dotstart= millis();
if (dot==true)
{if (Blinking ==true) {Blinking=false;analogWrite(DotPin,0);} else {Blinking=true; if (DarkNight==1) analogWrite(DotPin,DotNightBright); else analogWrite(DotPin,255);}} else analogWrite(DotPin,0);
if (ScreenTimer<120) ScreenTimer++; else ScreenTimer=0;
NeedToShowScreen = true;
if (DSphase==0) {digitalWrite(DSpowerpin, HIGH); } else
if (DSphase==1) {
ds.reset();
ds.write(0xCC);
ds.write(0x44);} else
if (DSphase==4) {
DSstatus=ds.reset();
ds.write(0xCC);
ds.write(0xBE);
CRC=0;
for (byte i = 0; i < 9; i++) tempdata[i] = ds.read ();
digitalWrite(DSpowerpin, LOW);} else
if (DSphase==5) {CRC=ds.crc8(tempdata,9);} else
if (DSphase==6) {
tempraw = (tempdata[1] << 8) | tempdata[0]; // Пересчитываем в температуру
int signBit = tempraw & 0x8000; // Проверяем самый левый бит: 0x8000= 0b10000000 00000000
if (signBit) // Если там единица - число отрицательное и его надо преобразовать
{ // Стандартное преобразование отрицательного числа, которое в микроконтроллере в дополнительной кодировке
tempraw = (tempraw ^ 0xffff) + 1; // Путем исключающего ИЛИ плюс единица
}
float celsius = (float)tempraw / 16.0;
if (signBit) celsius=-celsius; // Если отрицательное число
CurrentTemp=celsius;
if (CRC==0) TempPresent=true; else TempPresent=false;
if (DSstatus==0) TempPresent=false;
}
DSphase++;
if (DSphase>20) DSphase=0;
}
int encodeDigit(char c)
{
int m;
if (c=='-') m=20; else
if ((c>='0') and (c<='9')) m=c-48; else
m=22;
return m;
}
void Screen()
{
String b;
char s[16];
if (TempPresent==true)
{
b = dtostrf(CurrentTemp, 3, 0, s);
if (b==" -0") b=" 0";
if (b!=" 0") { if (b.charAt(1)==' ') b.setCharAt(1,' '); else if ((b.charAt(0)==' ') && (b.charAt(1)!='-')) b.setCharAt(0,' ');}
} else
{
b="---";
ScreenPage=0;
}
switch(ScreenPage) {
case 1: {
dot=false;
analogWrite(DotPin,0);
sm[0]=font[encodeDigit(b.charAt(0))];
sm[1]=font[encodeDigit(b.charAt(1))];
sm[2]=font[encodeDigit(b.charAt(2))];
sm[3]=font[21];
break;}
case 0:{
dot=true;
sm[3]=font[(byte)minute()%10];
sm[2]=font[(byte)minute()/10];
sm[1]=font[(byte)hour()%10];
sm[0]=font[(byte)hour()/10];
break;}}
digitalWrite (BrightnessPin, DarkNight);
digitalWrite(REG_SELECT, LOW);
SPI.transfer(sm[3]);
SPI.transfer(sm[2]);
SPI.transfer(sm[1]);
SPI.transfer(sm[0]);
digitalWrite(REG_SELECT, HIGH);
NeedToShowScreen = false;
}
void ScreenTZ()
{
dot=false;
analogWrite(DotPin,0);
sm[3]=font[(byte)abs(TIMEZONE)%10];
sm[2]=font[(byte)abs(TIMEZONE)/10];
if (TIMEZONE<0) sm[1]=font[20]; else sm[1]=font[22];
sm[0]=font[22];
digitalWrite(REG_SELECT, LOW);
SPI.transfer(sm[3]);
SPI.transfer(sm[2]);
SPI.transfer(sm[1]);
SPI.transfer(sm[0]);
digitalWrite(REG_SELECT, HIGH);
}
void Synctime()
{
byte monthes [13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; // Сколько дней по месяцам в невисокосном году.
if (Synced>0) Synced=0 ;
gps.f_get_position(&flat, &flon, &GPSage);
if ((flat !=TinyGPS::GPS_INVALID_F_ANGLE) && (flon !=TinyGPS::GPS_INVALID_F_ANGLE)) {GPSpresent=true;} else GPSpresent=false;
gps.crack_datetime(&GPSyear, &GPSmonth, &GPSday, &GPShour, &GPSminute, &GPSsecond, &GPShundredths, &GPSage);
if ((GPSage!=TinyGPS::GPS_INVALID_AGE) && (GPSpresent==true) && ((GPShour+GPSminute)>6))
{
GPShour=GPShour+TIMEZONE;
if (GPShour>23) { // Корректируем время с GPS под часовые зоны восточной долготы.
GPShour=GPShour-24;
if (((GPSyear % 4 == 0) && (GPSyear % 100 != 0)) || (GPSyear % 400 == 0)) {monthes[2]=29;} // Корректируем февраль, если год високосный
GPSday++;
if (GPSday>monthes[GPSmonth])
{
GPSday=1;
GPSmonth++;
if (GPSmonth>12) {GPSmonth=1; GPSyear++;}
}
}
if (GPShour<0) { // Корректируем время с GPS под часовые зоны западной долготы.
GPShour=GPShour+24;
GPSday--;
if (GPSday<1)
{
GPSmonth--;
if (GPSmonth<1) {GPSmonth=12; GPSyear--;}
if (((GPSyear % 4 == 0) && (GPSyear % 100 != 0)) || (GPSyear % 400 == 0)) {monthes[2]=29;} // Корректируем февраль, если год високосный
GPSday=monthes[GPSmonth];
}
}
if (flat == TinyGPS::GPS_INVALID_F_ANGLE) {flat=0.0;} else {flat=round(flat*100); flat=flat/100;}
if (flon == TinyGPS::GPS_INVALID_F_ANGLE) {flon=0.0;} else {flon=round(flon*100); flon=flon/100;}
if ((flat !=TinyGPS::GPS_INVALID_F_ANGLE) && (flon !=TinyGPS::GPS_INVALID_F_ANGLE))
{
LATITUDE = flat, LONGITUDE = flon;
if (GPSage>1500) {;} else {
setTime(GPShour, GPSminute, GPSsecond, GPSday, GPSmonth, GPSyear);
RTC.set(now());
Synced=2 ;
}
}
}}
void setup()
{
setSyncProvider(RTC.get);
if(timeStatus() != timeSet) Synced=-1 ; else Synced=0 ;
byte TZ=EEPROM.read(5);
if (TZ>24) TIMEZONE=defaultTimezone else TIMEZONE=TZ-12;
EEPROM.write(5, TIMEZONE+12);
pinMode(DotPin, OUTPUT); analogWrite(DotPin,0);
pinMode(BrightnessPin, OUTPUT); digitalWrite (BrightnessPin, 1);
pinMode(LightSensorPin, INPUT);
SPI.begin();
pinMode(REG_SELECT, OUTPUT);
DarkNight=digitalRead(LightSensorPin);
Screen();
pinMode(HourPin, INPUT_PULLUP);
pinMode(MinutePin, INPUT_PULLUP);
pinMode(DSpowerpin, OUTPUT); digitalWrite(DSpowerpin, HIGH);
CurMins=hour()*60+minute();
DSphase=0;
ScreenPage=0;
ScreenTimer=0;
NeedToShowScreen = true;
CurrentTemp=85;
TempPresent=false;
MsTimer2::set(500, TickEvery05s); // 500ms period
MsTimer2::start();
ss.begin(9600);
HourButt.update();MinuteButt.update();
if ((HourButt.read()== LOW) && (MinuteButt.read()== LOW)) {
ScreenTZ();
while ((HourButt.read()== LOW) || (MinuteButt.read()== LOW)){ HourButt.update();MinuteButt.update();}
byte ExiteMenu=false;
while (ExiteMenu==false) {
ScreenTZ();
if (HourButt. update()) {if (HourButt. read()== LOW) {TIMEZONE++; if (TIMEZONE> 12) TIMEZONE= 12; EEPROM.write(5, TIMEZONE+12); ScreenTZ(); HourButt .rebounce(500);}}
if (MinuteButt.update()) {if (MinuteButt.read()== LOW) {TIMEZONE--; if (TIMEZONE<-12) TIMEZONE=-12; EEPROM.write(5, TIMEZONE+12); ScreenTZ(); MinuteButt.rebounce(500);}}
if ((HourButt.read()== LOW) && (MinuteButt.read()== LOW)) ExiteMenu=true;
delay(100);
}
}
}
time_t prevDisplay = 0;
void loop()
{
bool newData = false;
if (ss.available()){
char GPSchar = ss.read();
if (gps.encode(GPSchar)) newData = true;}
if (newData==true)
{
gps.f_get_position(&flat, &flon, &GPSage);
if ((flat !=TinyGPS::GPS_INVALID_F_ANGLE) && (flon !=TinyGPS::GPS_INVALID_F_ANGLE)) {GPSpresent=true; NeedToShowScreen=true;} else GPSpresent=false;
}
if (TempPresent==true) {if (ScreenTimer>=MaxScreenTimer) {ScreenPage=1; dot=false; analogWrite(DotPin,0);} else {ScreenPage=0; dot=true;}} else {ScreenPage=0; dot=true;}
// Опрос кнопок
if (HourButt. update()) {if (HourButt. read()== LOW) {hh=hour();mm=minute();sc=second();dd=day();mn=month();ye=year(); hh++; if (hh==24) hh=0; setTime(hh, mm, sc, dd, mn, ye); RTC.set(now()); Synced=1; ScreenTimer=0; ScreenPage=0; Screen(); HourButt .rebounce(500);}}
if (MinuteButt.update()) {if (MinuteButt.read()== LOW) {hh=hour();mm=minute();sc=second();dd=day();mn=month();ye=year(); mm++; if (mm==60) mm=0; setTime(hh, mm, sc, dd, mn, ye); RTC.set(now()); Synced=1; ScreenTimer=0; ScreenPage=0; Screen(); MinuteButt.rebounce(500);}}
if (now() != prevDisplay) {
prevDisplay = now();
CurMins=hour()*60+ minute();
TimeToSync++;
if (TimeToSync >= MaxTimeToSync) {
Synctime();
TimeToSync=0;}
}
if ((TimeToSync%10==0) && (Synced < 1)) Synctime(); // Если время ещё не синхронизировано, то пытаемся его синхронизировать чаще.
DarkNight=digitalRead(LightSensorPin);
if (NeedToShowScreen == true) Screen();
if (Synced<0) if (millis()-dotstart>30) analogWrite(DotPin,0);
delay(1);
}