google-site-verification: google3bd66dd162ef54c7.html

ArduinoのCPUをスリープさせて消費電流を減らす関数 delayWDT2

 Arduinoのタイマーで、CPUを深いスリープ状態に入れて消費電流を減らすdelayWDT という関数を以前作りました。この関数を使うと、スリープ状態の消費電流を27μAまで減らすことが出来るので今でも便利に使っています。

 Arduinoではこれくらいが限界と思っていたのですが、もっと消費電流を減らすことが出来るという記事を見つけました。それは、
 tetsuaki baba さんのWebの、workshop/ArduinoBasis/sleep Arduino のSleep機能 という記事です。

 その記事には、ブラウンアウト検出(BOD)機能を止めることで、もっと省電流化する方法が書かれていて、何と 6.5μA まで減らすことが出来るそうです。なるほど、そういう手があったのかと感心しました。

 これはものすごい効果なので、delayWDT 関数にこの方法を組み込むことにしました。

 まずは仕組みのおさらいです。

▼ATmega328Pのスリープ機能一覧表(データーシートから抜粋)
ATmega328Pのスリープ機能一覧
 この表はCPUのスリープ機能の種類を表したものです。delayWDT関数では、いちばん消費電流が少なくなるパワーダウンモードを使っています。表の赤い横線部です。

 この表の右端にソフトウエアBOD禁止と言う欄がありますが、実は私、この意味が理解出来ていませんでした。調べてみると、ソフトウエアBOD禁止と言うのは、「ヒューズビットでBODを使うことが指定されていても、スリープ中はこの機能を解除するように、プログラムから指定出来ます」 という機能でした。

 深いスリープに入れた場合、クロックは止まっているので何も動いていません。ということは、たとえBODを検出したとしても、すぐに何かのアクションを起こさなくてもいいはずです。別の言い方をすると、クロック停止は安全側、つまりフェールセーフ設計にするのが定石なので、BOD状態になったとしてもすぐに何かしなくても大丈夫なずです。

 だったら、スリープ中はBODを止めても大丈夫。それに、BODを動かすためには20μAの電流が必要ですが、これがもったいない、ということなんだと思います。

 ちなみに、ソフトウエアBOD 禁止機能は ATmega328P など末尾(など)に P が入っている CPU に入っているようです。P は消費電流がピコアンペアオーダーになる、ということから付けられた記号だったと思いますが、なるほど、BODを有効にしていたのでは pA オーダーの消費電流にはなりません。ちなみに、ウォッチドッグタイマー (WDT) まで止めれば、消費電流はピコアンペアの領域に入るはずです。

▼ソフトウエアBOD禁止設定レジスタ
BOD設定
 MCUCR レジスタから操作するようです。但し、誤って設定されることを防止するためと思われますが、設定後 3クロック以内にスリープに入れないといけません。この条件を C 言語だけでクリアするのは難しいので、アセンブラ命令を併用する必要があるようです。

 具体的なやり方は、上記の tetsuaki baba さんのサイトで解説されていますが、その内容を delayWDT 関数に組み込んで使い易くしちゃいましょう、というのが今回の記事の目的です。

 なお、従来の関数 (delayWDT) との関係がややこしくなるので、新しい関数は、delayWDT2 という名前にしました。

 前置きが長くなりましたが、プログラムは以下の通りです。
/*
delayWDT2関数のデモプログラム。
パワーダウン中のCPUの消費電流は約6.5μA(Atmega328P@16MHz,5V)
このプログラムはdelayWDT関数にスリープ中はブラウンアウトを禁止することで
消費電流を更に下げたものです。勝手に使っていただいてかまいませんが、無保証です。
BODを無効化するための設定は下記を参考にさせていただきました。ありがとうございます
http://tetsuakibaba.jp/index.php?page=workshop/ArduinoBasis/sleep
fileName:_20180418_dalayWDT2.ino
2018/04/18 ラジオペンチ http://radiopench.blog96.fc2.com/
*/

#include <avr/sleep.h> // スリープライブラリ
#include <avr/wdt.h> // ウォッチドッグタイマー ライブラリ

