128×64ピクセルのOLEDをmbed (Nucleo)から制御

2016年06月19日 追記: Raspberry Pi 3で同じディスプレイを駆動した時のレポートはこちら→「Raspberry Piで128×64ピクセルのOLEDに日本語を表示(美咲フォント)


理研は退職したのですが、週末研究者として京都大学の榎戸さんなどと進めている「雷雲ガンマ線観測実験 (Thundercloud Project)」に引き続き参加しています。メーリングリスト上で議論したり、Skype会議などをしながら、2016年度の観測に向けて小型・分散配置型のガンマ線検出器(下図)のアップグレードをすすめています。

2015年度に試作した小型ガンマ線検出器システム。これをさらに小型・軽量・低コストにして北陸地方に分散配置します。写真に写っているのは榎戸さん。

その一環で、検出器やその中に組み込まれたRaspberry PiやFPGAの動作状況をひと目で確認できるようにしたい、という要求がありました。検出器を設置するのは、観測実験に協力してもらっている高校や大学の建物の屋上で、設置・撤収作業のときには雪が降っていることもあるので、Raspberry Piにディスプレイやキーボードを接続して動作確認したり、観測プログラムを開始・停止することはできません。10台や20台の分散配置を実現するには、装置を室内で組み上げて(できれば研究所で組み上げて、観測サイトにはそのまま梱包して発送して)、屋上に運んで電源を接続したらほとんど自動的に動作するようにする必要があります。

ということで、検出器の状態確認をしやすくするために、エレクトロニクスに有機ELディスプレイ (OLED; organic light-emitting diode)をつけてみることにします。いまや世界最大のショッピングサイトAlibabaで調べると、128×64ピクセルの1インチの小型ディスプレイが300円くらいで売っています。激安。

0.96 inch 4pin White OLED Module SSD1306 Drive IC 128*64 I2C

今回はChina Postの送料$1.82の発送形態で、注文してから10日弱で届きました。日本のAmazonでも同じような製品が売られていて、1300円くらいで買えるようです(でも、写真を見る限り、VCC(電源)とGNDのピン配置が違うようなので、ピンコンパチではないようです)。

[tmkm-amazon]B00XDY2SR8[/tmkm-amazon]

本番の検出器ではRaspberry Pi 3のI2CからPythonスクリプトで制御する予定ですが、日本からRaspberry Piが届くまで少し時間があるので、手持ちのNucleoマイコン(mbed対応)STM32F411REで動かしてみました(金曜のお昼ぐらいに届いたと連絡があって、金曜の午後は家に帰って動かしてみるのが楽しみでしかたなくて、帰宅後は夜中まで遊んでしまいました)。

STM32F411REは100MHzのARM 32-bit Cortex-M4 CPU (with FPU)が載っていて、RAM 128kB、フラッシュメモリは512kB、周辺インタフェースはUARTやI2C/SPI、ADCが複数チャネル使えるというパワフルなマイコン(詳細はこちら)ですが、ボードは秋月やネット通販で1500円くらいで買えてしまう、便利なもの。IDEはクラウド上にあり、ブラウザ経由でコーディング・コンパイル。MacからもUSBポートから直接プログラムの書き込みができるので、Macユーザにやさしいエコシステムになっています。

この写真でOLEDに斜線が入っているように見えるのは、リフレッシュレートとiPhoneのカメラのシャッタースピードの関係のせい。人間の目で見ると、ちらつきは感じられません。[/caption] それで、今回はワイヤフレーム(使用感の確認用のテストプログラム)として動けば十分なので、Neal HormanさんがAdafruitのライブラリをmbed用に移植したAdafruit_GFXを使用して、printf()で動作状態の画面を作ってみました。このライブラリの使い方はとても簡単で、
  1. 自分のプロジェクトにImport
  2. 以下のようにしてAdafruit_SSD1306クラスをインスタンス化(SSD1306はOLED制御ICの型番)
