TL; DR
AVR Dx/Ex series の datasheet によると、flash memory の書き換え回数は 1,000回と書かれています。Classic AVR や最近の AVR EB では 10,000回なのですが、そのたった1/10。おそらく安全寄りに振った値だと思われるので実際に検証したところ、約8.5万回だったという話です。
動機
上述の通り datasheet によると書き換え回数は 1,000回です。大昔の AT90S と同じ数字は2020年代の MCU として情ないにも程があります。発売当時の datasheet には 10,000回と書かれていたのに、製品ではなくdatasheetの方が訂正された (erratum ではなく仕様になった) 経緯も気になります。初期のSTM32H5でも似たような erratum がありましたが、こちらは製品の方が訂正されました。実験や開発で試行錯誤しているとわりと簡単に到達してしまいそうな数字なので、いくつか疑問を持った次第です。
- おそらく安全寄りに振った数字なので、実際はどのぐらいか
- 寿命が尽きたとき、どんな症状が出るのか
avrdude をブン回せば実際に試験できることに気付きました。やってみましょう。ひとまず実験前の予想はこんな感じです。
- 1万回は余裕として、2〜3万回ぐらいが寿命では
- Erase しても1にならない bit がポツポツと random に出てきて増加していくだろう
実験方法
執拗に erase / write して、結果が 0xFF / 0x00 になることを確認します。書き込む内容は program としてはまったく意味がないのですが、今回は問題ありません。ということで、以下の仕組みを考えました。
- Breadboard で SerialUPDI を構成する
- 使いさしの AVR32DB32 (QFP) をひたすら erase / write する
- 電圧は 3.3V
- Erase / write 一対を1回と数える
- Erase (verifyなし) => 0xFF になる
- Read => 全域が 0xFF になったことを確認する
- Write (verifyなし) => 0x00 を書き込む
- Read => 全域が 0x00 になったことを確認する
- 実験には Raspberry Pi Model 3B+ を使う (ふだん使っている開発環境に /dev/ttyUSBn や /dev/ttyACMn が生えると邪魔なので)
- tmux で ssh session を保護しておくこと
- Erase / write の結果がおかしくなっても継続する (壊れていく過程を観察する)
Erase / write は verify を省略しました。というのは「そもそも error を出すつもりでやっている」「毎回、後で全域読み出す」ゆえに「時間のむだ」というのが理由です。
「Read した結果は RAM に格納する」ところが一工夫です。これを律儀に host 側の PC の storage (今回は Raspberry Pi の micro SD) に書き込むと、AVR だけでなく micro SD の耐久試験をやることになってしまうので。
回路は breadboard 1枚に収まるごく簡単なものです。USB-UART変換は FT232RL を使いました。LED 2個と制限抵抗は任意で、FT232RL の CBUS0/CBUS1 につながっていて TX/RX に合わせて点滅するようになっています。AVR の電源には FT232RL の 3.3V 出力を使っています。

