ESP8266をWifiモデムとして使う

アナログ電波時計


前回、AT Version1.5からSNTPによる時刻問い合わせが使えることを紹介しました。
そこで、ILI9341のTFTと組み合わせてアナログ時計を作ってみました。
起動時と0時0分0秒にSNTPを使って時刻合わせをしているので電波時計並みに正確です。

スケッチサイズはぎりぎり32Kで収まります。
有線LANモジュール+Ethernetライブラリでは絶対に無理です。

最大32256バイトのフラッシュメモリのうち、スケッチが28498バイト(88%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が965バイト(47%)を使っていて、ローカル変数で1083バイト使うことができます。


MCUFRIEND_kbvライブラリは多数のTFTをサポートしているので、ILI9341以外のTFTでも動きます。
以下はTFTにR61505を使っています。


UNOのクローンはピンソケット(メス)の内側にピンヘッダー(オス)を付けることができます。
そこで、TFTでは使わないD10からD13と電源関係のピンにアングル付きのピンヘッダーを付けて
これらのピンを外に出しています。
D0とD1を使うとデバッグができなくなるので、ESP-01とのUARTにはTFTで使わないD10/D11を使っています。


こちらのTime ライブラリを 使ってRTCへの時刻設定とRTCからの時刻取得を行っています。
SNTPで正しい応答が戻るまで、少し時間がかかることが分かりましたのでリトライしています。
起動時の時刻合わせは本物っぽくしてみました。
リブートさせるコードが分かったので、12時と24時にリブートして、時刻合わせを行います。

TFTシールドもESP8266も消費電流が大きいです。
5V1AのUSBアダプターからの給電では安定して動きません。
2A以上のUSBアダプターが必要です。
/*
 * Analog Radio Clock with AT Command
 * 
 * ESP8266----------ATmega
 * TX     ----------RX(D10)
 * RX     ----------TX(D11)
 *
 */

#include <SoftwareSerial.h>
#include <TimeLib.h>     // https://github.com/PaulStoffregen/Time

#define rxPin    10    // D10
#define txPin    11    // D11

SoftwareSerial Serial2(rxPin, txPin); // RX, TX

#include <avr/wdt.h>
#include "Adafruit_GFX.h"// Hardware-specific library
#include <MCUFRIEND_kbv.h>  // https://github.com/prenticedavid/MCUFRIEND_kbv

MCUFRIEND_kbv tft;

// Color definitions
#define BLACK       0x0000      /*   0,   0,   0 */
#define NAVY        0x000F      /*   0,   0, 128 */
#define DARKGREEN   0x03E0      /*   0, 128,   0 */
#define DARKCYAN    0x03EF      /*   0, 128, 128 */
#define MAROON      0x7800      /* 128,   0,   0 */
#define PURPLE      0x780F      /* 128,   0, 128 */
#define OLIVE       0x7BE0      /* 128, 128,   0 */
#define LIGHTGRAY   0xC618      /* 192, 192, 192 */
#define DARKGRAY    0x7BEF      /* 128, 128, 128 */
#define BLUE        0x001F      /*   0,   0, 255 */
#define GREEN       0x07E0      /*   0, 255,   0 */
#define CYAN        0x07FF      /*   0, 255, 255 */
#define RED         0xF800      /* 255,   0,   0 */
#define MAGENTA     0xF81F      /* 255,   0, 255 */
#define YELLOW      0xFFE0      /* 255, 255,   0 */
#define WHITE       0xFFFF      /* 255, 255, 255 */
#define ORANGE      0xFD20      /* 255, 165,   0 */
#define GREENYELLOW 0xAFE5      /* 173, 255,  47 */
#define PINK        0xF81F
#define GRAY        0x5AEB

#define TIME_ZONE   9 // Change your time zone
#define _DEBUG_     0

float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0;    // Saved H, M, S x & y multipliers
float sdeg=0, mdeg=0, hdeg=0;
uint16_t osx=120, osy=120, omx=120, omy=120, ohx=120, ohy=120;  // Saved H, M, S x & y coords
uint16_t x00=0, x11=0, y00=0, y11=0;
uint32_t targetTime = 0;                    // for next 1 second timeout
boolean initial = 1;
time_t epoch;
char buf[64];

void reboot() {
  wdt_disable();
  wdt_enable(WDTO_15MS);
  while (1) {}
}

void putChar(char c) {
  char tmp[10];
  if ( c == 0x0a) {
    Serial.println();
  } else if (c == 0x0d) {
   
  } else if ( c < 0x20) {
    uint8_t cc = c;
    sprintf(tmp,"[0x%.2X]",cc);
    Serial.print(tmp);
  } else {
    Serial.print(c);
  }
}

//Wait for specific input string until timeout runs out
bool waitForString(char* input, int length, unsigned int timeout) {
  unsigned long end_time = millis() + timeout;
  char current_byte = 0;
  int index = 0;

   while (end_time >= millis()) {
   
      if(Serial2.available()) {
       
        //Read one byte from serial port
        current_byte = Serial2.read();
//        Serial.print(current_byte);
        if (_DEBUG_) putChar(current_byte);
        if (current_byte != -1) {
          //Search one character at a time
          if (current_byte == input[index]) {
            index++;
           
            //Found the string
            if (index == length) {             
              return true;
            }
          //Restart position of character to look for
          } else {
            index = 0;
          }
        }
      }
  } 
  //Timed out
  return false;
}

//Wait for CIPSNTPTIME response
int getSNTPtime(char* buf, int szbuf, unsigned int timeout) {
  int len=0;
  int pos=0;
  char line[40];

  Serial2.print("AT+CIPSNTPTIME?\r\n");
  long int time = millis();

  memset(line,0,sizeof(line));
  while( (time+timeout) > millis()) {
    while(Serial2.available())  {
      char c = Serial2.read(); // read the next character.
      if (_DEBUG_) Serial.print(c);
      if (c == 0x0d) {
         
      } else if (c == 0x0a) {
        if (_DEBUG_) {
          Serial.print("Read=[");
          Serial.print(line);
          Serial.println("]");
        }
        if (strcmp(line,"OK") == 0) return len;
        int offset;
        for(offset=0;offset<pos;offset++) {
          if(line[offset] == '+') break;
        }
        if (strncmp(&line[offset],"+CIPSNTPTIME:",13) == 0) {
          strcpy(buf,&line[13+offset]);
          len = strlen(buf);
          buf[len] = 0;
        }
        pos=0;
        line[pos]=0;
      } else {
        line[pos++]=c;
        if (pos == 40) return -1;
        line[pos]=0;
      }
    } 
  }
  return -1;
}

#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
#define SECS_PER_MIN  ((time_t)(60UL))
#define SECS_PER_HOUR ((time_t)(3600UL))
#define SECS_PER_DAY  ((time_t)(SECS_PER_HOUR * 24UL))
#define DAYS_PER_WEEK ((time_t)(7UL))
#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK))
#define SECS_PER_YEAR ((time_t)(SECS_PER_DAY * 365UL)) // TODO: ought to handle leap years
#define SECS_YR_2000  ((time_t)(946684800UL)) // the time at the start of y2k

// Convert Sat Jan 20 08:41:55 2018 to time_t
time_t makeTime(char* str) {
  char c_dow[10];
  char c_month[10];
  int year,month,day,hour,minute,second,dow;
  char *t_dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  char *t_month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

  sscanf(str,"%s %s %d %d:%d:%d %d",c_dow,c_month,&day,&hour,&minute,&second,&year);

  for(dow=0;dow<7;dow++) {
    if (strcmp(c_dow,t_dow[dow]) == 0) break;
  }

  for(month=1;month<13;month++) {
    if (strcmp(c_month,t_month[month-1]) == 0) break;
  }

  // API starts months from 1, this array starts from 0
  static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};

  time_t seconds;
  int i;

  // seconds from 1970 till 1 jan 00:00:00 of the given year
  seconds= (year-1970)*(SECS_PER_DAY * 365);
  for (i = 1970; i < year; i++) {
    if (LEAP_YEAR(i)) {
      seconds +=  SECS_PER_DAY;   // add extra days for leap years
     }
  }

  // add days for this year, months start from 1
  for (i = 1; i < month; i++) {
    if ( (i == 2) && LEAP_YEAR(year)) {
      seconds += SECS_PER_DAY * 29;
    } else {
      seconds += SECS_PER_DAY * monthDays[i-1];  //monthDay array starts from 0
    }
  }
  seconds+= (day-1) * SECS_PER_DAY;
  seconds+= hour * SECS_PER_HOUR;
  seconds+= minute * SECS_PER_MIN;
  seconds+= second;

  return seconds;
}

