やりたいこと
スタックチャンの顔表示ライブラリである M5Stack-Avatar を SPRESENSE に移植し、SPRESENSE にLCDを接続してスタックチャンの顔を表示させます。
使用するハードウェア
- SPRESENSEメインボード
- SPRESENSE拡張ボード
- 2.0インチ 320x240 カラーLCD (ST7789搭載, SPI接続)
M5StackのLCDが2.0インチ 320x240 なので、同様のものを使用しました。ただしドライバICは、M5StackのLCDで採用されているILI9341ではなくST7789です。AliExpressで購入したものですが、Adafruit 2.0" 320x240 Color IPS TFT Display に実装されているのもおそらく同等品です。Adafruitのやつはバックライト駆動トランジスタなども搭載された便利なブレークアウト基板となっています。
結線
SPRESENSE拡張ボードとLCDを下表のように結線します。
| SPRESENSE拡張ボード | LCD |
|---|---|
| GND | 1: GND 5: GND 6: GND 12: GND |
| GND (10Ω抵抗を介して) |
2: LED Cathode |
| 3.3V | 3: LED Anode 4: VDD |
| D08: GPIO | 7: D/C |
| D10: SPI4 CS | 8: CS |
| D13: SPI4 SCK | 9: SCL |
| D11: SPI4 MOSI | 10: SDA |
| D07: GPIO | 11: RESET |
今回使用したLCDのフレキは0.8mmピッチだったので、サンハヤトのピッチ変換基板SSP-81を使ってケーブルを引き出しました。バックライトの輝度制御は省略し、LEDのカソードを10Ω抵抗を介してGNDに落としました。前述のAdafruitのブレークアウト基板を使えば面倒なフレキのハンダ付けは不要で、バックライトもPWM制御できます。ただし、SPRESENSE はハードウェアPWMを出力できるピンが限られていることに注意してください。D09を使用するとよいと思います。
移植の方針
(1) M5Unified/M5GFX を LovyanGFX に置き換え
M5Stack-Avatarは、画面表示処理をM5Unifiedライブラリに依存しており、M5UnifiedライブラリはM5GFXライブラリに依存しています。M5GFXはM5Stack専用のライブラリですが、LovyanGFXライブラリをベースにしており、LovyanGFXはM5Stack以外のマイコンでも利用できます。そこで、M5Unified/M5GFXのAPIをLovyanGFXのAPIに置換します。特に、M5Canvas は LGFX_Sprite に置換します。
(2) LGFX_Device継承でディスプレイを定義
M5Unified/M5GFXではM5Stackのディスプレイが定義されていますが、LovyanGFXではディスプレイの定義を自分で記述する必要があります。LGFX_Deviceクラスを継承する形でディスプレイを定義します。具体的にはドライバICや接続ピン番号などを設定します。
(3) FreeRTOSのAPI を POSIX に置き換え
M5Stack-Avatarはマルチタスク処理をしておりRTOSの機能を利用しています。M5StackのRTOSは FreeRTOS ですが、SPRESENSE のRTOSは NuttX です。NuttXは POSIX準拠のRTOSです。そこで、FreeRTOSのAPIをPOSIXのAPIに置き換えます。
ライブラリの実装
上記の方針に基づき、M5Stack-Avatarを SPRESENSE に移植した Common-Avatar (アルファ版) を公開しました。現状、SPRESENSE でのみ動作確認していますが、FreeRTOSまたはNuttX搭載の他のマイコンにも対応は難しくないはずです。
ライブラリの使い方
examples/spresense_avatar/のサンプルスケッチを参考にしてください。
まず、ディスプレイを定義します。
#include <LovyanGFX.h>
#include <Display.h>
// ピン割り当て
#define TFT_MISO -1 // 接続しない
#define TFT_MOSI 11
#define TFT_SCLK 13
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 7
// 画面デバイスの定義
class MyLGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7789 _panel_instance;
lgfx::Bus_SPI _bus_instance;
public:
MyLGFX(void)
{
{ // SPIバスの設定
auto cfg = _bus_instance.config();
cfg.spi_port = 4; // SPRESENSEのSPIポート番号
cfg.spi_mode = 3; // SPI通信モード (0 ~ 3)
cfg.freq_write = 40000000; // 送信時のSPIクロック
cfg.freq_read = 20000000; // 受信時のSPIクロック
cfg.pin_sclk = TFT_SCLK;
cfg.pin_mosi = TFT_MOSI;
cfg.pin_miso = TFT_MISO;
cfg.pin_dc = TFT_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
{ // 表示パネルの設定
auto cfg = _panel_instance.config();
cfg.pin_cs = TFT_CS;
cfg.pin_rst = TFT_RST;
cfg.pin_busy = -1; // 接続しない
cfg.panel_width = 240; // 幅
cfg.panel_height = 320; // 高さ
cfg.offset_x = 0; // X方向オフセット
cfg.offset_y = 0; // Y方向オフセット
cfg.invert = true; // 明暗が反転する場合 true
_panel_instance.config(cfg);
}
setPanel(&_panel_instance);
}
};
次に、SET_DISPLAY_CLASS()という特別なマクロを使って、ディスプレイをCommonAvatarに設定します。
SET_DISPLAY_CLASS(MyLGFX);
あとは、M5Stack-Avatarと同じように使うことができます。
#include <Display.h>
#include <Avatar.h>
using namespace m5avatar;
Avatar avatar;
void setup()
{
Display.begin();
Display.setRotation(1); // 画面回転(横向き)
//Display.setBrightness(255); // バックライト100%(全点灯)
Display.fillScreen(TFT_BLACK);
avatar.init(); // start drawing
}
void loop()
{
delay(1);
}
TODO
M5Stack-Avatarでは、表情の変化と表示をそれぞれloop()とは別タスクで実行しており、優先度が設定されています。優先度は表情の表示が最低で、表情の変化がその次です。これと同等の設定を SPRESENSE のNuttXでおこなった場合、loop() で定期的にusleep() を呼ばないと表情の変化・表示が実行されませんでした。delay()でもダメなようです。これでは一般的なArduinoプログラミングのスタイルから外れて不便なので、とりあえず優先度の設定は無効にしました。
src/Avatar.cpp での pthread_attr_setschedparam()のコメントアウトが該当箇所です。
識者求む!
関連記事
