●機械式時計の歩度を測定する

機械式時計の歩度とは、時計が1日に遅れたり進んだりする度合いであり、その誤差を測定する装置を作成したいと思います。
シリアルモニタに機械式時計のタイミングをキャラクタ文字で出力するとタイムグラファーの様にグラフを表示できますが、解像度が粗くて使い物になりませんでした。
本当はタイムグラファーを作りたいのですが、ArduinoにLCDなどの画面を取り付けようとすると標準的なLCDなんて無くて、部品の型により設計がコロコロ変わってしまいますので、
機械式時計の精度を測定して表示するだけの装置を作成したいと思います。

ハードオフでジャンク品の懐中時計を買ってきて、分解したら動くようになりましたので、調整する装置を作ろうかなあと思った次第です。
ウォルサムの懐中時計を分解してみる で分解して修理しました。



■音をひろう

まずは、マイクで時計の音をひろって電気信号に変換しないと始まりません。
そこで、圧電スピーカーをマイク代わりに利用しようと思います。
直径20mmの圧電スピーカーです。

このように、セラミック部分に直接はんだ付けしようとすると蒸着された銀の部分がはんだに食われます。
この手の物を付ける時には銀入りはんだを使うと良いのでしょうが持ってませんので、 そういう時には半田ごてを350度に設定し、銅線に事前にはんだメッキをしておいて銀の部分にのせてから上からチョットだけ半田ごてを当ててくっつけます。

銅線が乗ってるだけに見えますが、これで銀の部分に付いています。
ちなみに私がマイクとして今回使用したのは、
トランスデューサ ピエゾ素子 20mm の部品です。
20個も入っていて格安ですのではんだ付けの失敗も怖くないです。
他の素子を使っても全く問題ありません。


■アンプの作成

次に、マイクアンプを作成します。
トランジスタでゴリゴリやるのも面倒なので、オペアンプを使用したいと思います。
オペアンプを使ってみる で作成したアナログ信号の増幅回路を基本にしてみます。


約1000倍の増幅率の回路を設計しました。
圧電スピーカーを使ったイヤホンを使う予定なので出力側にセラミックコンデンサが入っていませんので注意してください。


実際に作成した所です。
時計のチクタク音が大きな音で聞こえます。


■LEDを点滅させる

増幅された音をもう一つのオペアンプに接続して、基準電圧と比較して音のピークの時だけLEDを点滅させられるようにしてみます。
赤い部分が前回の回路図から変更した所です。


可変抵抗を微妙な感じで操作すると、音のピークの箇所でLEDが点灯するようにできました。
ただ、LEDが点灯するとノイズが乗るのが残念です。

LEDの箇所にオシロスコープを取り付けると、なんだか動きそうではあります。



■マイコンと接続する

接続してマイコン側の電源と共有するとあまりにもノイズが大きく音が拾えないので、フォトカプラで接続する事にします。
オペアンプの出力はテスターで測った所、2.2V程度でした、そのため、フォトカプラに接続する抵抗値は100Ωにしました。
フォトダイオードとフォトカプラを使ってみる で実験した方法で設計してみます。

この回路よりも下の方で改良した回路の方が明らかに高性能です。

実際に作成した所です、後はプログラムを書くだけでしょうか。


まずは手軽に実験するため、Arduinoに接続します。
フォトカプラのGND側はArduinoのGNDへ、マイコンのポートはデジタルピン2番に接続しました。
やはり、ピンの変化に対して即座に反応するためには割り込みが必要でしょう、そこで、Arduinoで割り込みを使う で調べた方法を使います。
試しに次のプログラムを書き込んでなんらかの動作をするのか確認します。

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, FALLING);
}
void loop () {
  digitalWrite(LED_BUILTIN, digitalRead(2));
}
unsigned long t;
void func_() {
  noInterrupts();
  unsigned long l;
  l = micros();
  Serial.println(l - t);
  t = l;
  interrupts();
}

プログラムを書き込んで机の上で動作させてみると机の振動を拾うため、マイクをタオルなどの上に置いて、マイクの上に時計を置く必要がありました。
ArduinoのLチカのLEDがチクタク音と連動して動作するように可変抵抗を調整します。


動作させてシリアルモニタを開いた所です。
周期的に値は何か取れているのですが、これでどうやって時間をもとめるのやら...


■タイミングを取得する

