3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

電子辞書 SHARP Brain PW-SH6 を Nerves/Elixir で KIOSK 端末にした

3
Last updated at Posted at 2026-09-16

電子辞書 SHARP Brain PW-SH6 を Nerves/Elixir で KIOSK 端末にした(有線/WiFi/BLE、USB・BT オーディオ、RTC 同期まで)

はじめに

注意: この記事は開発途中の試行錯誤の記録です。手順・数値は 2026-09 時点の私の環境での実測で、
再現性や動作を保証するものではありません。電子辞書の改造は自己責任で、失敗すると起動しなくなる可能性が
あります(本体の eMMC には触らず、microSD からの起動のみで作業しています)。

中古で手に入る SHARP の電子辞書 Brain PW-SH6(2017 年頃のモデル)は、中身が NXP i.MX283(ARM926EJ-S、454MHz、RAM 128MiB) の小さな Linux 機として遊べることが brain-hackers コミュニティの成果(Brainux)で知られています。

この記事は、その Brain を Nerves(Elixir の組み込み Linux フレームワーク)で動く KIOSK 端末にした約 2 週間の記録です。最終的にこうなりました。

  • 5.5 インチ 854×480 の LCD に LovyanGFX(NIF)で日本語 UI、タッチ・キーボード・電池・時計表示
  • USB ホスト化して 有線 LAN + WiFi + BLE を起動時に自動接続(3 系統同時)
  • BLE で SwitchBot 温湿度計/気圧/開閉/人感 を受信してライブ表示
  • USB オーディオでタッチ音・起動チャイム・時報と A〜Z の読み上げ(Open JTalk で事前合成)
  • Bluetooth スピーカー(A2DP)へ BlueZ + BlueALSA で出力
  • 本体内蔵 RTC と Linux 時計の双方向同期(電池が切れても LAN に繋げば Brain の時計が直る)
  • SD カードを焼くだけで全機能が上がるマスタイメージ、機種(PW-SH1/SH6…)は DTB から自動認識

ホーム画面(実機の /dev/fb0 をダンプ):
kiosk_home_ja.png

公開しているもの:

KIOSK アプリ本体(Elixir ソース)は現時点では公開していません。本文のコードはそこからの抜粋です。

ARMv5・soft-float・128MiB という、Nerves 公式ターゲット(RPi 系は ARMv6 以上)より一段低いところで動かしているので、同じような「非公式ボードで Nerves」を考えている人の参考になれば。

1. ハードウェアと制約

項目 内容
SoC NXP i.MX283、ARM926EJ-S(ARMv5TEJ)454MHz、FPU なし(soft-float)
RAM / ストレージ 128MiB / 内蔵 eMMC(触らない)+ microSD から起動
表示 5.5" 854×480、ILI9805 系パネル、/dev/fb0 RGB565
入力 抵抗膜タッチ + フルキーボード(evdev)
USB micro-B(OTG)。USB ホスト化には OTG ケーブル + セルフパワーハブが必要(バス給電では不安定)
音 内蔵スピーカーあり。ただし Linux から鳴らす経路は未解明(後述)
時計 i.MX28 内蔵 RTC(バックアップ電池なし → 主電池が切れるとリセット)
起動 Brain 純正ファームから 機種名の実行ファイル(例 edsh6exe.bin)= U-Boot を起こし、SD の zImage + 機種別 DTB を読む(brain-hackers の仕組み)

Nerves 公式は最小でも RPi Zero(ARMv6、512MiB)。Brain は ARMv5 で半分以下の RAM なので、以下は諦めています。

  • 実機上での Erlang/Elixir コンパイル、mix 実行 → 全部母艦(x86_64)でクロスビルドし beam を送る
  • 実機上での TTS・音声認識・重い画像処理 → 音声は事前合成、描画は C++(NIF)
  • fwup による OTA → SD の rootfs を直接更新する運用(SD 可搬)

1-2. 使った部品(Amazon で購入、実機で動作確認済み)

型番・ASIN は手元の購入履歴で補完してください。「確認内容」は実際に Brain 上で動いた範囲です。