```cpp #include "Adafruit_SSD1306.h" // OLEDとの通信に使用するI2Cオブジェクトを生成 I2C i2c(I2C_SDA, I2C_SCL); // OLED制御クラスのインスタンス化 uint8_t i2cAddress = SSD_I2C_ADDRESS; uint8_t rawHeight = 64; uint8_t rawWidth = 128; Adafruit_SSD1306_I2c oled(i2c, D10, i2cAddress, rawHeight, rawWidth); ``` 3. 以下のように_putc()関数をラップ(printf()と接続してもいいけど、今回は表示位置などを自分で制御したかったので、print()という独自関数を用意)。Adafruit_SSD1306の親クラスであるAdafruit_GFXの中で、drawCircle()やdrawRect()という描画関数も用意されているので、任意の形状も描くことができます。display()関数をコールするまで更新内容がディスプレイには表示されないので注意してください(_putc()やdrawRect()はSTM32内部のメモリ上のビットマップデータを書き換えているだけ。display()をコールした時にだけ、ビットマップデータがI2Cで書き込まれる)。 ```cpp void print(std::string str){ for(size_t i=0; i<str.size() ;i++){ oled._putc(str[i]); } // 表示を更新 oled.display(); } ``` 試しに以下の様な文字列を表示してみます。使用中のIPアドレスや、観測モード(Obs)、測定データを書き込むファイル名、温度・湿度、PMT用高圧電源の出力電圧、ディスクの空き容量などのサンプルデータを表示しています。 ```cpp char* lines[]={// "GROWTH-FY2016A [1.05]", "Wifi:192.168.100.100", "Time:09/31 23:59:59", " T/H:29.5 28.5 45%", " HV:1000V 950V", "[Obs]20160931233000", "Rate: 25 99 100 34", "Disk: 31GB 120GB" }; ``` 1インチなので画面は狭いですが、各ピクセルが自己発光しているので、視認性はとてもいいです。右下に入れたTwitterの鳥マークもちゃんと識別できます(本番環境では、検出器自身が自分の状態や環境計測の結果を定期的にTwitter上につぶやかせたり、特定のハッシュタグのつぶやきを収集してこのディスプレイに表示されるようにしたりしてみる予定です)。鳥マークの描画には以下のようにdrawBitmap()関数を使いました。bitmapデータは、1ビットが1ピクセルに対応していて、uint8_tにパックした状態で渡します。データの並びの問題で、bimatp_twitterの配列のままではだめで、X/Yを回転させる必要が有るため、以下の様なrotate8x8()関数を用意して回転させてからdrawBitmap()に渡しています。 ```cpp uint8_t bimatp_twitter[64] = { 0, 0, 0, 0, 0, 1, 1, 0, // 1, 1, 0, 0, 1, 1, 1, 1, // 0, 1, 1, 0, 1, 1, 1, 0, // 1, 1, 1, 1, 1, 1, 1, 1, // 0, 1, 1, 1, 1, 1, 1, 0, // 1, 0, 1, 1, 1, 1, 0, 0, // 0, 1, 1, 1, 1, 0, 0, 0, // 1, 1, 1, 0, 0, 0, 0, 0 // }; uint8_t bitmap_packed[8] = {0x06, 0xCF, 0x6E, 0xFF, 0xBC, 0x78, 0xE0}; void rotate8x8(uint8_t* in, uint8_t* out) { for (uint8_t i = 0; i < 8; i++) { out[i] = 0; for (uint8_t o = 0; o < 8; o++) { uint8_t maskedBit = in[o] & (0x01 << (7 - i)); uint8_t bit = (maskedBit == 0) ? 0 : 1; out[i] = out[i] + (bit << o); } } } ... 以下、main()関数内で ... // Twitter emoji uint8_t bitmap_rotated[8]; rotate8x8(bitmap_packed, bitmap_rotated); oled.drawBitmap(120, 56, bitmap_rotated, 8, 8, WHITE); oled.display(); ``` 斜めからみてもとても綺麗に見えます。 補足ですが、ぼくの環境ではAdafruit_SSD1306.cpp 内で、以下の様な追記をする必要がありました。これをしないと、画面の上下半分がそれぞれ下、上と逆に表示されてしまいました。 ```cpp 1. 冒頭のdefine文の一番最後に以下を追加。 #define SSD1306_COLUMNADDRESS 0x21 #define SSD1306_PAGEADDRESS 0x22 2. void Adafruit_SSD1306::begin(uint8_t vccstate)で、もともと入っている command(SSD1306_MEMORYMODE); command(0x00); // 0x0 act like ks0108 のあとに、以下を追加 command(SSD1306_COLUMNADDRESS); command(0x00); command(0x7F); command(SSD1306_PAGEADDRESS); command(0x00); command(0x07); ``` ということで試験の結果ですが、表示も見やすいし、情報もけっこう詰め込めるので、検出器の状態を表示するのに十分使えそうです。Raspberry Piでリアルデータを表示したり、Twitterメッセージを表示させたりするのが楽しみ!(ちなみにTwitterのメッセージ表示には8x8ピクセルで日本語表示に対応している奇跡のフォント「美咲フォント」を使わせてもらおうと思っています)。下の画像はオフィシャルページのサンプルなのですが、十分読めますよね!? 本当にすごい。