Мега-часы. 1. Просто часы, но с WiFi

Занимаясь на выходных разборкой и сортировкой скопившегося имущества, практически внезапно обнаружил давно забытые детали. Ну как забытые … я конечно помнил, что они где-то есть, но уж очень давно не держал их в руках. Ну и плюс ко мне в руки попали платы на 8051 микроконтроллере, со встроенным приемопередатчиком. Просто так баловаться не интересно, поэтому необходимо совместить приятное с полезным.

Скажу сразу, просто еще одни часы делать мне не интересно – один раз я уже делал аж 2 года назад, до сих пор работают у тещи. Внутри ардуинка и sure 3208

20131113_165232

Что у нас из приятного образуется? Во-первых, более большой и цветной индикатор.

В старых часах был одноцветный (только красный) индикатор, в котором была кучка светодиодов, расположенных 32 рядами по 8 штук. В новых будет аналогичная матрица, только умеющая уже 3 цвета и с матрицей светодиодов 64 на 16. Из-за размера светодиодов (в старой 5мм, в новой 3мм) разница в размерах не очень большая.

Во-вторых, использованная в первых часах микросхема DS1307 обладает небольшим уводом времени. Примерно минуту в неделю. Нет, я там приделал кнопки для коррекции времени, но это же несерьезно в 21м-то веке. Ловить всякие радиосигналы точного времени для меня безсмысленно, ибо лень. А вот подцепиться к WiFi и сбегать за точным временем в интернет к специализированному серверу – вот это да, уже стоит подумать.

И наконец, просто часы – это не интересно. Так как основное их место жительство будет кухня, то надо заставить их что-нибудь делать полезное. Курс доллара там показывать и радио играть. Или еще какую полезную работу выполнять. Список полезной работы будет уточняться по мере того, как я буду откапывать запчасти 🙂

Итак, пока часы. Схемы рисовать не буду, потому что 99% всего будет состоять из соединения проводками.

Для начала берем 2 индикатора sure 3216 и соединяем их приложенными к ним же проводами. Потом берем arduino leonardo и wifi shield. Все родное, итальянское, поэтому соединяем все в кучу. Достаем из пакетика мастеркитовский MP1095 и опять же соединяем проводками с ардуинкой. Все подписанное к подписанному (SDA к SDA, SCL к SCL и так далее).

И наконец соединяем полученный бутерброд из 3216 к ардуинке. На пины А0-А3. В нижеприведенном куске кода любой ардуинщик разберется, что куда подключается.

//Analog In 0 as PF7
//Analog In 1 as PF6
//Analog In 2 as PF5
//Analog In 3 as PF4
ht1632c ledMatrix = ht1632c(&PORTF, 7, 6, 5, 4, GEOM_32x16, 2);
ht1632c::ht1632c(const uint8_t data, const uint8_t wr, const uint8_t clk, const uint8_t cs, const uint8_t geometry, const uint8_t number)

И теперь добавляем магию, сиречь программирование. Я не стал сильно раздумывать, поэтому тупо собрал в одну кучу примеры общения с RTC, с NTP, c WiFi и с индикатором.

#include <ht1632c.h>
#include <Wire.h>
#include <RealTimeClockDS1307.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiUdp.h>

ht1632c dm = ht1632c(&PORTF, 7, 6, 5, 4, GEOM_32x16, 2);

int status = WL_IDLE_STATUS;

char ssid[20] = "multik";  //  your network SSID (name)
char pass[20] = "MyVerySecretPassword";       // your network password
int timeshift=3; // GMT offset

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;

char clock[4];
int clockset=0; // does need to set clock from NTP?
int clockprocess=0; // we start asking clock server?

void setup() {  
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  // check for the presence of the shield:

  while (WiFi.status() == WL_NO_SHIELD)
    Serial.println("NOWIFI");

  String fv = WiFi.firmwareVersion();
  if ( fv != "1.1.0" )
    Serial.println("FIRM");

  status = WiFi.begin(ssid, pass); // start connecting

  dm.clear();
  dm.setfont(FONT_8x16B);

  RTC.switchTo24h();
}