int led = 13; // LEDピン
//volatile int wdt_cycle = 0; // 必要ならコメントアウトを削除

void setup() {
pinMode(led, OUTPUT);
}

void loop() {
digitalWrite(led, LOW); // LED off
// 低電流でスリープ
delayWDT2(7); // 引数でスリープ時間指定(詳細はdelayWDT_setup参照)
// 通常モードに復帰
digitalWrite(led, HIGH); // LED on
delay(2000); // 普通のdelay(比較用)
// delayWDT2(7);
}

// ここから下を全て使う
void delayWDT2(unsigned long t) { // パワーダウンモードでdelayを実行
Serial.flush(); // シリアルバッファが空になるまで待つ
delayWDT_setup(t); // ウォッチドッグタイマー割り込み条件設定

// ADCを停止(消費電流 147→27μA)
ADCSRA &= ~(1 << ADEN);

set_sleep_mode(SLEEP_MODE_PWR_DOWN); // パワーダウンモード指定
sleep_enable();

// BODを停止(消費電流 27→6.5μA)
MCUCR |= (1 << BODSE) | (1 << BODS); // MCUCRのBODSとBODSEに1をセット
MCUCR = (MCUCR & ~(1 << BODSE)) | (1 << BODS); // すぐに(4クロック以内)BODSSEを0, BODSを1に設定

asm("sleep"); // 3クロック以内にスリープ sleep_mode();では間に合わなかった

sleep_disable(); // WDTがタイムアップでここから動作再開
ADCSRA |= (1 << ADEN); // ADCの電源をON(BODはハードウエアで自動再開される)
}

void delayWDT_setup(unsigned int ii) { // ウォッチドッグタイマーをセット。
// 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
// 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
// 6=1sec, 7=2sec, 8=4sec, 9=8sec
byte bb;
if (ii > 9 ) { // 変な値を排除
ii = 9;
}
bb = ii & 7; // 下位3ビットをbbに
if (ii > 7) { // 7以上(7.8,9)なら
bb |= (1 << 5); // bbの5ビット目(WDP3)を1にする
}
bb |= ( 1 << WDCE );

MCUSR &= ~(1 << WDRF); // MCU Status Reg. Watchdog Reset Flag ->0
// start timed sequence
WDTCSR |= (1 << WDCE) | (1 << WDE); // ウォッチドッグ変更許可(WDCEは4サイクルで自動リセット)
// set new watchdog timeout value
WDTCSR = bb; // 制御レジスタを設定
WDTCSR |= _BV(WDIE);
}

ISR(WDT_vect) { // WDTがタイムアップした時に実行される処理
// wdt_cycle++; // 必要ならコメントアウトを外す
}
 delayWDT2 関数を呼び出すサンプルプログラムの形になっています。Loop()の部分はLチカと同じ作りなので、説明は不要でしょう。

 何かに組み込む時は、ライブラリをインクルードしておいて、// ここから下を全て使う、以降をコピペしておけば、あとは delayWDT2(8); などと呼ぶだけで動くはずです。

 ポイントになるのは43から47行で、ここでBODを停止する設定を行った後で素早くスリープに入れています。なお、スリープから復帰する時に BOD は自動的に再起動するので、プログラムは何もしなくても良いようです。

 プログラムがうまく動いているか確認するために、デジタルマルチメーターで CPU の電源電流を測定しました。

▼CPU の消費電流測定
ArduinoのCPUの電流測定
 以前作った、ATmega328P の消費電流測定用のゲタを使って電流を測りました。このゲタには電源電流測定用の1Ωのシャント抵抗が入っているのですが、流れる電流が小さいので分解能が不足します。ということでシャント抵抗は使わず、DMMの電流レンジ (分解能10nA) で電流を直接測定しました。なお、使った基板はArduino UNO R3 です。

 以下、スリープ時の消費電流の値を見て行きます。

▼以前の delayWDT の場合
27μA
 以前作ったdelayWDT 関数では、スリープ中の消費電流は27.4μAでした。普通に動いている時は、3桁くらい上の16mAは流れるので、ものすごい省エネ効果ではあります。

