AE測定、長期間ロギングシステム構築 (その6) 最終検証編

前回までで、センサーから信号をとりだし、Arduinoで複数チャンネルのAD変換してイベントをとらえる事前検討が完了しました。ここでは、長期ロギングに向けて最終的な調整をしてきます。チャンネルごとにセンサーの設置状況も違うので、センサーごとに異なる閾値を設定したほうが良いと推測されます。
 

閾値検討用元データの取得用 Arduinoスケッチ

const int NUM_CHANS = 4;
const int MAX_HIST_INDEX = 200;
unsigned int histogram[MAX_HIST_INDEX + 1][NUM_CHANS];

// タイムアウト設定 (2時間 = 7,200,000ms)
const unsigned long TIMEOUT_MS = 7200000;
unsigned long startTime;

void setup() {
Serial.begin(115200);
ADCSRA &= ~(bit(ADPS2) | bit(ADPS1) | bit(ADPS0));
ADCSRA |= bit(ADPS2);

for (int i = 0; i <= MAX_HIST_INDEX; i++) {
for (int j = 0; j < NUM_CHANS; j++) histogram[i][j] = 0;
}

Serial.println(F("Long-term Noise Analysis Started..."));
Serial.println(F("Target: 20,000 non-zero samples OR 2 hours."));
startTime = millis();
}

void loop() {
bool limitReached = false;
unsigned long nonZeroCount = 0; // 0以外の総サンプル数

while (!limitReached) {
// タイムアウトチェック
if (millis() - startTime >= TIMEOUT_MS) {
Serial.println(F("Time limit reached (2 hours)."));
limitReached = true;
break;
}

for (int ch = 0; ch < NUM_CHANS; ch++) {
int rawVal = analogRead(A0 + ch) >> 2;

// 0以外の場合のみカウント
if (rawVal > 0) {
int index = (rawVal > MAX_HIST_INDEX) ? MAX_HIST_INDEX : rawVal;
histogram[index][ch]++;
nonZeroCount++;

// 0以外のサンプルが合計20,000に達したら終了
if (nonZeroCount >= 20000) {
Serial.println(F("Non-zero sample limit reached."));
limitReached = true;
break;
}
}
}
}

// 結果の出力
Serial.println(F("Value,CH0,CH1,CH2,CH3"));
for (int i = 1; i <= MAX_HIST_INDEX; i++) { // 0は除外して出力
Serial.print(i);
if (i == MAX_HIST_INDEX) Serial.print("+");
for (int ch = 0; ch < NUM_CHANS; ch++) {
Serial.print(",");
Serial.print(histogram[i][ch]);
}
Serial.println();
}

Serial.print(F("Total Non-Zero Samples: ")); Serial.println(nonZeroCount);
Serial.print(F("Elapsed Time (ms): ")); Serial.println(millis() - startTime);
Serial.println(F("--- Analysis Finished ---"));
while (1);
}

開発環境(室内)での事前検証結果

Long-term Noise Analysis Started...
Target: 20,000 non-zero samples OR 2 hours.
Time limit reached (2 hours).
Value,CH0,CH1,CH2,CH3
1,0,2,2,1
2,0,2,0,0
3,0,0,0,1
4,0,0,0,0
5,0,0,0,0
6,0,0,0,0
7,0,0,0,0
8,0,0,0,0
9,0,0,0,0
10,0,0,0,0
11,0,0,0,0
12,0,0,0,0
13,0,0,0,0


186,0,0,0,0
187,0,0,0,0
188,0,0,0,0
189,0,0,0,0
190,0,0,0,0
191,0,0,0,0
192,0,0,0,0
193,0,0,0,0
194,0,0,0,0
195,0,0,0,0
196,0,0,0,0
197,0,0,0,0
198,0,0,0,0
199,0,0,0,0
200+,0,0,0,0
Total Non-Zero Samples: 8
Elapsed Time (ms): 7200214
--- Analysis Finished ---

この結果から、2時間放置してもノイズ信号のMax値は3であり、少しだけ余裕を見て閾値を5に設定すればよさそうだと判断しました。 そして次はフィールドテストへシフトしました。

フィールドテストでの追加検討

 機器類を整理して、実環境に配置して、閾値を再検討してみました。2時間の放置検証して、事前検証と同じ結果を得ました。 チャンネル別に閾値を設定する想定でしたが、かなり管理低い閾値設定なので、すべてのチャンネルとも5に設定することにします。

 これで、長期ロギングに向けた最終検討に移行できそうです。
次回は、最終検証、本番運用に移行していきます。


関連記事

AE測定、長期間ロギングシステム構築 (その5) 複数信号キャプチャ実験編

前回までで、センサーからの信号をArduinoでAD変換して簡易オシロで観測するところの事前検討が完了しました。最終的なシステム構成の確定に向けた検証をしていきます。
 ここでは、前回課題として見つけたチャンネル間クロストークの影響の見極めと使用チャンネル数の確定をします。2ch分の取り出した(AE)信号をAD変換して読み込む実験を行い、妥当な情報をロギングできそうかを検証していきます。
 前回の確認で4チャンネルでもそれなりの波形確認できそうなので、2回路分の実装と2回路分のダミー実装(半固定抵抗を通してGND接続)の合計4回路分を用意しました。

分圧回路
分圧回路

作成済みの2回路分を接続しました。

2ch
2ch

これに2ch分のAE(ピエゾ素子)をつないで、信号を検証しました。Arduinoスケッチは前回のものと同じです。

2chのAEセンサーを横並びに机に押し当てて、その左側、右側の机をペンで叩いて衝撃を計測してみました。

2ch


 A0チャンネルとA1チャンネルの信号の大きさが交互に変わっていることがわかります。また、 A2、A3チャンネルは 0のままで、影響を受けていないことが分かります。現状のチャンネル切り替えスピードで影響を受けずに計測できそうです。

4チャンネルでも行けそうなので、回路構成は4chで確定して進めることにしました。
つぎは、最終的な本番構成にして、動作確認して問題なければフィールドテストでスライスレベルの調整などを検討しましょう。

関連記事

AE測定、長期間ロギングシステム構築 (その4) 信号検出実験編

前回までで、センサーから信号をとりだすアナログ部分と、AD変換を行うArduinoの事前検討が完了しました。ここでは、取り出した(AE)信号をAD変換して読み込む実験を行い、妥当な情報をロギングできそうかを検証していきます。
 ここで、前出の記事( AD変換部 事前検証編 )で、検討したリセット問題に関連して新たな課題を理解しました。avrdudeで、Flash readデータの吸出しを行う予定にしていましたが、avrdudeを実行するとリセットが発生することが分かりました。このため、avrdudeを利用するなら電解コンデンサーを追加してリセット発生を抑制する方法は使えないことが分かりました。リセットを発生させない運用ならロギング実装の中にデータを吸い出す実装が必要ということになります。どちらを選択するかが課題として残ります。その選択は信号検出実験の後に再検討することにしましょう。

やりかたは、まずは1ch、 そして最終ゴールを見据えて4ch分のデータ取得をやってみましょう。データは、とりあえずRAM上(変数)に保持して、データ転送し、シリアルプロッタで、プロットしてみます。 シリアルプロッタでの結果が、オシロスコープで見た波形と一致するかを見てみましょう。そして、データ取得の閾値や周期などを検討してみようという方針です。

AE信号出力の分圧回路の製作(手持ち部品から選抜)

前段のアンプは9V電源で、Max9Vの出力があります。Arduino Uni V3の耐圧というかアナログ信号入力の制限は0~5Vです。改めてオシロでアンプの出力をDCで確認すると、だいたい0~9Vの範囲で、振れていて、4.5Vあたりが中心電圧です。
オシロ 検出信号波形

osc_dc
osc_dc


時間もないので、手持ち部品から選抜して、できるだけ適した回路を作成してみます。
10kΩの半固定抵抗が4個あったので、これを使います。本番を想定して4回路分取る前提で、とりあえず1回路分を作成しました。

