DCF77電波時計&アナライザー v2.0:Arduino Mega/UNOプロジェクト
部品と材料
| Arduino Mega 2560 | ×1 |
| Arduino UNO | ×1 |
このプロジェクトについて
Arduinoプラットフォームを知り、自作電子機器開発の夢が叶いました。基本スケッチから始め、実用的なものを作りたくなりました。以前から電波時計(ドイツのDCF77信号と同期)が好きだったので、時刻表示だけでなく「今何が起きているか」を可視化する時計を作ることにしました。
数百時間の試行錯誤と数え切れない失敗の末、独自コードとプリント基板設計で動作する時計が完成しました。
その後、Udo Klein氏のDCF77スーパーフィルターを発見。電波状態が悪い時でもクリーンな信号を提供します。これは別のArduino Unoボードにスーパーフィルターソフトを載せたスタンドアロンの「ブラックボックス」として、DCF77アンテナと自分のスケッチを動かすArduino Megaの間に接続します。
初代時計のv2.0版を作成しました:
- コードを完全に書き直し
- 誰でも理解できるよう大幅にコメント追加
- PIR人感センサー追加で省電力化
- DCF77スーパーフィルターを統合
- 温度表示やその他用途(日の出/入り時刻等)用の追加ディスプレイ
デモ
動画はこちらで視聴可能
柱時計(グランドファーザークロック)への組み込み
動画はこちらで視聴可能
設計
フロントパネルとケースの設計:

