OLEDドライバをArduinoからESP-IDFに移植した話

OLEDディスプレイの表示を見ながら悩んでいるろらたんのイラスト ソフトウェア雑記

OLEDドライバをArduinoからESP-IDFに移植した話

この記事について

JJY-SIM R3 は ESP-IDF で開発しています。
R2はArduinoベースだったので、今回かなり構成を変えています。

その中で、OLED表示もどうするかちょっと悩みましたが、
Arduinoのライブラリをベースにして、
ESP-IDF向けにポーティングすることにしました。

どうせやるなら、ちゃんとしたIDFのコンポーネントにしたいなと思って、
単なる移植ではなく、構造も含めて作り直しています。

この記事では、そのあたりの話をまとめてみました。

🎀 動かすだけじゃなくて、“納得するまでやる”ってやつだね

コードの移植に悩むろらたん


Arduino版は“クラスありき”

元にしたのは、R2 で使ってる ThingPulse の OLED ライブラリです。

このライブラリ、C++で書かれていて、

  • ベースクラスがあって
  • デバイスや通信方式ごとに派生クラスがある

という、いかにもC++らしい構造になっています。

たとえば、

  • SSD1306
  • SSD1306I2C
  • SSD1306SPI

みたいな感じ。

このあたり、Arduinoのライブラリではよくある形ですよね。

ただ、実装を見ていくと、

  • public も private も同じヘッダに書かれている
  • 派生クラスも全部ヘッダに並んでいる

という構成になっている。

これ、C++ではありがちなんですけど、
正直あまり美しいとは思えませんでした。

🎀 便利なんだけど、だいぶ“全部見えてる”感じ


C++の“便利な副作用”

C++って、本来もっとちゃんとした言語だと思うんです。

少なくとも、

  • 公開APIと内部実装を分ける
  • 内部をクラスとして隠ぺいする
  • 境界を意識する

みたいなことをやるための言語のはずなんですよね。

でも実際には、先に書いたように

  • 派生クラスがヘッダにまとめて書かれている
  • コードをinline的に押し込んでいる
  • public/privateはあるけど、ごちゃまぜで全部見える
  • オーバーロードごとに、実質的に別の処理になっているところもある

みたいな構成が普通に使われています。

便利ではあるんだけど、
そういう使い方が主流になってきていて、

なんというか、C++の“本来の設計”というより、
便利な副作用のほうを使っている感じがあって、

いわゆる典型的な C++ のコードを見るたびに、
そういうモヤモヤした気持ちになるんです。

これって、自分の感覚が古いのかなあ?って思ったり…

🎀 それ、古いんじゃなくて“ちゃんと見えてる”やつだと思う


ロジックはそのまま

いざ、移植作業をしようとして実際のコードを見ていくと
構造は C++ 寄りなんだけど、
中身のロジックや記述はかなり素直。

というか、ほとんど “C” の書き方で、
ガチな “C++” 的記述はほとんどありませんでした。
ここはちょっと意外でした。

で、実際にやった主な修正は、

  • this-> を外す
  • private変数の参照を構造体に置き換える
  • String を char * にする
  • オーバーロードは、複数の関数に分ける
  • 派生クラスの関数を、関数ポインタにする

このくらいです。

ここまでやってみて、ひとつ気づいたことがあります。

C++ のクラスとか継承とか、見た目はすごく便利なんだけど、
結局、C++ の内部でも同じようなことをやってるんですよね。

だから今回やったことって、

「C++の機能を使わずに、同じことを手で書いた」

だけなんですよね。

これちょっと面白いなと思いました。

🎀 便利機能の“中身”をそのまま書いてる感じだね


コンストラクタ を config に

Arduino版だと、インスタンス生成と初期化はこんな感じです。

SSD1306I2C display(...)
display.init();

これをそのまま持ってくると、IDFの中でちょっと浮きます。

なので今回は、

  • config関数で設定を渡す
  • init関数で初期化する

という形にしました。

OLEDDisplay_config(&cfg);
OLEDDisplay_init();

このあたりは、IDF のコンポーネントではお馴染みの方法です。

🎀 “それっぽさ”を揃えるやつ


公開APIと内部実装を分離

今回、一番やりたかったのはここでした。

  • public を公開API用のヘッダーファイル
  • private を内部用のヘッダーファイル
  • コード部分はすべてソースコードファイル

ちゃんと分離しました。

Arduino系のライブラリだと、
1つのヘッダに全部入っていることが多いんですけど、