回路写真

Bunatu
分圧回路


これを使って、仮配線してみます。これで、システム全体の仮組み(仮SI)ができました。実験用のコードを動かして確認しましょう。プログラムは変数として使える2KBの範囲内で収まるようしました。要約すると、入力波形をAD変換しながら取得・蓄積して、閾値を超えたらPCに蓄積した波形をなげるというものです。

ロギングの検証

簡易オシロ Arduinoスケッチ(OSCsimulator.ino)

// --- 設定項目 ---
const int NUM_CHANS = 4;
const int BUFFER_SIZE = 1500; // バッファサイズ (1.5KB)
const int TRIGGER_CH = 0; // A0をトリガーに設定
const int THRESHOLD = 30; // 閾値 (8bit: 0-255) ※約2.5V
//const int PRE_TRIGGER_SAMPLES = 200; // トリガー前に何セット分表示するか
const int DISPLAY_POINTS = 50; // 画面に表示したい総行数
const int PRE_TRIGGER_SAMPLES = 5; // そのうちの20%(過去分)
const int POST_TRIGGER_SAMPLES = 50; // 残りの80%(未来分)

byte buffer[BUFFER_SIZE];
int head = 0; // 書き込み位置

void setup() {
Serial.begin(115200);

// ADCの高速化 (Prescaler 16 = 約1MHzのADCクロック)
// 分割比を16に設定することで、4chでも実用的な速度を確保します。
ADCSRA &= ~(bit(ADPS2) | bit(ADPS1) | bit(ADPS0));
ADCSRA |= bit(ADPS2);

Serial.println("System Ready. Waiting for trigger on A0...");
}

void loop() {
// --- 1. 定常サンプリング ---
int val = analogRead(A0 + (head % NUM_CHANS));
buffer[head] = (byte)(val >> 2);

// --- 2. トリガー判定 ---
if ((head % NUM_CHANS) == TRIGGER_CH && buffer[head] > THRESHOLD) {
captureAndSend(head);
// 送信が終わったら head をリセットして、次のトリガーに備える
head = 0;
// 少し待機(連続トリガーによるチャタリング防止)
delay(500);
}

head = (head + 1) % BUFFER_SIZE;
}

void captureAndSend(int tIndex) {
// 1. 未来分をサンプリング
for (int i = 0; i < (POST_TRIGGER_SAMPLES * NUM_CHANS); i++) {
head = (head + 1) % BUFFER_SIZE;
int v = analogRead(A0 + (head % NUM_CHANS));
buffer[head] = (byte)(v >> 2);
}

// 2. 画面を一度リセットするために「空のデータ」を少し送る
// これにより前の波形と新しい波形の間に隙間ができます
for (int i = 0; i < 50; i++) {
Serial.println("0,0,0,0");
}

// 3. 送信開始位置の計算
int startOffset = (tIndex - (PRE_TRIGGER_SAMPLES * NUM_CHANS) + BUFFER_SIZE * 2) % BUFFER_SIZE;
startOffset = (startOffset / NUM_CHANS) * NUM_CHANS;

// 4. データ送信
for (int i = 0; i < DISPLAY_POINTS; i++) {
for (int ch = 0; ch < NUM_CHANS; ch++) {
int idx = (startOffset + (i * NUM_CHANS) + ch) % BUFFER_SIZE;
Serial.print(buffer[idx]);
if (ch < NUM_CHANS - 1) Serial.print(",");
}
Serial.println();
// ここでdelayを入れると、波形が描画される「スピード」を調整できます
delayMicroseconds(500);
}
}

この仮の簡易オシロ スケッチで取得したシリアルプロットでの波形の観測動画です。 

AE仮配線観測

 とりあえずこれで、Arduinoで波形を観測できそうなことを確認できました。A0チャンネルの1回路にだけ接続して他はフリー状態ですが、A0の影響を受けて波形を観測しており、他のチャンネルを接続したとき影響を受けないか確認が必要そうです。つぎは、ほぼ本番の接続状態で、複数チャンネルでの観測を試しましょう。


関連記事

AE測定、長期間ロギングシステム構築 (その3の2) AD変換部 事前検証編

前回までで、センサーから信号をとりだすアナログ部分の仮構築が完了しました。ここからは、取り出したAE信号をAD変換してロギングする部分を構築していきます。
前出(アナログ部製作編)の採用するシステム構成の方針により、ロギングシステムの中核につぎのパーツを採用しました。

AD変換&ロギング部

[107385]Arduino Uno Rev3
https://akizukidenshi.com/catalog/g/g107385/

事前検討方針

 ドキュメントレベルの調査結果から、次の課題があることを確認しました。
・USBケーブル挿抜で再起動される(運用上、 通常ロギング時は単体で動作し、データ吸い上げ時だけUSB接続する運用にする必要がある)
・再起動でRAMはリセットされ、EEPROMはリセットされないが書き込み回数上限制限がある(カウンタ等の頻繁に更新される変数保存ができない)
・EEPROMの容量が小さい(長期間ロギングのため、できるだけ大きいデータ領域を確保したい)

これらの問題を、解決する必要があります。そこで2つめと3つめの課題は、次回検討するプログラミング実装レベルで解決することにしました。ここでは、USBケーブル挿抜に関わる挙動の確認と対策、そして再起動されていないかどうかについて確認する方法を検討することにしました。

Arduino IDE をインストールするPCのスペック確認

Arduino IDE に必要なスペックを確認しておく。Windows 10以降のPCならほぼ大丈夫。
問題なければ、インストール。
Arduino 公式ソフトウェアページ

動作確認

PCにArduinoを繋ぎ、デバイスマネージャーの「ポート(COMとLPT)」に「Arduino Uno」が表示されるか確認する。

USBを挿す前は Port のさきに COM3などが見えていましたが、 USBをさすとPortがグレーアウトされました。
Board: ”Arduino Uno” → Arduino AVR Boardsで Arduino Unoを選択しました。
→ これでは、まだ認識していないようです。
USBを一度抜挿して確認、これでもまだ認識していないようだが、 再度開きなおすと
下のキャプチャのようにCOM4(Arduino Uno)が表示される。

最初の動作確認

出荷時は、次のサンプルが動いているようです。
サンプルを開く:
「ファイル」→「スケッチ例」→「01.Basics」→ 「Blink」

delay(100);  など、任意の時間に変更して、
再度、画面左上の 「→(書き込み)」ボタン を押すと、コードを書き込んで点滅間隔がかわる。

Arduino Unoの動作状況確認

Arduino Unoは基本1方向らしい、吸出しなどはavrdude コマンドらしい。

“C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avrdude.exe” -C “C:\Program Files (x86)\Arduino\hardware\tools\avr\etc\avrdude.conf” -v -p atmega328p -c arduino -P COM4 -b 115200 -U flash:r:backup.hex:i

ユーザーパスにインストールしていたので、avrdude.exeは次のパスにありました。
C:\Users\<ユーザー名>demo\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17\bin

パラメータを確認
> .\avrdude.exe -?
Usage: avrdude.exe [options]
Options:
-p <partno> Required. Specify AVR device.
-b <baudrate> Override RS-232 baud rate.
-B <bitclock> Specify JTAG/STK500v2 bit clock period (us).
-C <config-file> Specify location of configuration file.
-c <programmer> Specify programmer type.
-D Disable auto erase for flash memory
-i <delay> ISP Clock Delay [in microseconds]
-P <port> Specify connection port.
-F Override invalid signature check.
-e Perform a chip erase.
-O Perform RC oscillator calibration (see AVR053).
-U <memtype>:r|w|v:<filename>[:format]
Memory operation specification.
Multiple -U options are allowed, each request
is performed in the order specified.
-n Do not write anything to the device.
-V Do not verify.
-u Disable safemode, default when running from a script.
-s Silent safemode operation, will not ask you if
fuses should be changed back.
-t Enter terminal mode.
-E <exitspec>[,<exitspec>] List programmer exit specifications.
-x <extended_param> Pass <extended_param> to programmer.
-y Count # erase cycles in EEPROM.
-Y <number> Initialize erase cycle # in EEPROM.
-v Verbose output. -v -v for more.
-q Quell progress output. -q -q for less.
-l logfile Use logfile rather than stderr for diagnostics.
-? Display this usage.

