はじめに――4行しかないからこそ、設計が必要だった
4行LCDと数個の物理ボタン。画面だけを見ると、とても単純なUIに見えます。
しかし、実際に製品として作り込んでいくと、画面の切り替え、フォーカスの移動、数値の編集、確定と取消、専用ハードウェアへの反映など、考えることが次々に増えていきました。
私がこの設計に取り組むうえで、出発点になったものが二つあります。
一つは、先輩エンジニアから繰り返し教わった「楽するために苦労する」という考え方です。目の前の一画面を作るだけなら、文字列や座標を直接書いた方が早く見えます。しかし、その方法を画面ごと、機種ごとに繰り返せば、後から必ず苦しくなります。
もう一つは、その先輩から受け継いだGTK風のWidget/Event設計です。いわば現場の「秘伝のたれ」のようなもので、私はそれを実際の製品へ適用しながら、必要なWidgetや物理ボタンの処理、専用ハードウェアとの境界を少しずつ継ぎ足してきました。
そして最後にものを言ったのは、設計図だけではなく実機でした。
ボタンを長押ししたとき、数値をどの速さで変化させるのか。変更値をいつハードウェアへ反映するのか。LCDの描画が追いつかないとき、どうすれば操作を不自然に感じさせないのか。装置を何度も触りながら、細かなバランスを調整しました。
本シリーズでは、こうして育ててきた4行LCD向けUI基盤を題材に、制約のある組み込み機器でUIをどう設計するかを整理していきます。
最終的に目指すのは、単に文字列を4行表示するだけの画面ではありません。物理ボタンでフォーカスを移動し、値を編集し、確定・取消までを自然に操作できる画面です。まずは、このシリーズで作る画面のイメージを示します。
このシリーズで扱う設計要素
- 4行LCDでも、画面をWidgetとイベントで構成する理由
- 物理ボタン、UI、LCD、専用ハードウェアを一体で考える必要性
- GTK風の考え方を、小さな組み込み環境へ持ち込むときの割り切り
- 後続記事で扱う設計の全体像
先に結論
4行LCD向けUIの設計で重要だったのは、画面を綺麗に描くことだけではありませんでした。
入力、画面状態、描画、装置制御を分けながら、ユーザーからは一つの自然な操作に見えるよう組み合わせることが重要でした。
そのために、次の三つを設計の柱にしました。
- 画面をWidgetの集合として扱う
- 物理ボタンと画面遷移を一緒に設計する
- UIイベントとLCD/専用ハードウェアの制御を分離する
この話の出発点
先輩から教わった言葉については、以前の記事にも残しています。
苦労して作ってはダメだ。楽して作りなさい。そして、楽するために苦労しろ。
この言葉は、単にコードを短くするという意味ではありません。
同じような画面処理をコピーし続ければ、その瞬間は早く作れます。しかし、仕様変更や機種展開が始まると、修正箇所を探し、同じ変更を何度も反映し、画面ごとの差異を確認する作業が増えていきます。
そこで、最初に共通部分を見つけ、再利用できる仕組みを作るために苦労します。後の画面追加や変更を楽にするための、先回りした苦労です。
4行LCD向けのUIライブラリは、この考え方を実際の製品設計へ適用した一例でした。
LCDへ文字を出すだけでは終わらなかった
画面が1枚だけなら、座標と文字列を直接指定する実装でも十分です。
lcd_write(0, 0, "Device Settings");
lcd_write(0, 1, "> Apply");
lcd_write(0, 2, " Port: P1");
ところが画面が増えると、次の処理が各画面へ散らばり始めます。
- 現在どの項目を選択しているか
- 上下左右キーで次にどこへ移動するか
- Enterが編集開始なのか、確定なのか、Buttonの実行なのか
- Cancelで画面だけを戻すのか、装置の状態も戻すのか
- 変更した行だけをどう再描画するか
- 無効または非表示の項目をどう読み飛ばすか
さらに組み込み機器では、画面の先に実際のハードウェアがあります。表示上の数値を変えれば、ASICやFPGA、別CPUなどへ設定を伝える必要があります。
ここまで来ると、問題はLCDドライバの使い方ではありません。小さな画面を持つ装置全体のUIを、どう分割して組み立てるかという設計問題になります。
全体アーキテクチャ
このシリーズで扱うUI基盤は、次のような役割へ分けます。
| 層 | 役割 |
|---|---|
| キー入力 | GPIOやキーマトリクスを読み、確定したキーイベントへ変換する |
| イベント配送 | 選択中のWidgetへキーを渡し、未処理ならフォーカスを移動する |
| Page/Widget | 画面、表示部品、選択状態、編集中の値を管理する |
| 表示ポート | Widgetの描画要求を、実際のLCDまたはモックへ渡す |
| 装置制御ポート | UI上の操作を、装置が理解する機能へ変換する |
| ハードウェアアダプタ | レジスタ、通信、デバイス固有の処理を担当する |
境界を置く目的は、層を増やして立派に見せることではありません。
- LCDが変わったとき、表示側の変更へ寄せる
- ASICやFPGAが変わったとき、装置制御側の変更へ寄せる
- 物理キーが変わったとき、画面のWidgetまでGPIO事情へ巻き込まない
- 実機がなくても、PC上のモックで画面遷移を確認する
変更理由の違うコードを離しておくことで、製品ごとの差分を追いやすくします。
Widgetは、画面の部品と振る舞いをまとめる
ここでいう Widget(ウィジェット) とは、ラベル、ボタン、選択項目などを、表示内容、状態、入力時の振る舞いとひとまとまりにして扱うUI部品です。
この基盤では、たとえば次のWidgetを使います。
-
GuiLabel: 表示するだけの文字列 -
GuiButton: Enterで処理を実行する部品 -
GuiCombo: 複数の候補から値を選ぶ部品 -
GuiSpin: 数値を増減する部品 -
GuiPage: 複数のWidgetをまとめる画面
画面ごとにキー処理を書くのではなく、「ButtonへEnterが届いた」「Spinの候補値が変わった」のように、Widgetごとのイベントとして扱います。
GTKそのものを移植したわけではありません。受け継いだのは、共通のWidgetを持ち、派生型ごとに振る舞いを差し替え、イベントとして処理をつなぐ考え方です。
受け継いだ設計を、製品の中で育てる
このUI基盤には、受け継いだ設計の原型と、製品開発の中で加えた工夫が混ざっています。
| 区分 | 内容 |
|---|---|
| 出発点 | 「楽するために苦労する」という考え方 |
| 受け継いだ原型 | GTK風のWidget、Event、継承風設計 |
| 製品に合わせた拡張 | Widgetの追加、物理キーとの統合、画面ごとのフォーカス設計 |
| 機種展開への工夫 | LCD描画と専用ハードウェア制御の境界 |
| 実機での調整 | 長押し加速、値の確定タイミング、画面切り替えの間合い |
完成した仕組みをそのまま保存していたわけではありません。製品の要求へ向き合うたびに必要な要素を足し、不要なものは持ち込まず、実機で確かめながら育ててきました。
物理ボタンもUIの一部
4行LCDのUIは、画面だけを見ても設計できません。
上下左右、Enter、Cancel、Home、ファンクションキー、専用ショートカットなど、どの物理キーが存在するかによって、自然な画面配置と移動規則が変わるからです。
たとえば、左右に並んだ二つの項目を上下キーだけで移動させると、画面上の位置と指の操作が一致しません。一方、値を編集中の上下キーは、フォーカス移動ではなく値の増減として使いたくなります。
そこで、キーを単なるGPIO入力としてではなく、操作面と画面を合わせた一つのUIとして設計します。第4回では、Widget同士の移動先を一列の順番ではなく、上下左右を持つフォーカスグラフとして表します。
UIイベントと装置制御を直結しない
Spinの値が変わったとき、イベントハンドラからFPGAのレジスタを直接書けば、最初の1機種は短く実装できます。
しかし画面コードがレジスタアドレス、ビット配置、通信方式まで知ってしまうと、制御デバイスが変わるたびにUIも修正対象になります。
そこで画面側は、「レベルを一段階上げる」「候補値をプレビューする」「設定値を確定する」といった、装置機能の言葉だけを使います。レジスタや通信への変換は、その下のアダプタへ閉じ込めます。
これはWebアプリやPC向けGUIでは見慣れた責務分離に近いものです。ただし、小さな組み込み機器へ大きなフレームワークを持ち込むわけではありません。機種差分や試験の境界として必要な部分だけを置きます。
リアルタイム制御ではなく、リアルタイムに感じられる操作
このシリーズでいう「リアルタイム」は、RTOSやハードリアルタイム制御の期限保証ではありません。
ユーザーの指、UI内部、専用ハードウェア、LCDは、それぞれ異なる速度で動きます。設計上の課題は、すべてを同じ速度で動かすことではなく、ユーザーから見て一貫した操作に感じられるよう整えることです。
実機では、分解能の細かい数値をSpinで長押しすると、LCD上の値とハードウェアの反映が一時的にずれることがありました。そこで、押し始めは1送り、長押し後半は5送り、10送りと段階的に変え、キーを離したときに最後の候補値を確定する仕組みを取り入れました。
LCDの画面切り替えでも、旧画面の一部が残って見えないよう、ユーザー体験を壊さない範囲で間合いを調整しました。
こうした部分は、構造体やクラス図だけでは決まりません。装置を繰り返し触り、表示と実動作の関係が自然に見える境界を探る作業でした。
本シリーズで扱うこと
1本へすべて詰め込まず、次の6回に分けます。
| 回 | テーマ |
|---|---|
| 第1回(本記事) | 4行LCD、物理ボタン、専用ハードウェアを含むUI設計の全体像 |
| 第2回 | C言語の先頭埋め込みによるWidget/クラス階層 |
| 第3回 | mallocなしの静的構築、イベント配送、差分描画 |
| 第4回 | 物理ボタンとフォーカスグラフ、外字 |
| 第5回 | Change/Submit/Cancelと、UI・ハードウェア境界 |
| 第6回 | 長押し、ハードウェア応答、LCD描画の速度差と操作感 |
実装の細部だけでなく、なぜその境界を置いたのか、どこを簡略化したのか、実機で何に苦労したのかも扱います。
シリーズで扱った構成を小さく動かして確認したい場合は、番外編:4行LCD向けの最小GUIライブラリをGitHubで公開しましたも参照してください。
公開用サンプルについて
本文の型名、コード、画面例は、設計経験を説明するために新しく書き起こした公開用サンプルです。実製品のソースコード、画面仕様、レジスタ定義、通信コマンドは使用していません。
また、ここで示すC言語版は説明用にmallocを使わない構成としています。当時扱ったすべての実装が動的確保なしだった、という意味ではありません。
まとめ
4行LCD向けUIの難しさは、表示できる情報が少ないことだけではありません。
- 物理ボタンと画面配置を一緒に考える
- Widgetとイベントで画面ごとの重複を減らす
- LCD描画と装置制御を分ける
- 異なる処理速度を、一貫した操作感へ整える
先輩から受け継いだ設計の原型を、実際の製品開発の中で少しずつ拡張し、実機を触りながら育ててきました。本シリーズでは、その中から別の組み込み機器にも応用できそうな考え方を取り出して紹介します。
次回は、GTK風設計の中核であるWidget階層とクラス関数表を、C言語でどう表現するかを見ていきます。
関連リンク(4行LCDから考える、組み込み機器のUI設計)
- 【4行LCD GUI設計・第1回】4行LCDでもUI設計は必要だった ― 物理ボタンと専用ハードウェアをつなぐ(この記事)
- 【4行LCD GUI設計・第2回】C言語でWidgetと継承風設計を作る ― 4行LCD向けGUIの内部構造
- 【4行LCD GUI設計・第3回】mallocなしでイベント駆動GUIを組み立てる ― 4行LCDの静的構築と差分描画
- 【4行LCD GUI設計・第4回】物理ボタンから4行LCDのUIを設計する ― フォーカス遷移と外字の使い方
- 【4行LCD GUI設計・第5回】UIイベントと専用ハードウェアを分離する ― 小型LCD機器の「なんちゃってMVC」
- 【4行LCD GUI設計・第6回】リアルタイムではない装置を、リアルタイムに感じさせる ― 4行LCDの操作感を実機で詰める

