Bluetooth Beaconプロジェクトの失敗の多くは、ハードウェアの問題ではなくファームウェアに起因します。電池を数年ではなく数週間で消費し、RF干渉下でクラッシュし、ブラウンバウト後にサイレントに広告を停止するファームウェアです。同じCR2032で3ヶ月しか持たないビーコンと3年持つビーコンの違いは、ファームウェアが無線イベントをどうスケジュールし、電力状態をどう管理し、障害からどう復旧するかにあります。本記事では、nRF52 + SoftDeviceを参照プラットフォームとして使用し、量産レベルのビーコンファームウェアのアーキテクチャを解説します。nRF52は現在の商用ビーコンの約70%で使用されています。

1. 全体像:ファームウェアアーキテクチャが電池寿命を決定する理由

ビーコンは寿命の99.97%をスリープ状態で過ごします。1秒の広告間隔と3チャネル3バイトペイロードの場合、無線は1秒あたり約300マイクロ秒しかアクティブになりません。つまり1日86,400秒のうち、無線が送信するのは合計約26秒です。残りの86,374秒はスリープであり、どれだけ深くスリープできるかがすべてを決定します。

状態 nRF52832消費電流 時間/イベント(1s) 平均電流寄与
ディープスリープ(RAM保持) 1.5 uA ~999.6 ms 1.499 uA
ランプアップ+クリスタル起動 3.2 mA ~130 us 0.416 uA
Ch37送信(0 dBm) 4.6 mA ~80 us (3バイト) 0.368 uA
チャネル間ギャップ 1.8 mA ~150 us 0.270 uA
Ch38+Ch39送信 4.6 mA ~160 us 0.736 uA
ランプダウン+RTC処理 2.1 mA ~90 us 0.189 uA
合計平均(1s間隔、0 dBm、3バイト) 3.478 uA

CR2032(公称220 mAh、3Vパルス負荷で使用可能約160 mAh):160,000 uAh / 3.478 uA = 46,000時間 = 5.2年。これが理論上限です。実際のファームウェアでは、センサーのポーリング、LED点滅、ボタンスキャン、ウォッチドッグ割り込み、RTCドリフト補正が追加され、それぞれが平均5〜50 uAを追加します。1秒あたり1回の不要な1 msウェイクアップが3 mAで3 uAを追加します。スリープ予算のほぼ2倍です。

2. 無線スケジューラ:広告イベントの実際のスケジューリング

SoftDevice(NordicのBLEスタック)は、プリエンプティブな無線スケジューラとして機能します。アプリケーションコードは無線に直接アクセスしません。代わりに広告パラメータを設定すると、SoftDeviceが無線イベントを内部タイムラインに配置します。各無線イベントにはセットアップオーバーヘッドがあるため、このタイムラインを理解することが重要です。

2.1 SoftDeviceイベントタイムライン

SoftDevice無線タイムライン(広告間隔ごと)

  [RC]  Tx37  [RD]  gap  [RC]  Tx38  [RD]  gap  [RC]  Tx39  [RD]
  130us  80us  20us      130us  80us  20us      130us  80us  20us

  RC = 無線ランプアップ+クリスタル安定化
  RD = 無線ランプダウン
  総アクティブ時間: ~610 us(3バイト、0 dBm)

チャネル間ギャップはユーザー設定不可です。内部的に最小150 usに設定されています。広告と並行してスキャンや接続モードを有効にすると、SoftDeviceがこれらのイベントをインターリーブし、広告タイミングが最大+/-200 usジッターします。

2.2 広告間隔とデューティサイクル

間隔 イベント/時間 無線アクティブ/時間 デューティサイクル 平均無線電流
100 ms 36,000 22.0 s 0.0061% 28.0 uA
200 ms 18,000 11.0 s 0.0030% 14.0 uA
500 ms 7,200 4.4 s 0.0012% 5.6 uA
1000 ms 3,600 2.2 s 0.0006% 2.8 uA
2000 ms 1,800 1.1 s 0.0003% 1.4 uA
5000 ms 720 0.44 s 0.0001% 0.56 uA

