EPS-01でJJYシミュレータ

#はじめに
電波時計にJJY電波が届かないのでESP-01でJJY信号のシミュレータを作成しました。NTPに接続して時刻合せが可能になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/f26de431-05fc-b92d-e960-e61d9859ff60.png)

#回路図
OLEDで状況を表示します。アンテナはUEW線を適当に巻いたものを使用しています。1Ω程度でしたので330Ωを直列に入れています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/d7eb8f36-1cf6-5c13-9746-234fc0040829.png)

#プログラム
プログラムはUTC+8になっています。適宜変更してください。
ESP-01のAnalogWriteのPWM周波数は38kHzが上限のようです。そのため、13,6kHzに設定し(実効で13.3kHzになる)、三倍高調波で40kHzを得ています。38kHzよりは感度が良いようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/f13d9478-b68f-7ee5-0165-df6c2d158a41.png)

OLEDの表示には意外に時間を必要としますので、ループ毎に表示するとJJYの規定から外れる可能性が高くなります。タイマーでの割り込みを使用したら不安定になったため、割り込みを使用していません。そのため、OLEDとSerialの出力は空き時間になるようにタイミングを固定しています。


```cpp:JJY.c
///////////////////////////////////////////////////////////////////////
// OLED with u8glib
//
// 1  VCC
// 2  GND
// 3  SCL  -- Clk to I2C SCL (GPIO 2)
// 4  SDA  -- Data to I2C SDA (GPIO 0)
///////////////////////////////////////////////////////////////////////
//
// Thanks!
// https://qiita.com/BotanicFields/items/a78c80f947388caf0d36#62-%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%E5%9F%BA%E6%9D%BF
//
//
//
///////////////////////////////////////////////////////////////////////

#include <ESP8266WiFi.h>  // for WIFI
#include <time.h>     // for time(), localtime()
#include <Wire.h>
#include <SSD1306.h>
#include <SSD1306Wire.h>


#include "U8glib.h"   // OLED disp library
//U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI
SSD1306  display(0x3c, 0, 2, GEOMETRY_128_32); //0x3d for the Adafruit 1.3" OLED, 0x3C being the usual address of the OLED

void drawTime( struct tm* tmNow );
void makeJJYData(struct tm* tmNow);
void disp(void);
struct tm tmPrev;
struct tm td;
unsigned long signalEnd;
time_t timeNow = time(NULL);
struct tm* tmNow;

#define STS_OFF 0x00
#define STS_SENDING 0x01

uint8_t status;

// Wi-Fi設定
#define WIFI_SSID   "YOUR_WIFI_SSDI"
#define WIFI_PASSWORD "YOUR_WIFI_PASS"

// NTP設定
#define TIMEZONE_SGT  (3600 * 8)  // 日本標準時は、UTC（世界標準時）よりも９時間進んでいる。
#define DAYLIGHTOFFSET_SGT  (0)   // 日本は、サマータイム（夏時間）はない。
#define NTP_SERVER1   "ntp.nict.jpntp.nict.jp"  // NTPサーバー
#define NTP_SERVER2   "ntp.jst.mfeed.ad.jp"   // NTPサーバー

// icon
const uint8_t denpa_bits[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
  0x00, 0x1c, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
  0xf8, 0x01, 0x00, 0x07, 0x70, 0xc2, 0xc0, 0x0c, 0x40, 0xc0, 0x61, 0x0c,
  0x00, 0xc0, 0x11, 0x1e, 0x00, 0x00, 0x07, 0x1e, 0x00, 0x00, 0x0e, 0x1f,
  0x00, 0x00, 0x9c, 0x1f, 0x00, 0x80, 0xc8, 0x1f, 0x00, 0x80, 0xe0, 0x1f,
  0x00, 0x40, 0xf0, 0x1f, 0x00, 0x60, 0xf8, 0x0f, 0x00, 0x20, 0xfc, 0x0f,
  0x00, 0x20, 0xff, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00, 0xe0, 0xff, 0x01,
  0x00, 0x80, 0x7f, 0x0c, 0x00, 0x00, 0x04, 0x0e, 0x00, 0x00, 0xc0, 0x0f,
  0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xc0, 0x0f,
  0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x00
};

static unsigned char denpaOFF_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x07, 0x00, 0xc0, 0xc0, 0x0c, 0x00, 0xc0, 0x71, 0x1c,
  0x00, 0xc0, 0x01, 0x1e, 0x00, 0x00, 0x07, 0x1e, 0x00, 0x00, 0x0e, 0x1f,
  0x00, 0x00, 0x9c, 0x1f, 0x00, 0x80, 0xc8, 0x1f, 0x00, 0x80, 0xe0, 0x1f,
  0x00, 0x40, 0xf0, 0x1f, 0x00, 0x60, 0xf8, 0x0f, 0x00, 0x20, 0xfc, 0x0f,
  0x00, 0x20, 0xff, 0x07, 0x00, 0xf0, 0xff, 0x03, 0x00, 0xe0, 0xff, 0x01,
  0x00, 0x80, 0x7f, 0x0c, 0x00, 0x00, 0x04, 0x0e, 0x00, 0x00, 0xc0, 0x0f,
  0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xc0, 0x0f,
  0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x00
};

int Ready = 0;

byte dataJJY[60];
#define JJY_Marker  0xFF
#define JJY_OFF     0x00
#define JJY_ON      0x01

int Int3bcd(int a)
{
  return (a % 10) + (a / 10 % 10 * 16) + (a / 100 % 10 * 256);
}
void makeBCD(byte* mem, int dat)
{
  mem[0] = (dat & 0x08) ? JJY_ON : JJY_OFF;
  mem[1] = (dat & 0x04) ? JJY_ON : JJY_OFF;
  mem[2] = (dat & 0x02) ? JJY_ON : JJY_OFF;
  mem[3] = (dat & 0x01) ? JJY_ON : JJY_OFF;
}
void makeBCDMin(byte* mem, int dat)
{
  mem[0] = (dat & 0x40) ? JJY_ON : JJY_OFF;
  mem[1] = (dat & 0x20) ? JJY_ON : JJY_OFF;
  mem[2] = (dat & 0x10) ? JJY_ON : JJY_OFF;
  mem[3] = JJY_OFF;
  mem[4] = (dat & 0x08) ? JJY_ON : JJY_OFF;
  mem[5] = (dat & 0x04) ? JJY_ON : JJY_OFF;
  mem[6] = (dat & 0x02) ? JJY_ON : JJY_OFF;
  mem[7] = (dat & 0x01) ? JJY_ON : JJY_OFF;
}
void makeBCDHour(byte* mem, int dat)
{
  mem[0] = JJY_OFF;
  mem[1] = JJY_OFF;
  mem[2] = (dat & 0x20) ? JJY_ON : JJY_OFF;
  mem[3] = (dat & 0x10) ? JJY_ON : JJY_OFF;
  mem[4] = JJY_OFF;
  mem[5] = (dat & 0x08) ? JJY_ON : JJY_OFF;
  mem[6] = (dat & 0x04) ? JJY_ON : JJY_OFF;
  mem[7] = (dat & 0x02) ? JJY_ON : JJY_OFF;
  mem[8] = (dat & 0x01) ? JJY_ON : JJY_OFF;
}
void makeBCDDays(byte* mem, int dat)
{
  mem[0] = JJY_OFF;
  mem[1] = JJY_OFF;
  mem[2] = (dat & 0x200) ? JJY_ON : JJY_OFF;
  mem[3] = (dat & 0x100) ? JJY_ON : JJY_OFF;
  mem[4] = JJY_OFF;
  mem[5] = (dat & 0x080) ? JJY_ON : JJY_OFF;
  mem[6] = (dat & 0x040) ? JJY_ON : JJY_OFF;
  mem[7] = (dat & 0x020) ? JJY_ON : JJY_OFF;
  mem[8] = (dat & 0x010) ? JJY_ON : JJY_OFF;
  mem[9] = JJY_Marker;
  mem[10] = (dat & 0x08) ? JJY_ON : JJY_OFF;
  mem[11] = (dat & 0x04) ? JJY_ON : JJY_OFF;
  mem[12] = (dat & 0x02) ? JJY_ON : JJY_OFF;
  mem[13] = (dat & 0x01) ? JJY_ON : JJY_OFF;

}
int Parity8(int a)
{
  int pa = a;
  for (int i = 1; i < 8; ++i)
  {
    pa += a >> i;
  }
  return pa % 2;
}
void makeJJYData(struct tm* tmNow)
{
  dataJJY[0] = JJY_Marker;
  // min
  makeBCDMin( &dataJJY[1], Int3bcd(tmNow->tm_min) ); // min
  dataJJY[9] = JJY_Marker;
  // hour
  makeBCDHour( &dataJJY[10], Int3bcd(tmNow->tm_hour) ); // hour 10
  dataJJY[19] = JJY_Marker;
  // days
  makeBCDDays( &dataJJY[20], Int3bcd(tmNow->tm_yday + 1) ); // days
  // parity
  dataJJY[34] = JJY_OFF;
  dataJJY[35] = JJY_OFF;
  dataJJY[36] = (Parity8(Int3bcd(tmNow->tm_hour))) ? JJY_ON : JJY_OFF;
  dataJJY[37] = (Parity8(Int3bcd(tmNow->tm_min))) ? JJY_ON : JJY_OFF;
  dataJJY[38] = JJY_OFF;
  dataJJY[39] = JJY_Marker;
  // year
  dataJJY[40] = JJY_OFF;
  makeBCD( &dataJJY[41], (tmNow->tm_year % 100) / 10 );  // year 10
  makeBCD( &dataJJY[45], tmNow->tm_year % 10 );    // year 01
  dataJJY[49] = JJY_Marker;
  // day of week
  dataJJY[50] = (tmNow->tm_wday & 0x04) ? JJY_ON : JJY_OFF;
  dataJJY[51] = (tmNow->tm_wday & 0x02) ? JJY_ON : JJY_OFF;
  dataJJY[52] = (tmNow->tm_wday & 0x01) ? JJY_ON : JJY_OFF;
  dataJJY[53] = JJY_OFF;
  dataJJY[54] = JJY_OFF;
  dataJJY[55] = JJY_OFF;
  dataJJY[56] = JJY_OFF;
  dataJJY[57] = JJY_OFF;
  dataJJY[58] = JJY_OFF;
  dataJJY[59] = JJY_Marker;

}

void conv_char(char *str, byte* data, int len)
{
  for (int i = 0; i < len; i++)
  {
    char moji;
    switch ( data[i] )
    {
      case 0:
        moji = '0';
        break;
      case 1:
        moji = '1';
        break;
      case JJY_Marker:
        moji = 'P';
        break;
      default:
        moji = '?';
    }
    str[i] = moji;
  }
  str[len] = '\0';
}


void setup()
{
  Serial.begin(115200);
  Serial.println("POWER ON");

  pinMode(3, OUTPUT);  // IO3 SIGNAL OUTPUT
  analogWriteFreq(13600); // PWM Frequency = carrior freq. 13600=13.3kHz

  Wire.pins(0, 2);  // Start the OLED with GPIO 0 and 2 on ESP-01
  Wire.begin(0, 2); // 0=sda, 2=scl
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.setBrightness( 16 ); // 0: low brightness, 254 : high
  // Connect to wifi
  Serial.println("");
  Serial.print("Connecting to ");
  Serial.print(WIFI_SSID);
  display.drawString(0, 0, "Connecting to Wifi...");
  display.display();
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi at ");
  Serial.print(WiFi.localIP());
  Serial.println("");
  display.drawString(0, 11, "Connected.");
  display.drawString(0, 22, "NTP sync...");
  display.display();

  // Wi-Fiセットアップ
  // WiFiネットワーク接続
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
  }
  // NTP同期
  configTime( TIMEZONE_SGT, DAYLIGHTOFFSET_SGT, NTP_SERVER1, NTP_SERVER2 );

  int i = 0;
  time_t timeNow = time(NULL);
  while ( ((timeNow / 100) * 100) == TIMEZONE_SGT )
  {
    timeNow = time(NULL);
    Serial.println(timeNow);
    delay(1000);
    i++;
    if (i > 30) // 偶然起動タイミングが8:00だった場合は、強制的にぬける
      break;
  }
  display.drawString(0, 22, "NTP sync...DONE");
  display.display();
  Serial.println("NTP READY");

  delay(100);
}

void loop()
{
  if (WiFi.status() == WL_CONNECTED) //Check WiFi connection status
  {
    // 現在時刻の取得
    timeNow = time(NULL);
    tmNow = localtime(&timeNow);
    td = *tmNow ;

    if ( tmNow->tm_sec != tmPrev.tm_sec ) // 秒変更のトリガー
    {
      // making JJY data
      makeJJYData(tmNow);

      // Send signal
      signalEnd = singalLength( dataJJY[tmNow->tm_sec] ) + millis();
      signalOn();
      status = STS_SENDING;

      disp();

      tmPrev = *tmNow ; // Save current time
    }
    else if ( STS_SENDING == status )
    {
      if ( signalEnd <= millis() )
      {
        signalOff();
        status = STS_OFF;
        disp();
      }
    }

  }
  else // attempt to connect to wifi again if disconnected
  {
    display.clear();
    display.drawString(0, 10, "Connecting to Wifi...");
    display.display();
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    display.drawString(0, 10, "Connected.");
    display.display();
    delay(1000);
  }

  delay(1);
}

void signalOn(void)
{
  analogWrite(3, 512);  // Duty 50%
}
void signalOff(void)
{
  analogWrite(3, 0);  // OFF
}
unsigned long singalLength(byte sig)
{
  unsigned long len = 0;
  switch ( sig )
  {
    case JJY_Marker:  //  0xFF
      len = 200L; // 0.2sec
      break;
    case JJY_OFF:   //     0x00
      len = 800L; // 0.8sec
      break;
    case JJY_ON:    //      0x01
      len = 500L; // 0.5sec
    default:
      len = 500L; // 0.5sec
      break;
  }
  return len;
}

void disp(void)
{
  // 日付
  char szDate[32];
  sprintf( szDate, "%04d/%02d/%02d",
           tmNow->tm_year + 1900,
           tmNow->tm_mon + 1,
           tmNow->tm_mday
         );

  // 時刻
  char szTime[32];
  sprintf( szTime, "%02d:%02d:%02d",
           tmNow->tm_hour,
           tmNow->tm_min,
           tmNow->tm_sec
         );

  // Display the date and time
  Serial.println("");
  Serial.print("Local date: ");
  Serial.println(szDate);
  Serial.print("Local time: ");
  Serial.println(szTime);

  // Display the date and time
  Serial.println("");
  Serial.print("Local date: ");
  Serial.println(szDate);
  Serial.print("Local time: ");
  Serial.println(szTime);

  char jjydata[64];
  memset( jjydata, 0, sizeof(jjydata));
  conv_char( jjydata, dataJJY , 60);
  Serial.println(jjydata);

  // print the date and time on the OLED
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString( 5, 0, szTime);
  display.drawString( 5, 10, szDate);
  char strJJYdisp[32];
  memset( strJJYdisp, 0x00, sizeof(strJJYdisp));
  strncpy( strJJYdisp, &jjydata[ tmNow->tm_sec ], 14);
  display.drawString( 5, 21, strJJYdisp );
  if ( STS_SENDING == status )
    display.drawXbm(92, 0, 30, 32, denpa_bits);
  else
    display.drawXbm(92, 0, 30, 32, denpaOFF_bits);

  display.display();

}

```


#おわりに
時間合せには4-5分は必要とするようです。気長にお待ちください。
OLEDには現在時刻とコードを表示していますので、眺めながらお待ちください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/5c4d6765-dae6-8aad-f79c-a5496bae5dc8.png)