チクタク音を電気信号に変換しても、スパッとON/OFFせずに、激しいチャタリングが起きていると同様の状態だと思います。
そこで、信号が変化後の50ミリ秒(50000μ秒)以内を無視することにしてチャタリングを取りたいと思います。
unsigned long t_cur, t_old;
bool flag;

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

void loop () {
  if (flag) {
    Serial.print(round((double)1000000 / (t_cur - t_old)));
    Serial.print(" : ");
    Serial.println(t_cur - t_old);
    flag = 0;
  }
}

void func_() {
  noInterrupts();
  unsigned long i;
  i = micros();
  if ((i - t_cur) > 50000) {
    flag = 1;
    t_old = t_cur;
    t_cur = i;

    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}
赤文字部分は、取得した時間を1秒(1000000μ秒)で割り振動数を計算しています。
このプログラムを実行すると、このようになりました。

とりあえず、振動数とタイミングは取得できているみたいです。
この時代の時計は振動数:18000回/h(5振動、2.5Hz)らしいので大体近い値が出てはいるみたいです。


■日差を計算する

古い懐中時計の日差を求めようとしていましたが、あまりに周期のバラつきが大きくてテストには向きませんでした。
そこで、他の機械式時計、6振動を用意しました。
実験として6振動の日差を求めるために、上のプログラムのloop関数内を修正しました。
プログラム中の赤文字の6の部分で6振動を決め打ちしています。

unsigned long t_cur, t_old;
bool flag;

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

int cycle;
unsigned long sec_old;
void loop () {
  if (flag) {
    Serial.print(round((double)1000000 / (t_cur - t_old)));
    Serial.print(" : ");
    Serial.println(t_cur - t_old);
    
    if (++cycle == (6 * 2)) {
      long pos = (1000000 * 2) - (t_cur - sec_old);
      Serial.println((double)pos * 30 * 60 * 24 / 1000000);
      sec_old = t_cur;
      cycle = 0;
    }
    flag = 0;
  }
}

void func_() {
  noInterrupts();
  unsigned long i;
  i = micros();
  if ((i - t_cur) > 50000) {
    flag = 1;
    t_old = t_cur;
    t_cur = i;

    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}

上記プログラムでは、6振動×2で2秒間の電気信号を取得して、Arduinoのタイマーと比較することにより、日差を求めています。
実行するとこのようになりました。

音のない静かな環境で微妙な調整をすると、ある程度は日差が取得できるみたいですが、周囲のわずかな音(赤枠の範囲)によって誤差が発生してしまいます。
現状では、回路が発振してしまい、調整が非常に微妙で静かな環境が必要です。
これでは、まともに使用できる物ではありません。


■回路を改良する

この回路では発振してうまく動作しません、
作るなら一番下の回路図が良いです
今までの回路では、可変抵抗を回してちょうどいい所に持っていこうとすると発振して、調整がものすごくシビアでした。
そこで、発振の原因を色々調べた所、オペアンプの負荷が高すぎるんじゃないかと思い、回路を次のように改良しました。
フォトカプラの電流制限抵抗は470Ωになってますが、入手できた部品によって調整してください。(私が817Bというあまり聞かない部品しか持っていないためです。)

オペアンプの出力にトランジスタを取り付ける事でLED点灯時の負荷を減らしています。
これにより、今までの調整のシビアさが無くなり、可変抵抗を軽く調整するだけで時計の音の周期をマイコンに伝えられるようになりました。
とは言ってもかなりシビアですので、多回転ボリュームを使った方がいいです。
あと、雑音を拾うため田舎のような静かな環境が絶対に必要です。

実際に作った全体像です。



■プログラムを改良する

今までのテストプログラムを参考として、それらしいプログラムを作成します。
まず、10個のデータ構造を用意して割り込みで順次取得した値を格納することにしました。
今回値と前回値を比較して振動数を計算します。
振動数によりデータ構造から必要な値を取り出して一日あたりの誤差を計算することにしました。

bool update_flag;

unsigned long ar[10];
unsigned int ar_pt;

void push(unsigned long val) {
  ar_pt++;
  if (ar_pt == 10) ar_pt = 0;
  ar[ar_pt] = val;
}

unsigned long pop() {
  return ar[ar_pt];
}

unsigned long pop_old(int gen) {
  int pos;
  pos = (int)ar_pt - gen;
  if (pos < 0) pos = 10 + pos;
  return ar[pos];
}

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

unsigned long sec_old;
void loop () {
  if (update_flag) {
    unsigned int cycle = round((double)1000000 / (pop() - pop_old(1)));
    //1秒あたりの誤差をマイクロ秒でもとめる
    long micro_sec = 1000000 - (pop() - pop_old(cycle));
    //一日当たりの誤差をもとめる
    double _day = (double)(micro_sec * 60 * 60 * 24) / (double)1000000;

    Serial.print(cycle);
    Serial.print("振動 一日あたりの誤差 ");
    Serial.print(_day);
    Serial.println(" 秒");
    update_flag = false;
  }
}

void func_() {
  noInterrupts();
  unsigned long i;
  i = micros();
  if ((i - pop()) > 50000) {
    push(i);
    update_flag = true;
    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}
このプログラムだと、起動1秒後にデータが揃うととりあえずそれらしい値を表示します。
正しいのか不明ですが、いつも進む6振動の時計を測定した結果です。
測定前にゼンマイをいっぱいに巻いておきました。
このまま24時間時計を放置し電波時計と比較した結果、実際には90秒進みました。


いつも遅れる8振動の時計も測定してみました。
測定前にゼンマイをいっぱいに巻いておきました。
このまま24時間時計を放置し電波時計と比較した結果、実際には24秒遅れました。

確認する方法が無いのでわかりませんが、それらしい値が出ています。


■ 可変抵抗をPWM(疑似アナログ出力)で代用してみる

PWMによる疑似アナログ出力 で調べた方法を使い、オペアンプの比較回路に与える基準電圧をソフトウエアでコントロールできるようにしてみたいと思います。
まずは、回路を改良しました。

基準電圧を作り出す可変抵抗を取り外して、赤色部分が新たに追加した回路です。 
Arduinoのデジタルピン3を使用してアナログ出力をしたいと思います。
追加した抵抗とコンデンサはローパスフィルタでPWMの高周波成分をならしてノイズがなるべく入らないようにしています。
フォトカプラは、Arduinoの電源ノイズを避けるために入れています。

試しに次のように接続して確認してみます。




この回路、ノイズが多くて使い物になりませんでした。
もっといい方法を考えます。


■新しい回路を作成しなおす

今までの回路は発振してしまいうまく動作させられませんでした。
そこで、新たに全てを作り直す事にしました。
ちなみに発振も無く、今までの回路は何だったんだ?というぐらいちゃんと動作します。
まずは、
増幅回路としてココで設計した回路を3段重ねて使用しました。
手持ちの2SC1815がYランク(hFE100程度)のため3段にしましたが、GRランク(hFE200〜400)だと変わるかと思います。
ちなみにベースに流す抵抗値は100KΩにしました。

3段増幅回路の回路図です、ただ単に3段積み上げただけです。


実際に作成するとこのようになります。

この回路に乾電池3本4.5Vを接続してマイクとイヤホンを接続すると、時計の音がガンガン聞こえました。
ノイズの関係で電源は乾電池が良いです。


▼コンパレータの作成

音のピークを信号として取り出すため、コンパレータを作成します。
しかし、一から作るのは面倒なのでこのモジュールを改造することにしました。
LM393 IR赤外線障害物回避センサ


このモジュールの回路図を簡略化すると次のようになっています。

増幅回路の出力の近くの赤で表した10KΩと、モジュールの回路図の10KΩが同じ役割を持っていますので増幅回路の赤色部分の10KΩを取り除く事にしました。
接近センサーのかなめとなる、青で囲った赤外線受信LEDと、回路にのってませんが赤外線送信LEDの2個は必要ないので取り外しました。

回路図に追加するとこのようになります。


実際に作成した所


この状態でマイクの上に時計を乗せて、電池をつなぎ、センサー基板上のボリュームを操作して調整すると時計の音に合わせてセンサー上のLEDが点滅します。
次に、このセンサーをArduinoに接続するためにフォトカプラをつなげます。
フォトカプラが無いとArduinoと電源を共有することになり、電源ノイズがアンプに影響を与えて音が拾えなくなります。


フォトカプラを追加した回路図です。
フォトカプラの接続はこちらの方法をつかいました。

実際に動作させている様子

この回路だと、発振も無くまともに動作しました、後はソフトを何とかするだけでしょうか。


■動作を検証するための方法を考える

Arduinoで作成しても正しく動作しているのか確認できません、そこでフリーのタイムグラファーソフト【びぶ朗】を使って検証できる環境を作成します。

ソフトの入手方法は、googleで びぶ朗 と検索すると、Vector でダウンロードできると思います。
私の場合には64ビットWindows10でも問題なく動作しました。

私自身、中華製激安超小型PCを使っているためマイクあたりのノイズが大きくネットで出回っている【びぶ朗】アンプでは時計の音の閾値が上手く取れず測定できませんでした。
(多分、電源が小さなスイッチングアダプタでノイズが多いのが原因じゃないかな?)
そこでノイズの影響を無くすために、パソコンのマイク端子にON/OFFのパルス入力をして【びぶ朗】を動作させます。

事前に、音の閾値はマイクの上に時計を置いてセンサーのLEDの点滅具合と音が一致するようにセンサーのボリュームを調整しておいてください。

マイク端子とフォトカプラの接続図

マイク端子にはGNDはマイナス、L/Rはプラスのプラグインパワーがパソコンから数ボルト流れており、それを利用します。

マイク端子をパソコンに接続し、【びぶ朗】を動作させます。

矢印でしめしたつまみを上下にうごかして信号内に入るように閾値を調整します。


動作している状態です。
もうArduinoで作ろうとしなくても、これでいいんじゃないか?と思えてきました。


■【びぶ朗】とArduinoを同時に接続できるようにする

回路のフォトカプラを2個取り付けて、びぶ朗とArduinoを同時に接続できるようにします。


Arduinoには次のプログラムを書き込みました。
bool update_flag;

unsigned long ar[10];
unsigned int ar_pt;

void push(unsigned long val) {
  ar_pt++;
  if (ar_pt == 10) ar_pt = 0;
  ar[ar_pt] = val;
}

unsigned long pop() {
  return ar[ar_pt];
}

unsigned long pop_old(int gen) {
  int pos;
  pos = (int)ar_pt - gen;
  if (pos < 0) pos = 10 + pos;
  return ar[pos];
}

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

unsigned long sec_old;
void loop () {
  if (update_flag) {
    unsigned int cycle = round((double)1000000 / (pop() - pop_old(1)));
    //1秒あたりの誤差をマイクロ秒でもとめる
    long micro_sec = 1000000 - (pop_old(0) - pop_old(cycle));
    //前回の1秒あたりの誤差をマイクロ秒でもとめる
    long micro_sec_old = 1000000 - (pop_old(1) - pop_old(cycle + 1));

    Serial.print(cycle);
    Serial.print(" 振動\t日差 ");

    //1サイクルずらした測定結果を合計して2で割る テンプの片振り対策
    Serial.print(round((micro_sec + micro_sec_old) / 2 / 11.57));

    Serial.print(" sec/day\t\t 片振り ");
    //前回のテンプの1サイクルと今回のテンプの1サイクルの時間を引いて、片振りの時間をもとめる /1000をしてマイクロ秒をミリ秒にする
    long be = abs(((signed long)pop_old(0) - (signed long)pop_old(1)) - ((signed long)pop_old(1) - (signed long)pop_old(2)));
    Serial.print((double)be / 1000);
    Serial.println(" ms");

    update_flag = false;
  }
}

void func_() {
  noInterrupts();
  unsigned long i;
  i = micros();
  if ((i - pop()) > 50000) {
    push(i);
    update_flag = true;
    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}

起動してマイクの上に時計を置いてシリアルモニタを開いてみます。


グラフは【びぶ朗】で、細かい数字はArduinoで棲み分けができそうな気がしてきました。
ちなみに、今回測定している時計を実際に使うと一日あたり7秒ぐらい遅れます。

なんだかんだ言って、グラフで表示できる 【びぶ朗】 はすばらしいです、ArduinoにLCD画面でも付けない限り同じ事はできなさそうです。
考えてみると、今回私は 【びぶ朗】 用パルス出力アンプを作成していたのかもしれません。


■ 測定精度を上げる

グラフを書けないのなら、数字だけでもなるべく正確に表示できるようにしようと思います。
現状では【びぶ朗】で日差+2秒に調整した時計をArduinoで作成中のプログラムで測定すると-25秒と表示され、 時計の実際の日差は-2秒となりました。
何かがおかしい、何か間違っている気がします。
もう少し、正確にならないかなあと考えてみます。


▼仮想的な8振動の時計を作成

水晶発振器を分周して仮想的で精度の高い8振動の信号を作り出そうと思います。
8振動とは、周波数に換算すると 4Hz であり、一秒間に4回ON/OFFする信号です。
発振する回路を作ってみる で作成した回路を基本にして 4Hz の信号を作り出します。



赤色部分を改造しました、この回路を使う事により8振動の精度が高い(機械式時計に比べて)信号を作り出せます。

実際に作ってみた所です。


この回路の疑似8振動で【びぶ朗】と Arduino に信号を送って動作させてみます。


なんと、Arduinoのプログラムでは日差が -24秒 と表示されました、プログラムの何かがおかしいです。
【びぶ朗】では、パソコンの操作をしていない状態だとまっすぐのグラフが表示されました、しかし、WEBブラウザでサイトを見た所グラフが乱れました。
マウスの操作や負荷状況によりパソコン内部のノイズが発生して歩度がプラス側に表示される事がわかりました。

私の場合、【びぶ朗】でクロノメーターの8振動機械式時計をプラス2秒で調整しても実際に24時間放置しておくと、マイナス2.5秒ぐらいの遅れが生じていました。
他の時計を調整しても常にマイナス側に流れていました。
原因はこれだったのだと納得しました。
これは、パソコンの機種やチップの良し悪しによって変化すると思いますので一概には言えません。
私のパソコンは手のひらサイズ、中華製激安パソコンですのでしょうがないのかもしれません。


▼Arduinoの発信器の誤差

測定した誤差は、当初割り込みに何か原因があるのかを調べていましたが、Arduino 自体を入れ替えて実験してみると個体差がある事がわかりました。


仮想的な8振動で計測した所、Arduino毎に誤差があり、左から1日あたり、-23秒、+77秒、+24秒でした。
つまり、歩度にこれだけ影響が及ぶということです。
Arduino毎に補正した値をプログラムに書き込んでゆけば良いのでしょうが、手持ちのArduinoの発信器の精度を測らなければならないという、
なかなかむつかしい作業になってきますね。


▼ Arduinoの発信器の誤差を測定する

なるべく正確なクオーツ時計(電波時計は不可)の音を拾って、Arduinoの発信器の誤差を測定したいと思います。
機械式時計に取り付けるマイクをクオーツ時計を取り付けて次のプログラムを走らせました。
volatile unsigned long t;

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

long count;
long aver;

void loop () {
  static unsigned long pos_old;

  if (t) {
    if (!pos_old) {
      pos_old = t;
      t = 0;
      return;
    }
    long pos = 1000000 - (long)(t - pos_old); //1秒の誤差をマイクロ秒でもとめる
    pos = (pos * 24 * 60 * 60) / 1000000; //日差を秒でもとめる
    Serial.print(pos);
    aver += pos;
    count++;
    Serial.print("\taverage count = ");
    Serial.print(count);
    Serial.print("\trate(sec/day) = ");
    Serial.print(aver / count);
    pos_old = t;
    t = 0;
    Serial.println();
  }
}

void func_() {
  noInterrupts();
  unsigned long i;
  static unsigned long old;
  i = micros();
  if ((i - old) > 900000) {//900ms以内を誤動作とする
    old = i;
    t = i;
    //ボード上のLEDをチカチカさせる
    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}
日差がほぼゼロのクオーツ腕時計にマイクを取り付けて50分ほど放置し平均を表示した結果がこのようになりました。

今、動かしているArduinoの発信器の誤差が一日あたり-20秒だとわかりました。
何度やっても大体同じ数字に落ち着くため、これが正しいのだろうと思います。


▼機械式時計の歩度を測定する

Arduino自体の誤差が測定できたので、その誤差を補正して歩度を測定できるプログラムを作成します。
実際に時計を1日放置して計測した値と、うまくいくと近い値が出ますが実用性はありません。
リセットボタンのタイミングやマイクの時計に取り付ける位置によりうまくいく時と失敗するときがあります。
結果はシリアルモニタに表示されます。

//Arduinoに搭載されている発信器の一日あたりの誤差(秒)
#define OSC_RATE_SEC -20
#define BUFF_SIZE 21
#define NUM_MEASURE 9

unsigned long ar[BUFF_SIZE];
unsigned int ar_pt;

void push(unsigned long val) {
  ar[ar_pt] = val;
  ar_pt++;
}

unsigned long pop(unsigned int index) {
  return ar[index];
}

void reset_buff() {
  for (int i = 0; i < BUFF_SIZE; i++) {
    ar[i] = 0;
  }
  ar_pt = 0;
}

bool empty() {
  //1秒プラス1回の値が取得できたら一杯になったとする
  if (ar_pt > 1) {
    if (1000000 < (unsigned long)(pop(ar_pt - 1) - pop(1))) {
      return false;
    }
  }
  if (ar_pt < BUFF_SIZE) {
    return true;
  }
  return false;
}

void setup () {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), func_, CHANGE);
}

long count;
long aver;
int cycle_old;
long rate_old;
int rate_count;
unsigned long sec_old;
int err_count;

void loop () {
  if (!empty()) {
    //****振動数を計算****
    unsigned long old_pos1 = 0, old_pos2 = 0;
    unsigned int cycle = 0;
    for (cycle = 0; cycle < BUFF_SIZE; cycle++) {
      //各サイクル毎の1秒に対する差を計算する
      unsigned long pos = abs(1000000 - (long)(pop(cycle) - pop(0)));
      if (old_pos1 > old_pos2 && old_pos2 < pos) {
        //old_pos2が一番差が少なく、振動数であると考えられる
        break;
      }
      old_pos1 = old_pos2;
      old_pos2 = pos;
    }
    cycle--;
    Serial.print(" ");
    Serial.print(cycle);
    Serial.print(" 振動");
    //****歩度を計算****
    //----1秒の誤差をもとめる----
    //1秒の誤差をマイクロ秒でもとめる
    long meas1 =  pop(cycle) - pop(0) - 1000000;
    //1日の誤差をマイクロ秒でもとめる
    long err_meas1 = 86400 * meas1;
    //1日の誤差を秒に変換する
    long err_meas_sec1 = err_meas1 / -1000000;

    //----1サイクルずらして1秒の誤差をもとめる----
    //1秒の誤差をマイクロ秒でもとめる
    long meas2 =  pop(cycle + 1) - pop(1) - 1000000;
    //1日の誤差をマイクロ秒でもとめる
    long err_meas2 = 86400 * meas2;
    //1日の誤差を秒に変換する
    long err_meas_sec2 = err_meas2 / -1000000;

    //テンプの片振り対策の為に1サイクルずらしたのと平均をとる
    long err_meas_sec = (err_meas_sec1 + err_meas_sec2 ) / 2;

    Serial.print("\t\t 歩度 ");
    //Arduino自体の歩度を引き算する
    int i = err_meas_sec - OSC_RATE_SEC;
    Serial.print(i);
    Serial.print("秒");
    if (count == 1) {//最初に取得した値との差が100以上の場合にはリセット
      if (abs(aver - i) > 100) {
        reset();
      }
    }
    int pos = aver / count;
    if (count < 2 || abs(pos - i) < 100) { //歩度が平均より100秒以上の場合にはエラーとする
      err_count = 0;
      if (cycle == cycle_old) {
        aver += i;
        count++;
        Serial.print("\taverage count = ");
        Serial.print(count);
        Serial.print("\trate(sec/day) = ");
        long rate = aver / count;

        Serial.print(rate);
        if (rate == rate_old) {
          rate_count++;
        } else {
          rate_count = 0;
          rate_old = rate;
        }
        if (rate_count > NUM_MEASURE) {
          Serial.print("\n測定終了\n");
          while (1);
        }
      } else {
        cycle_old = cycle;
        reset();
      }
    } else {
      err_count++;
      if (err_count > NUM_MEASURE) {
        reset();
      }
      Serial.print(" ERROR");
    }
    //****片振りを計算****
    //前回のテンプの1サイクルと今回のテンプの1サイクルの時間を引いて、片振りの時間をもとめる /1000をしてマイクロ秒をミリ秒にする
    long be = abs((long)(pop(1) - pop(0)) - (long)(pop(2) - pop(1)));
    Serial.print("\t\t 片振り ");
    Serial.print((double)be / 1000);
    Serial.println("ミリ秒");

    reset_buff();
  }
}

void reset() {
  count = 0;
  aver = 0;
  rate_old = 0;
  rate_count = 0;
  reset_buff();
}

void func_() {
  noInterrupts();
  if (empty()) {
    unsigned long i;
    static unsigned long old;
    i = micros();
    if ((i - old) > 50000) {
      old = i;
      push(i);
      //ボード上のLEDをチカチカさせる
      static bool f;
      digitalWrite(LED_BUILTIN, f = !f);
    }
  }
  interrupts();
}
先頭の赤文字部分でArduino毎の誤差を調べて入力するようになっています。
一秒の誤差を平均して10回変化がなかった場合に測定終了するようにしています。
6振動と8振動の時計で試してみましたが、他の振動数だと動作するかわかりません。


▼他の振動数でも動作するようにする

2022年10月にハードオフのジャンクコーナーで330円で稼働する機械式時計を購入してきました。
この時計の歩度を測定しようとしたところ、全くうまくいきません。

女性用で秒針なしの時計になります。


ムーブメントは SEIKO 10C という型名で、調べてみると、Cal: 10C / 振動数:19800 ということがわかりました。
振動数とは1時間当たりのテンプの往復回数と言う事で、19800(振動数)/60(分)/60(秒)の計算をしてみると、
1秒間に5.5振動、2.75Hzということになります。
なんと、1秒間では割り切れない振動数と言う事で、これに対応したプログラムを作成したいと思います。


▽プログラムを作成するにあたり考え方として、

1 1〜5秒の区切りの良いところまでの時間を測定する(歩度が一日あたり、±20分以下なら区切りがよいとする)
2 1秒あたりの誤差、振動数を計算する
3 2で測定した1秒あたりの誤差で歩度を計算する
4 歩度の平均を取りシリアルモニタに結果を出力する

歩度が一日あたり、±20分を計算する方法として、
±20 × 60 = ±1200秒
測定時間(秒)× 1200 を±20分の閾値とすればよさそうです。

一日の秒数は 24 × 60 × 60 = 86400
1200 ÷ 86400 × 100 = 1.38%

1.38%が一日あたりの20分の比率でこの範囲に収まった時には秒の区切りが良いと考えるとして、
測定時間(マイクロ秒)に対して秒の基準値を1.38%足した値と引いた値を用意しておいて、その範囲に収まった時は区切りが良いとすればよさそうです。


▽以上をふまえて、プログラムに新たに必要そうなものを考えます。

1秒毎の前後の区切りをつける値の配列 (事前に定数として計算しておくと計算量が減らせる)
const unsigned long PSEC ....
const unsigned long RSEC ....


測定のための関数
int measure(unsigned long*time,int*count)
戻り値で、1秒の区切り内だった場合には1それ以外は0
引数のポインタで測定時間(マイクロ秒)、信号の数を返す。

時計から信号を拾った時の割り込み関数
void _int()

割り込み時の時間を入れるグローバル変数、値が入ると処理のトリガーになり使用後は0にてリセットする
unsigned long contains_time

以上を踏まえてプログラムを作成します。


//Arduinoに搭載されている発信器の一日あたりの誤差(秒)
#define OSC_RATE_SEC -20
#define PSEC_COUNT 5
const unsigned long PSEC[] = {1000000 - (1000000 * 0.0138), 2000000 - (2000000 * 0.0138), 3000000 - (3000000 * 0.0138), 4000000 - (4000000 * 0.0138), 5000000 - (5000000 * 0.0138)};
const unsigned long RSEC[] = {1000000 + (1000000 * 0.0138), 2000000 + (2000000 * 0.0138), 3000000 + (3000000 * 0.0138), 4000000 + (4000000 * 0.0138), 5000000 + (5000000 * 0.0138)};
unsigned long contains_time;

void setup () {
  //delay(1000);
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), _int, CHANGE);
}

int measure(unsigned long*time, int*count) {
  static unsigned long cur;
  static int counter;
  if (!cur) {
    cur = contains_time;
  }
  *time = contains_time - cur;
  for (int i = 0; i < PSEC_COUNT; i++) {
    if (PSEC[i] < *time && RSEC[i] > *time) {
      *count = (counter * 10) / (i + 1);
      *time = *time / (i + 1);
      cur = 0;
      counter = 0;
      return 1;
    }
  }
  counter++;
  if (RSEC[PSEC_COUNT - 1] < *time) {
    cur = 0;
    counter = 0;
  }
  return 0;
}

void loop () {
  if (contains_time) {
    unsigned long time;
    int count;
    if (measure(&time, &count)) {
      float f = count;
      f = f / 10;
      Serial.print(f);
      Serial.print("振動 : 歩度");

      long pos;
      //1秒の誤差をマイクロ秒でもとめる
      pos = (long)time - 1000000;
      //1日の誤差をマイクロ秒でもとめる
      pos = pos * 86400;
      //1日の誤差を秒に変換する
      pos = pos / -1000000;
      //Arduino自体の歩度を引き算する
      pos = pos - OSC_RATE_SEC;
      Serial.print(pos);
      Serial.print("秒");

      //歩度の平均を取りシリアルモニタに結果を出力する
      static int avr_count;
      static int avr;
      static int avr_error;
      static int old_count;
      if (old_count != count) {
        old_count = count;
        avr_count = 0;
        avr = 0;
        avr_error = 0;
      }

      if (avr_count == 0 || avr_error > 2) {//平均エラーが3回続いたら初期化する
        avr_count = 1;
        avr = pos;
        Serial.println(" 平均初期化");
        avr_error = 0;
      } else {
        if (abs((avr / avr_count) - pos) < 100) {//歩度が平均より100秒以上の場合にはエラーとする
          avr_count++;
          avr += pos;
          Serial.print(" 平均カウンタ");
          Serial.print(avr_count);
          Serial.print("回 平均歩度");
          Serial.print(avr / avr_count);
          Serial.println("秒");
          avr_error = 0;
        } else {
          Serial.println(" 平均エラー");
          avr_error++;
        }
      }
    }
    contains_time = 0;
  }
}

void _int() {
  noInterrupts();
  unsigned long i;
  static unsigned long old;
  i = micros();
  if ((i - old) > 50000) {
    old = i;
    contains_time = i;
    //ボード上のLEDをチカチカさせる
    static bool f;
    digitalWrite(LED_BUILTIN, f = !f);
  }
  interrupts();
}
作りながら思ったのですが、このプログラムでは±20分以上の日差がある時計には使えません。
確かに例のムーブメントは、5.5振動と表示されて値が出力されました。
しかし、周囲の雑音で値がぶれて遅れる時計か進む時計かの大まかな傾向しかつかめませんでした。
最初にクオーツ時計をセットしてArduinoに搭載されている発信器の一日あたりの誤差の定数を調整する必要があります。
動作させるには周囲が静まり返ってマイクが効率よく音を拾ってくれる必要があります。


上記プログラムを使用して機械式時計の誤差を拾えるのかを確認してみます。
犠牲になるのは、Cal.ETA2892A2 、オメガの時計ですが、いわゆるETAポン。
クロノメーターも通っていて元の精度はいいはずなので実験台にはちょうどいいと思います。



このETA2892A2を壊すつもりで、歩度をプラスに持ってきます。
この部品を0.1mmぐらい竹の割り箸を当てて、ひげゼンマイが短くなる方に動かします。
ほんとに緩急針が動いたのか微妙ですがこれで結構進みます。
ちなみに、緩急針の先に付いている真鍮色の部品はあおり(フォークの幅)を調整するための部品ですので触らないのが無難です。



その後、30秒ほど放置してから測定してみます。
じょじょに精度が変化していくため時間を置かないと安定しないみたいです。

まずは、びぶ朗の画面ではこのようになりました。



グラフが明らかに上昇していて歩度がプラスになっているのがわかります。
次に今回作成したプログラム、深夜の周りが静まり返った時に測定しました。



歩度がプラス25秒と表示されています。
この時計を実際に一日使用してリアルな日差を測定した所、+23秒でした。
おおまかに今回作成したプログラムと近い値でした。
しかし実際に使用というのがミソで時計自体の姿勢差や腕の動きや振動などが影響しており、人によってはもっと違う結果が出る可能性が高いと思います。

緩急針は、ひげゼンマイの伸びたり縮んだりする部分の長さを調整するためにあります。
長くすると遅れて、短くすると進むようになります。
ルーペで拡大して見えるか見えないかのほんのわずか動かしてもけっこう日差が変化するため時計を壊す覚悟が無い人はやらない方がいいです。


■マイクで音をひろうコツ

うまく音をひろうにはマイクを時計本体にピッタリ押し付ける必要があります。
しかし押し付ける時に力を加えると圧電スピーカーのセラミック部分が割れてしまいますので、
グルーガンなどでセラミック部分を強化したほうがいいかもしれません。

グルーガンのホットボンドでセラミック側を強化して、なにもしていない裏側を時計に押し当てると問題なく音はひろえました。
スピーカの裏側を時計の平らな部分に当てて、セラミック部分が割れないように軽く洗濯ばさみではさんでいます。
これでしっかり音が拾えますが、机の上に直接置くと周囲の振動をひろってしまうのでタオルの上に置いています。

もし、急に音が拾えなくなったり、触ってもガサガサ音が入らずブーンとノイズばかりが入る時はマイクのセラミックが割れているかもしれません。


2022年6月作成


▲トップページ > マイコンなど