avrdude version 6.3-20190619, URL: <http://savannah.nongnu.org/projects/avrdude/>

■flash(プログラム領域)の吸い上げ の試行
※参考にする場合はパス(特に赤字)を環境に合わせてください。

.\avrdude.exe "-CC:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf" -v -p atmega328p -c arduino "-PCOM4" -b115200 -U flash:r:backup.hex:i

avrdude.exe: Version 6.3-20190619
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2014 Joerg Wunsch

System wide configuration file is "C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf"

Using Port : COM4
Using Programmer : arduino
Overriding Baud Rate : 115200
AVR Part : ATmega328P
Chip Erase delay : 9000 us
PAGEL : PD7
BS2 : PC2
RESET disposition : dedicated
RETRY pulse : SCK
serial program mode : yes
parallel program mode : yes
Timeout : 200
StabDelay : 100
CmdexeDelay : 25
SyncLoops : 32
ByteDelay : 0
PollIndex : 3
PollValue : 0x53
Memory Detail :

Block Poll Page Polled
Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff
flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff
lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00
signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00

Programmer Type : Arduino
Description : Arduino
Hardware Version: 3
Firmware Version: 4.4
Vtarget : 0.3 V
Varef : 0.3 V
Oscillator : 28.800 kHz
SCK period : 3.3 us

avrdude.exe: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude.exe: Device signature = 0x1e950f (probably m328p)
avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: reading flash memory:

Reading | ################################################## | 100% 5.22s

avrdude.exe: writing output file "backup.hex"

avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: safemode: Fuses OK (E:00, H:00, L:00)

avrdude.exe done. Thank you.

上の実行の結果、ファイルが実行パスに出力されていることを確認しました。
面白くなってきました。
このファイルのサイズは78KBで、テキストエディターでも見える形式です。
行頭に”:”があり4byte分アドレス相当の情報、実データ、最後に1byteチェックサムがついているようですね。

つぎにEEPROMの中身を読みだしてみます。

> .\avrdude.exe "-CC:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf" -v -p atmega328p -c arduino "-PCOM4" -b115200 -U eeprom:r:eeprom.hex:i

avrdude.exe: Version 6.3-20190619
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2014 Joerg Wunsch

System wide configuration file is "C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf"

Using Port : COM4
Using Programmer : arduino
Overriding Baud Rate : 115200
AVR Part : ATmega328P
Chip Erase delay : 9000 us
PAGEL : PD7
BS2 : PC2
RESET disposition : dedicated
RETRY pulse : SCK
serial program mode : yes
parallel program mode : yes
Timeout : 200
StabDelay : 100
CmdexeDelay : 25
SyncLoops : 32
ByteDelay : 0
PollIndex : 3
PollValue : 0x53
Memory Detail :

Block Poll Page Polled
Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff
flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff
lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00
signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00

Programmer Type : Arduino
Description : Arduino
Hardware Version: 3
Firmware Version: 4.4
Vtarget : 0.3 V
Varef : 0.3 V
Oscillator : 28.800 kHz
SCK period : 3.3 us

avrdude.exe: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude.exe: Device signature = 0x1e950f (probably m328p)
avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: reading eeprom memory:

Reading | ################################################## | 100% 3.11s

avrdude.exe: writing output file "eeprom.hex"

avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: safemode: Fuses OK (E:00, H:00, L:00)

avrdude.exe done. Thank you.

eepromのデータを読み出せました。ファイルサイズは3kbです。ですが、中身はFlashメモリの冒頭部分と同じようでした。 確認のため、 eeprom_clearというサンプルをアップロードして確認してみます。→結果、再度読み込んだファイルの中身は、変わりませんでした。 上のEEPROMの読み出しは機能していないようです。失敗しているよう。

サンプルに、eeprom_clearや eeprom_readがあるのでこれを使ってみます。eeprom_readをアップロードして確認してみます。右上のシリアルモニタとシリアルプロッタを使って、確認すると。EEPROMの1kb分が0で帰ってきました。

基本的な動きが確認できたので、ここでの検証課題のUSB挿抜でのリセット発生状況の確認と回避確認です。
と、ここで、想定外の情報、Arduino IDE(PC側)のシリアルモニタを開く操作をすると、Arduinoボード本体に自動的にリセットがかかるらしい、USB挿抜以外にもチェックするべき観点が増えた。
要約すると、おもに電源に関連する不安定要素によるものと、 通信制御によるリセットの2つで、通信制御によるものはUSB挿抜と、DTR/RTS信号によるオートリセットということらしい。電源に関してはACアダプターでの安定供給と、シールド対策をするとして、USB挿抜とDTR/RTS信号によるリセットされることについては「 RESET-GND間に10μFのコンデンサを挟む」対策をする。

 さて、要因は1つ増えたが、対策は増えていないので、方針は変えずに先に進めよう。
 さらに追加情報をみつけた、「リセット理由の切り分けは MCUSR (MCU Status Register) レジスタ」でできるらしい。

MCUSRレジスタのビットマップ解説
このレジスタの 下位4ビット がリセット要因を示します。
ビット番号 ビット名 名称 内容
Bit 3 WDRF ウォッチドッグ・リセット ウォッチドッグタイマーのタイムアウトによるリセット
Bit 2 BORF ブラウンアウト・リセット 電源電圧が規定値を下回ったことによるリセット(電源不安定が原因)
Bit 1 EXTRF 外部リセット RESETピンを「LOW」にしたことによるリセット(DTR信号/物理ボタンが原因)
Bit 0 PORF パワーオン・リセット 電源が完全にOFFの状態からONになった時のリセット

本番向け実装も考慮して、MCUSRレジスタも含めてデータ構造設計をします。方針は、あらかじめデータ保存領域を00クリアしておきます。データ登録はブロック単位でおこない、更新は追記のみで上書きはしないものとします。
データブロック構造を次のように定義する。
00 00 00 00 00
最初の1byteを、種別フラグとする。
意味を次のようにする。
00: 未使用
01: リセットデータ
2byte目:MCUSRレジスタ
02: 同期データ
2byte目:クロックタイマー
6?byte目:時刻データ
03: 未定義
1x: チャンネルデータ
  2byte目:クロックタイマー
6?byte目:AD変換データ

とりあえず、LED点滅回数でリセット回数が分かるようにしましょう

時刻データは、PCから受信を想定しています。
EEPROMの容量と1ブロックのサイズは、クロックタイマー+時刻データと クロックタイマー+AD変換データのうちの大きいほうを採用して決めます

LED点滅による通知仕様
リセット回数が5回なら 0.2秒間隔で5回点滅した後、2秒消えた後、5回点滅という繰り返しにします。

できるだけ長期間のロギングをしたいために、無駄なデータ領域を消費しないようにする。
なお、リセット回数は、 EEPROMにどこまで書き込まれたかとリセット回数をカウントアップして保持して、データ追加と点滅回数に使用します
1ブロック 8バイト固定の決定案

Unix Timeは4バイト(32bit)すべてを格納。

バイト01234567備考
01 (Reset)01MCUSR<—millis(4B)—>0000最もシンプルな構成
02 (Sync)02<—Unix Time(4B)—><—millis(4Bの一部)—>※後述
1x (Data)1x00<—millis(4B)—><—AD値(2B)—>

1. EEPROM走査関数: 起動時に先頭から8バイトずつ読み、01の数を数えつつ、最初の00を見つける。

2. リセット記録: setup()の最初で01レコード(MCUSR付き)を追記。