▼今回作ったdelayWDT2の場合
6.5μA
 delayWDT2 関数を使うと消費電流は6.57μAまで減りました。delayWDT と比べると 1/4 以下の消費電流になっています。ここまで減ると、Arduino をコイン電池で動かすことも視野に入ってきそうです。

◆まとめ
 昔 delayWDT 関数を作った時の話ですが、CPUのデーターシートではもっと消費電流が減らせそうな感じなのですが、あと一歩追い込めなくて、妥協しました。そんなことで、ちょっと気になっていたことが、解決出来て良かったです。これからはdelayWDT2 を使って行こうと思います。また、貴重な情報を公開していただいた tetsuaki baba さんに感謝します。

◆ご注意
 この関数を使う場合、色々な注意事項があります。そのあたりは、冒頭に紹介した delayWDT関数の記事の末尾に書いておきましたので、一読して頂くと幸いです。
 なお、今回の関数はATmega328P用で、ATmega328では動かないはずなのでご注意下さい。

PAM8403搭載のローコストデジタルアンプモジュールを購入

 電子工作するのに、小型のアンプモジュールをストックしておくと何かと便利です。ということで、Aliexpressから PAM8403というチップを使ったデジタルアンプモジュールを買ってみました。

▼Aliexpress のPAM8403搭載デジタルアンプモジュール
AliexpressのPAM8403デジタルアンプ
 この商品の Aliexpress のページはこちら

 これは3Wのステレオデジタルアンプモジュールで、値段は何と10個で送料込みで1.64ドル。つまり一つ約20円という衝撃価格です。

▼PAM8403モジュール到着
PAM8403搭載基板
 チャイナポストのトラッキング無し郵便で、注文から到着まで約10日で届きました。10個買ったのですが、写真のようにVカットされたままの状態で届きました。使う時は折り曲げて切り離すことになりますが、バラバラになっているよりこの状態の方が保管し易いです。

▼切り離したモジュール
PAM8403デジタルアンプモジュール
 下辺が信号の入力と電源。上の辺が出力端子です。BTLなので出力の二つの端子はどちらもGNDレベルから浮いています。なお、シルク印刷されている+、-の記号が間違っているという指摘がネットのあちこちに書かれていますが、そのあたりの話はあとで触れたいと思います。

▼標準接続図 (PAM9403のデーターシートより抜粋)
PAM8403接続図
 この図はTypical Apprication の回路図です。購入した物はRiが10kΩでした。コンデンサは、先人の方の解析結果によると、0.1μFと10μFの2種類だけが使われているようです。

 スピーカーの配線の上側に赤字で追記したのが基板のシルク印刷の極性で、PAM8403の端子名の極性とは逆になっています。

▼ブレッドボードに刺さるようにピンヘッダをはんだ付け
PAM8403アンプにピンヘッダを実装
 そのままではテストがやり難いので、ピンヘッダをはんだ付けしてブレッドボードに刺さるようにしました。なお、この基板の穴ピッチは2.54mmグリッドから微妙にずれているので注意が必要です。特に向き合う辺の穴間隔は7.5グリッドくらいになっているので、ピンヘッダの短い方の足を微妙に曲げて7グリッドになるように補正しました。

▼ブレッドボードで動作確認中
PAM8403をブレッドボードでテスト

PAM8403のテスト
 全体像です。スピーカーのインダクタンスが必須な回路になっているので、ダミー抵抗のような負荷は使えません。ということで、小さなスピーカーを接続してテストしました。BTL出力なので、差動測定する必要があるため、オシロのプローブを二本使っています。

 以下波形を見て行きます。波形は二つあり、上が入力信号で、下がアンプの出力端子の電圧です。信号源にはファンクションジェネレーターを使っています。また、アンプの電源電圧は3Vに設定しています。

▼無信号時
PAM8403無信号時
 入力信号が無いのに、出力端子には周期4.5μsで±3Vのパルスが交互に出ています。とは言っても、パルス幅は0.4μsしか無いので、スピーカーのインダクタンスの効果で、流れる電流はごくわずかになるはずです。なお、この写真のトリガは出力パルスのプラスエッジで掛けています。