フロントパネル設計はフリーソフト「Inkscape」で作成。実製作が最も困難でした。地元のFabLabでレーザーカッターとアルミ風コーティングの特殊アクリルを使用して製作を試みましたが、レーザーカッターの精度が不十分で失敗。その後、オンラインの写真印刷サービスでDiBondパネル印刷を依頼(28ユーロ送料込)。最初は1:1で印刷されず拡大されていましたが、問い合わせ後に正しい寸法で再送され、結果は期待以上でした。
その後、多くの穴あけとルーティング加工が必要でした。
コード
DCFアナライザー/クロック v2.1
/* ================================================================================
DCF77 Analyzer / Clock version 2
================================================================================
GNU Lesser General Public License v2.1以降の条件で再配布・改変可能
保証なし。詳細についてはGNU LGPLを参照。
Erik de Ruiter 2014-2020
2014年5月 初版
2016年3月 大幅改訂
2016年7月 v2.0クロック製作開始とスケッチ適応
Version 2.1 (2020-01-18) - パワーセーブ機能修正
Version 2.0 の主な変更点:
- Arduino MEGAとDCFスーパーフィルターをデフォルト使用
- 多様な電圧/電流仕様のLED駆動にMaxim 7219チップの代わりにArduino Megaのポートを直接使用
- 全てのMaximコモンアノード表示コードを削除
Version 1.72:
- 安価なPIR検知器で無活動時に選択的ディスプレイをオフにするオプション追加
- 夜間シャットダウンをPOWERSAVINGOFFTIMEとPOWERSAVINGONTIMEを両方ゼロで無効化可能
- 温度表示がパワーセーブモードでオフにならない問題修正
- エラーカウンター表示が毎時リセットされない問題修正
Version 1.71:
- 深夜0時に温度最小/最大メモリをリセットするユーザーオプション追加
Version 1.7:
- 温度表示分解能を0.5℃から0.1℃に向上
- DS18B20センサーの変換時間確保とコード簡潔化のため、温度表示を1分ごとに更新
- パリティチェックルーチン最適化
- 不正DCFデータでのRTC更新防止の信頼性向上
- EoBエラー時に内側LEDリングが正しくクリアされるよう修正
- DCF OK LEDがDCF信号状態をより確実に表示(エラー即座にオフ、3パリティビット全OKでオン)
Version 1.6:
- 温度関数を1分ごと1回計算に変更(delay(100ms)使用による奇妙なエラーを解消)
Version 1.5:
- scanSignal関数とその他コードの完全な見直し
- rPWとrPT LEDの動作修正
- End of Bufferエラーチェックルーチン修正
- DCF信号の3つのパリティビットをチェックし、全てOKの時のみ受信時刻情報を採用、表示更新、RTC同期
- 必要なら3つのデュアルカラーLED(コモンカソード)で各パリティビットのOK/FAILを表示可能
- 全ピン設定を1つの読みやすいテーブルにまとめ、配線変更を容易化
- DS18B20温度センサー1個使用時、I2Cデバイスアドレス特定不要に
- 大幅なコードクリーンアップ
- ディスプレイ消灯による省電力をPOWERSAVINGONTIME/OFFTIME変数で設定可能
- 変数名変更: Maximインスタンス lc/lc1 → MaximCC/MaximCA 等
- 起動時LED/ディスプレイテスト内蔵
簡単な説明:
起動時: LEDテスト実行。LEDとディスプレイを順次点灯して消費電力を抑制。
その後DCFパルス受信開始。分マーカー(2秒ギャップ)検出で分マーカーLED点灯、バッファーカウンターリセット。
内側LEDリングで受信DCFパルス表示(バッファーにも格納)。受信中3回パリティチェック実施。
有効データ受信時: 分末に分マーカー検出(BF LED点灯)かつ3パリティOK(DCF OK LED点灯)で、
バッファー情報から時刻・日付抽出、RTC更新(RTC Synced LED点灯)、内側リング情報を外側リングにコピー。
時刻・日付・週表示、曜日LED、夏/冬時間LED、閏年LEDを新時刻情報で更新。
無効データ時: パリティNGなら受信継続だがRTC/表示/LED更新せず。外側リング、RTC同期、DCF OK LEDリセット。
時刻・日付・週・曜日・夏/冬時間・閏年LEDは前回有効値を維持。Period Time/WidthエラーLEDでエラー表示、
エラーカウンター更新。毎時エラー表示リセット。EoB(End of Buffer)LEDは分マーカー前に
58ビット以上受信(ノイジー信号)で点灯。分マーカー検出で新サイクル開始。
温度: 30秒時点で過去期間の最高/最低温度表示。
チャイム: CHIMEスイッチONで毎正時にチャイム鳴動(夜間は無効)。
省電力 - ディスプレイ消灯: Power SaveスイッチHIGH時のみ動作。
1. 夜間消灯: ユーザー設定時刻でディスプレイ消灯/点灯。turnDisplaysOff関数で対象選択可能。
2. PIRセンサー: POWERSAVE_BY_PIR有効時、PIR検知で分カウンターリセット、PIR_DELAY_TIME以上
無検知でディスプレイ消灯。検知で即時点灯。時計機能はディスプレイ消灯中も正常動作。
DCFビープ: BUZZERSWITCHPIN接続スイッチで受信DCFビットを音で確認可能(パルス幅に応じ100/200ms)。
その他: RTC電池切れ/接続故障でRTC Error LED点灯。
クレジット: Matthias Dalheimer氏とThijs Elenbaas氏のDCF77デコーダーから多くを学びました。
自分の理解のために独自コードを書きました(二人のアイデアを参考)。効率的ではないが動作し、内容を把握しています。
参考サイト: Brett Oliver, Joop Tap, Thijs Elenbaas, Mathias Dalheimer, DCF77 Wikipedia, 他多数
*/
//----------------------------------------------------------------------------------------------------------
// ライブラリ
//----------------------------------------------------------------------------------------------------------
#include // Arduino Timeライブラリ https://www.pjrc.com/teensy/td_libs_Time.html
#include
#include // DS1307 RTCライブラリ https://www.pjrc.com/teensy/td_libs_DS1307RTC.html
#include // Maxim 7219ディスプレイライブラリ https://playground.arduino.cc/Main/LEDMatrix
// 注: コモンアノード対応には特別版LedControl.hが必要(通常はコモンカソードのみ)
#include // SPIインターフェース https://arduino.cc/en/Reference/SPI
#include // OneWire https://www.pjrc.com/teensy/td_libs_OneWire.html
// DallasTemperatureライブラリも利用可能 https://milesburton.com/Dallas_Temperature_Control_Library
//----------------------------------------------------------------------------------------------------------
// Arduino UNOピン接続(読みやすいテーブル形式)
//----------------------------------------------------------------------------------------------------------
#define DCF77PIN 2 // 入力 - アンテナPCBからのDCF信号(割り込み対応ピン必須)
#define PIRDETECTORPIN 3 // 入力 - PIR検知器:室内活動検知でディスプレイ活性化
#define BUZZERSWITCHPIN 4 // 入力 - スイッチ:DCF77「ビープ」ピエゾブザーON/OFF / ON=HIGH
#define CHIMESWITCHPIN 5 // 入力 - スイッチ:毎正時チャイム音ON/OFF / ON=HIGH
#define POWERSAVESWITCHPIN 6 // 入力 - スイッチ:省電力機能ON/OFF(常時表示)/ ON=HIGH
#define TEMPSENSORPIN 8 // 入力 - Dallas OneWire DS18B20温度センサー
#define TEMPRESETPIN 9 // 入力 - プッシュボタン:温度最小/最大メモリリセット / HIGH=リセット
#define MAXIMCCLD 10 // 出力 - CS/LOAD - Maxim 7219への疑似SPI接続(7セグメント表示)
#define MAXIMCCCLK 11 // 出力 - CLOCK - Maxim 7219疑似SPI
#define MAXIMCCDATA 12 // 出力 - DATA - Maxim 7219疑似SPI
// ピン22-53はLED専用
#define LED_SUNDAY 22 // 出力 - LED - 日曜日
#define LED_MONDAY 23 // 出力 - LED - 月曜日
#define LED_TUESDAY 24 // 出力 - LED - 火曜日
#define LED_WEDNESDAY 25 // 出力 - LED - 水曜日
#define LED_THURSDAY 26 // 出力 - LED - 木曜日
#define LED_FRIDAY 27 // 出力 - LED - 金曜日
#define LED_SATURDAY 28 // 出力 - LED - 土曜日
#define LED_CEST 29 // 出力 - LED - 夏時間(CEST)
#define LED_CET 30 // 出力 - LED - 冬時間(CET)
#define LED_LEAPYEAR 31 // 出力 - LED - 閏年
#define LED_RTCERROR 32 // 出力 - LED - RTCデータ読み取り問題(電池切れ/接続)
#define LED_RTCSYNC 33 // 出力 - LED - RTCがDCF時刻で正常同期時
#define LED_TEMP 34 // 出力 - LED - 温度表示中
#define LED_OPTION1 35 // 出力 - LED - オプション1データ表示中
#define LED_OPTION2 36 // 出力 - LED - オプション2データ表示中
#define LED_ERRORPT 37 // 出力 - LED - DCF Period Timeエラー
#define LED_ERRORPW 38 // 出力 - LED - DCF Period Widthエラー
#define LED_BUFFERFULL 39 // 出力 - LED - バッファーフル表示(次データ解析へ)
#define LED_MINUTEMARKER 40 // 出力 - LED - バッファー満杯前の分マーカー検出(データ破損)
#define LED_BUFFEROVERFLOW 41 // 出力 - LED - 1分間に予想以上のデータ受信(不良信号)
#define LED_DCFSTATUS 42 // 出力 - LED - 良好なDCFデータ時
#define LED_POWERSAVE 43 // 出力 - LED - 省電力モード作動中(一部表示オフ)
#define LED_PARITY1PASS 44 // 出力 - LED - パリティ1ビットOK
#define LED_PARITY1FAIL 45 // 出力 - LED - パリティ1ビット失敗
#define LED_PARITY2PASS 46 // 出力 - LED - パリティ2ビットOK
#define LED_PARITY2FAIL 47 // 出力 - LED - パリティ2ビット失敗
#define LED_PARITY3PASS 48 // 出力 - LED - パリティ3ビットOK
#define LED_PARITY3FAIL 49 // 出力 - LED - パリティ3ビット失敗
#define LED_PIRMOTION 50 // 出力 - LED - PIR動作検知時
// アナログピン
#define BUZZER A7 // 出力 - DCF77「ビープ」用ピエゾブザー(+極)
#define SPEAKERVOLPIN A6 // 出力 - サウンドボード音量 - LOW=音量1段階下げ
#define CHIMEPIN A5 // 出力 - チャイム起動 - OUTPUT LOW=チャイム起動
// DS1307 RTC用
// I2C DATA - RTC基板接続
// I2C CLOCK - RTC基板接続
//----------------------------------------------------------------------------------------------------------
// DS18B20初期化
//----------------------------------------------------------------------------------------------------------
OneWire ds(TEMPSENSORPIN);
//----------------------------------------------------------------------------------------------------------
// Maxim 7219マトリックスディスプレイ初期化
//----------------------------------------------------------------------------------------------------------
LedControl MaximCC = LedControl(MAXIMCCDATA, MAXIMCCCLK, MAXIMCCLD, 7, false);
//----------------------------------------------------------------------------------------------------------
// ユーザー設定、変数・配列定義
//----------------------------------------------------------------------------------------------------------
#define SPEAKERVOLUME 12 // サウンドボード「音量下げ」入力を起動後に何回アクティブにするか
#define PERFORM_LED_TEST 1 // 起動時LED/ディスプレイテスト実行: 1=Yes, 0=No
#define LEDTEST_DELAY_DISPLAYS 600 // 7セグメントディスプレイ間遅延(ms)
#define LEDTEST_DELAY_LED_RING 20 // LEDリング/その他LED間遅延(ms)
#define CONFIGURE_DS18B20 0 // DS18B20を最高分解能に1回設定: 1=ON, 0=OFF(初回実行後OFFに)
// 省電力時間 - 必須設定!
// 日中/夜間定義。省電力表示ON/OFF時間およびチャイム作動時間(日中のみ)に使用。
// ディスプレイのみ消灯、時計機能は常時動作。
// Power SaveスイッチOFF: 常時表示、チャイムは日中のみ。
// 値は「時」形式(20時なら'20'、'20:00'や'8PM'ではない)
#define POWERSAVINGOFFTIME 9 // ディスプレイ点灯
#define POWERSAVINGONTIME 22 // ディスプレイ消灯
#define POWERSAVE_BY_PIR 1 // 室内活動時にのみディスプレイ点灯: 1=ON, 0=OFF
#define PIR_DELAY_TIME 30 // 無検知後ディスプレイ消灯までの遅延(分)
#define TEMPRESET_MIDNIGHT 1 // 深夜0時に温度最小/最大メモリリセット: 1=リセット, 0=手動のみ
//----------------------------------------------------------------------------------------------------------
// その他パラメータ定義
//----------------------------------------------------------------------------------------------------------
#define DS1307_I2C_ADDRESS 0x68
#define DCF_INTERRUPT 0
// Maxim 7219ディスプレイ番号(デイジーチェーン順:最初が0、次が1...)
// コモンカソードディスプレイ
#define LedRingInner 0
#define LedRingOuter 1
#define DisplayBufferBitError 2
#define DisplayPeriodPulse 3
#define DisplayTempWeek 4
#define DisplayDate 5
#define DisplayTime 6
// ディスプレイ輝度レベル
#define BrightnessLedRingOuter 1
#define BrightnessLedRingInner 1
#define BrightnessDisplayTime 1
#define BrightnessDisplayDate 7
#define BrightnessDisplayTempWeek 15
#define BrightnessDisplayPeriodPulse 2
#define BrightnessDisplayBufferBitError 15
// パルスフラック
static unsigned long leadingEdge = 0;
static unsigned long trailingEdge = 0;
unsigned long previousLeadingEdge = 0;
volatile unsigned int DCFSignalState = 0;
int previousSecond = 0;
unsigned int previousSignalState = 0;
// DCFバッファーとインジケーター
static int DCFbitBuffer[59];
const int bitValue[] = {1, 2, 4, 8, 10, 20, 40, 80};
boolean MinuteMarkerFlag = false;
int bufferPosition = 0;
int previousMinute = 0;
int previousHour = 0;
// DCFビット有効性チェック用
bool dcfValidSignal = false;
int dcfP1counter = 0, dcfP2counter = 0, dcfP3counter = 0;
int dcfParityCheckP1 = 0, dcfParityCheckP2 = 0, dcfParityCheckP3 = 0;
// デコードされたDCF時刻格納用
int dcfMinute = 0, dcfHour = 0, dcfDay = 0, dcfWeekDay = 0;
int dcfMonth = 0, dcfYear = 0, dcfDST = 0, leapYear = 0;
int dayNumber, weekNumber;
int errorCounter = 0;
boolean errorCondition = false;
boolean daytimeChange = true, dayTime = false;
int dcf77SoundSwitch = 0;
// 温度変数
byte present = 0;
byte DS18B20Data[12];
int maxTemp = 0, minTemp = 0;
int lowByte = 0, highByte = 0;
float tempReading = 0;
int tempCelsius = 0;
boolean tempResetButton = false;
// PIR検知器変数
int pirActivity = 0, pirDisplaysState = 1;
unsigned int pirTimer = 0;
unsigned long previousTimePIR = 0;
//==============================================================================
// SETUP
//==============================================================================
void setup() {
Serial.begin(9600);
pinMode(DCF77PIN, INPUT);
pinMode(TEMPRESETPIN, INPUT);
pinMode(BUZZERSWITCHPIN, INPUT);
pinMode(CHIMESWITCHPIN, INPUT);
pinMode(POWERSAVESWITCHPIN, INPUT);
pinMode(PIRDETECTORPIN, INPUT);
pinMode(CHIMEPIN, OUTPUT);
pinMode(SPEAKERVOLPIN, OUTPUT);
for (int i1 = 22; i1 <= 50; i1++) pinMode(i1, OUTPUT);
initialize();
attachInterrupt(DCF_INTERRUPT, int0handler, CHANGE);
setSyncProvider(RTC.get);
if (timeStatus() != timeSet) {
digitalWrite(LED_RTCERROR, HIGH);
} else {
digitalWrite(LED_RTCERROR, LOW);
}
digitalWrite(SPEAKERVOLPIN, LOW);
digitalWrite(CHIMEPIN, LOW);
for (int i = 0; i <= SPEAKERVOLUME; i++) {
digitalWrite(SPEAKERVOLPIN, HIGH); delay(100);
digitalWrite(SPEAKERVOLPIN, LOW); delay(100);
}
if (CONFIGURE_DS18B20 == 1) configureDS18B20();
// テスト用手動時刻設定(必要時コメントアウト解除)
// setTime(23, 59, 40, 31, 12, 13);
// RTC.set(now());
calculateTemp();
if (PERFORM_LED_TEST == 1) ledTest();
else delay(750);
displayTemp();
ledDisplay(DisplayBufferBitError, "R", 0);
}
//==============================================================================
// LOOP
//==============================================================================
void loop() {
if (DCFSignalState != previousSignalState) {
previousSignalState = DCFSignalState;
scanSignal();
}
checkSwitches();
checkPIR();
tasksEverySecond();
tasksEveryMinute();
tasksEveryHour();
}
// 以下、主要関数の概要(全文は長大なため要約)
// scanSignal(): DCF信号の立ち上がり/立ち下がりエッジを検出し、パルス幅と周期をチェック
// processDcfBit(): 受信ビットを0/1判定、バッファー格納、パリティチェック、内側LEDリング表示
// finalizeBuffer(): 分マーカー検出時のバッファー最終処理、デコード、RTC同期、外側リング更新
// decodeBufferContents(): バッファーから分、時、日、曜日、月、年、夏時間、閏年を抽出
// bitDecode(): 指定ビット範囲をデコードして数値化
// checkSwitches(): 各スイッチ状態読み取り、ビープ音、チャイム、省電力モード制御
// checkPIR(): PIR動作検知でディスプレイ制御タイマーリセット
// tasksEverySecond/Minute/Hour(): 秒/分/時ごとの定期処理(表示更新、温度測定、エラーリセット等)
// displayTemp(): DS18B20から温度読み取り、最高/最低更新、表示
// calculateTemp(): 温度変換要求
// configureDS18B20(): DS18B20分解能設定
// ledTest(): 起動時LED/ディスプレイテスト
// ledDisplay(): Maxim 7219ディスプレイ制御
// error(): エラーLED点灯処理
// buzzer(): DCFビープ音出力
// turnDisplaysOff/On(): 省電力時のディスプレイ消灯/点灯
// dayWeekNumber(): 日付から通算日と週番号計算
// calculateLeapYear(): 閏年判定
// printDigits(): 時刻表示用ヘルパー
スーパーフィルタースケッチ(Arduino Uno用)
// DCFアナライザー/クロック v2.0で使用するスーパーフィルタースケッチ
// Udo Klein氏による素晴らしいフィルター
// Erik de Ruiter
// Arduino Unoピン接続
// DCF入力: A5 (19) = dcf77_sample_pin
// フィルター出力: 12 = dcf77_filtered_pin
// 半合成出力: A2 (16) = dcf77_semi_synthesized_pin
// 合成出力: 6 = dcf77_synthesized_pin
// LED DCF出力フィルター: A4 (18) = dcf77_monitor_pin
// LED 1Hzパルス: 10 = dcf77_second_pulse_pin
// LED DCF OK: 13 = dcf77_signal_good_indicator_pin
// LED 差分表示: 7, A0, 4
#include
const uint8_t dcf77_analog_samples = false;
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19; // A5
const uint8_t dcf77_inverted_samples = 0;
#if defined(__AVR__)
#define ledpin(led) (led)
#else
#define ledpin(led) (led<14? led: led+(54-14))
#endif
const uint8_t dcf77_monitor_pin = ledpin(18); // A4
const bool provide_filtered_output = true;
const uint8_t dcf77_filtered_pin = ledpin(12);
const uint8_t dcf77_inverted_filtered_pin = ledpin(11);
const uint8_t dcf77_filter_diff_pin = ledpin(7);
const bool provide_semi_synthesized_output = true;
const uint8_t dcf77_semi_synthesized_pin = ledpin(16);
const uint8_t dcf77_inverted_semi_synthesized_pin = ledpin(15);
const uint8_t dcf77_semi_synthesized_diff_pin = ledpin(14);
const bool provide_synthesized_output = true;
const uint8_t dcf77_synthesized_pin = ledpin(6);
const uint8_t dcf77_inverted_synthesized_pin = ledpin(5);
const uint8_t dcf77_synthesized_diff_pin = ledpin(4);
const uint8_t dcf77_second_pulse_pin = ledpin(10);
const uint8_t dcf77_signal_good_indicator_pin = ledpin(13);
volatile uint16_t ms_counter = 0;
volatile Internal::DCF77::tick_t tick = Internal::DCF77::undefined;
// ... (以下、Udo Klein氏のオリジナルスーパーフィルターコードが続く)
// 完全版はダウンロードしてご確認ください。
カスタムパーツとエンクロージャ
- 時刻・日付表示用PCB:
Maxim_7219_LED_display_unit_for_Adafruit_0_56inch_7_segment_v1_1.zip - コンパクトな7セグメント表示用PCB:
Maxim_7219_LED_display_unit_for_KingBright_7_segment_SC39_11SRWAv1_1.zip - チキング音とチャイム音ファイル:
Grandfather_Clock_Sound_files.zip
回路図
詳細な回路図(ZIPファイル)が提供されています:dcf77-analyzer-clock-v2_1_jVZT5sqIwn.zip
製造プロセス
- 鳩時計:歴史、職人技、現代への進化
- Arduino POVクロック:残像効果で浮かび上がるLED時計を自作する
- Arduinoで作るレトロNumitron管時計:シンプル、信頼性高く、省エネ
- Adafruit 60 LED NeoPixelリングとArduinoで作るスタイリッシュな壁掛け時計
- DIY Arduinoワードクロック – 文字で時刻を表示する洗練されたリアルタイムクロック
- Arduino で作るイスラム教礼拝時刻付きリアルタイムクロック
- Bluetooth操作のArduinoスパイボットを自作する
- FlickMote:ジェスチャー操作のIRリモコンでスマートホームを制御
- 12メートル先から見える大型LEDマスタークロック(アラーム機能付き)を自作しよう
- Arduino Dueと3.2インチTFTで作る温度監視付きリアルタイムクロック
- DS1302 RTCとArduino UNOで作る信頼性の高い目覚まし時計