分類 製品(チップ) 確認内容 備考
本体 SHARP Brain PW-SH6(中古)。PW-SH1 でも同 SD で起動確認 KIOSK 全機能 機種は DTB から自動認識
起動メディア microSD 4GB 以上(高耐久品推奨) Nerves rootfs + brain-hackers 起動チェーン 取り違えでデバッグが迷走した経験あり。ラベルを貼る
USB ホスト化 USB2.0 OTG ケーブル(micro-B オス → A メス) ホストモード成立 ケーブルによって給電挙動が変わる。安物で相性あり
USB ホスト化 セルフパワー USB2.0 ハブ(AC アダプタ付き) 有線 LAN + WiFi + BLE(または BT)+ USB オーディオの同時接続 バスパワーでは不安定(ドングル追加で WiFi が落ちた)。ハブ経由で本体充電も可
有線 LAN USB Gigabit LAN アダプタ(ASIX AX88179) DHCP・NTP・SSH、常用経路 ax88179_178a ドライバ
WiFi UWF-1(Atheros AR9271、ath9k_htc) WPA2 接続・DHCP、有線と同時稼働 2.4GHz のみ。ハブの Full-Speed 交渉で USB ハングした個体あり(ポート構成で回避)
BLE / Bluetooth エレコム LBT-UAN06C2/EC(Realtek RTL8761BU、BT 5.3) SwitchBot BLE 受信 / A2DP ソース(BlueZ+BlueALSA) 同時には使えない(hci0 排他)。両立にはもう 1 本
USB オーディオ USB サウンドアダプタ(GeneralPlus 1b3f:2008、UAC) tinyalsa で再生(タッチ音・チャイム・時報・A–Z)、マイク入力あり(未使用) S16_LE / 2ch 固定。1,000 円前後
BT スピーカー ZoeeTree S1+ A2DP でペアリング・自動再接続・再生 ストリーム開始時に約 1 秒の立ち上がり無音あり(記事本文の対策)
BT スピーカー LenoPlay Bluetooth スピーカー(IPX7、AUX 入力あり) 未検証(受信側スピーカー) AUX 経由なら USB オーディオの出力先として使える想定
センサ SwitchBot 温湿度計(dev_type 0x54)/ 温湿度計プラス系(0x69)/ 防水温湿度計(0x77) BLE 広告受信(温度・湿度・電池・RSSI) 最大 3 台表示
センサ SwitchBot 開閉センサ(0x64)/ 人感センサ(0x73) BLE 広告受信(開閉・人感・明るさ・電池)
(参考) 気圧計 piyo2 BLE 受信 自作機(Amazon 品ではない)

Amazon 以外に用意したもの: 母艦(x86_64 Linux)、SD カードリーダ。ロジアナ・オシロは使っていません(使えていれば内蔵音声の調査はもう少し進んだはず)。

2. 全体構成

母艦 (AlmaLinux 10, x86_64)
  ├─ nerves_system_brain  … Buildroot 2026.05.2 + nerves_system_br v1.34.3、Bootlin armv5-eabi glibc ツールチェーン
  │     rootfs_overlay/ (erlinit.config, 起動スクリプト, ファームウェア, dbus/bluez 設定)
  │     nerves_defconfig  (wpa_supplicant, iw, tinyalsa, dbus, bluez5_utils, bluez-alsa, alsa-utils)
  ├─ hello_kiosk_brain    … Elixir アプリ(OTP 29 / Elixir 1.20)+ LovyanGFX NIF(C++)
  └─ カーネル/U-Boot/DTB  … brain-hackers の buildbrain 成果物(Linux 6.1.70)を流用、BT/RTL 有効の再ビルド版

SD カード
  p1 (FAT)  zImage, imx28-pwsh1..7.dtb, edsh6exe.bin(U-Boot) など brain-hackers の起動チェーン
  p2 (ext4) Nerves rootfs(erlinit → /usr/bin/enable_net → Erlang VM)