▼入力信号ありの場合
信号入力あり
 一つ上の写真と同じ状態で、入力信号を加えた場合です。下側の出力波形が変化しています。もちろんスピーカーから音が出ています。

 これでは判り難いので、以下の写真では入力信号でトリガをかけます。

▼三角波の場合の波形
三角波応答
 三角波を入力した状態です。下の出力波形は、パルスの密度が入力波形で変調されている様子が良く判ります。これぞBTL出力のデジタルアンプ、という波形になっています。詳しく見ると、入力に対して出力波形は上下が反転していることが判ります。

 シルク印刷のプラス側にオシロの差動プローブのポジ側を接続しているので、シルク印刷を基準にすれば、このアンプは反転アンプ、ということになります。

 もう少し波形を見ていきます。出力ピンを見ても波形の様子が判り難いので、コイルと抵抗で簡単なローパスフィルタを作ってアナログ波形を観察することにします。具体的には、47μHのインダクタと8.2Ωの抵抗を直列にしたものを出力に接続し、8.2Ωの抵抗の両端電圧をオシロで観察します。なお、スピーカーは接続したままです。

▼正弦波の入力波形
大振幅正弦波
 ローパスフィルターを通したので波形が判り易くなりました。

 入力に対して出力波形が反転していることが良く判ります。また、大入力を加えた状態なので波形が少し歪んでいます。出力波形の幅が少し広いのは、スイッチング成分が少し残っているためです。なおこの写真の信号の周波数は1kHzです。

▼出力が飽和した状態
出力飽和波形
 上側が先にクリップしています。

▼高い周波数に対する特性
20kHzの応答
 周波数20kHzの矩形波を入れた状態です。しっかり喰らい付いていて、たいしたもので、流石デジタルアンプです。

◆出力端子の極性の話
 出力端子の極性について話を整理しておきます。結論を先に書くと、シルク印刷の極性は間違っているようです。

 こういう話はデーターシートを読むだけで結論を出せると良いのですが、ややこしいのは以下のブロック図です。

▼内部ブロック図(PAM8403のデーターシートより抜粋)
PAM8403ブロック図
 信号の極性を追うと、1段目のアンプはゲインコントローラで、ここでは信号の極性は変わりません。二段目のアンプは反転アンプになっているのでここで極性は反転します。その後の出力部の回路で極性がどうなるのかが書かれていないので、最終的な極性は判りません。

 出力端子に+OUT_Rと書かれていますが、これが出力部の極性なのか、アンプ全体の極性を表しているのかが判らないので、データーシートから結論が出せなかった訳です。

 で、しつこいですが実測した結論に基づき、このブロック図を使って説明すると、IN に入った信号の極性はそのまま増幅されて+OUTへ出ます(-OUTには逆極性で増幅されます)。つまりシルク印刷されている出力の極性は間違っています。

 (混乱するので括弧付きで書いておきますが、反転アンプと思えばシルク印刷は正しいです)

◆まとめ
 格安なパワーアンプが大量に手に入りました。持っていれば重宝すると思います。かなりのパワーがあるので、音に合わせてパワーLEDをバシバシ点灯させるようなことにも使えそうです。

 今回の調査では、世の中のオーディオアンプの入出力の極性がどうなっているのかが気になって、いろいろ調べてみました。しかし、検索して出てくるのは、電源の差し込みプラグの極性の話とスピーカーの片側逆接の話ばかりでした。まあ、アンプの入出力の極性なんて、マルチアンプとかやらなければ、全く問題にならない話ではあります。

