Samoregulujący się zegar z serwera NTP

Do tego potrzebne nam bedą:

  • Wyświetlacz LCD
  • Atmega (w moim przypadku była to 328, jednak 88/128 też powinny dać radę )
  • ESP8266 ( ja użyłem wersję 1 1MB)
  • Przejściówka USB <-> RS232

Schemat podłączenia:

Na początek instalujemy środowisko ArduinoIDE ( stąd: https://www.arduino.cc/en/main/software ) .

Uruchamiamy ArduinoIDE i wybieramy z górnego menu PLIK -> preferencje w polu „Dodatkowe adresy URL do menadżera płytek” wpisujemy: http://arduino.esp8266.com/stable/package_esp8266com_index.json i klikamy OK

Następnie przechodzimy do „Narzędzia” -> „Płytka …” -> „Menadżer płytek” , w polu wyszukiwania wpisujemy esp8266 i naciskamy enter. Pojawia nam się „esp8266 by ESP8266 Community” i klikamy instaluj. Czekamy aż się poprawnie się pobierze i zainstaluje płytka.

Gdy to zostanie zrobione instalujemy potrzebne biblioteki, a więc wchodzimy w „Narzędzia” -> „Zarządzaj bibliotekami” i instalujemy:

  • NTPClient
  • Time
  • Timezone

Następnie wybieramy Narzędzia -> Płytka -> ESP8266 Generic .

Wpisujemy taki kod:

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Time.h>
#include <TimeLib.h>
#include <Timezone.h>

// Właściwości NTP
#define NTP_OFFSET   0
#define NTP_INTERVAL 60 * 1000
#define NTP_ADDRESS  "time.coi.pw.edu.pl"  //adres serwera NTP, adresy innych serwerów można znaleźć w internecie


char ssid[] = "********";       // ssid
char pass[] = "********";       // hasło

///bufor ramki rs232
byte frame[10];
byte frCnt = 0;

//flaga czekania na odbiór ntp
boolean waitForReply = 0;
byte attempt = 0;

//obiekty UDP i protokołu NTP
WiFiUDP udp;
NTPClient timeClient(udp, NTP_ADDRESS, NTP_OFFSET, NTP_INTERVAL);

void setup()
{
  Serial.begin(9600);
  //Serial.println();
  //Serial.println();

  // Łączenie
  //Serial.print("Connecting to ");
  //Serial.println(ssid);
  WiFi.begin(ssid, pass);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }
  //Serial.println("");
  
  //Serial.println("WiFi connected");
  //Serial.println("IP address: ");
  //Serial.println(WiFi.localIP());

  //Serial.println("Starting NTP Client");
  timeClient.begin();

  //komunikat o poprawnym połączeniu
  Serial.print("ic!\n");

}

void loop()
{
  //odbierz dane
  if (Serial.available() > 0) {
    frame[frCnt] = Serial.read();
    if (frame[frCnt] == '\n') {
      //odebrano komplerną ramkę
      if(frame[2] == '?') {
        
        //odebrano zapytanie
        if (frame[0] == 't' && frame[1] == 'i') {
          //odebrano zapytanie o NTP

          //aktualizuj dane
          timeClient.update();

          //pobierz dane w formacie unix (liczba sekund od 1.01.1970, UTC)
          unsigned long unix =  timeClient.getEpochTime();
      
          // biblitoeka time.h wymaga używania formatu zmiennej time_t, dlatego tworzymy dwie zmienne (czas UTC i lokalny)...
          time_t utc, ti;
          //..i rzutujemy czas z serwera do zmiennej
          utc = unix;

          //Dane strefy czasowej wraz z regułami zmiany czasu. W naszym przypadku będzie to CET (zimowy) oraz CEST (letni).
          TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     //Central European Summer Time
          TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       //Central European Standard Time
          Timezone LocalTime(CEST, CET);
          //konwersja czasu
          ti = LocalTime.toLocal(utc);

          //wysyłamy dane
          Serial.print("ti=");
          Serial.print(year(ti));
          Serial.print("-");
          if (month(ti) < 10) Serial.print("0");
          Serial.print(month(ti));
          Serial.print("-");
          if (day(ti) < 10) Serial.print("0");
          Serial.print(day(ti));
          Serial.print("-");
          
          if (hour(ti) < 10) Serial.print("0");
          Serial.print(hour(ti));
          Serial.print("-");
          if (minute(ti) < 10) Serial.print("0");
          Serial.print(minute(ti));
          Serial.print("-");
          if (second(ti) < 10) Serial.print("0");
          Serial.print(second(ti)); 
          Serial.print("\n");       
        }
      }
      //zeruj
      frCnt = 0;
    } else {
      frCnt++;
    }
    
  }
}

(kod skopiowany z zegara nixie z majsterkowo https://majsterkowo.pl/zegar-nixie-synchronizowany-z-ntp-cz-1/ trzeba uzupełnić o nazwę sieci i hasło własnej sieci Wi-Fi)

Klikamy zweryfikuj, podłączamy GPIO0 do GND na esp8266 resetujemy go i klikamy wgraj.

I to tyle od strony ESP8266 :).

