0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESP32で偽JJY送信して電波時計を誤動作させる

Last updated at Posted at 2025-12-17

はじめに

 マイコンの1st takeと言えばLチカが定番ですが、ターゲットの1st takeというよりもホスト(USB-UARTブリッジ)の動作テスト用という意味でチョッと手間(変調)をかけてJJYに偽てみた。わざわざアンテナを作らなくて出力ピンから数十cmリード線を出すかLチカ回路にでも出力すれば漏れ電波で上に乗っけた電波時計くらいは反応します。チョットでも離すと反応しなくなるのでコレでヨシとしています。まぁ悪い電波で機器を狂わせる improper modification の一種ですね。ちゃんと時計が狂えばOKです。

プログラム

 定番だしAIがチャチャっと書いてくれるんで特段の工夫は無いです。

#include <WiFi.h>
#include "time.h"

// --- 設定 ---
const char* ssid     = "your SSID";
const char* password = "your passwod";
const int TX_PIN     = 26; 
const int FREQUENCY  = 40000; // 福島局:40000, 九州局:60000
const int DUTY_50    = 128;   // 8bit(256)の50%

// --- 信号制御 ---
void signal_on()  { ledcWrite(TX_PIN, DUTY_50); }
void signal_off() { ledcWrite(TX_PIN, 0); }

// 指定した秒(sec)になるまで待機して同期
void Sync_the_second(int sec) {
  struct tm timeinfo;
  while (true) {
    if (!getLocalTime(&timeinfo)) {
      Serial.println("Can't get time");
      delay(100);
      continue;
    }
    if (timeinfo.tm_sec == sec) break;
    delay(1); 
  }
}

// 信号送信の基本単位
void bit2signal(int sec, int ms) {
  Sync_the_second(sec);
  signal_on();
  delay(ms); 
  signal_off();
}

// JJYの各マーカー・ビット定義
void jjy_SyncMarker(int sec) { bit2signal(sec, 200); }
void jjy_bit(int sec, int flag) {
  // JJY仕様: 1(High)は500ms、0(Low)は800msの振幅
  bit2signal(sec, flag ? 500 : 800);
}

// --- 計算補助 ---
int int2bcd(int n) {
  return (n % 10) | (((n / 10) % 10) << 4) | (((n / 100) % 10) << 8);
}

int even_parity(int n) {
  n ^= n >> 8; n ^= n >> 4; n ^= n >> 2; n ^= n >> 1;
  return n & 1;
}

