神対応を無駄にする でaitendo さんが神対応をしてくれた電波時計モジュールと ATMega88 を一つ壊し、へこみまくってしばらく手を引いていた電波時計ですが、月末まで我慢できずに購入しました。
新しく届いたモジュールをブレッドボードで動作確認すると、前回壊したモジュールと同様、なぜかネガティブ信号出力のパルスは出るのに、ポジティブ信号出力が HIGH のままでした。
スケッチも回路も変更したくなかったのでポジティブ信号を使用したかったのですが、そういう仕様だということにしてあきらめてネガティブ信号を使用することにします。
また、IC の仕様書を見ると 5V で動作しそうですが、また壊してしまってはどうしようもないので、aitendoさん推奨の 3.3V で使用することにします。
で、ネガティブ信号を反転して、ポジティブ信号に変換し、 3.3V を 5V に変換するには?
とちょっと考えましたが、トランジスタ 1つあればできそうです。
![Radio_Clock_3v3](https://stat.ameba.jp/user_images/20140521/13/amano-jacky-nochio/49/16/p/o0640052012948126708.png?caw=800)
信号が LOW のときは C-E 間に電流が流れないので、マイコンの 8番ピンには HIGH の信号が入ります。
信号が HIGH のときは C-E 間が導通し、マイコンの 8番ピンには LOW になります。
また前回、負荷容量による発振周波数の変化を調べて、 秋月の表面実装クリスタルでは6.7pF くらいがベスト。という結果になりました。そこで、6pF と 7pF を購入しテストしたところ、どちらも 24 時間で 1 秒未満の誤差、特に 7pF のほうが誤差が少ないのでこちらを採用しました。
-----(2014/05/28追記)-----
最終的には 10pF のコンデンサを使用しています。
ブレッドボードと基板に組んだ状態の差なのか、クリスタルの個体差なのか、7pF では、時間が進んでしまいました。
--------------------------
3.3V の電源は DX.com で購入した Quality 3.3V Power Module を使用しました。
おそらく、aitendoさんの 極小DC-DCモジュール[DC1117-254-B] と同じものです。(使ってるチップが同じ)
入力電源:6-12V と書いていますが、3.3V のモジュールなら 4.5V 以上で動くと思われます。
電波時計モジュールの消費電力が少なそうだったので、けちって、1KΩと 2KΩの抵抗で 5V を分圧して 3.3V を作って動かしてみましたが、うまく動作しませんでした。
![シンクロ中](https://stat.ameba.jp/user_images/20140524/07/amano-jacky-nochio/46/85/j/o0800060012950885829.jpg?caw=800)
シンクロ中。
電源は気温/湿度/気圧計からとっています。
![電波時計1](https://stat.ameba.jp/user_images/20140524/06/amano-jacky-nochio/ad/76/j/o0640048012950878463.jpg?caw=800)
1mm 厚の FRP を切り出し、気温/湿度/気圧計と一体化しました。
![電波時計2](https://stat.ameba.jp/user_images/20140524/06/amano-jacky-nochio/07/bb/j/o0640048012950878464.jpg?caw=800)
アンテナはポリカの板にφ10 の穴を開けて挿入、基板との固定は 1mm 程度の切込みをつけて挟み込んでいるだけです。
いろいろ工作するたびに、失敗したり、壊したりしますが、そのたびにいろいろ勉強になり、知識が少しずつ増えている気がします。
-----(2014/05/28追記)-----
スケッチにバグがありました。
修正した内容はこちらで確認してください。
--------------------------
-----(2014/09/15追記)-----
さらにバグがありました。
修正した内容はこちらで確認してください。
--------------------------
ソースはこちら。「Radio_Clock.zip」
「電波時計」を選んで「Radio_Clock.zip」をクリック。
上の矢印をクリックするとダウンロードできます
<スケッチ>
#include <LiquidCrystal.h>
#include <avr/pgmspace.h>
#define Input_Pin 8
#define Input_Key 18
// 接続ピンをして指定してライブラリを初期化
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
// ----- 変数定義 -----
int year;
byte month;
byte week_no;
byte day;
byte day_uruu;
byte hour;
byte ampm_hour;
byte minutes;
byte sec;
const byte month_day[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int d_year[2];
byte d_month[2];
byte d_week_no[2];
byte d_day[2];
byte d_hour[2];
byte d_minutes[2];
unsigned int c_year;
unsigned int c_week;
unsigned int c_day;
unsigned int c_hour;
unsigned int c_minutes;
unsigned long pulse;
long pulse_width;
long sec_count;
int sec_offset;
byte code;
byte marker;
byte count;
byte i;
byte err;
// キー入力
byte pin_no;
byte active_pin;
byte pin_status[2];
byte pin_flag[2];
boolean heisei_flag = false;
boolean ampm_flag = false;
// 外字データ用
byte data[8];
enum E_FONT_INDEX
{
E_SUN,
E_MON,
E_TUE,
E_WED,
E_THU,
E_FRI,
E_SAT,
E_YEAR,
E_HEI,
E_SEI,
E_AM,
E_PM
};
PROGMEM prog_int8_t dots[][8] =
{
{ //日
0b11111,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b11111,
0b00000
},
{ //月
0b01111,
0b01001,
0b01111,
0b01001,
0b01111,
0b01001,
0b10001,
0b00000
},
{ //火
0b00100,
0b10101,
0b10101,
0b00100,
0b01010,
0b01010,
0b10001,
0b00000
},
{ //水
0b00100,
0b00101,
0b11110,
0b00110,
0b01101,
0b10100,
0b00100,
0b00000
},
{ //木
0b00100,
0b00100,
0b11111,
0b00100,
0b01110,
0b10101,
0b00100,
0b00000
},
{ //金
0b01110,
0b10001,
0b01110,
0b11111,
0b00100,
0b10101,
0b11111,
0b00000
},
{ //土
0b00100,
0b00100,
0b11111,
0b00100,
0b00100,
0b00100,
0b11111,
0b00000
},
{ //年
0b10000,
0b11111,
0b00100,
0b11111,
0b10100,
0b11111,
0b00100,
0b00000
},
{ //平
0b11111,
0b00100,
0b10101,
0b00100,
0b11111,
0b00100,
0b00100,
0b00000
},
{ //成
0b00101,
0b11111,
0b10100,
0b10101,
0b11010,
0b11010,
0b10101,
0b00000
},
{ //AM
0b00100,
0b01010,
0b11111,
0b10001,
0b00000,
0b11011,
0b10101,
0b10001
},
{ //PM
0b11110,
0b10001,
0b11110,
0b10000,
0b00000,
0b11011,
0b10101,
0b10001
}
};
enum E_LCD_GAIJI
{
LCD_GAIJI_YEAR = 0x00,
LCD_GAIJI_MONTH,
LCD_GAIJI_DAY,
LCD_GAIJI_WEEK,
LCD_GAIJI_HEI,
LCD_GAIJI_SEI,
LCD_GAIJI_AM,
LCD_GAIJI_PM
};
// ----- 関数 -----
// -- 外字定義 --
void lcdDefChar(uint8_t id, uint8_t eidx)
{
uint8_t i;
for (i = 0; i < 8; i++)
data[i] = pgm_read_byte(&dots[eidx][i]);
lcd.createChar(id, data);
}
// -- 1秒毎の処理 --
void cycle1s(void){
sec++;
if (sec == 60){
sec = 0;
minutes++;
if (minutes == 60){
minutes = 0;
hour++;
if (hour == 24){
hour = 0;
day++;
if (month == 2 && (year % 4 == 0)){ //うるう年判定
day_uruu = 1;
} else {
day_uruu = 0;
}
week_no++;
if (week_no == 7)
week_no = 0;
if (day > (month_day[month] + day_uruu)){
day = 1;
month++;
if (month > 12){
month = 1;
year++;
}
}
}
}
}
lcdDefChar(LCD_GAIJI_WEEK, week_no);
// 表示させる位置の指定 一行目頭
lcd.setCursor(0, 0);
if ( heisei_flag == true ){
lcd.write(LCD_GAIJI_HEI);
lcd.write(LCD_GAIJI_SEI);
lcd.print(year + 12);
} else {
lcd.print(year + 2000);
}
lcd.write(LCD_GAIJI_YEAR);
if (month < 10)
lcd.print(" ");
lcd.print(month);
lcd.write(LCD_GAIJI_MONTH);
if (day < 10)
lcd.print(" ");
lcd.print(day);
lcd.write(LCD_GAIJI_DAY);
lcd.print("(");
lcd.write(LCD_GAIJI_WEEK);
lcd.print(") ");
// 表示させる位置の指定 二行目頭
if (ampm_flag == true) {
lcd.setCursor(7,1);
if (hour < 12){
ampm_hour = hour;
lcd.write(LCD_GAIJI_AM);
} else{
ampm_hour = hour -12;
lcd.write(LCD_GAIJI_PM);
}
if (ampm_hour < 10)
lcd.print(" ");
lcd.print(ampm_hour);
} else {
lcd.setCursor(7, 1);
lcd.print(" ");
if (hour < 10)
lcd.print(" ");
lcd.print(hour);
}
lcd.print(":");
if (minutes < 10)
lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (sec < 10)
lcd.print("0");
lcd.print(sec);
}
// -- コード取得 --
byte get_code(void){
byte ret_code = 3; // エラーコード
// High になったら計測開始
while (digitalRead(Input_Pin) == 0){
}
pulse = millis() >> 1;
if (count < 2){
lcd.setCursor (0, 1);
lcd.print("SYNC");
lcd.print(count + 1);
lcd.print("E");
lcd.print(err);
} else {
lcd.setCursor (0, 1);
lcd.print("SYNC OK");
}
cycle1s();
while (digitalRead(Input_Pin) == 1){
}
lcd.setCursor (0, 1);
lcd.print(" ");
// パルス幅を計算
pulse_width = (millis() >> 1) - pulse;
if (pulse_width < 0){ // pulse_width がオーバーフローした場合
pulse_width = (pulse_width + 0x7FFFFFFF) << 1;
}else{
pulse_width = pulse_width << 1;
}
if ((pulse_width > 150) && (pulse_width < 250)) // 0.2sec
ret_code = 2; // marker
if ((pulse_width > 450) && (pulse_width < 550)) // 0.5sec
ret_code = 1; // High
if ((pulse_width > 750) && (pulse_width < 850)) // 0.8sec
ret_code = 0; // Low
if (ret_code == 3){
err++;
}
return(ret_code);
}
// -- デコード処理 --
void decode(byte no){
err = 0;
// 分の算出
c_minutes = 0;
for (i = 0; i < 8; i++){
c_minutes = c_minutes << 1 ;
c_minutes += get_code();
}
d_minutes[no] = (c_minutes >> 5) * 10 + (c_minutes & B1111) ;
code = get_code(); // ポジションマーカー
// 時の算出
c_hour = 0;
for (i = 0; i < 9; i++){
c_hour = c_hour << 1;
c_hour += get_code();
}
d_hour[no] = (c_hour >> 5) * 10 + (c_hour & B1111) ;
code = get_code(); // ポジションマーカー
// 日の算出
c_day = 0;
for (i = 0; i < 9; i++){
c_day = c_day << 1;
c_day += get_code();
}
d_day[no] = (c_day >> 5) * 100 + (c_day & B1111) * 10 ;
code = get_code(); // ポジションマーカー
c_day = 0;
for (i = 0; i < 4; i++){
c_day = c_day << 1;
c_day += get_code();
}
d_day[no] += c_day;
d_month[no] = 0;
do{
d_day[no] -= month_day[d_month[no]];
d_month[no]++;
} while( d_day[no] > month_day[d_month[no]]);
// ポジションマーカー待ち
while (get_code() != 2){
}
// 15 分 45 分は呼び出し符号(モールス信号)
if (d_minutes[no] != 15 && d_minutes[no] != 45){
// 年の算出
c_year = 0;
for (i = 0; i < 9; i++){
c_year = c_year << 1;
c_year += get_code();
}
d_year[no] = (c_year >> 4) * 10 + (c_year & B1111);
code = get_code(); // ポジションマーカー
// 曜日の算出
d_week_no[no] = 0;
for (i = 0; i < 3; i++){
d_week_no[no] = d_week_no[no] << 1;
d_week_no[no] += get_code();
}
}else{ // 呼び出し符号は飛ばす
delay(10500);
}
}
void setup() {
// 行と列を設定
lcd.begin(16, 2);
// 信号入力ピン設定
pinMode(Input_Pin, INPUT);
// 外字登録
lcdDefChar(LCD_GAIJI_YEAR, E_YEAR);
lcdDefChar(LCD_GAIJI_MONTH, E_MON);
lcdDefChar(LCD_GAIJI_DAY, E_SUN);
lcdDefChar(LCD_GAIJI_HEI, E_HEI);
lcdDefChar(LCD_GAIJI_SEI, E_SEI);
lcdDefChar(LCD_GAIJI_AM, E_AM);
lcdDefChar(LCD_GAIJI_PM, E_PM);
// キー入力設定
for (pin_no = Input_Key; pin_no < Input_Key + 2; pin_no++){
pinMode(pin_no, INPUT_PULLUP);
}
}
void loop() {
// 初回とAM2:00:30にここに来る
// 信号が LOW になるまで待つ
while (digitalRead(Input_Pin) == 1){
}
// 2回分のデコードが等しいか?
do{
count = 0;
// エラーのない2回分のデコード
do{
marker = 0;
// marker が2回続いたらスタート
do{
if (get_code() == 2){
marker++;
} else {
marker = 0;
}
} while (marker < 2);
// デコード
sec = 0; // 仮
decode(count);
if (err == 0){
count++;
} else {
count = 0;
}
} while (count < 2);
} while (d_year[0] != d_year[1] ||
d_month[0] != d_month[1] ||
d_week_no[0] != d_week_no[1] ||
d_day[0] != d_day[1] ||
d_hour[0] != d_hour[1] ||
d_minutes[0] + 1 != d_minutes[1] );
// 2回のデコード結果が一致したら決定
year = d_year[1];
month = d_month[1];
week_no = d_week_no[1];
day = d_day[1];
hour = d_hour[1];
minutes = d_minutes[1];
// marker がきたら 59秒200
while (get_code() != 2){
}
sec = 59;
sec_offset = millis() % 1000 - 200;
sec_count = (millis() - sec_offset) / 1000;
// 時間を計算
do{
if (sec_count != (millis() - sec_offset) / 1000){
sec_count = (millis() - sec_offset) / 1000;
cycle1s();
}
// キー入力
for (pin_no = Input_Key; pin_no < Input_Key + 2; pin_no++){
active_pin = pin_no - Input_Key;
pin_flag[active_pin] = pin_status[active_pin];
pin_status[active_pin] = digitalRead(pin_no);
pin_flag[active_pin] -= pin_status[active_pin];
}
if (pin_flag[0] == 1)
heisei_flag = !heisei_flag;
if (pin_flag[1] == 1){
ampm_flag = !ampm_flag;
}
} while (hour != 2 || minutes != 0 || sec != 30);
}