ArduinoのCPUのデーターを全部抜く(ダンプする)

 最近テレビで「池の水ぜんぶ抜く」、という番組をよくやってます。それに少し影響を受けて、Arduino の CPU の ATmega328P に入っている情報を全部抜くプログラムを作ってみました。まあ、抜くと言ってもデーターを、メモリーマップの形でシリアルにダンプするだけのものです。なお、この記事はArduino UNO で動作確認を行っています。

 普通のダンププログラムは、調べたい相手の状態を出来るだけ変えないで情報を出力します。それに対して、今回作ったプログラムは、ダンプ用のプログラムを書き込んで、その状態を出力しています。つまり、まるっきり相手を乗っ取った状態になっているので、あらためて調べなくても、ほとんどは事前に予想出来る情報のはずです。ということで、ちょっとインチキ臭い話です。

 そうは言っても、ダンプ出力を完全に予想するためには、システム全体に対する深い知識が必要ですが、簡単なことではありません。そういう意味では、このプログラムの存在意義はあると思います。

 ちなみに、CPU の内部構造の説明にメモリーマップがよく出てきますが、実データーが入った状態の解説はお目にかかったことがありません。本物のデーターを見ながら勉強すれば、より具体的にCPUを理解できるはずです。

 そんなことを考えて、CPU の情報を全部抜く(ダンプする)プログラムを作ってみました。

 先に実行した結果を示します。

▼CPUとダンプしたデータ
ダンプリストとCPUチップ
 これは、ダンプしたデータをプリントしたものです。小さな CPU の中に、A4 で14ページものデーターが入っているのは驚くばかりです。(両面印刷しています)

 以下、まずはプログラムです。
/* CPUの全レジスタとメモリーをダンプ
対象範囲(ATmega328P)
・IOメモリ領域:0x00-0x8FF(レジスタ、IOレジスタ、拡張IOレジスタ、RAM)
  8ビットx(2048+256) = 2304バイト
・EEPROM:0x00-0x1FF
 8ビットx512 = 512 バイト
・Flash Memory:0x00-0x3FFF
 16ビット×16384ワード = 32768バイト(32k)
2018/4/3 ラジオペンチ http://radiopench.blog96.fc2.com/
*/

#include <EEPROM.h>

char ascii[32]; // 文字列記録バッファ

void setup() {
Serial.begin(115200);
Serial.println("ATmega328P registers & memorys dump");
Serial.println();

Serial.println("REGISTER FILES"); // CPUレジスタ
IoDump(0, 0x001F);
Serial.println("I/O REGISTERS"); // IOレジスタ
IoDump(0x0020, 0x005F);
Serial.println("EXTENDED I/O REGISTERS"); // 拡張IOレジスタ
IoDump(0x0060, 0x00FF);
Serial.println("SRAM DATA MEMORY"); // SRAM
IoDump(0x100, 0x08FF);
Serial.println("EEPROM"); // EEPROM
eepromDump(0, 0x1FF);
Serial.println("FLASH PROGRAM MEMORY (2byte/word,Little Endian)"); // フラッシュメモリ
flashMemDump(0x0000, 0x3FFF);
}
void loop() {
}

void IoDump(unsigned int fromAdr, unsigned int toAdr) { // メモリ領域ダンプ
int p = 0;
unsigned int i;
byte data;
Serial.println("addr 0 1 2 3 4 5 6 7 8 9 A B C D E F <- ascii char ->");

for (i = fromAdr; i <= toAdr; i++) { // データーメモリ領域
if (( i & 0x0F) == 0) { // 行の先頭なら
printHex4chr(i); // アドレス表示
Serial.print(":");
clrAscii(15); // バッファを16文字分クリア
p = 0;
}
Serial.print(" "); // データ間にスペース入れる
data = _SFR_MEM8(i); // データメモリ読み出し
printHex2chr(data); // 16進2文字で表示
addAscii(data, p); // 文字コードなら記録
p++;

if ((i & 0x0F) == 0x0F) { // 行末だったら、
Serial.print(" ");
printAscii(15); // 該当文字を表示
Serial.println(); // 行末なので改行
}
if ((i & 0xFF) == 0xFF) { // 0x100間隔で1行開ける
Serial.println();
}
}
Serial.println();
}

