はじめに
電波時計にJJY電波が届かないのでESP-01でJJY信号のシミュレータを作成しました。NTPに接続して時刻合せが可能になりました。
回路図
OLEDで状況を表示します。アンテナはUEW線を適当に巻いたものを使用しています。1Ω程度でしたので330Ωを直列に入れています。
プログラム
プログラムはUTC+8になっています。適宜変更してください。
ESP-01のAnalogWriteのPWM周波数は38kHzが上限のようです。そのため、13,6kHzに設定し(実効で13.3kHzになる)、三倍高調波で40kHzを得ています。38kHzよりは感度が良いようです。
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には現在時刻とコードを表示していますので、眺めながらお待ちください。