ポイントは erlinit の --pre-run-exec に自前の起動スクリプト enable_net を挿し、Erlang が上がる前に RTC→時計・有線 LAN・WiFi・(設定があれば)BT スタックをバックグラウンドで立ち上げていること。Nerves の scrub-target.sh は /etc/init.d を消すので、init スクリプトの類は全部ここに集約しています。

3. 画面: LovyanGFX を NIF で

Elixir から /dev/fb0 に直接描くと ARMv5 では日本語フォント描画が重すぎるので、LovyanGFX(ESP32 でおなじみの描画ライブラリ、Linux fb バックエンドあり)を C++ の NIF にして、Elixir 側は「描画コマンド列」を 1 回の render/1 で渡す方式にしました。

[
  Draw.rect(0, 0, @w, 40, @panel),
  Draw.text(12, 20, :ml, :jp24, @fg, model_name()),        # "PW-SH6"
  Draw.text(@title_x, 20, :ml, :jp24, @accent, title),
  Draw.text(div(@w, 2), 12, :mc, :jp16, ccol, clock),
  ...
] |> Native.render()

ヘッダは毎秒描き直していますが、コマンド列を組み立てるのは Elixir、ラスタライズは NIF なので 128MiB/454MHz でも余裕です。「デモ」タブのスプライト 50 個アニメーションは NIF 側スレッドで 11~21fps。
kiosk_demo_ja.png

4. ネットワーク: USB ホスト化して有線 + WiFi + BLE

Brain の USB は OTG。OTG ケーブル + セルフパワーハブに、有線 LAN(AX88179)・WiFi(UWF-1 = AR9271/ath9k_htc)・BLE(ELECOM LBT-UAN06C2 = RTL8761BU)を挿しています。

  • 有線: udhcpc + 自前 wired.script(busybox の default.script は route applet が無くてハングした)
  • WiFi: wpa_supplicant(nl80211)+ udhcpc。/etc/wpa_supplicant.conf の実 PSK は git に入れず、Nerves の scrub 後に post-build で母艦ローカルから入れる
  • 経路: 有線 metric 0 / WiFi metric 20 で両方張り、NTP は ntpd -q

BLE は BlueZ を使わず、Erlang の :socket で AF_BLUETOOTH の HCI USER チャネルを直接開いて HCI コマンドを叩いています。

{:ok, sock} = :socket.open(31, :raw, 1)                 # AF_BLUETOOTH, BTPROTO_HCI
:socket.bind(sock, %{family: 31, addr: <<0::16, 1::16>>}) # hci0, HCI_CHANNEL_USER
hci(sock, 0x03, 0x0c, <<>>)             # Reset
hci(sock, 0x01, 0x0c, all_ones)         # Set Event Mask     ← これを忘れると広告が一切来ない
hci(sock, 0x01, 0x20, all_ones)         # LE Set Event Mask
hci(sock, 0x0b, 0x20, <<1, 0x10, 0, 0x10, 0, 0, 0>>)  # アクティブスキャン
hci(sock, 0x0c, 0x20, <<1, 0>>)         # Scan Enable

SwitchBot の広告(Service Data 0xFD3D + Manufacturer 0x0969)をデコードして、温湿度計 3 台・気圧計・開閉・人感をカードで表示:
kiosk_ble_ja.png

5. 音: 内蔵は諦めて USB、その先に Bluetooth

5-1. 内蔵スピーカーは鳴らせなかった

DTS には SGTL5000 コーデックがあることになっていますが実機の I²C にはおらず(EVK 由来の宣言)、代わりに 0x1a に正体不明の I²C デバイスがいます。PWM ブザーも、ドライバ(pwm-beeper)まで完璧に動くのに基板配線が届いていないらしく無音。ロジアナも回路図も無いので、ここは保留にして USB オーディオに切り替えました(調査記録はリポジトリの docs/ に)。

5-2. USB オーディオ + tinyalsa

GeneralPlus の 1,000 円くらいの USB サウンドアダプタ(UAC、S16_LE/2ch 固定)を tinyalsa(tinyplay)で鳴らします。alsa-lib より小さく設定ファイル不要。モノラル WAV は 2ch 固定のアダプタで開けないので、Elixir 側で L/R 複製してステレオ化しています。

音声は実機で TTS を回さず、Open JTalk(Mei ボイス)で母艦で事前合成した WAV を連結再生:

  • 時報: 「ただいま、N時」「M分です」の 84 本を組み合わせ
  • A〜Z: 押したキーを「えー」「びー」…と読む 26 本
    kiosk_voice_ja.png

5-3. Bluetooth スピーカー(A2DP)へ

買ってきた BT スピーカーが受信専用(当然)だったので、BLE 用の RTL8761BU ドングルで BlueZ 5.79 + BlueALSA 4.3.1 の A2DP ソースを立てました。Buildroot に dbus, bluez5_utils, bluez-alsa, alsa-utils を追加し、erlinit 配下で

dbus-daemon --config-file=/etc/dbus-1/brain-system.conf   # root で動く最小 system bus
bluetoothd -n --noplugin=sap,hostname,autopair             # main.conf: AutoEnable, bredr
bluealsa -p a2dp-source --keep-alive=120

を起動。ペアリング〜接続は Elixir から bluetoothctl を非対話で叩き(--timeout 8 scan on → pair → trust → connect)、bluealsa-cli list-pcms に A2DP PCM が出たら再生可、と判定します。再生は aplay -D bluealsa:DEV=<MAC>,PROFILE=a2dp。

ここで一番ハマったのがタッチ音(70ms)です。A2DP はストリーム開始時にスピーカー側で約 1 秒の立ち上がり無音があり、1 音ごとに aplay を起動する方式では短い音が丸ごと消えます(0.5 秒間隔で 8 連打 → 6 回しか聞こえない)。解決策は aplay を常駐ポートにして「ほぼ無音」を流し続け、効果音をその流れに混ぜること:

# 80ms ごとに、壁時計 + 260ms 先まで PCM を書く。待ち行列に音があればそれ、無ければ keep-alive 信号
def handle_info(:bt_tick, %{bt: bt, lead_ms: lead} = state) do
  need_ms = now_ms() - bt.t0 + lead - bt.written_ms
  ...
  {chunk, rest} = take_pcm(bt.pending, w_ms * @bt_bytes_per_ms)
  Port.command(bt.port, chunk)

「ほぼ無音」はデジタル 0 ではなく 25Hz・-49dBFS の正弦波 + ±2LSB ディザ(スピーカーの無音検出ミュートを外す)。デモ画面(NIF が CPU を占有)中は先行量を 1 秒に増やしてアンダーランを防ぎ、aplay には --start-delay を明示(既定の「バッファ満杯まで待つ」だと 1.3 秒の遅延が常時乗る)。CPU は常時 25% ほど食いますが、タッチから 0.5 秒程度で確実に鳴るようになりました。

同じドングルで BLE 受信と BT 音声は排他(bluetoothd が hci0 を掴むと USER チャネルは取れない)なので、機体ごとに /root/bt_speaker.conf の有無でモードを切り替えています。両立したければドングル 2 本。

6. 時計: Brain の RTC は「2000 年起点の JST」

Linux 側は起動すると 1970 年で、NTP が来るまで時刻が無い。一方 Brain 純正ファームの時計は主電池が持つ限り保持される……ので調べたら、i.MX28 内蔵 RTC(HW_RTC_SECONDS @0x80056030)を、純正ファームが「2000-01-01 起点の秒・ローカル時刻(JST)」として使っていることが分かりました。Linux の rtc-stmp3xxx ドライバは 1970 起点 UTC 前提なので、brain-hackers の DTS では RTC ノードが disabled になっています(そのまま有効化すると 30 年ずれる)。

ドライバは触らず、ユーザ空間でレジスタを直接読み書き:

  • 起動 5 秒(enable_net): devmem 0x80056030 → date -s(オフラインでも即・正時刻)
  • Erlang(BrainClock): NTP 後に Linux → RTC を書き戻す(NEW_REGS が 0 のときだけ、PERSISTENT レジスタは触らない)