void flashMemDump(unsigned int fromAdr, unsigned int toAdr) { // フラッシュメモリダンプ
int p = 0;
unsigned int i;
unsigned int data, dataH, dataL;
Serial.println("addr 0 1 2 3 4 5 6 7 8 9 A B C D E F <--------- ascii char --------->");

for (i = fromAdr; i <= toAdr; i++) { // 指定範囲をスキャン
if (( i & 0x0F) == 0) {
printHex4chr(i); // アドレス表示
Serial.print(":");
clrAscii(31); // 32文字分クリア
p = 0;
}
Serial.print(" "); // データ間にスペース入れる
dataL = pgm_read_byte(i * 2); // 1バイト目を下位
dataH = pgm_read_byte((i * 2) + 1); // 2バイト目を上位に(リトルエンディアンで格納)
data = (dataH << 8) + dataL; // 16ビットに結合

printHex4chr(data); // 16進4字で表示
addAscii(dataH, p); // 文字コードなら記録
p++;
addAscii(dataL, p); // 文字コードなら記録
p++;

if ((i & 0x0F) == 0x0F) { // 行末だったら、
Serial.print(" ");
printAscii(31); // 該当文字列を表示
Serial.println(); // 行末改行
}
if ((i & 0xFF) == 0xFF) { // 0x100間隔で1行開ける
Serial.println();
}
}
Serial.println();
}

void eepromDump(unsigned int fromAdr, unsigned int toAdr) { // EEPROMのダンプ
int p = 0;
unsigned int i;
unsigned int data;
Serial.println("addr 0 1 2 3 4 5 6 7 8 9 A B C D E F <- ascii char ->");

for (i = fromAdr; i <= toAdr; i++) { // 指定範囲をスキャンん
if (( i & 0x0F) == 0) { // 行の先頭だったら
printHex4chr(i); // アドレス表示
Serial.print(":");
clrAscii(15);
p = 0;
}
Serial.print(" "); // データ間にスペース入れる
data = EEPROM.read(i); // EEPROM 読み出し

printHex2chr(data); // 16進2文字で表示
addAscii(data, p); // 文字コードなら記録
p++;

if ((i & 0x0F) == 0x0F) { // 行末だったら、
Serial.print(" ");
printAscii(15); // 該当文字を表示
Serial.println(); // 行末を改行
}
if ((i & 0xFF) == 0xFF) { // 0x100間隔で1行開ける
Serial.println();
}
}
Serial.println();
}

void clrAscii(int n) { // 文字バッファを指定数クリア
for (int x = 0; x <= n; x++) {
ascii[x] = ' ';
}
}

void addAscii(byte d, int p) { // 文字コードだったら指定位置に記録
if ((d > 0x20) & (d < 0x7F)) { // キャラクタコードの範囲だったら
ascii[p] = d; // バッファの指定位置に記録
}
}

void printAscii(int n) { // 文字バッファの内容を指定数表示
for (int x = 0; x <= n; x++) {
Serial.print(ascii[x]); // バッファの文字を表示
}
}

void printHex2chr(byte x) { // 16進2桁で出力
Serial.print((x >> 4), HEX); // 上位
Serial.print((x & 0x0F), HEX); // 下位
}

void printHex4chr(unsigned int x) { // 16進4桁で出力
// x &= 0xFFFF;
Serial.print((x >> 12), HEX); // 最上位
Serial.print(((x & 0x0F00) >> 8), HEX);
Serial.print(((x & 0x00F0) >> 4), HEX);
Serial.print((x & 0x000F) , HEX); // 最下位
}
 
 吐き出す情報量が多いのでシリアルのビットレートを 115200bps と高くしています。あとは、細かい説明は省略しますが、下記がCPUからデーターを読み出している部分です。
・51行目の、
   data = _SFR_MEM8(i); // データメモリ読み出し
・82,83行目の
  dataL = pgm_read_byte(i * 2); // 1バイト目を下位
  dataH = pgm_read_byte((i * 2) + 1);
・118行目の
  data = EEPROM.read(i); // EEPROM 読み出し

 以下ダンプ出力を先頭から順に見て行きます。最初はデーターメモリー領域です。

