前回に引き続き「Raspberry Piで作る温度計」を作っていきます。
本テーマでは、Raspberry Piと温度センサ(BME280)、OLEDディスプレイ(SSD1306)を使って温度、湿度、気圧の表示をしてみます。
前回は温度計システムの設計を行いました。まだ見ていない方はこちらからどうぞ。
今回は、前回のシステム設計の結果をもとに、Raspberry Piのソフトウェア設計をしていきます。
それではソフトウェア設計編、いってみましょう。
Linuxのソフトウェアスタック
Raspberry Pi上で動作するソフトウェアを設計するには、Raspberry Pi及びLinuxの仕組みを理解しておく必要があります。
BME280とSSD1306と通信するにはI2Cを用いる必要がありますが、LinuxでI2Cを使うにはLinuxデバイスドライバの概念を理解しておく必要があります。
デバイスドライバとは、マイコンに具備されているI2CやUARTといった通信機能、その他HW部品の制御を担うモジュールを指しています。
HW部品の制御には部品固有の制御が必要になるため、ユーザーから扱いやすいように細かい制御を隠蔽する目的で作られます。
また、Linuxにはユーザー空間とカーネル空間というスペースが分離しており、HWにアクセスするには基本的にはカーネル空間からしかアクセスできない仕組みになっています。
これらを踏まえてソフトウェア構成を考えていきましょう。
ソフトウェア構成
まずはI2Cの制御に着目してみましょう。
linuxにはデフォルトでi2c-devというI2Cデバイスドライバが備わっていますので、今回はこちらを使います。
i2c-devはカーネル空間に存在しますので、ioctlというユーザー空間からカーネル空間へアクセスする手段を使ってi2c-devの制御を行います。
i2c-devは簡易的にI2Cを利用できる便利なモジュールではありますが、ioctl経由で専用の構造体を用いてread/write指示を行うため、手続きがシンプルなわけではありません。
今回、BME280とSSD1306ともにI2Cを使用するため、それぞれi2c-devを制御することになりますが、i2c-dev固有の制御をそれぞれの制御モジュールに実装するのは非効率ですね。
そこで、i2c-dev制御用のモジュールを挟んでやることで、BME280とSSD1306の制御モジュールから簡易的にi2c-devを使用することが可能になります。(下記右図)
この図はクラス図と呼ばれるもので、モジュールの依存関係を表現したものになります。
点線の矢印が依存関係を示しており、矢印の根本のモジュールは、矢印の先のモジュールを利用している、という意味になります。
ここで、BME280制御とSSD1306制御というモジュールについて説明します。
この2つのモジュールは、それぞれのデバイス固有の制御を行うモジュールになります。
BME280であれば、BME280の初期化や、読みだした温度情報をキャリブレーションして温度値に変換するといった機能です。
SSD1306であれば、SSD1306の初期化や、描画データの送信といった機能です。
さて、ここまではHWの制御モジュールについて検討してきましたが、改めてシステムの機能としてRaspberry Piがすべきことは何かを考えてみます。
今回のシステムでは、Raspberry Piは「描画データ作成」という機能が必要でした。(下図再掲)
安直に考えてしまうと、SSD1306制御モジュール内で描画データを作成してしまえばいいじゃないかと思われる方もいると思いますが、落とし穴があります。
SSD1306制御モジュールの中で温度計を描く機能を作ってしまうと、今後同様にSSD1306モジュールを使いたいとなった場合に、不要な温度計描画機能が存在していることになります。
気をつけるポイントとして、作ろうとしているモジュールが今回のシステムに特化しなければならないのか、または使い回せる形にした方がいいのかを検討すべきです。
基本的には、HWに近いモジュールほど、上位のアプリケーションが変わっても汎用的に使えることを意識するとよいです。
HWに近ければ近いほど、様々なモジュールから利用されることを想定すべきだからです。(下図赤枠)
以上を踏まえると、描画データを作成する機能はSSD1306制御モジュールの上位に作るとよさそうです。
BME280制御モジュールに関しては、今回のユースケースでは現在の温度を取得するだけなので上位モジュールを作らなくてもよさそうですが、作っておいた方が拡張性が高くなります。例えば、ある温度を超えた場合にアラームを発行するとか、起動してから最大・最小・平均の温度を保存しておいて参照できるような機能などです。
これらを加味したソフトウェア構成は下記のとおりです。
モジュールは全部で4層に分離されました。
1層目のシステム制御は、システムのタイミングを司るレイヤーをイメージしています。今回はシンプルにmainループの中から各機能を呼び出すという構成なので、登場人物はmainのみです。
2層目のシステム機能は、温度計システムが提供する機能を包含したレイヤーです。アプリケーションレイヤーと考えてもよさそうです。
3層目のコンポーネント制御は、温度センサのBME280、ディスプレイのSSD1306を制御するレイヤーです。
4層目はドライバ制御となります。
以上で、静的な構造の設計が終わりました。
次のセクションではこれらモジュールの動的な振る舞いを検討していきます。
ソフトウェアの振る舞い
ソフトウェア構成を踏まえて、モジュール間でどのようなやりとりが必要かを見ていきます。
まずはシステムシーケンスの温度計測~温度取得に着目したいと思います。(下図赤枠部)
温度計測に関係するモジュールは、「main」「温度管理」「BME280制御」「I2Cドライバ制御」になります。
上記モジュールに着目して、シーケンスを書くと下記のようになります。
mainからMeasureがコールされると、温度管理モジュール経由でBME280制御モジュールが計測実施コマンドを発行します。続いて、読み出しコマンドで計測したデータを読み出します。
BME280から読みだしたデータはそのままでは温度・湿度・気圧値になっておらず、読み出した後にCompensate_Valueで補償計算を実施しています。この計算式はBME280のデータシートに記載されています。
次に、画面描画のシーケンスに着目します(下図赤枠部)
画面描画においては全てのモジュールが登場します。
シーケンスを書くと下記のようになります。
先程計測した最新の温度・湿度・気圧は温度管理モジュールが保持していますので、mainから温度管理モジュール経由で値を取得します。
次に、mainからディスプレイ描画モジュールにDraw_ThermoMeterで温度計の描画データ作成を依頼します。このとき作成した描画データはディスプレイ描画モジュール内のバッファに溜め込みます。
その後、Flush_Canvasでディスプレイ描画モジュールに貯めたバッファをSSD1306に送信します。Get_Draw_CanvasにてSSD1306制御モジュールが保持している送信用バッファのアドレスを取得し、ディスプレイ描画モジュールが持っているバッファを書き込んでやります。
最後に、ディスプレイ描画モジュールからSSD1306制御のUpdate_Frameをコールして送信用バッファの転送を実施します。
以上をまとめると、次のシーケンスが完成します。
また、これらのメッセージ(関数)をソフトウェア構成に反映すると以下のようになります。
最後に
いかがでしたでしょうか。
システムレベルからソフトウェアレベルまで段々と設計が具体的になっていく様がイメージできたのではないでしょうか。
次回は、この設計を元に実装を進めていきます。
コメント