0 dBmのTX電流はnRF52832で4.6 mAです。100 ms間隔では無線だけで平均28 uAを消費し、2年のCR2032寿命を目指す場合のスリープ予算3 uAを既に超えています。100 ms間隔はUSB給電ビーコンまたはAA電池パック(2500+ mAh)のユニットでのみ実現可能です。

2.3 非接続広告と接続可能広告:レイテンシの罠

非接続広告(ADV_NONCONN_IND)はファイアアンドフォーゲットです。接続可能広告(ADV_IND)は各送信後に設定可能なリッスンウィンドウの間無線をRxモードに維持する必要があります。スキャンレスポンスを有効にすると、無線はチャネルあたり最大10 ms Rxモードに留まり、1間隔あたり30 msの5.4 mA Rx電流です。

モード 追加Rx時間/間隔 1s追加電流 5s追加電流
ADV_NONCONN(スキャンレスポンスなし) 0 ms 0 uA 0 uA
ADV_IND + スキャンレスポンス(10ms) 30 ms 162 uA 32.4 uA
ADV_IND + 接続(1s間隔) 継続 ~800 uA ~800 uA

スキャンレスポンスデータを広告する場合、1s間隔での162 uAオーバーヘッドはスリープ電流の47倍です。多くの量産ビーコンがスキャンレスポンスを完全に廃止し、すべてを31バイトのメーカー固有データペイロードに詰め込む理由です。

3. 電力状態マシン:ビーコンファームウェアの心臓

よく設計されたビーコンファームウェアには、明示的で文書化された電力状態マシンがあります。各状態には定義された消費電流、入出力条件、最大滞在時間があります。以下は大手OEMでの量産ビーコンファームウェアで使用される状態マシンです:

3.1 電力状態定義

ビーコン電力状態マシン

  [DEEP SLEEP] --RTC割込--> [ADV EVENT (Active)]
  1.5 uA                    5.2 mA
      |                          |
      | ボタン割込                | センサー保留?
      v                          v
  [BUTTON HANDLER]          [SENSOR READ]
  3.0 mA                    1.2 mA
      |                          |
      | 500msタイムアウト         | 2ms読取
      v                          v
  [CONFIG MODE]             [DEEP SLEEP]
  8.5 mA                    1.5 uA

  障害パス: WDTタイムアウト -> RESET -> INIT -> SLEEP

3.2 状態遷移予算

状態 電流 典型時間 エネルギー/イベント イベント/日 日次エネルギー
ディープスリープ(RAM保持) 1.5 uA 999.4 ms 1.499 uJ 86,400 129.5 mJ
広告イベント 5.2 mA 610 us 9.52 uJ 86,400 822.5 mJ
センサー読取(LIS2DH12) 1.2 mA 2 ms 7.2 uJ 2,880(30s) 20.7 mJ
ボタンスキャン(GPIO) 3.0 mA 0.1 ms 0.9 uJ 86,400 77.8 mJ
設定モード(GATT) 8.5 mA ~5 s 127.5 mJ ~2 255 mJ
RTC+イベント処理 3.2 mA 50 us 0.48 uJ 86,400 41.5 mJ
日次合計エネルギー 1,347 mJ

CR2032エネルギー:220 mAh x 3.0V = 2,376 J。使用可能(80%):約1,520 J。日次予算:1,347 mJ -> 1,520,000 / 1,347 = 1,128日 = 3.1年。1s間隔、30s加速度センサー、週次設定接続のビーコンのフィールドデータと一致します。

3.3 System OFF vs System ONの決定

nRF52は2つの低電力モードを提供します。間違った選択はビーコン設計で最も一般的なファームウェアのミスです。

パラメータ System ON (WFE) System OFF
電流 1.5 uA(RAM保持) 0.4 uA(RAM保持なし)
ウェイクアップレイテンシ ~2 us ~300 us(リセット+init)
RAM保持 あり なし
RTC動作 あり なし(GPIOセンス使用)
SoftDevice状態 保持 破棄(再init必要)

SoftDeviceを使用するビーコンではSystem ONが必須です。SoftDeviceは次の広告イベントをスケジュールするためにRTCを必要とします。System OFFは出荷/保管モードでのみ有用です。

4. タイマーアーキテクチャ:RTC vs SysTick vs アプリケーションタイマー

ビーコンファームウェアは通常3層のタイミングを使用し、それぞれ異なる精度と電力への影響があります:

ハードウェア クロック源 精度 電力 分解能
BLEスタック RTC1 32.768 kHz LFXO +/-20 ppm 0.3 uA 30.5 us
アプリタイマー RTC2 32.768 kHz LFXO +/-20 ppm 0.1 uA 30.5 us
高分解能遅延 TIMER0/1/2 16 MHz HCLK +/-50 ppm 5.5 mA 62.5 ns

SoftDeviceはRTC1を独占します。アプリタイマーはソフトウェアコールバックキューでRTC1を共有し、最大1 RTCティック(30.5 us)のジッターが発生します。ビーコンファームウェアでは無関係ですが、正確な100 Hzサンプリングにはハードウェアタイマーが必要です。

4.2 クロックドリフトの影響

+/-20 ppmのクリスタルは、RTCが1秒あたり最大20 usドリフトすることを意味します。24時間で1.728秒です。SoftDeviceは内部補償しますが、アプリレベルのタイマーはドリフトします:

#define CRYSTAL_PPM     20
#define SECONDS_PER_DAY 86400

float drift_per_day = (float)CRYSTAL_PPM * SECONDS_PER_DAY / 1e6;  // 1.728 s/day

4.3 タイマーコアレシング:ウェイクアップ削減

app_timerライブラリはコアレシングをサポートします。2つのタイマーが同じRTCティックウィンドウで期限切れになる場合、単一のウェイクアップにマージされます。最適化戦略:タイマーイベントをバッチ化します。30秒ごとに加速度センサーをポーリングし、かつ30秒ごとに温度センサーをポーリングする(2ウェイクアップ)代わりに、同じ30秒境界に揃えます(1ウェイクアップで両方を読む)。センサー読取エネルギー予算を半減します。

5. イベント駆動アーキテクチャ:割込み -> キュー -> ハンドラ

量産ビーコンファームウェアはビジーウェイトループを使用すべきではありません。すべての操作はイベント駆動であるべきです:

typedef enum {
    EVT_ADV_COMPLETE = 0,
    EVT_SENSOR_TIMER,
    EVT_BUTTON_PRESS,
    EVT_BATTERY_LOW,
    EVT_GATT_CONNECT,
    EVT_OTA_START,
    EVT_WATCHDOG,
    EVT_FAULT,
} beacon_event_t;

#define EVENT_QUEUE_SIZE 16
static event_t event_queue[EVENT_QUEUE_SIZE];
static volatile uint8_t eq_head = 0, eq_tail = 0;

void event_post(beacon_event_t type, uint32_t data) {
    uint8_t next = (eq_head + 1) % EVENT_QUEUE_SIZE;
    if (next == eq_tail) { fault_set(FAULT_EVENT_QUEUE_OVERFLOW); return; }
    event_queue[eq_head].type = type;
    event_queue[eq_head].data = data;
    event_queue[eq_head].timestamp = rtc_get_ticks();
    eq_head = next;
}

int main(void) {
    beacon_init();
    advertising_start();
    event_t evt;
    while (1) {
        if (event_get(&evt)) event_handler(&evt);
        else __WFE();
    }
}

設計原則:(1) ISRはイベントをポストするのみ。(2) メインループはFIFO順で処理。(3) 保留イベントがない場合、CPUはWFEに入り1.5 uAに低下。(4) キューオーバーフローはフォールトフラグを設定するがブロックしない。

6. ウォッチドッグと障害復旧

トリガー 応答 復旧時間
L1: ソフト障害 キュー溢れ、センサータイムアウト 記録、スキップ、継続 0 ms
L2: ハード障害 無線停止(60s)、SPIロック ペリフェラルリセット 50-200 ms
L3: システム障害 WDTタイムアウト(8s) フルリセット 500ms-2s
#define WDT_TIMEOUT_MS  8000

void wdt_init(void) {
    NRF_WDT->CONFIG = (WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos);
    NRF_WDT->CRV = 32768 * (WDT_TIMEOUT_MS / 1000);
    NRF_WDT->RREN = (1 << 0) | (1 << 1);
    NRF_WDT->TASKS_START = 1;
}
void wdt_feed(void) { NRF_WDT->RR[0] = 0x6E524635; }

6.3 ブラウンバウト復旧

CR2032の内部抵抗は新品5-15 ohm、寿命末期30+ ohmです。4.6 mA送信時、4.6 mA x 10 ohm = 46 mV低下します。2.3V電池では2.254Vに低下します。

電池電圧 内部抵抗 Vdrop Vmin BORリスク
3.0V 5 ohm 23 mV 2.977V なし
2.6V 10 ohm 46 mV 2.554V なし
2.3V 15 ohm 69 mV 2.231V
2.1V 30 ohm 138 mV 1.962V

7. メモリレイアウトと永続ストレージ

領域 サイズ 内容
SoftDevice (MBR + S132) 160 KB MBR + BLEスタック
アプリケーション 308 KB メインファームウェア
設定ページ(NVS) 16 KB 広告間隔、TX電力、UUID
アプリデータ(NVS) 16 KB センサーログ、障害履歴
ブートローダ 16 KB DFU + CRC

nRF52832はデュアルバンクOTAに十分なフラッシュがありません。安全なロールバックが必要な場合は1 MBのnRF52840が必要です。

8. BLEスタック統合

パラメータ 根拠
広告タイプ ADV_NONCONN_IND 通常運用時は接続不可
間隔 1000 ms 発見性と電池寿命のバランス
TX電力 0 dBm ~30m視距
チャネルマップ 37+38+39 全チャネル使用

設定モードでは100 ms接続可能間隔で~300 uAに跳ね上がります。5分タイムアウトで電池ドレインを制限:300 uA x 5分 = 25 uAh、CR2032の0.02%未満/セッション。

9. OTAファームウェア更新

パラメータ 備考
MTU 247バイト ネゴシエーション
接続間隔 15 ms スループットと電力のバランス
ファームウェアサイズ ~120 KB アプリ領域のみ
転送時間 ~8秒 508 x 15ms
OTA中電流 ~8.5 mA 接続+フラッシュ書込
電池影響 ~28 uAh 0.017%/更新

10. 量産指標とフィールドデータ

指標 目標 フィールド結果(n=2,847)
リセット間平均時間 > 6ヶ月 11.3ヶ月
WDTリセット < 1% 0.3%
OTA成功率 > 99% 99.7%
電池寿命(1s, 23C) > 24ヶ月 28.4ヶ月
電池寿命(1s, 0C) > 18ヶ月 19.7ヶ月

11. 一般的な落とし穴と修正

落とし穴 症状 修正
フローティングGPIO +8-15 uA プルダウンor出力Low
SPIバス解放忘れ +0.5 mA 各トランザクション後に無効化
SAADC有効残留 +0.2 uA uninit()呼出
過密フラッシュログ 早期摩耗 RAM蓄積5分毎フラッシュ
ISR内ブロッキング BLEタイミング違反 イベントポスト
スタックオーバーフロー ランダムフォールト 静的バッファ+MPUガード

13. まとめ

ビーコンファームウェアは本質的に、ほとんど何もしないことをほぼ常に続け、決して故障しないことに関するものです。スリープモードで節約する1マイクロアンペアごとに、フィールド寿命の数ヶ月が追加されます。カバーする障害パスごとに、ビーコン自体のコストを超えるサポートチケットを防ぎます。アーキテクチャを正しく設計すれば、Bluetooth Beaconは電池保証期間を超えて稼働し、フィールド返品ゼロを達成できます。