▼CPU レジスタ、IO レジスタ、拡張 IO レジスタ
出力例1
 ここには、CPU レジスタ、IO レジスタ、拡張 IO レジスタの順に並んで始まります。

 0x00から0x1Fまでは CPU レジスタです。ここの内容は激しく変化しているので、ダンプ結果はある瞬間を捉えたものにすぎず、内容にほとんど意味は無いと思います。でもデーターメモリ領域の最初の32バイトが、CPU レジスタに割り当てられていることは良く判ると思います。

 0x20から0x5Fの範囲は IO レジスタで、ここでデジタル IO ポートなどの基本的な IO の制御を行っています。ちなみに、この領域はアセンブラの IO 命令からアクセス可能で、その場合のアドレスは0x00から始まります。なお、レジスタが存在しないアドレスもあり、その場所の値は不定になりますが、このダンプでは0xB8となっています。

 0x60から0xFFまでの領域は拡張 IO レジスタで、ここは ADコンバーターや I2C などの設定を行う領域です。

 IO レジスタはタイマーやADコンバーター、通信機能をコントロールする部分です。ここをいじっているだけで、相当遊んでいられると思います。

▼SRAM
出力例2

 0x100から0x8FFまでの2kバイトがSRAM領域で、プログラムの変数やスタックなどの保存に使われています。

 右のアスキーキャラクタ欄を見ると、プログラムで入力した文字列がそのまま入っている部分があります。これらは固定されたデーターなので途中で変更されることはありません。それにもかかわらずRAM領域に置かれているのはもったいないです。

 ここまでがデーターメモリ領域で、次はEEPROM領域です。

▼EEPROM
出力例3

 EEPROM領域は0x000から0x1FFまでの512バイトです。
 過去に EEPROM に書き込みを行ったことがあれば、その内容が残っています。まっさらな CPU ならデーターは全部 0xFF になっているはずです。

 次はフラッシュメモリ領域です。この領域は32kバイトの容量がありますが、1ワードが2バイトの構成になっているので、ワードアドレスとしては16kまでの範囲です。

▼フラッシュ プログラム メモリー領域
出力例4

 アドレスは0x0000から0x3FFFまでの16kワードの範囲です。上の画像は先頭部分で、データーはこのまま10ページ以上続きます。

 なお、この領域をバイト単位で表現することもあるようで、上記のプログラムの pgm_read_byte() 関数はバイトアドレスでアクセスしています。でも話がややこしくなるので、ここではワード単位の表現に変換しています。

 2バイトで1ワードを構成する時のバイトオーダーは、AVR の作法に合わせて、リトルエンディアンにしています。こうするとマシン語が読み易くなります。一方でアスキー文字が読み難くなりますが、まあこれは仕方ないでしょう。

 ダンプリストを見ると、先頭アドレス付近にリセットや割込み処理へのジャンプルーチンがまとめて置かれていることがよく判ります。ちなみに、先頭の、0x940C, 0x0035 というのは、リセットがかかったら0x0035番地へジャンプというマシン語だと思います。

 フラッシュメモリの領域は、過去に書かれたプログラムの痕跡ががそのまま残っているため、どこまでが今回書き込んだプログラムの範囲なのかは、はっきりとは判りません。

 フラッシュメモリ領域の末尾にブートローダーが書かれているはずなのですが、残念ながら今回作ったプログラムでは内容を見ることが出来ませんでした。たぶん、何かのおまじないが必要なんでしょう。

◆まとめ
 ダンプ結果を見ると、CPU の内部構造が良く判ります。マイコンのアーキの勉強をやりたい人には、良い教材になると思います。なお、このプログラムは Arduino の IDE と UNOが動いていれば他に何も無くても動きます。

 実は最初は、IO レジスタの範囲だけをダンプするプログラムを作ろうと持っていました。でも、「池の水ぜんぶ抜く」のコピーが気に入ったので、全メモリー領域の情報を抜くことにしました。

 実はヒューズビットとシグネチャの情報もダンプしたかったのですが、今回はそこまで手が回りませんでした。ただ、例えば Arduino UNO で動いているということは、ヒューズやシグネチャの値ははぼ決まるはず、というか変な値になっていたら Arduino のIDE が動かないはずなので、これらを詳しくチェックしても、あまり意味が無いかも知れません。

 フラッシュの内容を逆アセンブルしてアセンブラに戻すと面白いかも知れません。でもそんなことはHEXファイルから遡った方が手っ取り早いですね。
カレンダー
03 | 2018/04 | 05
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 - - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード