LoginSignup
1
1

More than 3 years have passed since last update.

EPS-01でJJYシミュレータ

Posted at

はじめに

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

image.png

回路図

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

image.png

プログラム

プログラムはUTC+8になっています。適宜変更してください。
ESP-01のAnalogWriteのPWM周波数は38kHzが上限のようです。そのため、13,6kHzに設定し(実効で13.3kHzになる)、三倍高調波で40kHzを得ています。38kHzよりは感度が良いようです。
image.png

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

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(世界標準時)よりも9時間進んでいる。
#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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1