3. LED点滅関数: loop()内でmillis()を監視し、カウントされた回数分だけ点滅(delayを使わない)。

4. PC同期受信: シリアルで 0x02 を受信したら、続く4バイトをUnix Timeとして処理。

※補足:3ヶ月ロギングで1KBのEEPROM(128回分)が溢れるのが心配な場合は、追記時に「これ以上書かない」というガードも入れる。


1.書き込み位置特定: 8バイトすべてが 0x00(完全な未使用領域)であることを条件に、追記位置を決定します。

2.時刻同期(02): PCから [0x02][UnixTime 4B][CheckSum 1B] の計6バイトを受信する仕様とします。チェックサムはUnixTime 4バイトの加算下位1バイトとします。

3.データ構造の遵守:
 1. 01(Reset): [01][MCUSR][00 00 00 00 00 00] (計8バイト)
2. 02(Sync): [02][UnixTime 4B][millis 3B(上位)]
※02にのみ、PC時刻とArduino内時計を紐付けるための基準点としてmillisを保持します。

ここまで作業が進んだところで、EEPROMの読み込み問題に気が付いたので先に書いた対策を実施しました。詳しくはこちらを参照してください→「Arduino Uno 事前検証過程(その3の1)でのEEPROM読み出し問題の回避策

本体実装 (Arduinoスケッチ ResetDetector.ino)

まず、リセットと同期の実装を入れた実験版です。

/*
* Reset logging PreCode
*/

#include <EEPROM.h>
#include <avr/wdt.h>

// --- 定数定義 ---
const int BLOCK_SIZE = 8;
const int EEPROM_LIMIT = 1024;
const int LED_PIN = 13;

int totalResetCount = 0;
int nextAddr = 0;
byte lastMcusr = 0;

void setup() {
lastMcusr = MCUSR;
MCUSR = 0; // 次回のためにクリア

Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);

// 1. 書き込み位置の特定(全8バイトが0x00であることを確認)
findNextAddrAndCount();

// 2. リセット記録(設計案通り、millisを含まない構成)
recordReset();
}

void loop() {
updateLED(); // LED点滅処理(非ブロッキング)
checkSerial(); // PCからの同期信号(02)待ち
}

// 課題1:書き込み位置特定(種別00かつ全データ00を判定)
void findNextAddrAndCount() {
nextAddr = -1;
totalResetCount = 0;

for (int i = 0; i < EEPROM_LIMIT; i += BLOCK_SIZE) {
bool allZero = true;
byte type = EEPROM.read(i);

if (type == 0x01) totalResetCount++;

// 全8バイトが0x00かチェック
for (int j = 0; j < BLOCK_SIZE; j++) {
if (EEPROM.read(i + j) != 0x00) {
allZero = false;
break;
}
}

if (allZero && nextAddr == -1) {
nextAddr = i;
}
}
if (nextAddr == -1) nextAddr = 0; // 満杯時は先頭(運用に合わせて要調整)
}

// 課題4:当初案通りのリセット記録(余計なmillisは排除)
void recordReset() {
if (nextAddr + BLOCK_SIZE > EEPROM_LIMIT) return;

EEPROM.update(nextAddr, 0x01); // 種別フラグ
EEPROM.update(nextAddr + 1, lastMcusr); // MCUSR(補助情報)
for (int i = 2; i < BLOCK_SIZE; i++) {
EEPROM.update(nextAddr + i, 0x00); // 残りは00埋め
}

nextAddr += BLOCK_SIZE;
// 点滅用にカウントを更新
totalResetCount++;
}

// 課題2:時刻同期(チェックサム付き)
void checkSerial() {
if (Serial.available() >= 6) {
if (Serial.read() == 0x02) {
uint32_t unixTime = 0;
byte checksum = 0;

for (int i = 0; i < 4; i++) {
byte b = Serial.read();
unixTime |= ((uint32_t)b << (i * 8));
checksum += b;
}

byte receivedSum = Serial.read();
if (checksum == receivedSum) {
saveSyncData(unixTime);
Serial.println("OK: Sync Success");
} else {
Serial.println("Error: Checksum Mismatch");
}
}
}
}

void saveSyncData(uint32_t ut) {
if (nextAddr + BLOCK_SIZE > EEPROM_LIMIT) return;

uint32_t currentMs = millis();

EEPROM.update(nextAddr, 0x02); // 種別フラグ

// UnixTime (4byte) を記録
for (int i = 0; i < 4; i++) {
EEPROM.update(nextAddr + 1 + i, (ut >> (i * 8)) & 0xFF);
}

// millis() の上位3バイトを記録 (Byte 5, 6, 7)
// 下位8bit(0-255ms)を切り捨てることで、0.25秒精度の同期を実現
EEPROM.update(nextAddr + 5, (currentMs >> 8) & 0xFF);
EEPROM.update(nextAddr + 6, (currentMs >> 16) & 0xFF);
EEPROM.update(nextAddr + 7, (currentMs >> 24) & 0xFF);

nextAddr += BLOCK_SIZE;
Serial.println("OK: Sync Data Recorded (0.25s precision)");
}

void updateLED() {
static unsigned long lastUpdate = 0;
static int flashStep = 0;
static int currentFlash = 0;
static bool pausing = false;

unsigned long now = millis();

if (pausing) {
if (now - lastUpdate >= 2000) {
pausing = false;
currentFlash = 0;
lastUpdate = now;
}
return;
}

if (now - lastUpdate >= 200) {
lastUpdate = now;
if (currentFlash < totalResetCount) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
flashStep++;
if (flashStep >= 2) {
flashStep = 0;
currentFlash++;
}
} else {
digitalWrite(LED_PIN, LOW);
pausing = true;
}
}
}

同期実装(PowerShell sync.ps1)

同期処理を行うのに使用します。 ファイルを右クリックして、「PowerShellで実行」をくちっくして使用します

$ErrorActionPreference = "Stop"  # 全てのエラーを「中断(catchに飛ばす)」対象にする
# --- 設定項目 ---
$portName = "COM4" # Arduinoが接続されているポートを確認して変更してください
$baudRate = 9600

try {
# シリアルポートの初期化
$port = New-Object System.IO.Ports.SerialPort($portName, $baudRate, "None", 8, "One")
$port.Open()
Start-Sleep -Seconds 2 # Arduinoのリセット待ち(DTRによるリセット対策)

# 1. 現在のUnix Time(1970/1/1からの経過秒)を取得
$unixTime = [DateTimeOffset]::Now.ToUnixTimeSeconds()

# 2. 4バイトのバイナリ配列に変換 (リトルエンディアン)
$utBytes = [BitConverter]::GetBytes([uint32]$unixTime)

# 3. チェックサムの計算 (4バイトの加算下位1バイト)
[byte]$checksum = 0
foreach ($b in $utBytes) {
$checksum = ($checksum + $b) -band 0xFF
}

# 4. 送信データの組み立て [0x02] + [Data 4B] + [Checksum 1B]
[byte[]]$sendBuffer = @(0x02) + $utBytes + @($checksum)

# 5. 送信
$port.Write($sendBuffer, 0, $sendBuffer.Length)

Write-Host ("送信成功: UnixTime = {0} (CheckSum: 0x{1:X2})" -f $unixTime, $checksum)
Write-Host ("送信バイナリ: " + [System.BitConverter]::ToString($sendBuffer))

# Arduinoからの応答("OK: Sync Success"など)を確認
Start-Sleep -Milliseconds 500
if ($port.BytesToRead -gt 0) {
$response = $port.ReadExisting()
Write-Host "Arduino応答: $response"
}

}
catch {
Write-Error "エラーが発生しました: $_"
}
finally {
if ($port -and $port.IsOpen) {
$port.Close()
Write-Host "ポートを閉じました。"
}
}


# --- スクリプトの最後に追加 ---
Write-Host "`nEnterキーを押すと終了します..."
Read-Host

実験結果

同期処理の実行結果は次の通り