void loop()
{
  if(clockset==0)
  {
    //Serial.println("Setting clock");
    if(status==WL_CONNECTED) // ok, we online
    {
      //Serial.println("WiFi con");
      if(clockprocess==0)
      {
        Udp.begin(localPort);
        sendNTPpacket(timeServer); // send an NTP packet to a time server
        clockprocess=1;
      }
      else
      {
        if ( Udp.parsePacket() ) 
        {
          //Serial.println("packet received");
          Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

          //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:
          // Serial.print("Unix 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;
          // print Unix time:
          //Serial.println(epoch);


          // print the hour, minute and second:
          //Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
          //Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
          int h=(epoch  % 86400L) / 3600;

          RTC.setHours(h+timeshift);
          //Serial.print(h);Serial.print(':');
          //if ( ((epoch % 3600) / 60) < 10 ) {
          // In the first 10 minutes of each hour, we'll want a leading '0'
          //Serial.print('0');
          //}
          int m=(epoch  % 3600) / 60;

          //Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
          RTC.setMinutes(m);
          //Serial.print(m);Serial.print(':');
          //if ( (epoch % 60) < 10 ) {
          // In the first 10 seconds of each minute, we'll want a leading '0'
          //Serial.print('0');
          //}
          int s=epoch % 60;
          //Serial.println(s);
          //Serial.println(epoch % 60); // print the second
          RTC.setSeconds(s);
          RTC.setClock();
          clockset=1;
        }
      }
    }
    //    else
    //    {
    //      Serial.println("WiFi NOT con");
    //    }
  }
  else
  {
    // Serial.println("Clock already setted");
  }
  int x=0;
  int clr=GREEN;
  delay(1000);
  RTC.readClock();
  sprintf(clock, "%02d", RTC.getHours()); // RTC.getMinutes(),RTC.getSeconds());

  dm.setfont(FONT_8x16B);
  dm.putchar(x,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+9,0,clock[1],GREEN,0,BLACK);  

  // first :
  dm.plot(x+18,4,clr);
  dm.plot(x+18,4+1,clr);
  dm.plot(x+18+1,4,clr);
  dm.plot(x+18+1,4+1,clr);
  dm.plot(x+18,4+4,clr);
  dm.plot(x+18,4+1+4,clr);
  dm.plot(x+18+1,4+4,clr);
  dm.plot(x+18+1,4+1+4,clr);

  sprintf(clock, "%02d", RTC.getMinutes()); //,RTC.getSeconds());
  dm.putchar(x+21,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+30,0,clock[1],GREEN,0,BLACK);
  // second :
  dm.plot(x+39,4,clr);
  dm.plot(x+39,4+1,clr);
  dm.plot(x+39+1,4,clr);
  dm.plot(x+39+1,4+1,clr);
  dm.plot(x+39,4+4,clr);
  dm.plot(x+39,4+1+4,clr);
  dm.plot(x+39+1,4+4,clr);
  dm.plot(x+39+1,4+1+4,clr);

  sprintf(clock, "%02d", RTC.getSeconds());
  dm.putchar(x+42,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+51,0,clock[1],GREEN,0,BLACK);

  dm.sendframe();
  //Serial.println(clock);

}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
  //Serial.println("1");
  // 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)
  //Serial.println("2");
  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;

  //Serial.println("3");

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  //Serial.println("4");
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  //Serial.println("5");
  Udp.endPacket();
  //Serial.println("6");
}

В итоге получилось вот это

015dbb46cc547788abd94e57ccc08f1ec8fa680f70

И видео

Что получилось в итоге? Часы запускаются и пытаются подключиться к WiFi с заранее заданным SSID и паролем. Как только им это удается, то они запрашивают NTP сервер о точном времени и как только получают ответ, обновляют соответствующие значения в RTC. А пока все это крутится, то часы просто берут уже сохраненное ранее время и показывают его.

Просто и дешево. Но для дальнейшей работы необходимо немного расширить функционал и научиться обрабатывать ошибки. К примеру, если WiFi есть, а интернета в нем нет и так далее. Ну и предусмотреть … скажем так, подключение внешних устройств, в качестве которых будет как минимум одна Raspberry Pi плата. Опять же, фотодиод для замера освещенности надо поставить …

В общем, есть что писать в следующем посте 🙂