M5StackでLovyanGFXを試す その1 メモリの確認

概要

M5Stack Fireを入手したのでLovyanGFXの使い方を試してみます。M5StickCは画面が小さいので問題にならないことが、M5Stackでは考慮する必要があります。

描画用スプライトについて

画面に直接描画すると、描画している途中も表示されチラついて見えます。そのため描画用のスプライトを作成し、一括して描画する方法があります。

それではスプライトを作ってみましょう!

#include <LovyanGFX.hpp>

static LGFX lcd;
static LGFX_Sprite sprite;

static uint32_t tft_width ;
static uint32_t tft_height;

void setup(void)
{
  Serial.begin(115200);
  delay(100);

  lcd.init();

  tft_width = lcd.width();
  tft_height = lcd.height();

  // 空きメモリ確認
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DMA):%d\n", heap_caps_get_free_size(MALLOC_CAP_DMA) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DMA):%d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA) );

  // 画面サイズ確認
  Serial.printf("Width:%d, Height:%d\n", tft_width, tft_height);

  // 描画用スプライト作成
  void *p = sprite.createSprite(tft_width, tft_height);
  if ( p == NULL ) {
    Serial.println("メモリが足りなくて確保できない");
  }

  // 空きメモリ確認
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DMA):%d\n", heap_caps_get_free_size(MALLOC_CAP_DMA) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DMA):%d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA) );
}

void loop(void)
{
}

上記を実行した結果です。

heap_caps_get_free_size(MALLOC_CAP_DMA):287940
heap_caps_get_largest_free_block(MALLOC_CAP_DMA):124024
Width:320, Height:240
メモリが足りなくて確保できない
heap_caps_get_free_size(MALLOC_CAP_DMA):287940
heap_caps_get_largest_free_block(MALLOC_CAP_DMA):124024

使っているのはM5Stack Fireですが、PSRAMは無効にした状態で実行しています。画面は横320、縦240で問題ないのですが、スプライトの作成に失敗しています。

Arduinoで一般的な画面は1ドットあたり16ビット(2バイト)で管理することが標準的です。そのため320x240x2で153,600バイトのメモリが必要になります。

高速な転送をするためにはDMAが使えるメモリが必要なので空き容量を調べると287,940バイトです。一見メモリは足りているように見えますが、largestの値が124,024バイトです。ESP32のメモリは複数のメモリが飛び飛びで配置されているため、一度に確保できるメモリに制限があります。

解決方法

その1 小さいサイズを使う

横幅縦幅バイト
3202402153,600
3201802115,200
2402402115,200

ざっくりとサイズ別の容量です。どうやら縦と横のサイズを小さくすればメモリに収まりそうです。

試しに240×240で作成したところ、確保できました。

グラフなどの動的な部分と、それ以外のメニュー周りなどを分けてスプライトを確保することで回避することができそうです。実際のところ分割して転送量をなるべく少なくしたほうが高速描画が可能です。

空き容量最大空き容量
確保前287,940124,024
確保後172,720113,792
差分115,22010,232

上記がメモリの増減です。スプライトの容量より20バイト多く減っています。これはメモリ確保したときに内部で管理している領域分と推測できます。

さて、最大空き容量は若干減っていますが、それほど大きく変化していません。ただこの空き容量だとさらに240×240は確保できないですね。

その2 分割して確保

1つでは確保できないので、縦を2分割して320×120を確保してみます。

  sprites[0].createSprite(320, 120);
  sprites[1].createSprite(320, 120);

MovingIconsなどのスケッチ例が実際に分割して確保しています。

空き容量最大サイズ
確保前287,940124,024
確保後134,00446,908
差分153,93677,116

最低2分割すれば確保できそうです。ただし分割した場合にはちょっと描画が大変です。のちほど解説したいと思います。

その3 色数を減らす

//sprite.setColorDepth(1);   // 1ビット( 2色)パレットモードに設定
//sprite.setColorDepth(2);   // 2ビット( 4色)パレットモードに設定
//sprite.setColorDepth(4);   // 4ビット(16色)パレットモードに設定
sprite.setColorDepth(8);   // RGB332の8ビットに設定
sprite.createSprite(320, 240);

色数を減らすことで、1ピクセルあたり16ビットだったものが8ビット以下になります。ただし一般的な画面は16ビットで動いているため、転送する際に変換が必要となり、高速DMA転送が使えなくなります。

あまり速度と色数が必要ない場合には、お手軽に使えると思います。ただし色の指定方法などが変わりますので注意が必要です。

その4 PSRAMを使う

M5Stack Fireの場合にはPSRAMを有効にすることで、通常より低速ですが大きなメモリを使うことができます。

  sprites.setPsram(true);
  void *p = sprites.createSprite(320, 240);

通常はPSRAMを使わない設定になっているので、setPsram(true)を呼び出してPSRAMを使うように設定します。

PSRAMを使うことで大量に画面を確保することができますが、通常メモリよりかなり低速なので注意しましょう。

MovingIconsスケッチの分析

LovyanGFXのスケッチ例にあるMovingIconsを見ながら、使い方を確認してみます。このスケッチは3種類のアイコンを動かしながら大量に描画しています。

まず、描画ですが縦3分割して、320×80の描画を3回行っています。しかし描画用スプライトは2個しかありません。

どのように描画しているかというと、描画しているスプライトと転送しているスプライトに役割をわけています。

#スプライト描画画面転送
1スプライト1に描画エリア1を描画
2スプライト2に描画エリア2を描画スプライト1を描画エリア1に転送
3スプライト1に描画エリア3を描画スプライト2を描画エリア2に転送
4スプライト1を描画エリア3に転送

スプライト1とスプライト2は交互に描画と転送を繰り返します。スプライト1を画面にDMA転送しているしている間に、スプライト2に描画することができます。

DMA転送はCPUを使わないバックグラウンド転送なので、この技が使えます。色数が違ったりPSRAMを使った場合にはDMA転送が使えないので描画速度が落ちるはずです。

上記のことを頭においてスケッチ例をみてみると、動作が理解できると思います。

  for (int_fast16_t y = 0; y < tft_height; y += sprite_height) {
    flip = flip ? 0 : 1;
    sprites[flip].clear();
    for (size_t i = 0; i != obj_count; i++) {
      a = &objects[i];
      icons[a->img].pushRotateZoom(&sprites[flip], a->x, a->y - y, a->r, a->z, a->z, 0);
    }

とくに上記のループが初見ではわかりにくいですが、縦80ドットずつを3回に分けて描画しています。とりあえず全アイコンを描画してみて、描画担当範囲外の物はライブラリ側で描画しませんので無視されています。-yしているところがポイントです。

このスケッチを理解できていないと、LovyanGFXをうまく使えないと思いますのでがんばって読解してみてください。

ここは高速描画をするためには仕方ないのですが、本当は分割したスプライトをきれいにラッパーできると良さそうですね。

まとめ

なんとなくLovyanGFXというよりは、画面描画全般に関係する内容になってしまいました。次回があればもう少し突っ込んだ内容を紹介したいと思います。

コメント

  1. かにの網元 より:

    記事を参考にさせて頂きました!!
    やはりmallocで320x240x2byteを取るのは無理ということで、
    320x2Byteのラインバッファを240個分mallocすることにしました。

    スプライトとBGの描画で工夫する必要がありますが、
    どうにかM5Stackでオフスクリーンを作る事ができました。
    詳細はQiitaで記事にする予定です。

    また、よろしくお願いいたします。