送信成功: UnixTime = 1767166738 (CheckSum: 0xA2)
送信バイナリ: 02-12-D3-54-69-A2
Arduino応答: OK: Sync Data Recorded (0.25s precision)
OK: Sync Success

ポートを閉じました。

Enterキーを押すと終了します...

リセットの発生状況の検証を実施しました。 USBの挿抜では、リセットは発生していないようです。
9V ACアダプタ電源のコンセント抜き差しでも、リセット発生していません。電源供給の9V ACアダプタとUSBの切り替えがうまくいっているようです。つまり、ここまでは、LEDの点滅が1回の状態が続いています。
 つぎに、USBと9V ACアダプタ電源のコンセント両方を抜きました。再度刺したところで、LEDは2回点滅に変わりました。これは、想定どおりです。ということで、USBの挿抜でのリセット発生は必ず起こるものではなさそうです。
このあと、一度同期をおこなって、追加で「DTR/RTS信号によるオートリセット」を強制的に行ってみます。

検証用コード (PowerShell: reset.ps1)

# --- 設定 ---
$portName = "COM4"
$baudRate = 115200

try {
# 1. ポートの初期化
$port = New-Object System.IO.Ports.SerialPort($portName, $baudRate, "None", 8, "One")

# 2. ポートを開く (この時点で多くのボードは一度リセットされます)
$port.Open()
Write-Host "Port $portName Opened."

# 3. DTR/RTSを操作して明示的にリセットをかける
# 一旦両方をFalse(High電位)にしてからTrue(Low電位)にする
Write-Host "Resetting Arduino..."
$port.DtrEnable = $false
$port.RtsEnable = $false
Start-Sleep -Milliseconds 200 # 信号を安定させるための待ち時間

$port.DtrEnable = $true
$port.RtsEnable = $true

Write-Host "Reset signal sent."

# 4. ブートローダーが立ち上がるまで少し待つ
Start-Sleep -Seconds 2
Write-Host "Arduino should be restarted now."

}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
finally {
if ($port -and $port.IsOpen) {
$port.Close()
Write-Host "Port Closed."
}
}

Write-Host "Press Enter to exit..."
Read-Host

実行結果は次の通り。 このリセット後は、LEDの点滅は3回になりました。 設計通りの挙動です。  このあと、もう一度同期して一旦実験完了です。 

Port COM4 Opened.
Resetting Arduino...
Reset signal sent.
Arduino should be restarted now.
Port Closed.
Press Enter to exit...

それでは、EEPROMにロギングした結果を吸い出して検証してみましょう。

:100000000100000000000000023AD25469D50F0040
:1000100001000000000000000212D35469CA000071
:10002000010000000000000002CCDD54697B0400E8
:1000300000000000000000000000000000000000C0
:1000400000000000000000000000000000000000B0
:1000500000000000000000000000000000000000A0
:100060000000000000000000000000000000000090
:100070000000000000000000000000000000000080
:100080000000000000000000000000000000000070
:100090000000000000000000000000000000000060
:1000A0000000000000000000000000000000000050
:1000B0000000000000000000000000000000000040
:1000C0000000000000000000000000000000000030
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F0000000000000000000000000000000000000
:1001000000000000000000000000000000000000EF
:1001100000000000000000000000000000000000DF
:1001200000000000000000000000000000000000CF
:1001300000000000000000000000000000000000BF
:1001400000000000000000000000000000000000AF
:10015000000000000000000000000000000000009F
:10016000000000000000000000000000000000008F
:10017000000000000000000000000000000000007F
:10018000000000000000000000000000000000006F
:10019000000000000000000000000000000000005F
:1001A000000000000000000000000000000000004F
:1001B000000000000000000000000000000000003F
:1001C000000000000000000000000000000000002F
:1001D000000000000000000000000000000000001F
:1001E000000000000000000000000000000000000F
:1001F00000000000000000000000000000000000FF
:1002000000000000000000000000000000000000EE
:1002100000000000000000000000000000000000DE
:1002200000000000000000000000000000000000CE
:1002300000000000000000000000000000000000BE
:1002400000000000000000000000000000000000AE
:10025000000000000000000000000000000000009E
:10026000000000000000000000000000000000008E
:10027000000000000000000000000000000000007E
:10028000000000000000000000000000000000006E
:10029000000000000000000000000000000000005E
:1002A000000000000000000000000000000000004E
:1002B000000000000000000000000000000000003E
:1002C000000000000000000000000000000000002E
:1002D000000000000000000000000000000000001E
:1002E000000000000000000000000000000000000E
:1002F00000000000000000000000000000000000FE
:1003000000000000000000000000000000000000ED
:1003100000000000000000000000000000000000DD
:1003200000000000000000000000000000000000CD
:1003300000000000000000000000000000000000BD
:1003400000000000000000000000000000000000AD
:10035000000000000000000000000000000000009D
:10036000000000000000000000000000000000008D
:10037000000000000000000000000000000000007D
:10038000000000000000000000000000000000006D
:10039000000000000000000000000000000000005D
:1003A000000000000000000000000000000000004D
:1003B000000000000000000000000000000000003D
:1003C000000000000000000000000000000000002D
:1003D000000000000000000000000000000000001D
:1003E000000000000000000000000000000000000D
:1003F00000000000000000000000000000000000FD
:00000001FF

ほぼ想定の結果が得られました。比較的うまくいっているようです。これからわかることは、01 (reset)の次はすべて00で、MCUSRレジスタから得られた情報はなさそう。
同期情報もしっかり記録されており、同期も機能していそうです。
 事前検証はこれで、完了とします。つぎはいよいよ、AD変換でのデータ収集の実験に入っていきます。

参考サイト:

avrdude の使い方 (電子工作の実験室)
Arduinoのプログラムを吸い出す方法 (Lang-ship)


関連記事




Arduino Uno 事前検証過程(その3の1)でのEEPROM読み出し問題の回避策

暫定でEEPROMにログ保存して、読み出しを検証しようかとEEPROMをavrdudeコマンドで読みだそうとしたが、何をとち狂ったかFlashメモリの冒頭部分を読みだしていることに気が付いた。回避策もないようなので、Arduino スケッチ(Arduino に書き込むプログラム)とPowerShell実装の組み合わせで対処することにした。
 Arduinoの仕組みのせいか、なかなかめんどくさい。


目的

Arduino UNO 上の EEPROM 内容を確実に取得する。
avrdude(ブートローダ経由)では EEPROM/Flash の取り違えや挙動差があるため、
Arduino 自身に EEPROM を読ませて PC に転送する方式を採用する。


全体構成

  • Arduino 側
    EEPROM → Intel HEX 形式 → UART(USB CDC)送信
  • PC 側
    UART 受信 → 同期マーカー判定 → HEX ファイルとして保存

Arduino 側の動作原理

  1. EEPROM.read() を用いて EEPROM アドレス空間を順次走査
  2. 読み出したバイト列を Intel HEX レコードとして整形
  • アドレス
  • データ長
  • チェックサムを含む
  1. UART(Serial)へ ASCIIテキストとして出力

同期制御

  • データ送信の前後に明示的なマーカーを出力
EEPROM_BEGIN
:10....
:10....
...
:00000001FF
EEPROM_END

これにより PC 側は、

  • 受信開始点
  • 正常終了点
    を確実に判定できる。

再送設計

  • 送信処理は一定周期(例:20秒)で繰り返す
  • PC 側の起動タイミングや USB 再接続に依存しない

PC(PowerShell)側の動作原理

  1. System.IO.Ports.SerialPort で COM ポートをオープン
  2. 行単位(ReadLine())で受信
  3. 受信内容を以下のルールで処理
受信内容動作
EEPROM_BEGIN書き込み開始
: で始まる行HEX レコードとして保存
EEPROM_END書き込み終了・クローズ
  1. ファイルは スクリプトの配置ディレクトリに生成
    $PSScriptRoot を基準に絶対パス化)

