ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【M5StickC Plus/Arduino】M5StickC Plusで文字列スクロール

 最近M5StickC Plusをいじっています。

 今回は「文字列スクロール」をやってみたのでその備忘録です。

 では、始めます。


1:efontの導入
 今回も日本語表示のためにefontを使用します。

 まだefontを入れてない場合は、以下の記事にある「1:日本語フォントを導入する」と「2:M5StickC Plus用のファイルを作成する」を行ってください。


2:文字列スクロールのサンプル
 文字スクロールは以下のライブラリの「examples/Standard/」の中にある「LongTextScroll.ino」を参考にさせていただきました。


 実際に文字スクロールを行ったスケッチが以下になります。
(※2021/03/29:色々と間違っていたのでスケッチを修正しました)

・M5StickCPlus_efont_scroll.ino

#include <M5StickCPlus.h>
#include "efont.h"
#include "efontM5StickCPlus.h"
#include "efontEnableJaMini.h"

const uint16_t TEXT_COLOR    = WHITE; // 文字色
const uint16_t TEXT_BG_COLOR = GREEN; // 文字背景色
const uint8_t TEXT_SIZE = 7;          // 文字サイズの倍率

// M5StickC Plusの画面解像度は(135*240)
const int SCROLL_PIXEL = 8;      // 1回にスクロールするpixel数
const int DELAY_TIME   = 0;      // 1スクロールする時のディレイタイム(ミリ秒)
const int32_t SPRITE_POS_X = 0;  // スプライトを表示するX座標
const int32_t SPRITE_POS_Y = 11; // スプライトを表示するY座標
const uint16_t SPRITE_WIDTH_PIXEL  = 400;          // スプライトの横幅
const uint16_t SPRITE_HEIGHT_PIXEL = 16*TEXT_SIZE; // スプライトの高さ

// スクロールする文字列
static char TEXT_MESSAGE[] = "[start]メロスは激怒した。必ず、かの邪智暴虐(じゃちぼうぎゃく)の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。[end] ";
const int TEXT_ARRAY_MAX = sizeof(TEXT_MESSAGE) / sizeof(TEXT_MESSAGE[0]); // 文字列の配列数

TFT_eSprite tftSprite = TFT_eSprite(&M5.Lcd); // スプライト

// 文字幅とフォントの構造体
struct TextChar {
  int width;
  int height;
  byte font[32];
  char *str;
};

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

  M5.begin();
  M5.Axp.ScreenBreath(10); // 画面の明るさ(7-12)
  M5.Lcd.setRotation(3);   // 画面を横向きに(0-3)

  // フォントサイズ倍率と文字色、文字背景色を設定
  tftSprite.setTextSize(TEXT_SIZE);
  tftSprite.setTextColor(TEXT_COLOR, TEXT_BG_COLOR);

  // スプライト範囲の作成
  tftSprite.createSprite(SPRITE_WIDTH_PIXEL, SPRITE_HEIGHT_PIXEL);
  tftSprite.setCursor(0, 0); // カーソル移動

  showScrollChar(&tftSprite, TEXT_MESSAGE, 0); // スクロールを表示
  Serial.println("setup end!");
}

int cursorX = 0;  // スクロール位置
int char_num = 0; // 文字列の配列番号

void loop() {
  M5.update();
  
  tftSprite.pushSprite(SPRITE_POS_X, SPRITE_POS_Y);  // スプライト描画

  char *str = &(TEXT_MESSAGE[char_num]);
  
  // 一文字分の文字サイズを計算
  TextChar textChar = getCharWidthFont(str, tftSprite.textsize);
  int char_size = textChar.width;

  if (cursorX >= textChar.width) {
    // 半角全角判定して次の文字の配列番号を取得
    char_num = char_num + getNextchar_number(char_size, tftSprite.textsize);

    // 全部の文章が終わっていた場合
    if (char_num >= (TEXT_ARRAY_MAX-1)) {
      char_num = 0;
    }

    // スクロールを戻す
    tftSprite.scroll(char_size, 0);
    cursorX = cursorX - char_size;
    tftSprite.setCursor(0, 0); // カーソル移動

    // スクロール用文字列を表示
    showScrollChar(&tftSprite, TEXT_MESSAGE, char_num);
  }

  cursorX += SCROLL_PIXEL;
  tftSprite.scroll(-SCROLL_PIXEL, 0); // スクロールさせる
//  showData(char_num, TEXT_ARRAY_MAX); // 情報表示

  delay(DELAY_TIME);
}

