Raspberry Piを使用した電波時計リピーター
ラズパイを使用した電波時計リピーターです。
実際に受信した電波をあらためて送信し直すのではく、ラズパイの日時情報から生成したタイムコードを送信するものですので、正確にはリピーターではなくシミュレータですが、この手の既存製品もリピーターと呼ばれていることも多いので、ここでもリピーターと呼びます。
回路について
主に
・ラズパイからのタイムコード受け取り部
・キャリア信号発振部
・信号加工部
・スイッチング、送信部
からなります。
5Vの電源は基板側へACアダプタを接続し、コネクタを通してラズパイへ供給しています。
キャリア信号の生成は40kHzまたは60kHzの水晶振動子を使っています。
両方使いたい場合はスイッチで切り替えるようにしても良いと思いますが、私は2ピンソケットで差し替えるようにしました。
ラズパイの別のポートを使って切替制御することも可能かと思いますが、稼働させ始めたらそれほど頻繁に切り替えるようなものでもないので、今回は見送り。
信号加工はCD4011というNANDゲートのICを使用しています。
水晶振動子を使った発振回路用のXORゲート、ラズパイからのタイムコートパルスと発振回路のキャリア信号のNANDゲート、それをさらに反転させるためのXORゲートとして使っています。
スイッチングにはパワーMOSFETを使用。
送信は自作のフェライトバーアンテナと、外部アンテナとして適当なリード線を使用。
私が使ったフェライトバーは昔ラジオから取り出して温存しておいた、断面が6x12mm、長さ70mmの角型のもの。
それに0.29mmのポリウレタンワイヤーを、1次側10m、2次側5mほど巻きました。
壊れた電波時計などがもしあれば、そこからフェライトバーを取り出して流用するのもいいのではないでしょうか。
電波到達距離はフェライトバーアンテナのみで50cmほど、リード線を付けて部屋の中いっぱいくらいでした。
このアンテナ次第で電波の飛びが大きく変わったりしますが、この辺あまり詳しくないので詳細は省きます…
あまり強くしすぎると電波法にも引っかかってくるのでご注意。
回路図とパーツ配置例
回路設計については素人同然ですので詳しい方から見ればツッコミどころも多々あるかと思いますが、目を瞑っていただければ(^^;
作成した基板と、ラズパイZeroを挿した様子
離れた位置にも水晶振動子が一個挿さってますが、単に使っていないほうを無くさないための置き場所です。
水晶振動子は小さい上に足も細く抜き差しして使うにはあまり向いていないので、ピンソケットをハンダ付けして使っています。
水晶振動子を使った発振回路部分は、部品個体差などの影響が出やすい箇所かと思います。
当初、C1とC2それぞれ15pFで仮組みしてみましたが、発振周波数を見てみると40kHzの振動子で80kHz、60kHzの振動子で120kHz出たり、触るとふらついて更に増減したりと不安定でしたが、最終的にC1側に15pFを1個、C2側に15pFを2個並列で30pF(回路図では30pFを1個として書いています)とすることで、40kHz、60kHzどちらの振動子でも安定して目的の周波数で発振してくれるようになりました。
実は今回の製作中でいちばん手こずった部分でもありますが、いちばん楽しかった部分でもあります。
この辺りの試行錯誤は、こちらのページの情報が大変参考になりました。
水晶発振回路の豆知識/なひたふ新聞
調整しているうち、40kHzの振動子では正しく発振するのに60kHzの振動子ではおかしい(あるいは逆)という状態になることもあるかもしれませんが、どうしても両方で正しく発振するよう調整しきれなければどちらか一方で手を打つか、CD4011のNANDゲートの空き1個も使って40kHz用と60kHz用それぞれ別に発振周りの回路を作り、スイッチかジャンパで選択するようにしてしまうのも手かもしれません。
あるいは水晶振動子ではなく、LTC1799のようなオシレータを使うのも有りかと思います。
(LTC1799を使ったこともありますが、これはこれで半固定ボリュームでの微妙な調整が難しかったです)
稼働中の様子
発熱に関しては、既に数日稼働させていますがCPUの裏あたりを触るとほんのり温かく感じる程度なので、とくに心配することもなさそうです。
(ずっと稼働させている状態で、室温25℃の時に vcgencmd measure_temp で確認したCPU温度は36℃程でした)
稼働状態の消費電力も監視してみましたが、基板とラズパイZeroを接続した状態でラズパイ起動時には最大200mA程、起動完了後しばらくして安定し始めたらだいたい平均100mA前後でしたので、ほとんどラズパイZero単独で稼働させた時と同じでした。
とはいえ、回路の作り方(特にフェライトバーアンテナ)によっても変わってくるかと思います。
スクリプト
■ JJYシミュレータ(jjy.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
#!/usr/bin/python # coding: utf-8 import datetime import time import os import re import RPi.GPIO as GPIO adjustTime = 0 # 送信日時を現在日時からずらしたい場合は相対秒数を指定 (ファイル jjy_adjust.txt がある場合はそちらを優先) GPIO_TIMECODE_BCM_NUMBER = 18 # タイムコード出力BCM番号 BCM18(GPIO.1) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_TIMECODE_BCM_NUMBER, GPIO.OUT) def between(num, start, end): return True if num >= start and num <= end else False def parity(n): return len(format(n, 'b').replace('0', '')) & 1 pulseWidth = {0:0.8, 1:0.5, 2:0.2} def sendBit(bit): GPIO.output(GPIO_TIMECODE_BCM_NUMBER, 1) print('%s %02s' %(now.strftime('%Y/%m/%d %H:%M:%S'), bit)) time.sleep(pulseWidth[bit]) GPIO.output(GPIO_TIMECODE_BCM_NUMBER, 0) while True: # ファイル jjy_adjust.txt があれば読み込んで相対秒数とする adjustFilePath = os.path.dirname(os.path.abspath(__file__)) + '/jjy_adjust.txt' if os.path.isfile(adjustFilePath): with open(adjustFilePath, 'r') as r: s = r.readline().strip() if re.match('^\-?\d+$',s): adjustTime = int(s) r.close() d = datetime now = d.datetime.now() + d.timedelta(seconds = 1 + adjustTime) # 西暦 year = now.year # 通算日 days = int(now.strftime('%j')) # 時 hour = now.hour # 分 minute = now.minute # 秒 second = now.second # 曜日 weekday = now.isoweekday() % 7 # タイムコードのビット配置に合わせた各BCD値 bcdYear = year % 10 + (int(year / 10) % 10 << 4) bcdDays = days % 10 + (int(days / 10) % 10 << 5) + (int(days / 100) << 10) bcdHour = hour % 10 + (int(hour / 10) << 5) bcdMinute = minute % 10 + (int(minute / 10) << 5) def s(n): return n - second # 送信ビット初期値 bit = 0 # マーカー(M), ポジションマーカー(P0~P5) if second == 0 or second % 10 == 9: bit = 2 # 分 elif between(second, 1, 8): bit = (bcdMinute >> s(8)) & 1 # 時 elif between(second, 12, 18): bit = (bcdHour >> s(18)) & 1 # 通算日 elif between(second, 22, 33): bit = (bcdDays >> s(33)) & 1 # PA1(時パリティ) elif second == 36: bit = parity(bcdHour) # PA2(分パリティ) elif second == 37: bit = parity(bcdMinute) # 年 elif between(second, 41, 48): bit = (bcdYear >> s(48)) & 1 # 曜日 elif between(second, 50, 52): bit = (weekday >> s(52)) & 1 # 秒が変わるタイミングまでsleepした後ビット送信 time.sleep(1 - datetime.datetime.now().microsecond / 1e6) sendBit(bit) |
シミュレータ本体。
Pythonで書かれています。
JJYのタイムコードをシミュレートしてラズパイのGPIOポートに出力します。
設置パス例
/home/pi/script/jjy.py
設置したファイル jjy.py のパーミッションを0744にしておきます。
後述の送出タイムコード日時変更ユーティリティを使用する場合は、
/home/pi/script/ に jjy_adjust.txt という空ファイルを作成してそのファイルのパーミッションを0666にしておきます。
実行中はCtrl+Cで中断できます。
バックグラウンドプロセスとして実行する場合は
バックグラウンドプロセスはCtrl+Cでは中断できません。
起動時に自動でバックグラウンド実行させたい場合は、
末尾のexit 0という行より手前に
/home/pi/script/jjy.py > /dev/null &
を追記して保存します。
再起動後より自動実行が有効になります。
自動実行させる方法は /etc/rc.local への追記以外にもいくつかありますが、ここでは割愛。
ラズパイ内部の時計を正確に保つため、ntpdでnictのNTPサーバへ同期するよう設定しておくのもいいと思います。
最新のRaspbianではntpdが入っていないという話も聞きますので、「ラズパイ ntpd インストール」で検索すれば、私がここで説明するよりずっと分かりやすく解説されている記事がたくさん見つかると思います。
実際のJJYの標準電波には毎時15分と45分の40秒台の9秒間、モールス信号でのJJYコールサインがありますが、電波時計にとってそれは必要無いというかむしろノイズでしかないので、本シミュレータでは実装していません。
コールサインもお聞きになりたいのであれば、聴感上の再現を主としてJavascriptで作成したこちらをどうぞ。
■ JJYシミュレータ用 送出タイムコード日時変更ユーティリティ(index.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
<?php /** * JJYシミュレータ 送出タイムコード日時変更ユーティリティ */ $jjyAdjustFile = '/home/pi/script/jjy_adjust.txt'; // jjy_adjust.txtのパス $result = false; if($_POST) { $result = file_put_contents($jjyAdjustFile, isset($_POST['adjust']) ? $_POST['adjust'] : '0'); $message = $result ? 'ok' : "設定保持ファイル {$jjyAdjustFile} を更新できません。\n {$jjyAdjustFile} のパーミッションを0666に変更して下さい。"; header('Content-type: text/plain'); print $message; exit; } if(file_exists($jjyAdjustFile)) { $file = file_get_contents($jjyAdjustFile); $adjustSec = (preg_match('/^\-?\d+$/', $file)) ? $file : 0; } else { error_exit("設定保持ファイル {$jjyAdjustFile} が見つかりません。<br />空ファイル {$jjyAdjustFile} を作成し、パーミッションを0666に設定して下さい。"); } function error_exit($mes) { header('Content-type: text/html'); print "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>{$mes}</body></html>"; exit; } $timeNow = microtime(true) * 1000; ?> <!DOCTYPE html> <html> <head> <meta charset='utf-8'> <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'> <title>jjy.py 送出タイムコード日時変更</title> <script> var localDate = new Date(); var nowDate = new Date(<?=$timeNow?>); var diff = nowDate - localDate; var sendSec = <?=$adjustSec?>; var adjust = <?=$adjustSec?>; var dg = function(id) {return document.getElementById(id);} window.onload = function() { var lastSec; var lastTime = new Date(new Date().getTime() + diff).getTime(); setInterval( function() { var now = new Date(new Date().getTime() + diff); if(lastSec != now.getSeconds()) { if(Math.abs(lastTime - now.getTime()) > 2000) { location.reload(true); } lastTime = now.getTime(); lastSec = now.getSeconds(); dg('nowDate').innerHTML = getFormat(now); dg('sendDate').innerHTML = getFormat(new Date(new Date().getTime() + diff + adjust * 1000) , 1); if(chgEnabled) dg('d'+ sel[selNum]).style.backgroundColor = selColor; } }, 10 ); } function getFormat(d, m) { return( (m ? '<span id="dy">' : '') + d.getFullYear() + (m ? '</span>' : '') + '.' + (m ? '<span id="dm">' : '') + ('0' + (d.getMonth()+1)).slice(-2) + (m ? '</span>' : '') + '.' + (m ? '<span id="dd">' : '') + ('0' + d.getDate()).slice(-2) + (m ? '</span>' : '') + ' <span class="week">' + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getDay()] + '</span>' + ' ' + '<span class="timeView">' + (m ? '<span id="dh">':'') + ('0' + d.getHours()).slice(-2) + (m ? '</span>' : '') + ':' + (m ? '<span id="di">':'') + ('0' + d.getMinutes()).slice(-2) + (m ? '</span>' : '') + ':' + (m ? '<span id="ds">':'') + ('0' + d.getSeconds()).slice(-2) + (m ? '</span>' : '') + '</span>'); } var sel = ['y', 'm', 'd', 'h', 'i', 's']; var selNum = sel.length -1; var selColor = '#bbf'; var repeatId = 0; var touchFlg = false; function buttonPress(n, tf) { t = sel[selNum]; if(tf) touchFlg = true; if(!tf && touchFlg) return; if(!chgEnabled) return; adjustRelUpdate(t, n); if(!repeatId) { repeatId = setTimeout(function() { repeatId = setInterval(function() { adjustRelUpdate(t, n); }, 30); }, 400); } } function buttonRelease() { if(repeatId) { clearInterval(repeatId); repeatId = 0; } } function adjustRelUpdate(t, n) { var d = new Date().getTime() + diff + adjust * 1000; var sd = new Date(d); var sd_ = new Date(d); if(t == 'y') sd.setFullYear(sd.getFullYear() + n); else if(t == 'm') sd.setMonth(sd.getMonth() + n); else if(t == 'd') sd.setDate(sd.getDate() + n); else if(t == 'h') sd.setHours(sd.getHours() + n); else if(t == 'i') sd.setMinutes(sd.getMinutes() + n); else if(t == 's') sd.setSeconds(sd.getSeconds() + n); var p = ~~((sd.getTime() - sd_.getTime()) / 1000); if(t != 's') p = ~~((p + (30 * n)) / 60) * 60; adjust += p; var hy = 315576e4; if(n == 1 && adjust >= hy) adjust -= (hy * 2 - 86400); if(n == -1 && adjust <= -hy) adjust += (hy * 2 - 86400); adjustUpdate(); } function chgSel(n) { if(!chgEnabled) { chgEnabled = true; ceTimeoutUpdate(); dg('d' + sel[selNum]).style.backgroundColor = selColor; return; } dg('d' + sel[selNum]).style.backgroundColor = ''; selNum += n; if(selNum < 0) selNum = sel.length -1; else if(selNum > sel.length -1) selNum = 0; dg('d' + sel[selNum]).style.backgroundColor = selColor; chgEnabled = true; ceTimeoutUpdate(); } function adjustUpdate(m) { sendDate = new Date(new Date().getTime() + diff + adjust * 1000); dg('sendDate').innerHTML = getFormat(sendDate, 1); if(!m) { dg('d' + sel[selNum]).style.backgroundColor = selColor; ceTimeoutUpdate(); } if(adjust == sendSec) { dg('sendBtn').style.backgroundColor = ''; dg('result').innerHTML = ''; } else { dg('sendBtn').style.backgroundColor = '#f80'; dg('result').innerHTML = '確定するまで変更内容は反映されません'; } } var chgEnabled = false; var ceTimeoutId; function ceTimeoutUpdate() { if(ceTimeoutId) clearTimeout(ceTimeoutId); ceTimeoutId = setTimeout( function() { chgEnabled = false; dg('d' + sel[selNum]).style.backgroundColor = ''; }, 4000); } function cancelChg() { setNow(1); } function setNow(m) { adjust = m ? sendSec : 0; adjustUpdate(1); if(ceTimeoutId) { clearTimeout(ceTimeoutId); chgEnabled = false; } } function sendData() { var a = new XMLHttpRequest(); var url = '<?='http'.($_SERVER['SERVER_PORT'] == 443 ? 's' : '').'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']?>'; a.open('POST', url); a.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); a.send('adjust=' + adjust); popUpMessage('Waiting...', 1e6); a.addEventListener('load', function() { if(a.status == 200 && this.response == 'ok') { if(ceTimeoutId) { clearTimeout(ceTimeoutId); chgEnabled = false; } sendSec = adjust; adjustUpdate(1); popUpMessage('設定を変更しました', 2000); } else { if(a.status == 200) { alert(this.response); } else if(a.status == 404) { alert(url + 'が見つかりません'); } else { alert(url + 'からの応答がありません'); } } }, false); } var pId; function popUpMessage(mes, t) { var d = dg('popUp'); d.innerHTML = mes; d.style.left = '53px'; if(pId) clearTimeout(pId); pId = setTimeout( function() { d.style.left = '-1000px'; }, t); } </script> <style> @import url('https://fonts.googleapis.com/css?family=M+PLUS+1p:300&subset=japanese'); html { touch-action: manipulation; -webkit-touch-callout: none; -webkit-user-select: none; font-family: 'M PLUS 1p', sans-serif; } input[type='button'], input[type='submit'] { -webkit-appearance: none; -webkit-user-select: none; border-radius: 4px; background-color: #fff; border: 1px solid black; font-family: 'M PLUS 1p', sans-serif; } .btnSel{ width: 50px; max-width: 50px; height: 122px; font-size: 15px; position: absolute; } .btnUd{ width: 66%; height: 60px; max-width: 196px; font-size: 15px; position: absolute; } .btnBar{ width: 50%; max-width: 149px; height: 35px; position: absolute; bottom: 0px; } .btnBar2{ width: 100%; max-width: 300px; height: 35px; } .bRight { right: 0px; } .bUp { left: 52px; } .bDown { left: 52px; top: 62px; } .bNow { right: 0px; } .t { width: 100%; height: 164px; max-width: 300px; position: relative; } .dateView { font-size: 16px; } .timeView { font-size: 22px; } .week { display: inline-block; width: 30px; font-size: 16px; } .popUp { position: absolute; padding-top: 8px; padding-bottom: 8px; top: 160px; left: -1000px; width: 200px; height: auto; background-color: #446; color: #fff; border: 4px #ccf solid; border-radius: 5px; text-align: center; vertical-align: middle; font-size: 20px; z-index: 99; } </style> </head> <body onmouseup='buttonRelease()' onmouseleave='buttonRelease()' ontouchend='buttonRelease()'> <h3>jjy.py 送出タイムコード日時変更</h3> <span class='dateView'>現在日時:<span id='nowDate'></span></span><br /> <span class='dateView'>送出日時:<span id='sendDate'></span></span><br /> <br /> <div class='t'> <input type='button' value='<' onclick='chgSel(-1)' class='btnSel bLeft'> <input type='button' value='+' onmousedown='buttonPress(1)' ontouchstart='buttonPress(1, 1)' class='btnUd bUp'> <input type='button' value='-' onmousedown='buttonPress(-1)' ontouchstart='buttonPress(-1, 1)' class='btnUd bDown'> <input type='button' value='>' onclick='chgSel(1)' class='btnSel bRight'> <input type='button' value='変更を取り消す' onclick='cancelChg()' class='btnBar bCancel'> <input type='button' value='現在日時に合わせる' onclick='setNow()' class='btnBar bNow'> </div> <p style='font-size:9px;'> 現在日時 … Raspberry Pi上での現在の日時です<br /> 送出日時 … jjy.pyがタイムコードとして送出している日時です<br /> </p> <input type='button' id='sendBtn' value='変更を確定する' class='btnBar2' onclick='if(adjust != sendSec) sendData()'><br /> <p style='color:red' id='result'></p> <div class='popUp' id='popUp'></div> </body> </html> |
リピーターが送信するタイムコードの時刻設定を他のPCやスマホのWEBブラウザから変更するためのスクリプトですが、こちらは設置しなくてもリピーターは動作します。
PHPで書かれています。
ラズパイ上でApache2等のwebサーバを稼働させている必要があり、PHPのインストールも必要です。
設置パス例
/var/www/html/jjy/index.php
同一ネットワーク上のブラウザから
http://raspberrypi/jjy/
http://raspberrypi.local/jjy/
http://[192.168.xxx.xxx等、ラズパイのローカルネットワーク上でのIPアドレス]/jjy/
等のURLでアクセスできます。
設定や環境によって変わる場合もありますので、その場合は適宜調べてください。
インターネット経由での外部からのアクセスは不可。あくまで同一LAN上からのアクセスに限ります。
インターネット経由でアクセスできるようにする方法もありますが、リピーターの時刻補正に外部からのアクセスは不要ですのでここでは触れません。
実行すると、jjy.plが送出するタイムコードを実際の日時に対して進めたり遅らせたりするための更新画面が表示されます。
ここで設定されるものはあくまで「どれだけずらすか」という現在時刻との相対時間のみですので、ラズパイ自身の時刻設定そのものが変更されることはありません。
jjy.pyを別のPCからSSH経由でフォアグラウンドプロセスとして実行させ、ブラウザからタイムコードの送出日時設定変更を行ない、変更した設定が反映される様子
適当なケースに収納。
ケーブル類は短めの延長コードでケース外へ。
使用した主な部品リスト
■ ピンソケット 2×20
ラズパイとの接続用
■ 水晶振動子 40kHz
標準電波のキャリア信号発生用
■ 水晶振動子 60kHz
標準電波のキャリア信号発生用
必要なほうどちらか片方だけでも大丈夫です。
■ 積層セラミックコンデンサ 15pF
当初15pFで設計しましたが、発振周波数が2倍、3倍となったりして不安定だったので並列に増減しながら様子を見て、C1側15pF、C2側並列2個で30pFで落ち着きました。
■ 抵抗 10MΩ
■ 抵抗 220kΩ
■ CD4011BE 4回路NANDゲートIC
手持ちがあったのでこれを使いましたが、SN74HC00N等でも構いません。
回路図でのピン番号の違いは適宜読み替えて下さい。
■ ICソケット 14pin
無くても大丈夫。
ハンダ付けの熱でICを壊さないか心配な場合はあったほうが安心。
■ 適当なフェライトバーアンテナ
■ 外部アンテナ
付けなくてもフェライトバーアンテナのすぐ近くであれば届きますが、私はたまたま手元に余らせていた3.5mmミニプラグのオーディオケーブルをアンテナとして使ってます。
既存ケーブルを流用する場合は、芯線だと物によってはシールドされている場合もあって電波が飛ばないので、ケーブルのGND側に結線。
■ 3.5mmミニジャック
3.5mmオーディオケーブルを外部アンテナとして流用する場合の接続用
■ LED
手元にあった1.8mm白色チップLED
■ LED保護及び輝度調整用の抵抗
抵抗値が低ければ明るく光りますが、低すぎたり抵抗を繋がなかったりするとLEDに電流が流れすぎてすぐ切れます。
逆に抵抗値が高ければ暗くなりますが、高すぎると電圧が足りず全く光らなくなります。
私は手元にあった24kΩのを使ってます。
24kΩだとかなり暗いですが、照明用途ではなくタイムコードのパルス確認用なので、明るすぎないのはむしろ好都合。
■ ユニバーサル基板
回路が収まれば何でもいいです。
せっかく小さなZeroを使うので、それに合わせた小さな基板にまとめてもいいかもしれません。
■ マイクロUSB端子
回路の電源供給用です。
電源自体は市販のUSB ACアダプター等を使ってください。
■ 丸ピンソケット 1×12
いくつか切り離して水晶振動子差替え用のソケットにしました。
■ Raspberry Pi Zero WH
タイムコードシミュレート及びパルス制御させる本体。
他のラズパイでも大丈夫かと思いますが、常時稼働する前提なので消費電力の少ないZeroで、更にWi-Fiによる設定変更やNTPサーバとの同期、コネクタ接続も必須なのでWHが最適。
こんな小さなロジックボードひとつでサーバとしての機能がパワーはそこそこであれひと通り実現できてしかも安く入手できるのですから、いい時代になったものです。
■ タカチ電気工業 SS型プラスチックケース(SS-160W)