この方式のポイント

  • EEPROM と Flash の経路を完全に分離
  • 読み出し主体は MCU 本体
  • PC 側は「単なる受信・保存」
  • ブートローダ/avrdude 非依存
  • テキスト転送
  • ロジアナ/ターミナルで可視
  • 途中破損の検出が容易
  • 再送前提設計
  • 信頼性は通信層ではなく運用で確保


Arduinoスケッチ(EEPROMreader.ino)

#include <EEPROM.h>

const unsigned long INTERVAL = 20000;

uint8_t checksum(uint8_t *buf, uint8_t len) {
uint16_t sum = 0;
for (uint8_t i = 0; i < len; i++) sum += buf[i];
return (uint8_t)(-sum);
}

void sendHexLine(uint16_t addr) {
uint8_t rec[5 + 16];
rec[0] = 16; // length
rec[1] = addr >> 8;
rec[2] = addr & 0xFF;
rec[3] = 0x00; // record type

for (int i = 0; i < 16; i++) {
rec[4 + i] = EEPROM.read(addr + i);
}

uint8_t cs = checksum(rec, 4 + 16);

Serial.print(':');
for (int i = 0; i < 4 + 16; i++) {
if (rec[i] < 0x10) Serial.print('0');
Serial.print(rec[i], HEX);
}
if (cs < 0x10) Serial.print('0');
Serial.println(cs, HEX);
}


void setup() {
Serial.begin(115200);
while (!Serial) { ; }
}

void loop() {
Serial.println("EEPROM_BEGIN");
for (uint16_t addr = 0; addr < 1024; addr += 16) {
sendHexLine(addr);
}
Serial.println(":00000001FF"); // EOF
Serial.println("EEPROM_END");

digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(200); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(INTERVAL);
}

PowerShell(eepromGet.ps1)

$port = New-Object System.IO.Ports.SerialPort COM4,115200,None,8,one
$port.ReadTimeout = 5000
$port.Open()

#$outFile = "eeprom.hex"
#$sw = New-Object System.IO.StreamWriter $outFile
$outFile = Join-Path $PSScriptRoot "eeprom.hex"
$sw = New-Object System.IO.StreamWriter($outFile)
$sw.AutoFlush = $true

Write-Host "Waiting for EEPROM_BEGIN..."

# ===== BEGIN待ち =====
while ($true) {
try {
$line = $port.ReadLine().Trim()
if ($line -eq "EEPROM_BEGIN") {
Write-Host "BEGIN received"
break
}
} catch {
# timeout → 何もしないで待ち続ける
}
}

# ===== データ受信 =====
while ($true) {
try {
$line = $port.ReadLine().Trim()

if ($line -eq "EEPROM_END") {
Write-Host "END received"
break
}

$sw.WriteLine($line)
Write-Host $line

} catch {
# timeout 継続
}
}

$sw.Close()
$port.Close()

Write-Host "DONE"

初期状態で得られる結果

eeprom.hex

:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:1000300000000000000000000000000000000000C0
:1000400000000000000000000000000000000000B0
:1000500000000000000000000000000000000000A0
:100060000000000000000000000000000000000090
:100070000000000000000000000000000000000080
:100080000000000000000000000000000000000070
:100090000000000000000000000000000000000060
:1000A0000000000000000000000000000000000050
:1000B0000000000000000000000000000000000040
:1000C0000000000000000000000000000000000030
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F0000000000000000000000000000000000000
:1001000000000000000000000000000000000000EF
:1001100000000000000000000000000000000000DF
:1001200000000000000000000000000000000000CF
:1001300000000000000000000000000000000000BF
:1001400000000000000000000000000000000000AF
:10015000000000000000000000000000000000009F
:10016000000000000000000000000000000000008F
:10017000000000000000000000000000000000007F
:10018000000000000000000000000000000000006F
:10019000000000000000000000000000000000005F
:1001A000000000000000000000000000000000004F
:1001B000000000000000000000000000000000003F
:1001C000000000000000000000000000000000002F
:1001D000000000000000000000000000000000001F
:1001E000000000000000000000000000000000000F
:1001F00000000000000000000000000000000000FF
:1002000000000000000000000000000000000000EE
:1002100000000000000000000000000000000000DE
:1002200000000000000000000000000000000000CE
:1002300000000000000000000000000000000000BE
:1002400000000000000000000000000000000000AE
:10025000000000000000000000000000000000009E
:10026000000000000000000000000000000000008E
:10027000000000000000000000000000000000007E
:10028000000000000000000000000000000000006E
:10029000000000000000000000000000000000005E
:1002A000000000000000000000000000000000004E
:1002B000000000000000000000000000000000003E
:1002C000000000000000000000000000000000002E
:1002D000000000000000000000000000000000001E
:1002E000000000000000000000000000000000000E
:1002F00000000000000000000000000000000000FE
:1003000000000000000000000000000000000000ED
:1003100000000000000000000000000000000000DD
:1003200000000000000000000000000000000000CD
:1003300000000000000000000000000000000000BD
:1003400000000000000000000000000000000000AD
:10035000000000000000000000000000000000009D
:10036000000000000000000000000000000000008D
:10037000000000000000000000000000000000007D
:10038000000000000000000000000000000000006D
:10039000000000000000000000000000000000005D
:1003A000000000000000000000000000000000004D
:1003B000000000000000000000000000000000003D
:1003C000000000000000000000000000000000002D
:1003D000000000000000000000000000000000001D
:1003E000000000000000000000000000000000000D
:1003F00000000000000000000000000000000000FD
:00000001FF

1回PowerShellを実行して一部しかデータ取得できなかった場合は、即座にもう一回実行すると、20秒後には全データを取得できます。

とりあえず、これで、avrdudeコマンドで読みだしたのとほぼ同じ内容が取れていると思います。 本番とほぼ同じデータ形式になるので、このデータで解析用の実装を作成しておけば、ほとんど改修なしで流用できるはずです。


関連記事

AE測定、長期間ロギングシステム構築 (その2) アナログ部 検証編 

的が遠いシステムを構築する場合、つまり多数のコンポーネントを組み合わせてシステムを構築するSI(システムインテグレーション)では、一歩一歩検証しながら作業を進めていく必要があります。そして、その検証には測定器や計測器(OSC)が必要です。そしてその妥当性を確認できる手法です。

 今回は特に、中古部品も活用して構築するので、その部品自体の妥当性も検証が必要でした、というか早めにやっておいてよかった。9VのACアダプタですが、以前に購入して使っていた変圧とプラグ切り替え機能付きのACアダプタを使う予定でした。しかし、事前にテスターで確認すると9Vに設定しても9Vにならないことが判明し、ACアダプタを追加調達しました。 切り替えで12Vになりっぱなしだったり、一瞬17Vになったり、他の部品を壊してしまう原因になっていたかもしれません。

検証用の機器

既存のデジタルマルチテスター:
  電圧や導通試験に使用。このテスター自体も年代物で怪しいところがあったので事前に電池などで妥当性検証しておきました。前述の、ACアダプタの問題はこのテスターで検証して確認しました。

新規オシロスコープ:
[112972]DSO Shell 200kHz (DSO150) 完成品
https://akizukidenshi.com/catalog/g/g112972/


  オシロスコープとしては激安です。どこまで使えるか分かりませんが調達してみました。別案として激安の中古品(オークション)という線もありましたが、今回は納期と妥当性を勘案して、この製品を採用してみました。

検証

検証過程で、いろいろ動作しない事象にぶつかり、修正を繰り返して、最後は下の画像や動画のとおりの信号検出の確認ができました。

まず、最初は、それらしい信号がでてこず、何度か確認と修正をしています。参考のため注意しておくべきチェック観点を書いておきます。
・基板への部品取り付けは、後戻りが大変なので十分に注意してやりましょう。
・配線、入力と出力が同じミニジャック
・配線、R,L2回路ある
・ICの向き。 ICソケットの向きは注意して半田付けしたが、IC挿すときも注意
・電源の入力などなど
・その他の接触不良