// --- JJYメイン出力 ---
void jjy_output(struct tm *tm) {
  // 通算日の計算
  const int OrdinalDayOfMonth[] = {0,31,59,90,120,151,181,212,243,273,304,334};
  int dd_raw = OrdinalDayOfMonth[tm->tm_mon] + tm->tm_mday;
  if (((tm->tm_year % 4 == 0) && (tm->tm_year % 100 != 0)) || (tm->tm_year % 400 == 0)) {
    if (tm->tm_mon > 1) dd_raw++; // 閏年補正
  }

  // BCDって昭和だよなぁ
  int mm = int2bcd(tm->tm_min);
  int hh = int2bcd(tm->tm_hour);
  int dd = int2bcd(dd_raw);
  int yy = int2bcd(tm->tm_year % 100);
  int ww = tm->tm_wday;

  // 0-59秒の信号送出
  jjy_SyncMarker( 0);                    // :00 M
  jjy_bit( 1, mm & 0x40); jjy_bit( 2, mm & 0x20); jjy_bit( 3, mm & 0x10);
  jjy_bit( 4, 0);
  jjy_bit( 5, mm & 0x08); jjy_bit( 6, mm & 0x04); jjy_bit( 7, mm & 0x02); jjy_bit( 8, mm & 0x01);
  jjy_SyncMarker( 9);                    // :09 P1
  jjy_bit(10, 0); jjy_bit(11, 0);
  jjy_bit(12, hh & 0x20); jjy_bit(13, hh & 0x10);
  jjy_bit(14, 0);
  jjy_bit(15, hh & 0x08); jjy_bit(16, hh & 0x04); jjy_bit(17, hh & 0x02); jjy_bit(18, hh & 0x01);
  jjy_SyncMarker(19);                    // :19 P2
  jjy_bit(20, 0); jjy_bit(21, 0);
  jjy_bit(22, dd & 0x200); jjy_bit(23, dd & 0x100);
  jjy_bit(24, 0);
  jjy_bit(25, dd & 0x80); jjy_bit(26, dd & 0x40); jjy_bit(27, dd & 0x20); jjy_bit(28, dd & 0x10);
  jjy_SyncMarker(29);                    // :29 P3
  jjy_bit(30, dd & 0x08); jjy_bit(31, dd & 0x04); jjy_bit(32, dd & 0x02); jjy_bit(33, dd & 0x01);
  jjy_bit(34, 0); jjy_bit(35, 0);
  jjy_bit(36, even_parity(hh));          // パリティ時
  jjy_bit(37, even_parity(mm));          // パリティ分
  jjy_bit(38, 0); 
  jjy_SyncMarker(39);                    // :39 P4
  jjy_bit(40, 0); 
  jjy_bit(41, yy & 0x80); jjy_bit(42, yy & 0x40); jjy_bit(43, yy & 0x20); jjy_bit(44, yy & 0x10);
  jjy_bit(45, yy & 0x08); jjy_bit(46, yy & 0x04); jjy_bit(47, yy & 0x02); jjy_bit(48, yy & 0x01);
  jjy_SyncMarker(49);                    // :49 P5
  jjy_bit(50, ww & 0x04); jjy_bit(51, ww & 0x02); jjy_bit(52, ww & 0x01);
  jjy_bit(53, 0); jjy_bit(54, 0);        // 閏秒
  jjy_bit(55, 0); jjy_bit(56, 0); jjy_bit(57, 0); jjy_bit(58, 0);
  jjy_SyncMarker(59);                    // :59 P0
}

void setup() {
  Serial.begin(115200);
  ledcAttach(TX_PIN, FREQUENCY, 8);
  signal_off();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi Connected!");

  configTime(9 * 3600, 0, "ntp.nict.jp");
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Waiting for NTP...");
    delay(1000);
    return;
  }

  // 0秒になったら1分間の送信サイクルを開始
  if (timeinfo.tm_sec == 0) {
    const char* wd[] = {"日", "月", "火", "水", "木", "金", "土"};
    Serial.printf("Send JJY: %04d/%02d/%02d (%s) %02d:%02d\n",
                  timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
                  wd[timeinfo.tm_wday], timeinfo.tm_hour, timeinfo.tm_min);
    
    jjy_output(&timeinfo);
  }
  delay(100);
}

まとめ

 僕はLチカ回路のリードを長めして電波時計を乗っけてます。恣意的に日付とか時刻尾を変えてライターで焼けばUARTとかI2Cとか3.3Vや5Vを気にする必要も無いしLチカで書き換えが成功したか点滅間隔を変えるみたいな感じで使ってます。Lチカ点減周波数とかデューティーを変えるよりも数桁の数字を出力できるから表現の幅も広いし7セグLEDを付けるより楽だしね。時計が狂うまでチョイ時間がかかるのが難点だけどSSD1306とかだとフォントデーターを用意したりとか評価やdebugのためにサイズとか手間がかかるけどコレならどこにでもある電波時計とLチカ回路させあればOK。それに実験終了後に放っておけば正規電波拾って正しい時刻に戻るしね。当たり前ですがJJYエミュレーション送信を実用的に使おうとするならチャンとした出力段の検討が必要になります。スピーカーから3倍波を出してどうのとかはLife hack的でナイスだなぁって思うけどLPFがどうのとかまで行っちゃうとアレなんで皆様に御任せします。しいて自分でやるとしたらソフト屋なんでDMAで正弦波かなぁ。別に時計合わせが仕事とか趣味(目的)じゃないからなぁ。狙った通りに時計が狂えば目的は十分に果たしているんです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?