フォト
2025年4月
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      
無料ブログはココログ

« 次はSTM32ボードを積極的に使ていきたい(25) グラフィック液晶(6) | トップページ | Arduino用漢字フォントライブラリ SDカード版のSdfat対応 »

2018年7月 9日 (月)

次はSTM32ボードを積極的に使ていきたい(26) グラフィック液晶(7)

フォント描画処理の改善

SPI接続のグラフィック液晶モジュール(コントローラー ILI9341)利用の調査を継続中です。

Dscn8386

利用しているAdafruit_ILI9341_STMライブラリのフォント表示、ビットマップ表示の
描画処理の遅さが気になり何とかならないかと試行錯誤してみました。

特に横向きに利用した場合、ハードウェア的にスクロール機能が無いため、
表示内容を上にスクロールするには、全画面再表示をする必要があり、
描画速度が遅いのは致命的です。

遅さの原因を調べるためライブラリのソースを見ると、
文字表示、ビットマップ表示は1点毎にdrawPixel()関数で描画しているようです。
これはかなり効率の悪い処理です。1点毎にグラフィック液晶モジュールに
コマンドとデータの送信を行っています。

そこで処理を前回の画像表示で高速化に貢献したDMAを利用する方式を適用してみました。
なるべくコマンド送信回数を減らす作戦です。


実装する画面構成

  ・グラフィック画面解像度 320x240ドット
  ・文字表示 53x30文字
  ・フォントサイズ 6x8ドット(半角256文字、自作IchigoJam互換コード採用フォント )

動作テストとしては、画面いっぱいに文字を表示し(53x30文字)、その表示処理時間の
改善を図ります。

この処理が高速化されれば、スクロール表示処理も当然改善されます。
実際に、改善を施したビフォー&アフターは次のような感じです。
前半が改善前、後半が改善後です。


かなり改善出来た雰囲気が伝わると思います。
数値的には、53x30文字表示全表示に かかる時間は、

  改善前 808.099 ms
  改善後 35.149 ms
と大幅に処理速度が改善出来ました。
方式的には、1ライン(320ドット)用のバッファを用意し、1ライン毎にDMA転送を行いまいた。
1280バイト(320ドットx16ビットx2)ほどバッファ用のメモリを消費しますが、効果は絶大です。

さて、理論的に最大何処まで改善可能なのか見積もってみると、
今回のSPIの転送速度はクロックが36MHzなので、データ送信能力は
    36,000,000ビット/秒 
です。

一方、グラフィック画面の1画面のデータは、
    320x240x16ビット= 1,228,800 ビット
です。

1画面の更新に必要とする時間は、
   1,228,800 ビット ÷   36,000,000ビット/秒   = 0.034133333 秒 = 34.13 ms

理論的な最大パフォーマンスに近い値が出せていることが分かります。


検証用スケッチ
//
// BluePill(Arduino STM32) グラフィック液晶(ILI9341) フォント表示改善デモ
// 2018/07/09 by たま吉さん

#include <Adafruit_GFX_AS.h>      // Core graphics library
#include <Adafruit_ILI9341_STM.h> // Hardware-specific library
#include <font6x8tt.h>            // 6x8ドットフォント

// TFT制御用ピン
#define TFT_CS  PA0
#define TFT_RST PA1
#define TFT_DC  PA2

// TFT制御用
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);

// フォント管理用
uint8_t* fontTop;
#define CH_W     6            // フォント横サイズ
#define CH_H     8            // フォント縦サイズ

// スクリーン管理用
#define SC_W     53           // キャラクタスクリーン横サイズ
#define SC_H     30           // キャラクタスクリーン縦サイズ
#define SCSIZE   (SC_W*SC_H)  // スクリーンサイズ
uint8_t screen[SCSIZE];       // スクリーンバッファ

// 指定座標に文字の表示
void drawChar(uint16_t x, uint16_t y, uint8_t c) {
  uint8_t* ptr = &fontTop[c*8];       // フォントデータ先頭アドレス
  for (uint8_t i=0; i <8; i++) {
    for (uint8_t j=0; j < 6; j++) {
      if ( (*ptr) & (0x80>>j)) {
        tft.drawPixel(x+j,y+i,ILI9341_WHITE);
      } else {
        tft.drawPixel(x+j,y+i,ILI9341_BLACK);
      }
    }
    ptr++;
  }
}

// 従来の画面更新表示
void sc_updateOld() {
  for (uint16_t y=0; y < SC_H; y++) {
    for (uint16_t x=0; x < SC_W; x++) {
      uint8_t c = screen[SC_W*y+x];  // キャラクタの取得
      drawChar(x*CH_W, y*CH_H, c);
    }
  }
}

// 指定位置の文字の更新表示
void sc_updateChar(uint16_t x, uint16_t y) {
  uint8_t c    = screen[SC_W*y+x];    // キャラクタの取得
  uint8_t* ptr = &fontTop[c*8];       // フォントデータ先頭アドレス
  tft.setAddrWindow(x*CH_W, y*CH_H, x*CH_W+CH_W-1, y*CH_H+CH_H-1);
  for (uint8_t i=0; i <8; i++) {
    for (uint8_t j=0; j < 6; j++) {
      if ( (*ptr) & (0x80>>j)) {
        tft.pushColor(ILI9341_WHITE);
        //SPI.write(ILI9341_WHITE);
      } else {
        tft.pushColor(ILI9341_BLACK);
        //SPI.write(ILI9341_BLACK);
      }
    }
    ptr++;
  }
}


// 指定行をTFT画面に反映
// 引数
//  ln:行番号(0~29)
void sc_updateLine(uint16_t ln) {
  uint8_t c;
  uint8_t dt;
  uint16_t buf[2][SC_W*CH_W];
  uint16_t index;

  for (uint16_t i=0; i < CH_H; i++) { // 1文字高さ分ループ
    index = 0;
    for (uint16_t clm = 0; clm < SC_W; clm++) { // 横文字数分ループ
      c  = screen[SC_W*ln+clm]; // キャラクタの取得
      dt = fontTop[c*8+i];      // 文字内i行データの取得
      for (uint16_t j=0; j < 6; j++) {
        if ( dt & (0x80>>j)) {
          buf[i&1][index] = ILI9341_WHITE;
        } else {
          buf[i&1][index] = ILI9341_BLACK;
        }
        index++;
      }
    }
    tft.pushColors(buf[i&1], SC_W*CH_W, 1);
  }
}

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

  // TFTの初期化
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK);
  fontTop = (uint8_t*)font6x8tt+3;
  memset(screen, 0, SCSIZE);

  // ダミーデータのセット
  for (uint16_t i=0; i < SCSIZE; i++)
    screen[i] = i % 256;
}

void loop() {
  Serial.print("Hit any key to start");
  while(Serial.read() <0);
  Serial.println();

  // 従来方式
  uint32_t t1, t2;
  uint8_t c = 0;
  for (uint16_t i=0; i < 15; i++) {
    t1 = micros();
    sc_updateOld();
    t2 = micros();
    Serial.print("update Time =");
    Serial.println(t2-t1,DEC);
    memmove(&screen[0], &screen[SC_W*1], SCSIZE-SC_W);
    for(uint8_t j=0;j < SC_W; j++)
      screen[SC_W*29+j] = c++;
    //delay(10);
  }

  delay(3000);

  // 高速対応
  for (uint16_t i=0; i < 300; i++) {
    t1 = micros();
    tft.setAddrWindow(0, 0, SC_W*CH_W-1, 29*CH_H+CH_H-1);
    for (uint8_t y=0; y < 30; y++) {
       sc_updateLine(y);
    }    
    t2 = micros();
    Serial.print("update Time =");
    Serial.println(t2-t1,DEC);
    memmove(&screen[0], &screen[SC_W*1], SCSIZE-SC_W);
    for(uint8_t j=0;j < SC_W; j++)
      screen[SC_W*29+j] = c++;
    //delay(10);
  }
}

pushColors()関数の利用が今回の改善の立役者です。
内部的にDMA転送をバックグランドで行ってくれます。
setAddrWindow()、pushColors()、pushColor()関数は使い方によってかなり描画処理の
改善を行うことが出来ます。

いずれは、豊四季Tiny BASIC for Arduino STM32にこのテキスト描画を採用しようと思います。

« 次はSTM32ボードを積極的に使ていきたい(25) グラフィック液晶(6) | トップページ | Arduino用漢字フォントライブラリ SDカード版のSdfat対応 »

arduino」カテゴリの記事

表示器制御関連」カテゴリの記事

ARM」カテゴリの記事

STM32」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

« 次はSTM32ボードを積極的に使ていきたい(25) グラフィック液晶(6) | トップページ | Arduino用漢字フォントライブラリ SDカード版のSdfat対応 »