「JJY」と言うのは、NICT(情報通信研究機構)が運用する、標準周波数局のコールサインです。所謂、「電波時計」が受信している電波を送信している送信局です。
NICT、JJYについて検索する(別ウインドウが開きます)
現在、日本には、福島県と九州(佐賀県)に送信局があり、福島からは40kHz、九州からは60kHzのキャリア周波数でタイムコードが送信されています。
この電波が結構シビアみたいで、昔、同じ市内のアパートに住んでいた時にはちゃんと受信したのですが、今のマンション(直線距離で約800m移動)に引っ越してから、窓際に置こうが、ベランダ(外)に置こうが、一向に受信してくれなくなってしまいました。
つまり、居住領域という大雑把な要素だけではなく、建物の構造、向き、位置、周囲の建物の状態、等々、様々な要因によって、受信できたり、出来なかったりと言うことが起こるようです。
少なくとも、現在の筆者宅では、電波を受信できず、折角の電波時計が意味をなしていません。そこで、JJYが送ってくるタイムコードをArduinoでエミュレートしてみよう、と言うのが今回のテーマです。
きっかけは、「JJYのエミュレーションをスマホでできるアプリ(別ウインドウが開いて検索します)」を見かけたことです。
何だか、トリッキィな方法を考える方がいらっしゃるのですね。スマホにイヤホンを接続すると、イヤホンからは13kHzの音が流れるそうです。13kHz≒40kHz/3ということでしょうか??音が割れるほどの音量にすることで波形を崩し、フーリエ変換とかした時に出てくる高調波成分で40kHzの信号を作っているみたいです。しかも、イヤホンのケーブルをアンテナ代わりに使うとは……。
ここのページでも前回(「#5-8 MMLで音楽を奏でる」で)、Arduinoを使ってイヤホンから音楽を奏でてみたので、スマホと同じ様にJJYエミュレータを作れるはずだ、と言うのがスタートです。
この実験が可能な環境は、かなり限られますので、以下の条件を満たせているか、まずはチェックお願いします。
これらの条件を満たしている方は、先に進みましょう。
多分、普通に電波が受信できる環境にお住まいの方は、こんなことで煩わされたりしないのでしょうね……。標準電波を受信できない環境では、電波時計がただの時計になり下がり、定期的に合わせて回るのが煩わしくてなりません……。
冒頭で検索したNICT、JJYのページから、電波の仕様を詳しく知ることができます。
要約すると、1分毎にタイムコード(下図)が送信されています。
「電波」であることを除けば、よくある通信方式ですね。「#4-3 赤外線リモコン(送信)」で試した、赤外線リモコンの送信波形(キャリア周波数38kHz、デューティ1/3)が近いです。
Arduinoでは、高調波を使わずとも直接40kHzのPWMを出力できます。スマホアプリの様に、13kHzの可聴域を使うと、イヤホンから漏れる高音が耳障りなので、仕様通り40kHzのPWM(デューティ50%)を使います。
ちょっと心配なのは、信号の出力強度です。スマホのアプリは13kHzの主信号に対する、高調波成分を使っている(主信号より出力は低下する)のに対して、Arduinoでは直接40kHzの信号を使うので、同じ感覚で出力すると電波法に違反する「違法電波」になりかねません。
この実験においては、電波法に違反しないように細心の注意を払う必要があります。具体的な注意点は、後ほど説明します。
タイマを使って時間を計測しながら、1秒毎にデータを送信します。キャリアの発信時間(0.2s、0.8s、0.5s)が1/10秒単位なので、1/10秒間隔で割り込みを発生して制御します。
8ビットタイマ(TC2)ではカウンタ(0〜255)が足りなくて、1/10秒間隔の割り込みを発生することができません。……clkio/1,024のカウントクロックを使った時で、約16msが一番長い間隔の割り込み周期となります……。
今回のアプリケーションでは、もう一つ、キャリアとなる40kHzのPWMを出力するのにもタイマが必要なので、TC2はPWM出力用に使用することにして、1/10秒間隔の割り込みは16ビットタイマ(TC1)を使います。
以下にTC1、TC2のタイマ設定値を示します。
TC1(1/10秒毎割り込み設定) | |||
レジスタ名 | アドレス | 設定値 | 説明 |
TCCR1A | 0x0080 | 0x00 |
波形モード:CTC(OCR1A) Mode 出力モード:Normal port |
TCCR1B | 0x0081 | 0x0b |
波形モード:CTC(OCR1A) Mode クロック選択:clkio/64 |
OCR1A | 0x0088 | 0x61a7 | 割り込み周期1/10秒 |
TIMSK1 | 0x006f | 0x02 | OCI1A割り込み有効 |
TC2(40kHzデューティ50%のPWM出力設定) | |||
レジスタ名 | アドレス | 設定値 | 説明 |
TCCR2A | 0x00b0 | 0x43 |
波形モード:Fast PWM Mode (OCR2A) 出力モード:toggle OC2A |
TCCR2B | 0x00b1 | 0x08 |
波形モード:Fast PWM Mode (OCR2A) クロック選択:clkio/1 |
OCR2A | 0x00b3 | 0xc7 | PWM周波数40kHz |
タイムコードの送信は毎分0秒から始まって、60秒間続きます。この期間をさらに1/10に分割しますので、1分間のタイムコード中に600回の割り込みが発生することになります。従って、毎分の0秒でリセットして、1/10秒毎に599までカウントするカウンタjjy_cntを用意し、タイムスロットを管理することにします。
以下、制御コードの一部です。
ISR (TIMER1_COMPA_vect)
{
// OCI1A interrupt service routine
uint8 cnt1;
uint8 cnt2;
// counter
jjy_dsec++;
jjy_cnt++;
if (jjy_cnt>=600)
jjy_cnt=0;
// send data
cnt1=jjy_cnt/10;
cnt2=jjy_cnt - cnt1*10;
if (jjy_dat[cnt1]==jjy_BIT0)
{
// bit "0"
if (cnt2==0)
{
// pwm on
TCCR2B |= 0x01;
}
else if (cnt2>=8)
{
// pwm off
TCCR2B &= (~0x07);
}
}
else if (jjy_dat[cnt1]==jjy_BIT1)
:
タイムスロットjjy_cntのカウント部分と、それを秒カウントcnt1、1/10秒カウントcnt2に分離した後で、キャリアの発信と停止を制御している部分を示しています。全体像は、完成版プログラムでご確認ください。
カレンダ日付を取り扱うとき、どうしても避けて通れないのが、「うるう年」です。定義に従って、「4で割り切れて100で割り切れない年、または、400で割り切れる年」をうるう年と判定します。
jjy_lp=false;
if (jjy_yr%400==0 || (jjy_yr%4==0 && jjy_yr%100!=0))
jjy_lp=true;
JJYのタイムコードには、月/日と言う概念が無く、1月1日からの経過日数が使われています。よって、mo月dy日という日付から、経過日数に換算する必要があります。
まず、必要なのが、各月の日数。覚え方は、「西(2,4)向く(6,9)士(サムライ、11)」です。これをテーブルにしておきます。
const char jjy_dim[13]= // days in the month
{
0, // +1 offset
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
};
あとは、2月を越える月だった場合に、うるう年判定結果が必要になります。
jjy_dy=dy;
for (idx=1; idx<mo; idx++)
jjy_dy += jjy_dim[idx];
if (mo>2)
jjy_dy += jjy_lp;
曜日の計算式って、考え出すと、「キィーッ」って、なりますネ……。ところが、ちゃんと曜日を求めることのできる「ツェラーの公式」というものがあるそうです。ツェラーの公式を使って、日付から曜日を求める関数を作っておきます。
int jjy_day_of_week (
int yr,
char mo,
char dy)
{
// calculate day of week in jjy format
// return: Sun0 .. Sat6
char yy;
char cc;
char rr;
char hh;
if (mo<=2)
{
yr--;
mo += 12;
}
cc=yr/100;
yy=yr - cc*100;
rr=-2*cc + cc/4;
hh=dy + 26*(mo + 1)/10 + yy + yy/4 + rr;
hh=(hh + 7 - 1)%7;
return (hh);
}
この関数が返すのは、日曜日を0〜土曜日を6とする、JJYの曜日コードです。
いつもの様に、シリアルモニタからコマンドを実行可能にしておきます。
現在時刻を取得するため、「#2-5 リアルタイムクロックを使ってみる」で使ったRTCを使用可能としておきます。ただし、RTCが無くても、マニュアルで現在時刻を設定できる様にしてあります。
微弱無線局を理解する
「イヤホンケーブルをアンテナ代わりにする」と言っても、インピーダンスマッチングを取っていない回路では、大した電波が飛ぶはずがありません。言ってみたら、「電子機器からノイズが漏れた」程度の話です。しかし、ノイズだろうが何だろうが、規定された強度を越えたら、それは「違法電波」です。 |
ここまで到達した皆様は、これから実験する内容に関係する電波法について理解したとみなします。
まず、RTC(DS1302)をお持ちの方は、以下の様に接続します。RTCはオプションなので、お持ちでない場合は、接続しなくてもOKです。
UNO | 接続 | RTC(DS1302) |
5V | → | VCC |
GND | ⇔ | GND |
D5 | → | RST |
D6 | ⇔ | DAT |
D7 | → | CLK |
JJYエミュレーション信号の出力回路構成は以下の通りです。
前回(「#5-8 MMLで音楽を奏でる」)と、ほぼ同じ回路となっています。ただし、信号の振幅を調整するLPFの回路定数(時定数)は、十分大きな値(C1=10μF、R1=1MΩ)からスタートします。異なる回路定数を使用する場合は、最初の一回目で、万が一にも「微弱無線局」で規定された電波強度を超えないように、細心の注意を払って回路定数を決めてください。
イヤホンのコードを電波時計の周りにループ状に配置します。実験の様子は以下の様な感じでした。
※可聴域外(40kHz)の信号を、そこそこの振幅で送り込むことになるので、イヤホンにダメージを与えないとも限りません。壊れても惜しくないようなイヤホンをお使いください。ありませんか?何かのおまけについてきた形ばかりのイヤホン。音も安っぽいので、とても音楽を聴く気になれずに、しまい込んであるような……。ちょっと探したら、こんなに(↓)ありました。
では、Arduinoで動作させるプログラムをリンクしておきます。
完成版プログラム:jjy_emulator.ino
コンパイルしてArduinoへ書き込んでください。シリアルモニタを開いて、以下の様に入力します(もちろん、日時は、実験時の実際の時間に変更してください)。
実験サンプル:【 】内はシリアルモニタからの送信データ
【mset,19,05,25,09,57,20】
OK: mset 0
JJY: 19/5/25[145](Sat) 9:57
【jjy,1】
OK: jjy 1(ON)
JJY: 19/145(Sat) 10:0
JJY: 19/145(Sat) 10:1
JJY: 19/145(Sat) 10:2
:
【mset】コマンドで、現在の日時をArduinoに設定します。【jjy】コマンドでJJYエミュレーションを有効にすると、JJYタイムコードの送信を開始します。
電波時計を「受信開始」状態にしてください。製品毎に異なりますが、大抵の電波時計には、電波の強制受信ボタンが備わっています。LCD表示などに電波の受信状態が反映される機器では、受信しているかどうか受信状態を確認してください。
これも製品によって異なりますが、理論上は、3分間、連続で信号を受信できれば、時刻を設定することができるはずです。手持ちの電波時計では、早いもので6分程度、遅いものだと20分以上かかるものもあり、様々でした。各製品のマニュアル等で確認してください。
おそらく、上の回路図で示した回路定数で始めた場合には、受信できないはずです。LPFの時定数が大きすぎるので、ほとんど振幅の無い信号が出力されています。ここから、少しづつ、R1の抵抗値を小さなものに変えながら、受信状態を確認して行ってください。
根気のいる作業ですが、端折らずに少しづつ下げて行ってください。どこかで電波時計が受信したら、成功です。絶対に、それ以上抵抗値を下げないでください。
LPFの抵抗値R1を小さくする ⇒ 時定数CRが小さくなる ⇒ 出力信号の振幅が大きくなる ⇒ イヤホンケーブルから発せられる信号が強くなる、という関係です。今、徐々にR1を小さくしたことで、電波時計が信号を受信する、ギリギリの抵抗値R1を見つけたわけです。
念のため、最初に受信したその状態で、電波時計を1mほど離れたところに移動して、電波を受信できないことを確かめてください。離しても電波を送受信できてしまうようだと、「微弱無線局」で規定された電波強度を超えている恐れがありますので、即刻実験を中止してください。……そうならないように、大きな抵抗値から徐々に下げる様、提案しています。くれぐれも慎重に。
今回は、手動(マニュアル)で、Arduinoに日時を設定したのですが、もっと良い応用として、ESP32などを使ってSNTPで時刻を取得(「#4-5 WiFiインターネット通信」)し、JJYエミュレータで電波時計を自動的に設定すると、かっこ良いかもしれませんね?……「WiFi電波時計」になります。……ん?そこまで行ったら、Arduinoで時計表示したら良いことか??(1^_^)