ATmega328pの省電力機能を使う

1.5V乾電池やボタン電池で長期間、または小さな太陽電池で無期限に動作するデバイスを作るためには、デバイスが動作していないときの消費電力を抑えることが重要です。そういったデバイスの実現方法については色々な記事があるのですが、今ひとつまとまった記事が見つからなかったのでATmega328pを使う場合について自分なりにまとめて見ました。
# ATmega328pの省電力機能
ATmega328pはいくつかの省電力モードを持っています。
いづれもCPUコアといくつかの周辺回路を停止して消費電力を減らし、動作している周辺回路からの割り込みを受けて起動するものです。停止する周辺回路によってモードが別れています。代表的なモードはこの３つです。

| 省電力モード| CPUの起動に使える周辺機能 | 省電力効果|
|:-----------|:------------:|:------------:|
| アイドルモード       | すべて | 小さい         |
| パワーダウンモード     | 外部割り込み,I2C,WDT      | 大きい       |
| パワーセーブモード       | 外部割り込み,I2C,WDT,カウンタ２ | 大きい   |

これらのモードについて消費電力を比較していきます。

# 測定条件など
今回の比較調査ではCPUクロックはチップ内蔵の8MHzCR発振器使用で1/8分周(すなわち1MHz)、電源電圧3.3Vで使用しています。
消費電流測定のため電源ラインに抵抗100Ω（誤差5%）を直列に接続し、電圧降下をテスタ（分解能0.1mV）で測定しています。


## 測定1-1:パワーセーブモード
一番効果の高そうなのはパワーダウンモードなのですが、パワーダウンモードではタイマカウンタが使用できません。普通、一定時間ごとにCPUを起動するにはタイマを使うので、タイマカウンタ２が使えるパワーセーブモードを使ってみます。

```C++:タイマカウンタ２の設定を行い、省電力に入る
TIMSK2 = 1<<TOIE2; //タイマカウンタ2オーバーフロー割り込み許可
TCNT2 = 0; // TCNT2初期化
TCCR2B = 0x07; // プリスケーラ1024分周
sei(); // 割込みを許可する。

while (1)
{
	set_sleep_mode(SLEEP_MODE_PWR_SAVE); // 省電力モード設定
	sleep_enable();
	sleep_cpu(); // ここでCPU停止
	sleep_disable(); // 割り込みを受けてCPU起動
}
```

このサンプルではまずプリスケーラでクロックを1/1024しているのでカウンタのクロックは約1kHz、これを256(8bit)までカウントしてオーバーフロー割り込み発生させて、約260msでCPUを起動しています。
測定結果は140μA。

## 測定1-2：パワーセーブモード＋BOD無効
マニュアルに低電圧検出器(BOD)を無効化すると節電できると書かれています。これをやってみます。BODはヒューズを設定すれば無効にできますが、ここではソフトウェアで無効化します。ソフトウェアで無効化した場合は、待機状態から起動して通常状態に戻ると自動でBOD機能が有効になり、起動中はBODの機能を生かしつつ待機中は無効にして省電力化できます。ちなみにヒューズで設定してもソフトで設定しても電流値は同じでした。

```C++:1-1にBOD無効化を追加
TIMSK2 = 1<<TOIE2; //タイマカウンタ2オーバーフロー割り込み許可
TCNT2 = 0; // TCNT2初期化
TCCR2B = 0x07; // プリスケーラ1024分周
sei(); // 割込みを許可する。

while (1)
{
	set_sleep_mode(SLEEP_MODE_PWR_SAVE); // 省電力モード設定
	sleep_enable();
	// BOD無効処理
	MCUCR |= (1<<BODSE) | (1<< BODS);
	MCUCR = (MCUCR & ~(1 << BODSE))|(1 << BODS);
	sleep_cpu(); // ここでCPU停止
	sleep_disable(); // 割り込みを受けてCPU起動
}
```
レジスタ設定でBODを無効化したあと３クロックでBODは自動的に有効状態に戻ってしまうので、sleep_cpu()の直前でレジスタ操作を行っています。
測定結果は120μA。

## 測定1-3:パワーセーブモード＋BOD無効＋タイマ無効
CPUを自身で起動するのではなく、外部割り込みでCPU外から起動する場合はそもそもタイマがいりません。この場合を想定して、タイマも不使用とします。
測定結果は20μA。

## 測定2-1:パワーダウンモード
パワーダウンモードではタイマカウンタが使えませんが、代わりにウォッチドッグタイマが使えます。ウォッチドッグタイマは通常のタイマと違い、細かい正確な時間設定が出来ませんが長時間の設定ができるようになっています。これを使ってCPUを起動してみます。
最初にウォッチドッグタイマの設定。

```C++:ウォッチドッグタイマの設定
void setup_WDT( uint8_t delay )
{
	// ウォッチドッグタイマのタイムアウト設定
	if( delay > 9 ) delay = 9;
	uint8_t reg_data = delay & 7;
	if( delay & 0x8 )
	{
		reg_data |= 0x20;
	}

	MCUSR &= ~(1 << WDRF);
	WDTCSR |= (1 << WDCE) | (1 << WDE); // 設定変更許可
	// WDTCSR = reg_data | (1 << WDE);	// タイムアウト設定
	// これは誤り。これではタイムアウトでリセットもかけられる設定になる。
	// 正しくは以下。割り込みだけ有効になる。
	WDTCSR = reg_data;	// タイムアウト設定
	WDTCSR |= (1 << WDIE); // 割り込み許可
}
```
上記setup_WDT関数の引数には以下に定義した16ミリ秒から8秒までのタイムアウト時間定数をひとつ指定します。