Teraz przechodzimy do Eclipse i programujemy naszą atmegę 🙂

tworzymy nowy projekt do którego kopiujemy już bibliotekę UART (np. z Peter Fleury), oraz do obsługi LCD (ja używam bibliotekę z niebieskiej książki Mirosława Kardasia ), jednak ta od Peter Fleury też powinna działać poprawnie.

Tworzymy main.c, wpisujemy:

// Standard library:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <string.h>

// Own library
#include "uart/uart.h"
#include "lcd/lcd44780.h"
// Global variables

signed char h;
signed char m;
signed char s;
signed char day;
signed char month;
signed char year;
signed char date[15];
volatile uint16_t time;
unsigned int get_data;
char buffor[26];
uint16_t counter = 0;

#define UART_BAUD_RATE	9600

// Define functions:
void initialize_screen();
void initialize_wifi();
void initialize_clock();
void initialize_timer0();
void print_time();
void clock(char jumpDays);
void get_uart_message();
unsigned char computeDayNum(unsigned char y,unsigned char m);

//***************************** Main function: *******************************/
int main(void)
{
	// Initialize functions
	initialize_screen();
	initialize_wifi();
	initialize_clock();
	initialize_timer0();
	_delay_ms(3000);
	// enable sei
	sei();
	// main loop
	while(1)
	{
		get_uart_message();
	}
}

/*************** Functions *******************/
void print_time(){
	lcd_cls();
	lcd_int(h);
	lcd_str(":");
	lcd_int(m);
	lcd_str(":");
	lcd_int(s);
	lcd_locate(1,0);
	lcd_int(day);
	lcd_str("-");
	lcd_int(month);
	lcd_str("-20");
	lcd_int(year);
}
void initialize_screen(){
	lcd_init();
	lcd_str("Uruchamiam");
}

void initialize_wifi(){
	uart_init(UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU));
	uart_puts("ti?\r\n");
}

void initialize_clock(){
	h = 0;
	m = 0;
	s = 0;
	year = 19;
	day = 1;
	month = 1;
}

void initialize_timer0(){
	/*
	 * Init Timer0
	 */
	TCCR0A |= (1<<WGM01);
	OCR0A = 77;
	TIMSK0 |= (1 << OCIE0A);
	TCCR0B |= (1<<CS02) | (1<<CS00);
}

void clock(char jumpDays) {

	if (s >= 60) {
		s = 0;
		m++;
	}

	if (m >= 60) {
		m = 0;
		h++;
	}

	if (h >= 24) {
		h = 0;
		if (jumpDays) day++;
	}

	if (day > computeDayNum(year,month)) {
		day = 1;
		month++;
	}

	if (month > 12) {
		month = 1;
		if(jumpDays) year++;
	}

	if(year > 99) year = 0;

	if (s < 0) {
		s = 59;
		m--;
	}

	if (m < 0) {
		m = 59;
		h--;
	}

	if (h < 0) h = 23;

	if (day < 1) {
		month--;
		day = computeDayNum(year,month);
	}

	if (month < 1) month = 12;
	if (year < 0) year = 99;
}

unsigned char computeDayNum(unsigned char y,unsigned char m) {
	if (m==4 || m==6 || m==9 || m==11) return 30;
	else if (m==2) {
		if (y % 4) {
			return 28;
		} else {
			return 29;
		}
	} else return 31;
}

void get_uart_message(){
        do{
                get_data = uart_getc();
                if ( get_data & UART_NO_DATA )  return;
                else
                {
                        if ( get_data & UART_FRAME_ERROR )      return;
                        if ( get_data & UART_OVERRUN_ERROR )    return;
                        if ( get_data & UART_BUFFER_OVERFLOW )  return;
                }
                buffor[counter] = get_data;
                counter++;
        }while(get_data != '\n');
        counter = 0;
		year  = (buffor[5] - '0') * 10;
		year  += buffor[6] - '0';

		month = (buffor[8] - '0') * 10;
		month += buffor[9] - '0';

		day   = (buffor[11] - '0') * 10;
		day   += buffor[12] - '0';

		h = (buffor[14] - '0') * 10;
		h += buffor[15] - '0';

		m = (buffor[17] - '0') * 10;
		m += buffor[18] - '0';

		s = (buffor[20] - '0') * 10;
		s += buffor[21] - '0';
}

// ISR:
ISR(TIMER0_COMPA_vect){
	time++;
	if((time%100)==0){
		// add seconds after 1 seconds
		s+=1;
		clock(1);
		print_time();
	}
	if(time >=1000){
		// get new date after 10 seconds
		uart_puts("ti?\r\n");
		// set time=0
		time=0;
	}
}

Kompilujemy i wgrywamy program do atmegi328 i na ekranie LCD powinien ukazać się aktualna data i godzina.

w razie jakichkolwiek pytań, proszę o komentarz.