Zápisník experimentátora
Hierarchy: Arduino MKR1000
V tomto článku si zistíme presný čas z NTP servera pomocou Arduina MKR1000. Do Internetu sa pripojíme cez WiFi. Budeme vychádzať zo vzorového príkladu, ktorý je dodávaný ku knižnici WiFi101, program ale upravíme tak, aby sa výsledok zobrazoval na displeji Nokia 5110.
Tento displej rád používam v mojich projektoch. Klasické Arduino na 5 V potrebuje na jeho ovládanie level shifter. Toto všetko môže pri MKR1000 odpadnúť, pretože používa rovnako ako displej 3,3 V. Čiže celé zapojenie sa scvrkne do prepojenia niekoľkých vodičov.
Na ovládanie displeja budeme používať knižnicu Adafruit_PCD8544. Dá sa nainštalovať aj v správcovi knižníc, ale má to jeden háčik. Je potrebná minimálne verzia 1.0.1, ktorá vie spolupracovať s mikrokontrolérmi SAMD21. V čase písania článku bola už úprava na GitHub, ale nebola ešte k dispozícii v správcovi knižníc. Preto ju bolo potrebné z GitHub stiahnuť ručne a prepísať s ňou nainštalovanú knižnicu.
Ďalšou potrebnou knižnicou je WiFi101. Táto sa dá nainštalovať aj v správcovi knižníc. V čase písania článku tam bola verzia 0.11.0.
Celé zapojenie som testoval v IDE 1.6.12.
Program sa prihlási do vašej WiFi siete a z NTP servera si stiahne aktuálny čas. Ten sa bude zobrazovať na displeji a pretože sa jedná o program, ktorý slúži na testovanie, bude vypisovať aj veľa ladiacich informácií cez sériový port. Informácie o čase sa budú aktualizovať približne každých 10 sekund.
Program sa skladá z dvoch súborov:
Prejdime si dôležité časti programu.
Includovanie knižníc a nastavenie displeja.
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <WiFi101.h>
#include <WiFiUdp.h>
#include <time.h>
#include "dump.h"
// Software SPI (slower updates, more flexible pin options):
// pin 1 - Serial clock out (SCLK)
// pin 2 - Serial data out (DIN)
// pin 3 - Data/Command select (D/C)
// pin 4 - LCD chip select (CS)
// pin 5 - LCD reset (RST)
Adafruit_PCD8544 nokia = Adafruit_PCD8544(1, 2, 3, 4, 5);
Nastavenie hesiel do vašej WiFi siete.
int status = WL_IDLE_STATUS;
char ssid[] = "XXXX"; // your network SSID (name)
char pass[] = "XXXX"; // your network password
Nastavenie čísla portu, na ktorom budeme počúvať, IP adresa NTP servera a buffer, cez ktorý sa budú prenášať údaje o čase. Komunikačný protokol pre NTP server je UDP.
NTP používa na prenos údajov buffer s dĺžkou 48 bajtov. Na server sa odošle s niekoľkými nastavenými hodnotami a server odpovie rovnako, len má vyplnené aj údaje o čase.
unsigned int localPort = 2390; // local port to listen for UDP packets
IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
V nastavení sa udeje niekoľko krokov:
void setup()
{
// Open serial communications and wait for port to open:
Serial.begin(9600);
nokia.begin();
nokia.setContrast(60);
drawIntro();
nokia.clearDisplay();
nokia.display();
// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
TRACETEXT("WiFi shield not present");
// don't continue:
while (true);
}
// attempt to connect to Wifi network:
while ( status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
TRACETEXT("Connected to wifi");
printWifiStatus();
TRACETEXT("Starting connection to server...");
uint8_t res;
Udp.begin(localPort);
TRACE1("Udp.begin",res)
}
Vo funkcii loop sa periodicky odošle žiadosť o presný čas, prijme sa a spracuje tak, aby ju bolo možné zobraziť cez sériový port a displej Nokia 5110. Po odoslaní žiadosti sa čaká 1 sekundu a potom sa spracúva odpoveď. Takto to bolo naprogramované v originálnom deme a nie je to úplne najšťastnejšie riešenie. Ale nemal som toľko času na ladenie a povedal som si, že sa v budúcnosti tomuto aj tak budem venovať viac a neskôr prepíšem tieto napevno nastavené čakania na serióznejšiu formu.
Zastavím sa pri riadku DUMP(packetBuffer). Toto volanie makra vypíše celý obsah prijatého buffera a je to dobrá pomôcka na hľadanie prípadnej chyby. Vo výpise na konci programu sú takto zobrazené dva po sebe idúce buffre a ľahko tam zbadáte, na ktorých miestach v bufferi sa menia údaje o čase. Ale je to aj napísané v poznámke, že čas sa nachádza v bufferi od bajtu číslo 40 (ale indexujeme od 0, čiže je to 41. znak).
Nasleduje prevod údajov času do premennej secsSince1900. Už podľa názvu sa dá uhádnuť, že je to čas v sekundách od roku 1900. Historicky je dané, že v počítačoch sa čas meria od roku 1970. Ani mikrokontroléry firmy Atmel neporušujú túto tradíciu a preto si odčítaním získame aktuálnu hodnotu v sekundách, ktorú je možné použiť na ďalšie výpočty.
Nasledovnú časť programu som kompletne zmenil. Vzorový príklad (WifiUdpPNtpClient) tam robil všetky výpočty ručne. To ale nie je podľa mňa žiadúce, keď máme na prácu s časom štandartné funkcie. Preto môj kód pracuje iba s nimi. Používajú sa nasledovné funkcie a štruktúry:
Tieto funkcie som použil na to, aby som získal pekné výpisy času aj na sériovom porte, aj na displeji. Na fotografii je vidno, čo sa zobrazí na displeji a výpis sériového portu je na konci textu.
void loop()
{
sendNTPpacket(timeServer); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);
if ( Udp.parsePacket() ) {
TRACETEXT("Packet received");
// We've received a packet, read the data from it
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
DUMP(packetBuffer)
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = " );
Serial.println(secsSince1900);
// now convert NTP time into everyday time:
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
time_t unixtime = epoch;
tm *timeinfo;
timeinfo = gmtime( &unixtime );
char bufout[256];
sprintf(bufout, "Unix time = %u is year=%d, month=%d, day=%d, wday=%d, hour=%d, min=%d, sec=%d",
(unsigned int)unixtime,
timeinfo->tm_year + 1900,
timeinfo->tm_mon,
timeinfo->tm_mday,
timeinfo->tm_wday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
Serial.println(bufout);
DUMPVAL(*tzname);
sprintf(bufout,"asctime = %s",asctime(timeinfo));
Serial.println(bufout);
sprintf(bufout,"%s",asctime(timeinfo));
nokia.clearDisplay();
nokia.setCursor(0, 0);
nokia.print(bufout);
nokia.setCursor(0, 24);
strftime (bufout,80,"%T",timeinfo);
nokia.print(bufout);
nokia.setCursor(0, 32);
strftime (bufout,80,"%F",timeinfo);
nokia.print(bufout);
nokia.setCursor(0, 40);
strftime (bufout,80,"%A",timeinfo);
nokia.print(bufout);
nokia.display();
}
else
TRACETEXT("Packet lost");
// wait ten seconds before asking for the time again
delay(10000);
}
Odoslanie žiadosti. Funkciou memset sa celý buffer naplní nulami a potom sa nastaví niekoľko konkrétnych údajov. Toto som nemenil, iba som doplnil ladiace výpisy. Podľa Internetových zdrojov sa ale zdá, že sa v bufferi nastavuje aj to, čo nie je nevyhnutne treba. Ak si nájdem trochu času, otestujem viac časových serverov a prípadne porovnám kód s nejakými C++ implementáciami tohoto istého riešenia, aby sme našli aj pre Arduino kód, ktorý bude zodpovedať zvyšku sveta.
unsigned long sendNTPpacket(IPAddress& address)
{
TRACEFUNC()
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
int res;
res=Udp.beginPacket(address, 123); //NTP requests are to port 123
TRACE1("Udp.beginPacket",res)
res=Udp.write(packetBuffer, NTP_PACKET_SIZE);
TRACE1("Udp.write",res)
res=Udp.endPacket();
TRACE1("Udp.endPacket",res)
}
Takto vyzerá výpis na sériovom porte. Z výpisu by ste sa mohli dovtípiť, na ktorom mieste zdrojového kódu sa nachádzajú príkazy, ktoré vypíšu konkrétne riadky na výpise.
Get current time from NTP server. Attempting to connect to SSID: UPC3A7CE89 [016541] Connected to wifi SSID: UPC3A7CE89 IP Address: 192.168.0.38 Signal strength (RSSI): -75 dBm [016546] Starting connection to server... Udp.begin: res=0 [016550] long unsigned int sendNTPpacket(IPAddress&) Udp.beginPacket: res=1 Udp.write: res=48 Udp.endPacket: res=1 [017555] Packet received Dump: packetBuffer 200003F4 - 24 01 06 E3 00 00 00 00 00 00 00 00 41 43 54 53 $...........ACTS 20000404 - DB DF 33 E5 D1 43 39 36 00 00 00 00 00 00 00 00 ..3..C96........ 20000414 - DB DF 34 20 80 DD 3A BB DB DF 34 20 80 DE 54 00 ..4 ..:...4 ..T. Seconds since Jan 1 1900 = 3688838176 Unix time = 1479849376 is year=2016, month=10, day=22, wday=2, hour=21, min=16, sec=16 Dump: *tzname=GMT 2000030C - F0 DC 00 00 .... asctime = Tue Nov 22 21:16:16 2016 [027570] long unsigned int sendNTPpacket(IPAddress&) Udp.beginPacket: res=1 Udp.write: res=48 Udp.endPacket: res=1 [028575] Packet received Dump: packetBuffer 200003F4 - 24 01 06 E3 00 00 00 00 00 00 00 00 41 43 54 53 $...........ACTS 20000404 - DB DF 34 27 D1 4C F5 D1 00 00 00 00 00 00 00 00 ..4'.L.......... 20000414 - DB DF 34 2B 86 64 DF 2C DB DF 34 2B 86 65 62 6F ..4+.d.,..4+.ebo Seconds since Jan 1 1900 = 3688838187 Unix time = 1479849387 is year=2016, month=10, day=22, wday=2, hour=21, min=16, sec=27 Dump: *tzname=GMT 2000030C - F0 DC 00 00 .... asctime = Tue Nov 22 21:16:27 2016
Zdrojový kód sa nachádza na GitHub.
Video sa nachádza na YouTube.
Počas ladenia mi program niekoľkokrát kompletne zamrzol. Problém som vystopoval potiaľ, že UDP odoslanie sa uskutočnilo. Ťažko povedať, čo presne je za tým. Možno je problém v implementácii WiFi a podľa dostupných informácií viem, že Atmel pracuje na modifikácii firmvéru. Je možné, že s novou verziou pominú aj tieto problémy. Keď to bude k dispozícii, môžem otestovať tento kód znovu.
22.11.2016