//Send AT Command
void sendCommand(char* buff) {
  if (_DEBUG_) {
    Serial.println("");
    Serial.print(buff);
    Serial.println("-->");
  }
  Serial2.println(buff);
  Serial2.flush();
}

//Print error
void errorDisplay(char* buff) {
  Serial.print("Error:");
  Serial.println(buff);
  while(1) {}
}


void clearBuffer() {
  while (Serial2.available())
    Serial2.read();
//  Serial.println();
}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

void drawWatch(char * dateStr, uint16_t frame) {
  uint16_t wid = tft.width();
  uint16_t ht = tft.height();
  if (wid < ht) {
    tft.setRotation(0);
  } else {
    tft.setRotation(1);
  }
  tft.fillScreen(DARKGRAY);
  tft.setTextColor(WHITE, DARKGRAY);  // Adding a background colour erases previous text automatically
 
  // Draw clock face
  tft.fillCircle(120, 120, 118, frame);
  tft.fillCircle(120, 120, 110, BLACK);

  // Draw 12 lines
  for(int i = 0; i<360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x00 = sx*114+120;
    y00 = sy*114+120;
    x11 = sx*100+120;
    y11 = sy*100+120;

    tft.drawLine(x00, y00, x11, y11, GREEN);
  }

  // Draw 60 dots
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x00 = sx*102+120;
    y00 = sy*102+120;
    // Draw minute markers
    tft.drawPixel(x00, y00, WHITE);
   
    // Draw main quadrant dots
    if(i==0 || i==180) tft.fillCircle(x00, y00, 2, WHITE);
    if(i==90 || i==270) tft.fillCircle(x00, y00, 2, WHITE);
  }

  tft.fillCircle(120, 121, 3, WHITE);
