本エントリの更新版がRaspberry Piで電波時計を修正(完成版)にあります。
Raspberry Piだけで標準電波相当の電波を出力し、電波時計を修正する。
試行から3年、ようやく標準電波相当の電波の出力に成功した。受信可能距離は10 cm程度ではあるが、何かのご参考まで。ポイントとなったのは、格安のオシロスコープキットDSO150とpigpioライブラリであった。以下、Raspberry Pi 3 Model BにRaspbian Stretch (2017-09-07)をクリーンインストールした状態から説明する。
chronyのインストール
Raspbian Stretchはntpdがデフォルトでインストールされていない。そこで、最近はRedHat系Linuxディストリビューションでは標準となっているchronydをインストールし、上位NTPサーバと時刻同期を行う。apt-getコマンドでインストールから稼働まで簡単に終わる。当方では、上位サーバにntp.nict.jpを指定した。
$ sudo apt-get update $ sudo apt-get install chrony $ sudo vi /etc/chrony/chrony.conf (以下のとおり修正) #pool 2.debian.pool.ntp.org iburst server ntp.nict.jp iburst $ sudo systemctl restart chrony (しばらく待ってから) $ chronyc sources
pigpioのインストール
こちらもapt-getコマンドで。Python使いの方はpython-pigpioかpython3-pigpioもインストールすると幸せになれるだろう。
$ sudo apt-get install pigpio
標準電波相当の電波の出力
さて本題の電波出力である。関東では40 KHzとなる標準電波は、12.5マイクロ秒毎に出力のオン/オフを切り替えることで代替可能と考えていた。ところが、既存のbcm2835ライブラリーやWiringPiライブラリーを用いても良い結果が得られずにいた。これは、bcm2835_delayMicroseconds()
やdelayMicroseconds()
によるタイミングでは正確にならず、最大で44 KHzまで振れて安定しないためであった(bcm2835ライブラリーやWiringPiライブラリー自身の問題ではない)。一方、pigpioライブラリーのgpioHardwareClock()
関数は正確に40 KHzの出力が可能であった。以下、使用したプログラムである。
#include <pigpio.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#define WAIT_DURATION 180 // 3 min in sec
#define RUN_DURATION 30 // 30 min
#define MIN_TOLERANCE 500000 // 0.5 sec in microsec
#define SEC_TOLERANCE 5000 // 5 millisec in microsec
#define MARKER 2
#define OUTPUT_PIN 4 // GPIO4 -> pin 7
#define OUTPUT_FREQ 40000 // 40 KHz
#define OUTPUT_MARKER 200000 // 0.2 sec in microsec
#define OUTPUT_ZERO 800000 // 0.8 sec in microsec
#define OUTPUT_ONE 500000 // 0.5 sec in microsec
struct timeval now_epoch;
struct tm *now;
struct tm *next;
int timecode[60] = {0}; // consists of codes from 0 sec to 59 sec
void wait_until_59_sec(void);
void set_now(void);
void set_timecode(void);
void set_next(void);
void set_bcd(int start, int bits, int value);
void output_timecode(void);
void wait_until_exact_second(void);
void transmit_wave(int microsec);
void terminate(int signal);
void print_now(void);
void print_timecode(void);
int main(int argc, char *argv[]) {
if (gpioInitialise() < 0) {
perror("failed gpioInitialise()");
exit(EXIT_FAILURE);
}
if (gpioSetMode(OUTPUT_PIN, PI_OUTPUT)) {
perror("failed gpioSetMode()");
exit(EXIT_FAILURE);
}
struct sigaction action;
action.sa_handler = terminate;
action.sa_flags = SA_RESETHAND;
sigemptyset(&action.sa_mask);
if (sigaction(SIGINT, &action, NULL)) {
perror("failed sigaction(SIGINT)");
exit(EXIT_FAILURE);
}
if (sigaction(SIGQUIT, &action, NULL)) {
perror("failed sigaction(SIGQUIT)");
exit(EXIT_FAILURE);
}
if (sigaction(SIGTERM, &action, NULL)) {
perror("failed sigaction(SIGTERM)");
exit(EXIT_FAILURE);
}
fprintf(stdout, "waiting for the next minute ...\n");
wait_until_59_sec();
for (int timer = 1; timer <= RUN_DURATION; timer++) {
set_timecode();
output_timecode();
}
terminate(0);
}
//-------------------------------------
void wait_until_59_sec(void) {
set_now();
time_t start = now_epoch.tv_sec;
while (1) {
set_now();
if (now->tm_sec == 59 && now_epoch.tv_usec <= MIN_TOLERANCE) {
break;
}
if (now_epoch.tv_sec > start + WAIT_DURATION) {
perror("failed wait_until_59_sec()");
exit(EXIT_FAILURE);
}
}
}
void set_now(void) {
if (gettimeofday(&now_epoch, NULL)) {
perror("failed gettimeofday()");
exit(EXIT_FAILURE);
}
now = localtime(&now_epoch.tv_sec);
if (now == NULL) {
perror("failed localtime()");
exit(EXIT_FAILURE);
}
}
//-------------------------------------
void set_timecode(void) {
set_now();
set_next();
/* not elegant but elephant */
int min10 = next->tm_min / 10;
int min1 = next->tm_min % 10;
int hour10 = next->tm_hour / 10;
int hour1 = next->tm_hour % 10;
int yday100 = next->tm_yday / 100;
int yday10 = next->tm_yday % 100 / 10;
int yday1 = next->tm_yday % 10;
int year10 = next->tm_year % 100 / 10;
int year1 = next->tm_year % 10;
int wday1 = next->tm_wday;
timecode[0] = MARKER;
set_bcd(1, 3, min10);
set_bcd(5, 4, min1);
timecode[9] = MARKER;
set_bcd(12, 2, hour10);
set_bcd(15, 4, hour1);
timecode[19] = MARKER;
set_bcd(22, 2, yday100);
set_bcd(25, 4, yday10);
timecode[29] = MARKER;
set_bcd(30, 4, yday1);
timecode[36] = (timecode[12] + timecode[13]
+ timecode[15] + timecode[16] + timecode[17] + timecode[18])
% 2;
timecode[37] = (timecode[1] + timecode[2] + timecode[3]
+ timecode[5] + timecode[6] + timecode[7] + timecode[8])
% 2;
timecode[39] = MARKER;
set_bcd(41, 4, year10);
set_bcd(45, 4, year1);
timecode[49] = MARKER;
set_bcd(50, 3, wday1);
timecode[59] = MARKER;
}
void set_next(void) {
time_t next_sec = now_epoch.tv_sec + 1;
next = localtime(&next_sec);
if (next == NULL) {
perror("failed localtime()");
exit(EXIT_FAILURE);
}
}
void set_bcd(int start, int bits, int value) {
for (int i = 0; i <= start + bits - 1; i++) {
timecode[start + i]
= value & (1 << (bits - 1 - i)) ? 1
: 0
;
}
}
//-------------------------------------
void output_timecode(void) {
for (int sec = 0; sec <= 59; sec++) {
wait_until_exact_second();
if (sec == 0) {
print_now();
putchar(':');
print_timecode();
putchar('\n');
}
switch (timecode[sec]) {
case 0:
transmit_wave(OUTPUT_ZERO);
break;
case 1:
transmit_wave(OUTPUT_ONE);
break;
case MARKER:
transmit_wave(OUTPUT_MARKER);
break;
default:
perror("failed output_timecode()");
exit(EXIT_FAILURE);
}
}
}
void wait_until_exact_second(void) {
set_now();
time_t next_sec = now_epoch.tv_sec + 1;
while (1) {
set_now();
time_t now_sec = now_epoch.tv_sec;
if (now_sec == next_sec && now_epoch.tv_usec <= SEC_TOLERANCE) {
break;
}
if (now_sec > next_sec) {
perror("failed wait_until_exact_second()");
exit(EXIT_FAILURE);
}
}
}
void transmit_wave(int microsec) {
/*
print_now();
*/
if (gpioHardwareClock(OUTPUT_PIN, OUTPUT_FREQ)) {
perror("failed gpioHardwareClock()");
exit(EXIT_FAILURE);
}
gpioDelay(microsec);
gpioHardwareClock(OUTPUT_PIN, 0);
/*
fprintf(stdout, ":%d\n", microsec);
*/
}
//-------------------------------------
void terminate(int signal) {
gpioHardwareClock(OUTPUT_PIN, 0);
gpioTerminate();
if (signal) {
fprintf(stdout, "terminated by signal %d.\n", signal);
}
exit(EXIT_SUCCESS);
}
//-------------------------------------
void print_now(void) {
set_now();
fprintf(stdout, "%04d-%02d-%02d %02d:%02d:%02d.%06lu",
now->tm_year + 1900,
now->tm_mon + 1,
now->tm_mday,
now->tm_hour,
now->tm_min,
now->tm_sec,
now_epoch.tv_usec);
}
void print_timecode(void) {
for (int sec = 0; sec <= 59; sec++) {
switch (timecode[sec]) {
case 0:
putchar('0');
break;
case 1:
putchar('1');
break;
case MARKER:
putchar('m');
break;
default:
putchar('\n');
perror("failed print_timecode()");
exit(EXIT_FAILURE);
}
}
}
上記を例えば~/src/JJY_simulator.c
として保存し、gcc -Wall -pthread -lpigpio -lrt -o ~/bin/JJY_simulator ~/src/JJY_simurator.c
としてコンパイルした後、sudo ~/bin/JJY_simulator
すれば動作する
図では分かりづらくなってしまったが、7番ピン(GPIO 7)と6番ピン(GND)をDSO150に接続すると、安定して40 KHzの出力が得られていることが見て取れる。
DSO150ではなくぐるぐる巻きにしたリード線を接続すると、弱いながらも電波が出力される。執筆時点では、アマゾンにメスメスのジャンパワイヤや0.6 mmエナメル線の在庫があったので、使用されるとよいだろう。ただ、当方で所有する時計のいくつかは、時刻合わせに長時間を要したり失敗したりしており、なお調査が必要と思われる。
コメント