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


関連記事