```C++:タイムアウト時間の定義
#define WDT_8s 9
#define WDT_4s 8
#define WDT_2s 7
#define WDT_1s 6
#define WDT_500ms 5
#define WDT_250ms 4
#define WDT_125ms 3
#define WDT_64ms 2
#define WDT_32ms 1
#define WDT_16ms 0
```
タイムアウト時間はパワーセーブモードの測定条件に近い250msとします。

```C++:ウォッチドッグタイマを設定して省電力に入る
	sei(); // 割込みを許可する。

	while (1)
	{
		setup_WDT(WDT_250ms); // タイムアウトは250ms
		set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 省電力モード設定
		sleep_enable();
		sleep_cpu(); // ここでCPU停止
		sleep_disable(); // 割り込みを受けてCPU起動
	}
```

測定結果は25μA。

## 測定2-2:パワーダウンモード＋BOD無効
「測定1-2:パワーセーブモード＋BOD無効」と同様にBODを無効にしてみます。

```C++:2-1にBOD無効を追加する
	sei(); // 割込みを許可する。

	while (1)
	{
		setup_WDT(WDT_250ms); // タイムアウトは250ms
		set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 省電力モード設定
		sleep_enable();
		// BOD禁止処理
		MCUCR |= (1<<BODSE) | (1<< BODS);
		MCUCR = (MCUCR & ~(1 << BODSE))|(1 << BODS);
		sleep_cpu(); // ここでCPU停止
		sleep_disable(); // 割り込みを受けてCPU起動
	}
```

測定結果は5μA。

## 測定2-3:パワーダウンモード＋BOD無効＋タイマ無効
測定1-3と同様に、ウォッチドッグタイマも無効にしてみます。
測定結果は1μA以下（うちのテスタの測定限界以下）。

# まとめ
| | 省電力モード| CPU起動割り込み | 消費電流|
|:---|--------|:------------:|:------------:|
| 1-1 |パワーセーブ  | タイマカウンタ２ | 140μA |
| 1-2 |パワーセーブ＋BOD無効  | タイマカウンタ２   | 120μA      |
| 1-3 |パワーセーブ＋BOD無効＋タイマ無効  | 外部割り込み | 20μA   |
| 2-1 |パワーダウン  | ウォッチドッグタイマ | 25μA         |
| 2-2 |パワーダウン＋BOD無効  | ウォッチドッグタイマ   | 5μA       |
| 2-3 |パワーダウン＋BOD無効＋タイマ無効  | 外部割り込み | 1μA以下   |
| 参考1 |アイドルモード  | タイマカウンタ２ | 600μA程度   |
| 参考2 |通常走行  | タイマカウンタ２ | 900μA程度   |
参考1: アイドルモードで待機。タイマカウンタ２割り込み使用
参考2: 通常モード無限ループで待機。タイマカウンタ２割り込み使用

参考としてアイドルモード、省電力モードを使わない場合も載せておきました。
プロセッサ内部の割り込みで起動する場合は、パワーダウンモードとBOD無効の組み合わせで使うのが最も効果的なようです。

## ADCの電源について
内臓のADCは電源を入れているだけで200μA以上の電流を消費します（1MHz/3.3Vの場合）。ADCを使っている場合は省電力モードに入る前にADC電源を切っておかないと省電力の意味がなくなります。

```C++:ADC電源OFF
void disable_adc()
{
	ADCSRA &= ~(1<<ADEN);
}
```

## DC-DCコンバータについて
1.5V乾電池１本でATmega328pを駆動する場合、DC-DCコンバータで電源電圧を1.8V以上に上げる必要があります。手軽に手に入るDC-DCコンバータの電流についても簡単に調べておきました。
無負荷時を除いて、全てATmega328pを1MHz,パワーダウンモード,BOD無効の状態とし、温度センサ（3.3V時消費電流2μA）も接続しています。
コンバータへの入力は単４乾電池１本、開放電圧1.55Vです。

**秋月電子製/HT7733A使用**

| 出力電圧| 負荷への供給電流 | DC-DCコンバータ入力電流|
|:--------:|:------------:|:------------:|
| 3.3V | 7μA | 33μA |
| 3.3V |0μA（無負荷） | 15μA      |

**ストロベリー・リナックス製/TPS61291使用**
（追記）**TPS61291**は起動時電圧が最低1.5V必要なので、乾電池一本のアプリケーションには使えません。（スイッチサイエンスで扱っている**TPS610986**なら使えそうです。）
下記は参考データ。

| 出力電圧| 負荷への供給電流 | DC-DCコンバータ入力電流|
|:--------:|:------------:|:------------:|
| 3.3V | 7μA | 33μA |
| 3.0V |5μA（3.3V時と同じ負荷） | 21μA      |
| 2.5V |1μA以下（3.3V時と同じ負荷） | 10μA      |



## 参考URL
http://radiopench.blog96.fc2.com/blog-entry-486.html