// 情報を表示する関数
void showData(int char_num, int array_max_num) {
  M5.Lcd.setCursor(0,10);
  M5.Lcd.setTextColor(RED);
  M5.Lcd.print(" char_num:");
  M5.Lcd.println(char_num);
  M5.Lcd.print(" cursorX:");
  M5.Lcd.println(cursorX);
  M5.Lcd.print(" array_max_num:");
  M5.Lcd.println(array_max_num);
}

// 一文字分の大きさを取得する関数
TextChar getCharWidthFont(char* str, uint8_t textsize) {
  byte font[32];

  // フォント取得とchar*の次の位置を取得
  uint16_t strUTF16;
  char *strNext = efontUFT8toUTF16( &strUTF16, str );
  getefontData( font, strUTF16 );

  // 文字横幅
  int width = 16 * textsize;
  if( strUTF16 < 0x0100 ){
    width = 8 * textsize; // 半角
  }

  // 結果の構造体を返す
  TextChar textChar;
  textChar.width = width;
  textChar.height = 16 * textsize;
  memcpy(textChar.font, font, sizeof(font)); // フォント配列をコピー
  textChar.str = strNext;

  return textChar;
}

// 半角全角を判定して次の文字の配列位置にプラスする値を計算
int getNextchar_number(int char_size, int textSize) {
  int result;

  int font_size = char_size / tftSprite.textsize;
  if (font_size < 16) {
    result = 1; // 半角
  } else {
    result = 3; // 全角
  }

  return result;
}

// スクロール用文字列を表示
void showScrollChar(TFT_eSprite *sprite, char message[], int char_num) {
  printEfontScroll(sprite, message, char_num); // 現在位置の文字表示

  // スプライト範囲内を満たすまで先頭から繰り返す
  while (tftSprite.getCursorX() <= tftSprite.width()) {
    printEfontScroll(sprite, message, 0);
  }
}

// スクロール用の文字表示
void printEfontScroll(TFT_eSprite *sprite, char message[], int char_num) {
  int posX = sprite->getCursorX();
  int posY = sprite->getCursorY();
  uint8_t textsize = sprite->textsize;
  uint32_t textbgcolor = sprite->textbgcolor;

  // 現在の文字列位置のポインタ取得
  char *str = &(message[char_num]);

  while( *str != 0x00 ) {
    // 一文字分のフォントとサイズを取得
    TextChar textChar = getCharWidthFont(str, textsize);
    str = textChar.str;

    // 文字幅と高さを取得
    int width = textChar.width;
    int height = textChar.height;

    // 背景塗りつぶし
    sprite->fillRect(posX, posY, width, height, textbgcolor);

    // 一文字だけ表示して描画カーソルを進める
    displayOneChar(sprite, textChar.font, posX, posY);
    posX += width;

    // スプライトの範囲外まで行ったらループ抜ける
    if (posX > sprite->width()) {
      break;
    }
  }

  // カーソルを更新
  sprite->setCursor(posX, posY);
}

// 一文字だけ表示
void displayOneChar(TFT_eSprite *sprite, byte font[], int posX, int posY) {
  uint8_t textsize = sprite->textsize;
  uint32_t textcolor = sprite->textcolor;

  for (uint8_t row = 0; row < 16; row++) {
    word fontdata = font[row*2] * 256 + font[row*2+1];
    for (uint8_t col = 0; col < 16; col++) {
      if( (0x8000 >> col) & fontdata ){
        int drawX = posX + col * textsize;
        int drawY = posY + row * textsize;
        if( textsize == 1 ){
          sprite->drawPixel(drawX, drawY, textcolor);
        } else {
          sprite->fillRect(drawX, drawY, textsize, textsize, textcolor);
        }
      }
    }
  }
}

 このスケッチを書き込むと、TEXT_MESSAGEで設定した文字列がスクロールで表示されます。

 少し解説すると、画面よりも大きい範囲でスプライトを作成してそこに表示できるだけ文字列を表示し、スクロールして一文字消えたら次の文字を後ろに追加して表示させているというだけです。またスクロールが1週すると最初の文字からまた表示するようにもしています。


 以上がM5StickC Plusで文字列スクロールをした内容になります。

 M5StickC Plusの画面表示やスプライトについての解説もやりたいのですが、それも含めると長くなりそうなのでそれはまた別の機会に記事にしようかと思います。


・参考資料