以上から、このような shell script を実装しました。
#!/bin/sh
partno="avr32db32"
programmer="serialupdi"
port="/dev/ttyUSB0"
avrdude="avrdude -q -q -p $partno -P $port -c $programmer"
i=1
erase_expected="erased.hex"
write_expected="zero.hex"
tempdir="/run/user/1000"
while [[ $i -le 100000 ]]
do
echo "count = "$i", erase expected:" $erase_expected ", write expected: "$write_expected
# chip erase
$avrdude -e
if [ $? -ne 0 ]
then
echo "Error occured while erasing. Abort."
exit 1
fi
# read erased image
tempfile=`printf "erased_%06d.hex" $i`
$avrdude -U flash:r:$tempdir/$tempfile:i -A
if [ $? -ne 0 ]
then
echo "Error occured while reading erased memory. Abort."
exit 2
fi
# diff
diff $erase_expected $tempdir/$tempfile > /dev/null
if [ $? -eq 0 ]
then # match
rm $tempdir/$tempfile
else # don't match
echo "ERASE ERROR!!"
mv $tempdir/$tempfile .
erase_expected=$tempfile
fi
# write 0x00 without erase and verify
$avrdude -U flash:w:zero.hex:i -D -V > /dev/null
if [ $? -ne 0 ]
then
echo "Error occured while writing. Abort."
exit 3
fi
# read written image
tempfile=`printf "write_%06d.hex" $i`
$avrdude -U flash:r:$tempdir/$tempfile:i -A
if [ $? -ne 0 ]
then
echo "Error occured while reading written memory. Abort."
exit 4
fi
# diff
diff $write_expected $tempdir/$tempfile > /dev/null
if [ $? -eq 0 ]
then # match
rm $tempdir/$tempfile
else # don't match
echo "WRITE ERROR!!"
mv $tempdir/$tempfile .
write_expected=$tempfile
fi
i=`expr $i + 1`
done
準備 (書き込み/比較に使うfileを用意する)
avrdude で書き込んだり、erase / write した結果が期待通りかを確認したりするため、あらかじめ .hex file を作っておく必要があります。Erase 後の方は簡単。まずは avrdude で erase して、
avrdude -p avr32db32 -c serialupdi -P /dev/ttyUSB0 -e
全域が 0xFF になったところを dump すればできあがります。
avrdude -p avr32db32 -c serialupdi -P /dev/ttyUSB0 -U flash:r:erased.hex:i
少し面倒なのは書き込み後の 0x00 の方で、dd -> avr-objcopy -> avrdude で書き込み -> avrdude で読み込み という手順をたどりました。まずは 0x00 が連続した binary file を作るため、
dd if=/dev/zero bs=32768 count=1 of=zero.bin
とします。このままでは Intel Hex ではないので以下のように変換して、
avr-objcopy -I binary -O ihex zero.bin temp.hex
とします。ただ比較を楽して diff でやりたいなーというとき1行あたりの byte 数が違うと当然ながら不一致になるので、形を avrdude の出力と揃えたいところです。まずは一回書き込んで、
avrdude p avr32db32 -c serialupdi -P /dev/ttyUSB0 -U flash:w:temp.hex:i
Dump すれば欲しい形 (32bytes/line) の file になります。
avrdude -p avr32db32 -c serialupdi -P /dev/ttyUSB0 -U flash:r:zero.hex:i
実験結果
実験中に avrdude が異常終了することなく time out が連続してしまう事象が何回かありました。以下のような error が連続するというものです。
avrdude error: wait NVM ready timed out
avrdude error: updi_nvm_wait_ready() failed
***failed;
これが出てしまうと放っておいても解消しません。また原因が avrdude なのか PC なのか FT232RL なのか、あるいは実験台の AVR32DB32 かも分かっていません。ただ「この直前に何回まで成功したか」という情報が失われてしまうので、以下に示す回数は「正しく記録が残っている範囲で」となり、実際には数千回の上乗せがあるものと見ています。
10万回実行したうち、erase / write が不完全だったのは以下の通りでした。
| Cycle | Erase | Write |
| 84,521 | Error | OK |
| 88,500 | Error | OK |
| 88,656 | Error | OK |
| 89,552 | Error | OK |
| 90,689 | Error | OK |
| 92,616 | Error | OK |
| 93,398 | Error | OK |
| 95,190 | Error | OK |
| 96,000 | OK | Error |
| 98,329 | Error | OK |
| 98,629 | Error | OK |
84,521回目で最初の不良が出ています。ただ実際には使いさしの素子であったことや、script の開発で試験的に erase / write していることから、実際には85,000回前後といったところでしょう。ここから掲題の8.5万回という数字が出てきます。これ以外では常に erase で 0xFF、write で 0x00 になっていました。つまり「単発の障害で、再実行すると問題ない」という結果です。
壊れ方を見てみると、意外にも「同じ箇所で同じように bit が壊れる」ことが分かりました。以下は最初の障害である84,521回目ですが、erase で出る障害はすべて同じです。
$ diff erased.hex erased_084521.hex
1,8c1,8
< :20000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00
< :20002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
< :20004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
< :20006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
< :20008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
< :2000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
< :2000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
< :2000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
---
> :20000000FF85858585858585858585858585858585858585858585858585858585858585C6
> :20002000858585858585858585858585858585858585858585858585858585858585858520
> :20004000858585858585858585858585858585858585858585858585858585858585858500
> :200060008585858585858585858585858585858585858585858585858585858585858585E0
> :20008000FF00858585858585858585858585858585858585858585858585858585858585CB
> :2000A0008585858585858585858585858585858585858585858585858585858585858585A0
> :2000C000BE8585858585858585858585858585858585858585858585858585858585858547
> :2000E000858585858585858585858585858585858585858585858585858585858585858560
続いて write で起きた方を見てみましょう。
$ diff zero.hex write_096000.hex
1,8c1,8
< :200000000000000000000000000000000000000000000000000000000000000000000000E0
< :200020000000000000000000000000000000000000000000000000000000000000000000C0
< :200040000000000000000000000000000000000000000000000000000000000000000000A0
< :20006000000000000000000000000000000000000000000000000000000000000000000080
< :20008000000000000000000000000000000000000000000000000000000000000000000060
< :2000A000000000000000000000000000000000000000000000000000000000000000000040
< :2000C000000000000000000000000000000000000000000000000000000000000000000020
< :2000E000000000000000000000000000000000000000000000000000000000000000000000
---
> :200000000085858585858585858585858585858585858585858585858585858585858585C5
> :20002000858585858585858585858585858585858585858585858585858585858585858520
> :20004000858585858585858585858585858585858585858585858585858585858585858500
> :200060008585858585858585858585858585858585858585858585858585858585858585E0
> :200080000000858585858585858585858585858585858585858585858585858585858585CA
> :2000A0008585858585858585858585858585858585858585858585858585858585858585A0
> :2000C000BE8585858585858585858585858585858585858585858585858585858585858547
> :2000E000858585858585858585858585858585858585858585858585858585858585858560
いずれも先頭 256bytes だけが同じように 0x85 になるという結果になりました。当初の想定を大幅に裏切る実際の結果は以下の通りです。
| 想定 | 結果 |
| 1万回は余裕として、2〜3万回ぐらいが寿命では | 8.5万回 (Datasheet の85倍) |
| Erase しても1にならない bit がポツポツと random に出てきて増加していくだろう | 先に erase が問題になるのは想定通り。ただし先頭256bytesの特定の箇所が0x85になるだけ。増減しない |
確かに不具合は出てきていますが、再実行すれば問題ありません。さすがに製品に組み込むのはマズいものの、開発用なら問題なく使えてしまいそうです。
今後の実験
Flash memory の特定の領域のbitがまれに壊れる (単発的に erase / write が正常に行われない) ことは分かりました。しかし他の領域はまったく問題ありません。これでは「寿命が尽きたとき、どんな症状が出るのか」の答えとは言い難く、実験を継続するしかないようです。
avrdude の timeout error は厄介です。avrdude が終了するわけではないので shell などから制御が効きません。また正確な回数が失なわれてしまうのも困りものです。正確な回数については AVR 内蔵の EEPROM に書き込んでいけばよさそうという感触があります。
本来は使いさしを活用しての予備実験のつもりで、実際には新品で実験するつもりでした。ただ 8.5万回も耐えてしまうので、いまさら最初から実験するには腰が重いです。
なお EEPROM は datasheet でも100,000回となっているので、耐久試験をすることはないと思います。なんぼなんでもムリ。
まとめと、残る疑問
今回の実験を通して、以下の結果が得られました。
- Datasheet に書かれた書き換え寿命は、大幅に安全寄り
- 実際の erase / write cycle は数万回。趣味の実験・開発ではまず壊れない (仮に1万回として 50回/日で書き込んで200日。5万回なら1,000日)
- Random に bit が壊れるのではなく、特定の領域に同じ不具合が発生する
- Bit が壊れたら二度と直らないわけではなく、次の erase / write で直る
一方で「どういう条件 (温度や電圧など) で寿命が縮むのか」は分からないままです。このあたりは datasheet に書いてほしいところです。