//  tft.setCursor(20, 260);
  tft.setCursor(30, 260);
  tft.setTextSize(3);
  tft.println(dateStr);
 
}

void drawTime(uint8_t hh, uint8_t mm, uint8_t ss) {
    // Pre-compute hand degrees, x & y coords for a fast screen update
    sdeg = ss*6;                  // 0-59 -> 0-354
    mdeg = mm*6+sdeg*0.01666667;  // 0-59 -> 0-360 - includes seconds
    hdeg = hh*30+mdeg*0.0833333;  // 0-11 -> 0-360 - includes minutes and seconds
    hx = cos((hdeg-90)*0.0174532925);   
    hy = sin((hdeg-90)*0.0174532925);
    mx = cos((mdeg-90)*0.0174532925);   
    my = sin((mdeg-90)*0.0174532925);
    sx = cos((sdeg-90)*0.0174532925);   
    sy = sin((sdeg-90)*0.0174532925);

    if (ss==0 || initial) {
      initial = 0;
      // Erase hour and minute hand positions every minute
      tft.drawLine(ohx, ohy, 120, 121, BLACK);
      ohx = hx*62+121;   
      ohy = hy*62+121;
      tft.drawLine(omx, omy, 120, 121, BLACK);
      omx = mx*84+120;   
      omy = my*84+121;
    }

      // Redraw new hand positions, hour and minute hands not erased here to avoid flicker
      tft.drawLine(osx, osy, 120, 121, BLACK);
      osx = sx*90+121;   
      osy = sy*90+121;
      tft.drawLine(osx, osy, 120, 121, RED);
      tft.drawLine(ohx, ohy, 120, 121, WHITE);
      tft.drawLine(omx, omy, 120, 121, WHITE);
      tft.drawLine(osx, osy, 120, 121, RED);

      tft.fillCircle(120, 121, 3, RED);
}