つぎに、それらしい信号が見えるようになったところで、不安定な信号を見つけてそれの対策をしました。接触不良が主な原因でした、なかでもオシロ付属のプローブが不安定な信号の主要因でした。仕方がないので、自前のBNCケーブル(無線用のもの)で、オシロと、アンプの出力を直結して対処しました。

 最後は、オシロの使い方ですね。 このオシロは、 安価なつくりに必要最小限の実装を詰め込んでいるので、ぱっと見操作が良くわからない。さらに説明書が簡単すぎですね。 この機種は初めてオシロを使う人にはわかりにくいかもしれません。オシロスコープとはどういうものかをそれなりの機種で理解したうえで(購入する)使うのが良いかもしれません。

検証動画

センサーを叩くと、検出した信号波形をオシロで捉えることができました。波形は約5kHzで、この素子の共振周波数とほぼ一致していました。

関連記事

Win11リモートデスクトップ周りの設定対処していたらテキストコピーができなくなった→copy問題解決

Windows 11での「リモートデスクトップでモヤっと。」を直そうとしていろいろトライしていたら、テキストコピーができなくなった。 まずモヤっと問題と関連があるか調査が必要だが、作業効率を考えると、こっちを優先して対処する必要がある。以前の設定変更を確認して、 書き戻し作業で原因を特定して復旧しました。やはり、モヤっと問題の対処でいじくったとことが問題でした。

問題の原因

記事を確認すると、以下の設定を実施されています:

# グラフィック最適化の無効化  
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v fDisableClip /t REG_DWORD /d 1 /f

このfDisableClip = 1がクリップボードのリダイレクト(コピペ機能)を無効化しています!

解決方法

【最優先】クリップボードのリダイレクトを有効化

**接続先PC(サーバー側)**で以下を実行してください:

# レジストリで無効化されているクリップボード機能を有効化

# パターン1: ポリシーレジストリ
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v fDisableClip /t REG_DWORD /d 0 /f

# パターン2: RDP-Tcp設定(念のため確認・修正)
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v fDisableClip /t REG_DWORD /d 0 /f

# 再起動
Restart-Computer

これで復旧しました。

関連記事

AE測定、長期間ロギングシステム構築 (その1) アナログ部製作編 

AE測定、といっても….。狙いはAEつまり、100kHzレベルですが、なかなかそのレベルまでのものを用意するのは高額になってしまいます。そこで、できる限りそのレベルを目指した設計で、かつ低コストで実現できるシステムを目指すことにしました。
 高度なレベルと低コストをバランスを取って実現するために、「イベント信号波形の取得」ではなく「イベントが発生した時刻の取得」に特化することにしました。
もう一つ重要な要件として、「短時間で環境整備する必要がある」ということです。そこで、採用するシステム構成の方針を次のようにしました。

・比較的安価であること
・可能な限り、既製品を利用する
・短期間/短納期で入手できる部材を採用する
・検証しながら作業を進められるようにする
・既保有の部材や道具を利用する

採用部材(その1、アナログ部(プレアンプ))

直ぐに入手するために、国内に在庫があり即入手できるものから探しました。安価でも、中国からの船便となると2週間くらいかかってしまいます。そこで、30年ぶりくらいの部品通販での調達です。以前なら直接店舗で購入でしたが…。

■センサー

[104118]圧電スピーカー(圧電サウンダー)(13mm)PKM13EPYH4000-A0
https://akizukidenshi.com/catalog/g/g104118/
https://akizukidenshi.com/goodsaffix/murata-piezo-speaker-tape.pdf
10個買い

■アンプ

[112145]2回路入J-FET入力オペアンプ NJM072D  ←
https://akizukidenshi.com/catalog/g/g112145/

[112309]NJM4580DD使用ヘッドホンアンプキット
https://akizukidenshi.com/catalog/g/g112309/
このキットにNJM072D を置き換えて使う



[108853]3.5mmステレオミニプラグ⇔スクリュー端子台
https://akizukidenshi.com/catalog/g/g108853/

[108854]BNCオスコネクター⇔スクリュー端子
https://akizukidenshi.com/catalog/g/g108854/

■その他

電源は006Pの9電池でも動作するが、長期間ロギングの必要があるため、9VのACアダプターを使用します。
センサー、アンプ間の配線は既保有のアンテナ線などの廃材を使用します。半田やはんだごてなどは既保有なので省きます。

製作

とりあえず、アンプを製作、40年ぶりくらいのキットでの電子工作です。30分ほどの半田付け作業で完成しました。

左がセンサー代わりのピエゾです、とりあえず直結してます。アンプ基盤の右側が出力です。


以上で、アナログ部の製作は完了です。
そのあとは、アナログ部の動作検証を行う予定です。→(アナログ部検証編はこちら)そしてその後のAD変換でのデータロギングのプログミングを含む作業が待っています。多分これが一番大変でしょう。

関連記事

MCR-4TCでの温度測定について(Tタイプ熱電対使用、4か所測定)

