hoboNicolaアダプターの消費電流
電気を大切にね! ということで hoboNicola 1.6.1 で定常動作時やスリープ時の消費電流を減らすための工夫の話。
マイコンボードごとの消費電流
USB版のhoboNicolaアダプターの消費電流を、対応したマイコンボードごとに計測した。計測は、以前に作ったWROOM-02とINA226を組合せたWiFi電流モニタ を使って、セルフパワーHUBとアダプター間のUSBケーブルに流れるVBUS電流を観察することで行った。hoboNicolaアダプターやキーボードをつないだ作業用PC自体をスリープさせて電流をモニタするので、WiFiを介してスマホで電流が読める必要があった。
システム | マイコン | アクティブ状態 | スリープ状態 | 擬似サスペンド有効 |
Pro Micro + miniUHS + キーボード |
ATmega32U4 3.3V/ 8MHz |
75.2mA | 67.9mA | 3.85mA |
(キーボード無し) | 18.2mA | 3.45mA | ||
QT Py SAMD21 + miniUHS + キーボード |
SAMD21E-18A 48MHz |
73.2mA | 66.5mA | 2.30mA |
(キーボード無し) | 16.2mA | 1.92mA | ||
XIAO-m0 + miniUHS + キーボード |
SAMD21G-18A 48MHz |
75.3mA | 67.8mA | 3.65mA |
(キーボード無し) | 18.2mA | 3.30mA | ||
ISP1807MB + miniUHS + キーボード |
ISP1807 (nRF52840) 64MHz |
76.3mA | 65.5mA | 4.1mA |
(キーボード無し) | 19.4mA | 3.75mA | ||
XD87 | ATmega32U4 5V/ 16MHz |
42.8mA | 14.4mA | — |
SBKアダプター + キーボード |
ATmega32U4 5V/16MHz + MAX3421E |
91.5mA | 66.5mA | 3.50mA |
- アクティブ状態とは、アダプターに接続したキーボードから通常に文字を入力できる状態。
- スリープ状態とは、アダプターをセルフパワーのUSB HUBを介して接続しているPCをスリープさせている状態。マイコンボード自体のスリープ時電流+miniUHS+キーボードの消費電流に相当する。
- 擬似サスペンド有効は、hoboNicola1.6.1の設定にある PSEUDO SUSPEND を有効にしてPCをスリープさせている状態。マイコンボードのスリープ時電流+miniUHS(サスペンド)+キーボード(サスペンド)の消費電流に相当する。
今回hoboNicolaアダプターに接続したキーボードは、富士通コンポーネント社のFKB8769-052。すでにディスコンになっている。
上表についての考察・備考
- いずれのアダプターも、アクティブ状態の消費電流には大差ない。ATmega32U4(8Mz)と、SAMD21やnRF52840では性能に相当な違いがあると思うのだが、省電流性能も向上していることが分かる。
- (キーボード無し) としたときの電流は、一つ上の行のアダプターにキーボードをつないだ状態で電流モニタリングを開始し、そのままキーボードを抜いたときの値を記録した。つまり、マイコンボード+miniUHSの消費電流ということになる。
- キーボード有無の電流差から FKB8769-052の定常時の消費電流は約57mA、このキーボードをスリープさせたときの消費電流は0.35~0.4mAほどだろうということが分かる。ただ、負荷が変わることでUSBハブからのVBUS電圧も変化するので単純には評価できないが。
- Pro Micro、XIAO-m0 ではオンボードのPON(PowerOn) LEDが点灯している。それに対して、QT Py-m0とISP1807MBにはPON LEDがない(ISP1807MBのPON-LEDは外してしまった)。+3.3V動作のマイコンボードでは、PON LEDの有無で約1~1.5mAほど消費電流が変わることが多い (LEDのVFと直列抵抗の値による) 。
xd87について
xd87 PCBには、14個のRGB LED (おそらく、WS2812B)が実装されている。
RGB LEDは一つ一つにCPUが入っているので、消灯(RGB値をすべて0に) してもそれなりに電流が流れてしまう。データシートを見ても、消灯時の消費電流にはっきりとは言及されていないので検索してみたところ、高性能Arduinoボードのteensyシリーズで有名なpjrc.comに How Much Current Do WS2812 / NeoPixel LEDs Really Use? というページがあった。ここの実測値として消灯時の消費電流は1個につき約1mA程度とのこと。
したがって、xd87 PCB では何もしていなくても14mA程度が消費されてしまうことになる。あまり頑張ってもしょうがないかもしれないし、14mAのためにRGBLEDにハンダごてをあてて剥がすのも気が引ける。
省電流のための実装
デバイスごとの初期設定はスケッチ(inoファイル)と同じフォルダにおいた device_setup.h に、その後のアクティブ状態、サスペンド状態での実装は hoboNicolaライブラリの hobo_sleep.cpp に入れた。
Adafruit QT Py SAMD21 用を使った以下のテスト用スケッチを例に説明してみる。このスケッチは、usb_hobo_nicola.inoを単純化しminiUHSとの接続やHIDキーボードとしての機能を除いたものになる。このスケッチは、hoboNicolaライブラリ1.6.1がArduino IDEのライブラリとして追加されていればビルドできる(はず)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include "Adafruit_TinyUSB.h" #include "device_setup.h" #include "hobo_sleep.h" void setup() { hobo_device_setup(); } void loop() { #if 1 if (USBDevice.suspended()) enter_sleep(500); else enter_sleep(); #else enter_sleep(); #endif } |
このスケッチの、各部分をコメントにしたり、#if 1 を #if 0に書き換えたりすることで、消費電流を削減する効果を確認した。
スケッチの内容 | 消費電流 (VBUS電流) |
備考 |
hobo_device_setup() 無し enter_sleep() 無し |
13.9mA | 空のスケッチに相当 |
hobo_device_setup() 有り enter_sleep() 無し |
12.5mA | 1.4mAの削減 |
hobo_device_setup() 有り enter_sleep() 有り |
6.93mA | 5.6mAの削減 8行目を #if 0 に |
hobo_device_setup() 有り enter_sleep(500) 有り |
0.35mA | さらに -6.55mA。 上記スケッチの状況 |
- USBDevice.suspended(); は、マイコンボードのUSBがサスペンド状態のとき true を返す。
- enter_sleep(500); とすることで、SAMD21はスタンバイスリープ状態 に入り、500msecに一度だけメインループが回ることになる。
- 実際のusb_hobo_nicola.inoでは、500msecごとにMAX3421Eに接続されているキーボードで何か入力があったかどうかを調べ、入力があったならば USBDevice.remoteWakeup(); を呼んでサスペンド状態からの復帰(リジューム)を要求する。
hobo_device_setup()
usb_hobo_nicola.inoの setup()では、inoと同じフォルダにある device_setup.h 内の hobo_device_setup(); を呼び出し、ハードウェア依存の初期設定を行っており、マイコン内蔵周辺デバイスのうち使わないものを停止している。
SAMD21では、PM (Power Manager)のAPBCMASK レジスタを操作することで、APBC (Advanced Peripheral Bus C)に接続されている周辺デバイスのクロックを停止している。hoboNicolaでは、SPI以外のSERCOM (SERial COMmunication Interface) は使わないので6つのSERCOMのうちの5つを停止している。QT Py SAMD21用のコードを切り出すと以下のようにしている。
1 2 3 4 5 |
uint32_t pm = PM_APBCMASK_ADC | PM_APBCMASK_AC | PM_APBCMASK_DAC | PM_APBCMASK_I2S | PM_APBCMASK_PTC | PM_APBCMASK_TCC0 | PM_APBCMASK_TCC1 | PM_APBCMASK_TCC2 | PM_APBCMASK_TC3 | PM_APBCMASK_TC4 | PM_APBCMASK_TC5 | PM_APBCMASK_TC6 | PM_APBCMASK_TC7; pm |= PM_APBCMASK_SERCOM0 | PM_APBCMASK_SERCOM1 | PM_APBCMASK_SERCOM3 | PM_APBCMASK_SERCOM4 | PM_APBCMASK_SERCOM5; PM->APBCMASK.reg &= ~pm; |
SPI用のSERCOMはXIAOとQT Pyとで異なっている。相違についてはこちらの投稿を参照のこと。
またQT Py SAMD21のオンボードLEDとして NeoPixel (WS2812) が1つだけ載っており、このLEDのVDDもGPIOポート(PA18, D12)に接続されている。XD87の話でも書いたように、NeoPixelモノは点灯しなくても電流食うのでこういう構成は助かる。以下のようにすることで、0.3mAほど消費電流が減った。
1 2 3 |
#if defined(ADAFRUIT_QTPY_M0) digitalWrite(PIN_NEOPIXEL_VCC, 0); #endif |
hobo_device_setup() の呼び出しにより、消費電流を 1.4mA 削減できた。
(NexoPixel LEDの消費電流について、XD87は+5V電源、QT Pyは+3.3V電源の相違がある。今気がついたが、+3.3V電源のWS2812B のバリエーションが載っているんだろうか)。
アクティブ状態
アクティブ状態とは、スケッチの loop() が回っている状態のことで、USB版のhoboNicolaでは MAX3421Eに対するSPI通信が絶え間なく行われている。hoboNicola1.6.1では短期間のアイドルスリープを行うことで、消費電流を抑えるようにした。
samd21では、WFI (Wait For Interrupt) 命令の実行によってマイコンのスリープモードがアクティブになる。この命令を実行すると、マスクされていない割込みが起きるまでマイコンはアイドルスリープまたはスタンバイスリープ(ディープスリープ)状態に入る。
スリープ用に用意した関数 enter_sleep(); の内部では、引数の値に応じてアイドルスリープまたは指定期間のスタンバイスリープに入るようにした。アイドルスリープからの復帰は、SAMD21用Arduinoコアが用意している1msec周期のSysTick割込みによるので、特に何もしなくてもすぐにアクティブ状態に戻る。テスト用スケッチでは、約1msec間のアイドルスリープにより、消費電流は 5.6mA削減された。
AVRでの set_sleep_mode(SLEEP_MODE_IDLE) の実行と、Timer/Counter0 ユニットのオーバーフロー割込み (16MHz版は1msec周期、8MHz版は2msec周期) による復帰と似ているが、SAMD21の方が省電流効果は高かった。
サスペンド状態
アダプターを接続しているPCやハブのUSBがサスペンド状態に入ったことを検出すると、hoboNicolaはサスペンド状態に入る。MAX3421Eとキーボードの擬似スリープについては後述する。
USBがサスペンド状態かどうかは、TinyUSBライブラリの USBDevice.suspended() または Arduinoライブラリの USBDevice.isSuspended() で検出できる。上記のテスト用スケッチでは、サスペンド状態ならば enter_sleep(500); として500msec間のスタンバイスリープを繰り返す。
スタンバイスリープに入るときは、復帰要因とするためにSAMD21がもつRTCを使っており enter_sleep() の引数の期間経過したら割込みを起こすようにした。その直前に SysTick割込みをマスクし、 SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; としてマイコン自体を低消費電力モードとしている (実装は、hobo_sleep.cpp) 。
SAMD21のRTCの初期化方法は、QT PyとXIAO-m0でほぼ共通だが、XIAO-m0は32.768kHzの外部オシレータを備えているので、そのあたりの切り分け (#ifdef CRYSTALLESS) が入っている。
アイドルスリープ時が6.90mAだったのに対し、スタンバイスリープ期間中の消費電流は0.35mAまで少なくできた。さらに、USBのクロックを止めるとかD+のプルアップをやめるとかすると消費電流はもっと減るが、その瞬間PCからUSBデバイスの認識ができなくなってしまうので採用しなかった。USBを止めてしまうとサスペンド状態が終了したことも検出できない。
AVRでは、set_sleep_mode(SLEEP_MODE_PWR_DOWN); によってパワーダウンスリープに入り、ウォッチドッグタイマー割込みで復帰するようにしている。最初に載せた表からすると、パワーダウンスリープ中のPro Micro (3.3V/8MHz)の消費電流は1.4mA程度と推測されるが、PON LEDが点灯していることを考えれば、まずまずといったところだろう。
nRF52での省電流化について
今回のバージョンでは、nRF52の消費電流を減らすためのコードは特に書いておらず、enter_sleep(interval) の中身は同期間の delay(interval) を呼び出すだけにした。つまり、アクティブ時の消費電流の削減は、Adafruit nRF52 ArduinoのFreeRTOS の実装任せとした。詳しくは、https://www.freertos.org/low-power-tickless-rtos.html を参照のこと。内蔵周辺デバイスを止めるなどの工夫により、もうちょっと消費電流を減らせるような気もしているが、まあ、これからでしょう。
キーボードの擬似サスペンド機能
今回使った富士通のキーボードのアクティブ時の消費電流は57mA程度なのだが、従来(1.5版まで)のhoboNicolaアダプターでは、PCをスリープしても常に57mA (+アダプターの消費電流)が流れていた。つまり、頑張ってマイコンボードの電流を10mA減らすよりも miniUHSやキーボード自体の消費電流を抑えなければあまり意味がない。
miniUHS(およびキーボード)の制御を行う USB Host Library 2.0 (UHSライブラリ)には、ざっと見たところそういったコードは入っていなかったので、ライブラリにAPIを追加し、MAX3421Eおよびキーボードをサスペンドさせたりリジュームさせたりできるようにした。
UHSライブラリのhoboNicolaへの包含について
まず、今回のバージョンからUHSライブラリはArduino IDEに追加したものではなく、hoboNicolaライブラリのソースツリーに包含するようにした (src/UHSLib2.0) 。これは、ATSAMD用ツールチェーンに含まれている CMSIS-Atmel 内のいくつかの型定義名がオリジナルのUHSライブラリ内の定義と重複しており、 UHSライブラリ内で広く使われている構造体やクラス名を変更する必要があったことによる。
たとえば、オリジナルの struct UsbDevice は struct UsbDeviceDefinition とし、class USB は class USBHost にした。さらには、inoファイル内で従来 USB Usbとしていたところは、USBHost Usbhost に変更した (Usbというインスタンス名もCMSISと競合する)。
今回のUHSライブラリ内部の変更にあたってファイル名は変更しなかったが、class USBHostの定義がUsbCore.hにあって実装は Usb.cpp にあるなど、オリジナルより更に分かりにくくなったかもしれない。
以下、UHSライブラリ内部についての記述は、hoboNicola用に定義名を変更したコードにもとづいている。MAX3421Eのレジスタ構成やビット構成については、MAX3421Eプログラミングガイド を参照。
以下のコード断片に登場する regWr() や regRd() という関数はUHSライブラリ内に定義されており、SPI を介してMAX3421Eのレジスタにアクセスするためのもの。その内容については、次回のUHSライブラリについての投稿に記載している。
キーボードをサスペンド状態に
Usb.cpp に追加した、void USBHost::suspendKeyboard() によってキーボードとMAX3421Eをサスペンド状態としている。
1 2 3 4 5 6 7 8 9 10 |
void USBHost::suspendKeyboard() { uint8_t mode = regRd(rMODE); mode &= ~bmSOFKAENAB; regWr(rMODE, mode); // stop SOF or KeepAlive delay(5); regWr(rHIRQ, bmSUSDNIRQ); // clear SUSDNIRQ delay(5); regWr(rUSBCTL, bmPWRDOWN); // Enter powerdown state of MAX3421 delay(5); } |
bmSOFKAENAB や bmPWRDOWN といったシンボルは、UHSライブラリの max3421e.h 内に定義されておりMAX3421Eのレジスタビットを表している。
rMODE (MODEレジスタ)の SOFKAENAB ビットを落とすことで、MAX3421Eに接続されているUSBデバイスに対するSOFパケットまたはKeepAliveパルスを停止する。これによって多くのキーボードはサスペンド状態に入る。SOFKAENAB ビットを0としたことで、SUSDNIRQ ビットがセットされているはずなので、bmSUSDNIRQを書き込むことでクリアしている。
この操作により、QT Py + miniUHS + キーボードの消費電流は約10.2mAまで下がる。最初に載せた表からすると単純スリープ時が66.5mAだから、キーボードをサスペンド状態とすることでさらに56.3mAの削減効果がある。
次に USBCTLレジスタの PWRDOWNをセットすることで、MAX3421E自体をサスペンド状態としている。これをやると、消費電流はさらに減って 約2.3mAとなる。10.2mAとの差から、MAX3421Eのアクティブ時の消費電流は 7.9mA 程度ということが推測できる。
なお、MAX3421Eプログラミングガイドによると、「ホスト
として動作しているときには、CPU が POWERDOWN = 1 を設定しないようにしてください。」と書いてあるのだが、試してみたところ特に害もなさそうだったので採用することにした。
今回の実装はいろいろと試すために上記のようなコードにしたが、 regWr(rUSBCTL, bmPWRDOWN); とするだけで SOFKAENAB ビットをクリアしなくても、キーボードはサスペンド状態に入る。
キーボードをリジュームする
やはりUsb.cpp に追加した、void USBHost::resumeKeyboard() によってキーボードとMAX3421Eのサスペンド状態を解除する。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void USBHost::resumeKeyboard() { #if 0 regWr(rHCTL, bmSIGRSM); // while(!(regRd(rHCTL) & bmSIGRSM)); // wait for SIGRSM is cleared. // while(!(regRd(rHIRQ) & bmBUSEVENTIRQ)); //wait for sample operation to finish #endif regWr(rUSBCTL, 0); delay(10); uint8_t mode = regRd(rMODE); mode |= bmSOFKAENAB; regWr(rMODE, mode); delay(10); } |
プログラミングガイドを読むと、#if 0 ~ #endif で囲まれた部分のように SIGRSMビットをセットすることでバスレジュームイベントを生成し、レジューム信号の送出が完了するのを待つのが正しそうなのだが、これをやってもキーボードは復活しなかった (MAX3421Eをサスペンドしたままだったから?)。
上記のように、USBCTLに0を書いてサスペンド状態を解除し、その後 SOFKAENABをセットすることでキーボードを復活させるようにした。
メインスケッチでの処理
USBのサスペンド状態にかかわるUSB版のメインスケッチは以下のようなコードになっている。
1 2 3 4 5 6 7 8 9 10 11 12 |
void loop() { if (is_usb_suspended() ) { hobo_nicola.error_led(false); hobo_nicola.nicola_led(false); if (Settings().is_use_kbd_suspend()) Usbhost.suspendKeyboard(); // suspend keyboard and max3421E enter_sleep(500); // suspend myself. if (Settings().is_use_kbd_suspend()) Usbhost.resumeKeyboard(); // resume keyboard and max3421E to poll keyboard Usbhost.Task(); // check any key pressed. } else { .... |
is_usb_suspended() は、TinyUSBライブラリとArduinoコアライブラリの相違をラップした(ホスト側の)USBサスペンド状態を検出する関数。サスペンドならば、動作設定の擬似サスペンドが有効 ( Settings().is_use_kbd_suspend() ) のとき Usbhost.suspendKeyboard() によってMAX3421Eおよびキーボードをサスペンドさせる。そして500msec間のディープスリープ状態に入り、その後 Usbhost.resumeKeyboard(); によっていったんキーボードを復活させ、Usbhost.Task() を呼んでMAX3421Eにキー入力の検出をさせている。
キー入力があった場合 Usbhost.Task() の中から、スケッチ内に実装があるKeyboardEventクラスの Parse() が呼ばれる。Parse() の最初でホスト側にウェイクアップ要求を送る usb_wakeup(); を実行するので、PCはスリープ状態から復帰する。
キーボードをサスペンドさせているとき、マウスに触ったことなど別の要因でPCがリジュームすることも考えられる。そういったケースでも、enter_sleep(500) の後のUsbhost.resumeKeyboard() は必ず実行されるので、MAX3421Eやキーボードがサスペンド状態に留まることはないだろう。
きょうのまとめ
USB版のhoboNicolaライブラリ1.6.1版は、以下の3部分がマイコン依存になっており、おのおの切り分けて実装している。
- 省電流(スリープ絡み)
- 設定の保存(EEPROM、FlashStorage, InternalFS)
- SPI (miniUHS = MAX3421Eとのインタフェース)
今回は省電流の話でした。他の話も忘れないうちに早くまとめておく予定です。