void setup(void)
{
  delay(1000);
  Serial.begin(9600);

  //Make sure ESP8266 is set to 4800
  Serial2.begin(4800);

  //Enable autoconnect
  sendCommand("AT+CWAUTOCONN=1");
  if (!waitForString("OK", 2, 10000)) {
    errorDisplay("AT+CWAUTOCONN Fail");
  }
  clearBuffer();

  //Restarts the Module
  sendCommand("AT+RST");
  if (!waitForString("WIFI GOT IP", 11, 10000)) {
    errorDisplay("ATE+RST Fail");
  }
  clearBuffer();

  //Local echo off
  sendCommand("ATE0");
  if (!waitForString("OK", 2, 1000)) {
    errorDisplay("ATE0 Fail");
  }
  clearBuffer();

  if (_DEBUG_) {
    //Get local IP address
    sendCommand("AT+CIPSTA?");
    if (!waitForString("OK", 2, 1000)) {
      errorDisplay("AT+CIPSTA? Fail");
    }
    clearBuffer();
  }
 
  //Sets the Configuration of SNTP
  sprintf(buf,"AT+CIPSNTPCFG=1,%d\r\n",TIME_ZONE);
  sendCommand(buf);
  if (!waitForString("OK", 2, 1000)) {
    errorDisplay("AT+CIPSNTPCFG Fail");
  }
  clearBuffer();

  //Wait for SNTP response
  while(1) {
    if (getSNTPtime(buf, 64, 5000)) {
      if (_DEBUG_) Serial.println("SNTP Time is [" + String(buf) +"]");
      epoch = makeTime(buf);
      Serial.println("epoch = " + String(epoch));
      if (epoch != 0) break;
      delay(1000);
    } else {
       errorDisplay("getSNTPtime Fail");
    }
  }
 
  setTime(epoch);
  Serial.print("now=" + String(year()) + "/" + String(month()) + "/" + String(day()));
  Serial.println(" " + String(hour()) + ":" + String(minute()) + ":" + String(second()));

  uint16_t ID = tft.readID();
  Serial.print("Device ID: 0x"); Serial.println(ID, HEX);
  tft.begin(ID);
  uint16_t wid = tft.width();
  uint16_t ht = tft.height();
  Serial.println("width:" + String(wid) + " height:" + String(ht));

  char dateStr[11];
  sprintf(dateStr,"%04d/%02d/%02d %s",year(),month(),day());
  if (hour() < 13) drawWatch(dateStr,ORANGE);
  else drawWatch(dateStr,GREEN);

  Serial.println("Wait for a moment");
  uint8_t h2 = hour();
  if (h2 > 13) h2 = h2 - 12;
  for(int hhh=0;hhh<h2;hhh++) {
    for(int mmm=0;mmm<60;mmm++) {
      drawTime(hhh,mmm,0);
      delay(10);
    }
  }
  for(int mmm=0;mmm<=minute();mmm++) {
    drawTime(hour(),mmm,0);
    delay(10);
  }
  for(int sss=0;sss<=second();sss++) {
    drawTime(hour(),minute(),sss);
    delay(10);
  }
  targetTime = millis() + 1000;

}

void loop() {
  char dateStr[11];
  uint8_t hh,mm,ss;

  if (targetTime < millis()) {
    targetTime = millis()+1000;
    hh = hour();
    mm = minute();
    ss = second();

    if (hh == 12 && mm == 0 && ss == 0) reboot();
    if (hh == 0 && mm == 0 && ss == 0) reboot();
    drawTime(hh, mm, ss);
  }
}

D12とD13のディジタルピンが余っています。
そこで、コンパレータ経由でアナログ光センサーを付けて、部屋の明るさに応じてTFTを消したり付けたりしてみました。

続く....