前の記事に続いて、 MCR-4TCを使っての温度測定の話です。
2025/12/21から屋外測定を開始しました。(つぎも参考
4点の測定結果はつぎのとおり

T4
T4

昼間の時間帯の測定値ばらつきが大きく見えている部分がありますが、そのばらつきが大きいところは直射日光や風が当たっていた時間帯と思われます。全体的に想定通りの温度変化をしています。良い感じです。

次は、サンプルの測定結果です。1分に1回測定しています。以下はサンプルのCSVファイルです。 MCR-4TCで取得したファイルは、USB経由でWindows PCに回収できます。独自のバイナリ形式での保存ですが、Graphソフトで CSV形式での出力も可能です。次がそのファイルのサンプルです。


今後の測定分については、上記のようにzip化したCSVファイルをこの下に追加していく予定です。

関連記事

hsBoxで、ATOM Cam 2の映像をキャプチャしてNASに蓄積、さらにクラウドにアップロードする

先の記事「コンクリート劣化の原因調査」に関連して画像を撮影してアップロードする仕組みを構築しました。ここでは、構築までの手順を活用できる形で紹介していきます。事前準備がほぼ出来上がっているので意外と簡単に構築できました。

10分おきに自動更新している画像

※画像が更新されていない場合は、ブラウザキャッシュが効いていることがあります。F5キーで再読み込みするか、Ctrl+右クリックで画像を、別タブで開いてF5キーを押すと再読み込みされます。

キャッチ画像から作成した動画

動画化

このように、NASへの蓄積から動画生成や、クラウドへのアップロードも自動化できました。

使用した製品

10年前のノートPCで動いているhsBoxの他には、下の製品を使用して構築しています。

カメラ: アトムテック株式会社(ATOM tech Inc.) 製 ATOM Cam 2
https://www.atomtech.co.jp/products/atomcam2

NAS: BUFFALO 製 LS710D
https://www.buffalo.jp/product/detail/ls710d0101.html

camLS710D

実装方針、実装方法

できるだけ手間をかけず(つまりATOM Cam 2の改造など特殊なことはしない)、運用やhsBoxへの負荷も最低限になるように設計する。 ということで、hsBoxを使い10分間隔でrtspでATOM Cam2から画像を取得して、NASに保存、同時にクラウド上に送ることにしました。

事前準備

hsBoxに必要なライブラリが入っているかなど環境状況を確認します。 結果、必要なものはすべてこれまでの構築等でインストールされていることを確認しました。 インストールされていない場合インストールしてください。
※ 本記事内のRTSP URL、トークン、ユーザー名・パスワードはすべてダミーです。

■ATOM CAM2のrtspのURLを確認する
rtsp://<*user>:<*pass>@192.168.**.**/live ★


■hsBoxライブラリの確認
●FFmpeg (コマンドラインツール)
ffmpeg -version

●NFS / CIFS (NAS接続用ユーティリティ)
# インストールされているパッケージの確認
dpkg -l | grep -E 'nfs-common|cifs-utils'

●3. Python環境の確認
#hsBoxの制御コードを書くためのベースを確認します。
#Python はhsBoxで構築済みなので省きます。

●マウントポイントの作成
#NASの画像を置くためのディレクトリをhsBox内に作成します。
#すでに作っていれば不要
sudo mkdir -p /mnt/nas_cam

●手動マウント
マウントは他の記事(proxy設定)も参考にしてください

アトムテックの製品のうち、rtspに対応しているのは公式にはATOM Cam 2のみです。ATOM Cam SwingやATOM Cam GPTは対応していないようです。 また、最初のころのATOM Cam2はrtspは公開されていなかったと記憶しています。ファームウェアバージョンにも依存するようです。対応状況やrtspのURL確認方法などの詳細はアトムテックのサイトを確認してください。

事前準備が終わったら、手動で画像が取れるか確認してみましょう。

ffmpeg -rtsp_transport tcp -ss 00:00:01 -i "rtsp://**:**@192.168.**.**/live" -vframes 1 -q:v 2 -y test_out.jpg

*マークの箇所は環境に合わせて修正してください。

これで、画像が撮れれば問題ありません。取れなければIPが変わっていないかなどネットワーク的な問題を確認・解決する必要があります。

 IPは動的に変わるので、MACからIPを確認する実装を組み込みます。そこで使用するMACアドレスを確認します。

# arp -a |grep 192.168.**.**
? (192.168.**.**) at 7c:dd:e9:**:**:** [ether] on enp1s0

MACアドレスが確認できました。 ここまで準備できればほぼ完成です。

サンプル実装(hsBox側実装)

★印の箇所は環境に合わせて設定してください。
コードは、Windows、hsBox (Linux) 共用です。
※ 実運用では、環境変数や設定ファイルを用いてコード外に分離してください。

import os
import subprocess
import sys
import re
import base64
from datetime import datetime

# --- 設定項目 ---
# Windowsで試す際は、ここをカメラのIP等に書き換えてください
#RTSP_URL = "rtsp://**:**@192.168.*.*:554/live"
# 確認したMACアドレスをここに(ハイフンでもコロンでもOK)
TARGET_MAC = "7c-dd-e9-**-**-**" # ★要更新
RTSP_USER = "**" # ★
RTSP_PASS = "**" # ★
# 保存先設定
# 保存先設定
#SAVE_DIR = "/mnt/nas_cam" if os.name != 'nt' else r"C:\temp\atom"
SAVE_DIR = r"\\192.168.**.*****" if os.name == 'nt' else "/mnt/nas****" # ★
#
url = "https://******.jp/**/upload.php" # ★ クラウドアップロードURL
token = "your_secret_token" # ★
#認証の情報
user = "*****" # ★
pass = "*****" # ★
# -----------------------
BASE_DIR = SAVE_DIR

def get_next_serial(BASE_DIR ):
counter_file = os.path.join(BASE_DIR , "counter.txt")

# 現在の番号を読み込む
if os.path.exists(counter_file):
with open(counter_file, "r") as f:
try:
count = int(f.read().strip())
except:
count = 0
else:
count = 0

# 次の番号を決定(0-199のリング)
next_count = (count + 1) % 200

# 更新して保存
with open(counter_file, "w") as f:
f.write(str(next_count))

return f"{count:03d}.jpg" # 000.jpg 形式

def upload_to_xserver(local_file_path, serial_name):
# ユーザー名とパスワードを結合してBase64エンコード
user_pass = f"{user}:{pass}"
auth_encoded = base64.b64encode(user_pass.encode('ascii')).decode('ascii')
# curlで「サーバー側の名前」を指定して送る # ★
cmd = [
'curl', '-s', '-X', 'POST',
#'--user', f'{basic_user}:{basic_pass}', # ここで認証を通す # ★
#'-H', f'Authorization: Basic {auth_encoded}', # ヘッダーで認証を通す # ★
'-F', f'image=@{local_file_path};filename={serial_name}',
'-F', f'token={token}',
url
]
subprocess.run(cmd)



def capture_with_auto_management():
ip = get_current_ip(TARGET_MAC)
if not ip:
print("Error: Camera not found.")
sys.exit(1)

# 1. 日付ごとのディレクトリ名を生成 (例: 2025-12-23)
today_str = datetime.now().strftime("%Y-%m-%d")
daily_dir = os.path.join(BASE_DIR, today_str)

# 2. ディレクトリがなければ作成
if not os.path.exists(daily_dir):
os.makedirs(daily_dir, exist_ok=True)

# 3. ファイル名の生成 (例: 20251223_183000.jpg)
filename = datetime.now().strftime("%Y%m%d_%H%M%S.jpg")
save_path = os.path.join(daily_dir, filename)

# --- ffmpeg実行部 ---
rtsp_url = f"rtsp://{RTSP_USER}:{RTSP_PASS}@{ip}/live"
cmd = [
'ffmpeg', '-rtsp_transport', 'tcp', '-i', rtsp_url,
'-ss', '00:00:01', '-vframes', '1', '-q:v', '2', '-y', save_path
]

try:
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True, timeout=20)
print(f"Saved: {filename} (via {ip})")
serial_name = get_next_serial(BASE_DIR )
upload_to_xserver(save_path, serial_name)
except Exception as e:
print(f"FFmpeg failed: {e}")


def get_current_ip(mac):
"""MACアドレスから現在のIPアドレスを特定する (Win/Linux共用)"""
target_mac_raw = mac.replace(":", "").replace("-", "").lower()

# --- 1. ARPテーブルの強制更新 (Linuxのみ) ---
if os.name != 'nt':
subprocess.run(["ping", "-c", "2", "-b", "192.168.*.255"], # ★
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

try:
# ARPテーブルを取得
res = subprocess.check_output(["arp", "-a"]).decode("cp932" if os.name == 'nt' else "utf-8")

for line in res.splitlines():
# 解析中の行からも記号をすべて消す
line_raw = line.replace(":", "").replace("-", "").lower()

# 純粋な英数字の並びでマッチング
if target_mac_raw in line_raw:
# 行からIPアドレス部分だけを抜き出す
match = re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", line)
if match:
return match.group(1)
except Exception as e:
print(f"Network scan error: {e}")
return None

if __name__ == "__main__":
capture_with_auto_management()

NASの指定ディレクトリの下に日付ディレクトリを作成し、その下にファイルを蓄積していきます。
ファイル自動削除の実装はありません。
必要に応じて、WindowsのエクスプローラーやNAS管理画面から手動で削除してください。

 

■サンプル実装(クラウド側実装)

※ 本PHPコードは動作確認用の簡易実装です。
※ 実運用では、IP制限、HTTPS、拡張子チェック等の追加を推奨します。

<?php
//---★環境に合わせてアクセス制限等を追加してください
$token = "your_secret_token"; // 簡易認証用
if ($_POST['token'] !== $token) exit("Access Denied");

$save_dir = "/cam_images/";

if (!is_dir($save_dir)) mkdir($save_dir, 0755);

if (isset($_FILES['image'])) {
// クライアントが指定したファイル名(001.jpg等)で保存
$file_name = basename($_FILES['image']['name']);
move_uploaded_file($_FILES['image']['tmp_name'], $save_dir . $file_name);

// 常に最新を確認するための固定名コピーも同時に行う
copy($save_dir . $file_name, $save_dir . "latest.jpg");
echo "OK";
}

アップロードされたファイルをディレクトリに保存します。 アップロードする際に000~199の番号をシリアルでつけてアップロードしてくるので、それを上書き保存します。あふれることはないので削除は不要です。
 同時に、”latest.jpg”にコピーします。 latest.jpgに常に最新の画像が保存されます。

あとはcronに設定を追加するだけで、画像を蓄積し続けます。
cron設定についてはhsBox本家サイトの記事(hsBoxで作る“LAN監視システム・アラート”)を参考にしてください。

関連記事