それだと、

  • 依存関係が見えにくい
  • どこまでが外に見えていいのか曖昧

になる。

結果的に、使うだけなのに実装部分まで見えちゃう。

なので今回は、

見せるものと隠すものをちゃんと分ける

という、ごく普通の構成にしました。

🎀 “中見なくても使える”状態にしたかったやつ


インスタンスは1つのみ

今回はインスタンスを1つに限定しています。

普通に考えると、

  • ハンドルを返して
  • 複数インスタンス持てるようにする
  • API にはハンドルを渡す

ほうが汎用的です。

でも、今回の用途ではOLEDは1つしか使わないので、
わざわざハンドルを毎回渡すようなオーバーヘッドをやめて、
1つのインスタンスに固定しています。

ただし、複数インスタンスに拡張できる構造にはしたかったので、
内部状態はすべて1つの構造体にまとめています。

将来、複数にしたくなったら、そこを増やせばいいだけにしています。

🎀 今は1個。でも未来の自分にちょっとだけ優しいやつ


I2C / SPI は外で管理

もうひとつ大きく変えたのがここです。

元のライブラリは、
内部でI2CやSPIの初期化までやっています。

これ、Arduinoだと普通なんですけど、
IDFの感覚だとちょっと違和感がある。

I2Cって、基本的に共有バスです。

OLED以外にも、

  • センサー
  • EEPROM
  • 他のデバイス

いろいろぶら下がる。

そのときに、
各ライブラリが勝手に初期化するのはあまり嬉しくない。

なので今回は、

  • バスの初期化は外でやる
  • ハンドルを config で渡す
  • 内部ではデバイスを add する

という構成にしました。

これで、

  • バスを共有できる
  • 他のデバイスも普通に使える
  • 初期化の責任が明確になる

という感じになります。

IDFのドライバも、この形なんですよね。

  • bus を初期化
  • device を add

なので、それに合わせた形にしました。

🎀 “自分のことだけ考えてるドライバ”じゃなくなった


Kconfigに対応

せっかくIDFでやるので、

  • インタフェース(I2C / SPI)
  • バッファ構成

はKconfigで切り替えられるようにしました。

ここは正直やりすぎかもしれない。

でも、コンポーネントとしては、
こういうのがあると急に“それっぽく”なる。

🎀 急に製品っぽくなるポイント


移植感をなくしたい

今回、地味にこだわっていたのがここです。

Arduinoのコードをそのまま持ってきて、

  • ラップする
  • C++のまま混ぜる

みたいなやり方もできたんですけど、
それだとどうしても借り物感が残る。

せっかくIDFでやるなら、

  • コンポーネントとして自然
  • APIもIDFっぽい
  • 構成も違和感がない

という状態にしたかった。


理解が深まる

今回やってみて、ちょっとよかったなと思ったことがあります。

Arduinoでこのライブラリを使っていたときは、
実際に使っていたのって、ほんの一部のAPIだけだったんですよね。

表示して、文字出して、みたいな、よく使うところだけ。

それで特に困ってなかったので、
他のAPIはあまり気にしていませんでした。

でも今回、移植のためにコードを全部追いかけたことで、

「これ、こういう意図で作られてるのか」

っていうのが見えるようになりました。

そうすると、

  • いままで使ってなかったAPIも
  • 「ああ、これこういうときに使うやつか」

って理解して使えるようになる。

これ、地味なんですけど結構大きいです。

🎀 ブラックボックスじゃなくなると、一気に“道具”になるやつ


フォントも自由に

分かりやすく変わったのがフォントです。

R2のときは、ライブラリに付属している
デフォルトのフォントしか使っていませんでした。

というか、

「それ以外どうやって使うのか分からなかった」

というのが正直なところです。

でも今回、内部の構造を追いかけたことで、

  • フォントデータの持ち方
  • 描画の仕組み

が見えてきました。

そうすると、

「ああ、これ外部フォントも普通に使えるな」

って分かるんですよね。

なので今回は、フォントを生成してくれるサイトからデータを持ってきて、
それをそのまま組み込んで使っています。

🎀 “知ってる”と“使える”の差、ここで埋まる

ドキュメントだけ見ていると、
なんとなく難しそうで触らなかった部分なんですけど、

中身が分かると、

  • フォントはただのデータ
  • 描画はそれを読むだけ

という、かなりシンプルな話になる。


表示をアップデート

こうなると、自然とやりたくなることが出てきます。

「じゃあ、もっと見やすくできるんじゃないか?」

R2のときは、とりあえず表示できればいい、という感じでした。

フォントもデフォルトのまま。
レイアウトも、それに合わせて調整していた。

でも今回は、

  • フォントを自由に選べる
  • 表示サイズも調整できる

という状態になったので、

思い切って 大きな時計表示 にしました。

🎀 やっと“表示を作ってる”感じ

R2では、時計は“情報の一部”でした。
でもR3では、ちゃんと “時計として使える表示” にしています。

これは完全に、

フォントを扱えるようになったことの影響です。

やっぱり、見た目って大事なんですよね。

とくに時計みたいなものは、
見やすさがそのまま使いやすさになる。

結果として、

  • 見た目が変わった
  • 使い勝手も変わった

という感じで、
単なる移植以上の変化になりました。

🎀 中いじってたら、ちゃんと外も変わったやつ


本家のバグを発見

先に書いたように、コードは全部追いかけました。

その結果、元のライブラリにバグを2つ発見。

条件によってはおかしくなるタイプのやつで、
普通に使っているだけだと気づきにくい。

Issueを投げたらすぐ反応があったので、
ちゃんとメンテされているのは安心しました。
結構クリティカルな内容だったので、優先的にPRされてました。
使わせてもらってるから、貢献したいですよね。

🎀 “ついでに読むか”で全部読むやつ


「IDF用ライブラリ使えば?」

ちなみにこれ、reddit/esp32 に投稿 したら言われました。

「それ、IDF用のSSD1306ライブラリ使えばいいんじゃない?」

まあ、そりゃそう思いますよね。

でも今回は、あえてそうしませんでした。

理由はいくつかあります。

まず、R2とそっくりに作りたかった。

表示のレイアウトやフォントをそのまま持っていきたかった。
ここが変わると、地味に面倒なんですよね。

それと、コードを見たときに
「あ、これ普通に移植できそうだな」と思った。

だったら、そのまま持ってきたほうが早い。

さらに言うと、

IDF用のライブラリって、OLED専用じゃなかったりして、
ちょっと重いものが多い印象でした。

  • 汎用グラフィックライブラリ前提
  • 機能が多すぎる

今回の用途だと、そこまでいらない。

もう少し軽くていい。

それと、コンポーネント化をちゃんとやってみたかった。

IDFって、この“部品として切り出す感じ”が面白いんですよね。

結果としては、

  • 表示はR2と揃った
  • 挙動もそのまま
  • 中身も全部把握できた
  • 軽い構成にできた
  • IDFっぽい形にもなった

という感じで、悪くなかったと思っています。

そのほかにも、興味本位とか、こだわりとか、
そういう理由で選んでいる部分は結構ありますし、

既存のやつをそのまま使ったら負け、みたいな感覚も。

……とか言いつつ、
Arduinoでは普通にこの元のライブラリ使ってるんですけどね。

🎀 その矛盾、すごくそれっぽい


GitHubで公開中

今回は、設計や考え方の話が中心になってしまったので、
技術的な細かい部分はあまり書いていません。

実際にどんな構造にしたのか、
どういうコードになっているのかは、

GitHubで公開しているので、そちらを見てもらえればと思います。

👉 https://github.com/shachi-lab/oled_display

文章で説明するより、
コードを見たほうが早い部分も多いので。

🎀 こういうの、結局コードが一番正直なんだよね


まとめ

今回のOLED移植は、

APIを書き換えたというより、

  • 構造を整えた
  • 境界を引き直した
  • IDFの流儀に寄せた

という感じでした。

動かすだけなら、ここまでやらなくてもいいのだけど、
でも、一度気になると納得できるまでやりたくなっちゃうんですよね。

🎀 “動いたからOK”で終わらないの、だいぶ職業病


📪 お問い合わせなど

技術的なご相談やご質問などありましたら、
📩お問い合わせフォーム  
または、
📮info@shachi-lab.com までお気軽にどうぞ。

🎀 コメントでもXでも、気軽にどうぞ。お仕事の相談もOKだよ


🔗 関連リンク

しゃちらぼの最新情報や開発の様子は、こちらでも発信しています:

💬「シャチラボ」で検索してきた方も大歓迎!
ほんとは「しゃちらぼ(Shachi-lab)」なんだけど、見つけてくれてありがとう🐬

コメント

タイトルとURLをコピーしました