これで Brain 純正ファームに戻しても時計が合っている状態になりました(電池が切れても LAN に繋げば直る)。

7. ハマったこと(抜粋)

事象 原因 対処
起動後 30 秒以上タッチが効かない :ssh の user_passwords が PBKDF2 を回し、ARMv5 で 35 秒 CPU を占有 pwdfun に置き換え
busybox で chmod/head/awk/route/wc が無い Nerves のシェルは最小 applet 絶対パス・File.chmod!・自前 udhcpc スクリプト
BLE 広告が 1 件も来ない Set Event Mask / LE Set Event Mask を送っていなかった 上記 §4
BLE ドングルを挿すと WiFi が切れる OTG ケーブル/ハブの給電 セルフパワーハブ + 別ケーブル
UWF-1(AR9271)が USB ハング ハブ経由で Full-Speed 接続になり HS 前提のディスクリプタと不整合 ポート・ハブ構成を変更
tinyalsa でモノラル WAV が無音 UAC アダプタが 2ch 固定 再生時にステレオ化
dbus-daemon が起動しない 設定 XML のコメント内に --(--config-file と書いていた) コメント修正。ソケットも /run/dbus(Nerves は /var/run→/run の symlink なし)
buildroot が host-libxcrypt で止まる 母艦 perl に core の open.pm が無い(最小インストール) dnf install perl-open
実機で 500KB のバイナリを Elixir の binary comprehension で舐めたら固まった eval_bits がインタプリタ実行 母艦へ持ち帰って strings/cmp
電源 OFF ボタンで再起動してしまう 5V 給電中は i.MX28 のシリコン仕様で電源断維持不可 仕様として案内表示
バックライトを消したい 制御線が LCD 電源と一体、GPIO を触るとパネルが白飛び 諦め(純正は 5 段階調光できるので経路はあるはず。調査中)

8. 開発サイクル

SD を抜き差しせずに回せるのが快適でした。

MIX_ENV=prod mix compile                       # 母艦でクロス(beam は機種非依存)
sftp user@192.168.1.42 <<< "put _build/prod/lib/hello_kiosk_brain/ebin/Elixir.HelloKioskBrain.Kiosk.beam /srv/erlang/lib/hello_kiosk_brain-0.1.0/ebin/"
ssh user@192.168.1.42 ':code.purge(HelloKioskBrain.Kiosk); :code.load_file(HelloKioskBrain.Kiosk)'
./scripts/screenshot.sh                        # /dev/fb0 をダンプして PNG に

SSH は「Elixir 式をそのまま評価する」exec 専用にしていて、ssh user@brain 'HelloKioskBrain.BrainClock.read_rtc()' のように使えます(対話シェルは持たない。日本語や改行を式に含めるとパーサに蹴られるので ASCII で)。

システム側の変更(Buildroot)は rootfs_overlay/ と nerves_defconfig に集約し、素の buildroot ビルドから同じ rootfs が再現できることを確認しています。稼働中の機体には sftp で beam・スクリプト・ライブラリを送って /sbin/reboot(erlinit は --run-on-exit /bin/sh なので VM 停止では再起動しない)。

9. まとめ

  • ARMv5・128MiB でも Nerves は動く。ただし「実機で重いことはしない」(クロスビルド、事前合成、描画は NIF)を徹底
  • 起動時処理は erlinit --pre-run-exec の自前スクリプトに集約し、必ずバックグラウンド化
  • USB ホスト化で有線/WiFi/BLE、USB オーディオ、BT スピーカーと「外付け」で機能を足していける
  • 純正ファームの流儀(RTC の起点、機種別起動ファイル)を読み解くと、Nerves 側から素直に共存できる
  • できなかったこと(内蔵音声・ブザー・バックライト)は「なぜできないか」まで docs/ に残した

動かしてみたい方は nerves_system_brain の examples/hello_kiosk から。同じ Brain を持っている方、あるいは別の「Nerves 非対応ボード」に挑む方の参考になれば嬉しいです。

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?