HOME
  Security
   Software
    Hardware
  
FPGA
  CPU
   Android
    Raspberry Pi
  
nLite
  Xcode
   etc.
    ALL
  
LINK
BACK
 

2023/02/18

SSD1306 OLEDの描画を高速化する方法 SSD1306 OLEDの描画を高速化する方法

(How to Speed Up OLED Drawing Speed)

Tags: [電子工作]




● ESP32で東方の Bad Apple!!の動画を 128 x 64 dotの OLED SSD1306で再生する!

2023/02/18
ESP32で東方の Bad Apple!!の動画を 128 x 64 dotの OLED SSD1306で再生する!
ESP32で東方の Bad Apple!!の動画を 128 x 64 dotの OLED SSD1306で再生する!

  ESP32で東方の Bad Apple!!の動画を再生する!実際にはパラパラ漫画です

 注意: ESP32で MP3を再生して音楽が鳴ります!
Touhou Bad Apple!! Demo ESP32 with MP3 Audio and SSD1306 OLED (128x64 dot)



● OLED SSD1306の描画を高速化する方法

 「ESP32で東方の Bad Apple!!の動画を再生する!」は実際にはパラパラ漫画です。

 パラパラ漫画と言う事は画面への描画速度がキモになります。

 30 fpsとして 1秒 / 30フレーム = 33.33ms以内に画面への描画を完了する必要が有ります。
 現状では 1画面あたり 15msで間に合っています。

 しかし、プログラマとしては 33.33msに描画が間に合っていても、あの手この手で高速化をしたいものです。たとえそれが自己満足であっても!趣味なら大丈夫!速さは正義だ!(大昔の SHARP X68000の頃の標語)


 ここに Bad Apple!!で行なった OLEDの描画速度改善の改造の内容を書いていきます。

 1) SSD1306の OLEDのメモリ構造に一致する様にビットマップデータを転送する
 2) SSD1306のライブラリの I2Cの転送バイト長を 17バイトから ESP32の上限の 128バイトにする
 3) I2Cの SCLKの周波数をクロックアップする

 ESP32の I2C OLED SSD1306の高速化のネタとしては上記で出し切ったと思います。


● 1) SSD1306の OLEDのメモリ構造に一致する様にビットマップデータを転送する

 簡単に言うと、SSD1306の OLEDのメモリ構造に一致する様にビットマップデータを転送します。

 具体的には Bad Apple!!の
mod Rotate 90 for Direct Write OLED Buffer(470965 bytes)
 の所で、元々の描画は 1ドット単位で 8回描画バッファにアクセスして 1バイト(8ドット分)のビットマップデータを書き込んでいましたが、90度回転する事により 1回の描画バッファへのアクセスで 1バイト(8ドット分)の書き込みをする様にしました。
 単純に 8倍以上 OLEDの描画が高速になります。

 Bad Apple!!の場合は元となる描画データも 90度回転の再変換を行ないました。

・OLED SSD1306 Graphic Display Data RAM (GDDRAM)
OLED SSD1306 Graphic Display Data RAM (GDDRAM)


SSD1306 Datasheet by DFRobot
 8.7 Graphic Display Data RAM (GDDRAM)から引用

 図の様に OLED SSD1306の Graphic Display RAMは 1バイトのデータが縦になっています。
 例えば 0番地から 0x01, x02, 0x0F, 0xA3, 0xE0のデータ書き込むと、
 画面の左上が下記の表示になります。
■・■■・ < LSB 0x01
・■■■・
・・■・・
・・■・・
・・・・・
・・・■■
・・・・■
・・・■■ < MSB 0x80

 下記の場合は、FREEの文字を描きます。
0x1F, 0x05, 0x05, 0x01, 0x00 // F
0x1F, 0x05, 0x0D, 0x12, 0x00 // R
0x1F, 0x15, 0x15, 0x11, 0x00 // E
0x1F, 0x15, 0x15, 0x11, 0x00 // E
 画面の左上が下記の表示になります。
■■■■・■■■・・■■■■・■■■■・ < LSB 0x01
■・・・・■・・■・■・・・・■・・・・
■■■・・■■■・・■■■・・■■■・・
■・・・・■・■・・■・・・・■・・・・
■・・・・■・・■・■■■■・■■■■・
・・・・・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・ < MSB 0x80

 SSD1306のライブラリは 1バイトが横向きになっているので 1バイトの書き込みに 8バイト分の書き込みが必要になります。(つまり 8倍遅い)
esp8266-oled-ssd1306/src/OLEDDisplay.cpp
void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image)

OLEDDisplay::drawInternal()
void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) {
 この OLEDDisplay::drawInternal()の中の処理を見ると良くわかります。

 ThingPulseの SSD1306のライブラリは何故か無駄に処理が入っていて、直接 OLED用の描画バッファをシンプルに操作するメソッドが有りません。

this->buffer
this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + BufferOffset);
 ただし、幸いにも this->bufferでその描画バッファに外部からアクセスできるのでそれを使用して自前で描画バッファをいじくります。

 Bad Apple!!では下記のコミットで描画バッファを直接いじくっています。
mod Optimize Direct Draw OLED buffer


● 2) SSD1306のライブラリの I2Cの転送バイト長を 17バイトから ESP32の上限の 128バイトにする

 (1)の 90度回転のプログラムのロジック変更で高速化が難しい場合はこの方法(ライブラリ側の改善)で超お手軽に高速化が可能です。

 Arduinoのライブラリなので AVRでの制限値に抑えているのか、I2Cのデータの転送長が 17バイトになっています。(最初の 0x40と 16バイトの描画データの合計 17バイト)

esp8266-oled-ssd1306/src/SSD1306Wire.h
  byte k = 0;
  for (y = minBoundY; y <= maxBoundY; y++) {
    for (x = minBoundX; x <= maxBoundX; x++) {
      if (k == 0) {
        _wire->beginTransmission(_address);
        _wire->write(0x40); // 最初の 0x40
      }

      _wire->write(buffer[x + y * this->width()]);
      k++;
      if (k == 16)  { // 16バイトの描画データ
        _wire->endTransmission();
        k = 0;
      }
    }
    yield();
  }

● 17バイト長から 128バイト長にします

 下記の感じで _display()を実装しました。
TwoWire* _wire = &Wire;

inline void _sendCommand(uint8_t command) {
  _wire->beginTransmission(0x3c);
  _wire->write(0x80);
  _wire->write(command);
  _wire->endTransmission();
}

#define COLUMNADDR 0x21
#define PAGEADDR 0x22

void _display()
{
  uint8_t* ppImage = display.buffer;
  _sendCommand(COLUMNADDR);
  _sendCommand(0);
  _sendCommand((128 - 1));

  _sendCommand(PAGEADDR);
  _sendCommand(0x0);
  _sendCommand(0x7);

  // displayBufferSize = displayWidth * displayHeight / 8;
  // 127 * 8
  for (uint16_t i=0; i < 8; i++) {
    _wire->beginTransmission(0x3c);
    _wire->write(0x40);
    for (uint8_t x = 0; x < 127; x++) {
      _wire->write(*ppImage++);
    }
    _wire->endTransmission();
  }

  // (128 - 127) * 8
  _wire->beginTransmission(0x3c);
  _wire->write(0x40);
  for (uint8_t x = 0; x < 8; x++) {
    _wire->write(*ppImage++);
  }
  _wire->endTransmission();
}

  // Update Display frame
  // display.display();
  _display();

#define I2C_SCLK_FREQ 1000000
 で
 1画面の描画速度を 13.84msから 11.82msになり 2.02ms高速化できました!

 6573 frame * 2.02 ms = 13.277 sec速くなるハズ、でしたが、何故か遅くなりました。

 原因は、esp8266-oled-ssd1306/src/SSD1306Wire.hのオリジナルの display()では書き換わった部分だけを検出して必要最低限の矩形領域だけを転送していたからでした。
// Calculate the Y bounding box of changes
void display(void) {
  ...

#ifdef OLEDDISPLAY_DOUBLE_BUFFER
  // Calculate the Y bounding box of changes
  // and copy buffer[pos] to buffer_back[pos];
  for (y = 0; y < (this->height() / 8); y++) {
    for (x = 0; x < this->width(); x++) {
     uint16_t pos = x + y * this->width();
     if (buffer[pos] != buffer_back[pos]) {
       minBoundY = std::min(minBoundY, y);
       maxBoundY = std::max(maxBoundY, y);
       minBoundX = std::min(minBoundX, x);
       maxBoundX = std::max(maxBoundX, x);
     }
     buffer_back[pos] = buffer[pos];
   }
   yield();
  }

※ OLEDDISPLAY_DOUBLE_BUFFERはデフォルトで有効になっています。(defineで定義している)
// Use DOUBLE BUFFERING by default
// Use DOUBLE BUFFERING by default
#ifndef OLEDDISPLAY_REDUCE_MEMORY
  #define OLEDDISPLAY_DOUBLE_BUFFER
#endif

 と言う訳で変化領域のロジックを使用したかったのでオリジナルの SSD1306Wire.hを下記に改造します
下記を追加する
#if defined(ARDUINO_ARCH_ESP32)
  #define I2C_MAX_TRANSFER_BYTE 128
#else
  #define I2C_MAX_TRANSFER_BYTE 17
#endif

  void display(void) {
    ...
       _wire->write(buffer[x + y * this->width()]);
       k++;
       if (k == 16)  {
を下記に変更する
    if (k == (I2C_MAX_TRANSFER_BYTE - 1))  {
      _wire->endTransmission();
      k = 0;
    }
 1画面の描画速度を 15.80msから 12.02msになり 3.78ms高速化できました!

 速度改善として esp8266-oled-ssd1306ライブラリにプルリク(改善提案)を出しました。
ESP32 improve display speed for SSD1306 #380

 プルリクを出した当日(2023/03/12)にマージ(ThingPulseの責任者が内容を確認して承認)されました!

Releases 4.4.0
 4.4.0で全世界にリリースされました!!

ESP32-D0WDQ6 (revision v1.0)
ESP32-D0WD-V3 (revision v3.0)
ESP32-S3 (revision v0.1)
ESP32-C3 (revision v0.3)

 で動作を確認済みです。(動作確認の為だけに RISC-V系の ESP32-C3が欲しくなったなw >> その為だけに買いましたww)
SSD1306Wire.hノーウェイトの実行時間
I2C_MAX_TRANSFER_BYTE 17(オリジナル)1:00.068
I2C_MAX_TRANSFER_BYTE 128(改造版)0:47.030、13秒高速化(27% UP)
※ ESP32-D0WDQ6での測定結果

 OLED SH1106の同様のプルリク(改善提案)を出しました。
ESP32 improve display speed for SH1106 #382


● 3) I2Cの SCLKの周波数をクロックアップする

 (1)の 90度回転のプログラムのロジック変更で高速化が難しい場合は I2Cの SCLKの周波数をクロックアップする事で超お手軽に高速化が可能です。

 SSD1306自身は SCLKは最高 10MHzで駆動できます。
SSD1306.pdf
Table 13-5 : 3-wire Serial Interface Timing Characteristics
tcycle, Clock Cycle Time, Min. 100ns
・OLED SSD1306 SCLK Max Frequency
OLED SSD1306 SCLK Max Frequency



 Adruinoの SSD1306ライブラリのデフォルトは 700kHz。
esp8266-oled-ssd1306/src/SSD1306Wire.h
int _frequency = 700000
SSD1306Wire(uint8_t _address, int _sda = -1, int _scl = -1, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64, HW_I2C _i2cBus = I2C_ONE, int _frequency = 700000) {

 ESP32の I2Cの SCL SCLKは最高 4MHzで駆動できます。
ESP32 Inter-Integrated Circuit (I2C)
 MAX freq for SCL 4 MHz

 と言う訳で、700kHzから 4MHzにクロックアップして SSD1306の描画を 5.7倍速に高速化します。
 と言いたい所ですが、ESP32のライブラリ内部で上限が 1MHzに制限されています。
 と言う訳で 1MHzに設定します。(理論値で 1.43倍になります)
#define I2C_SCLK_FREQ 1000000
#ifdef I2C_SCLK_FREQ
  SSD1306 display (0x3c, I2C_SDA, I2C_SCL, GEOMETRY_128_64, I2C_ONE, I2C_SCLK_FREQ);
#else
  SSD1306 display (0x3c, I2C_SDA, I2C_SCL);
#endif

 下記の記事にまとめていますが、実際には実測値で 1.37倍になります。(892.19kHz / 648.64kHz)

 ESP I2Cの SCLKの動作周波数の実測値や、動作周波数を黒魔術でクロックアップする{禁断の裏ワザ}は下記の記事に記載しています。

2023/02/18
EPS32の I2Cの SCLの周波数をクロックアップして SSD1306 OLEDの描画を高速化する方法
EPS32の I2Cの SCLの周波数をクロックアップして SSD1306 OLEDの描画を高速化する方法

  OLED SSD1306で I2Cの SCLK周波数をドーピングで高速化して描画速度を爆速にする方法



Tags: [電子工作]

●関連するコンテンツ(この記事を読んだ人は、次の記事も読んでいます)

ESP32で東方の Bad Apple!!の動画を 128 x 64 dotの OLED SSD1306で再生する!
ESP32で東方の Bad Apple!!の動画を 128 x 64 dotの OLED SSD1306で再生する!

  ESP32で東方の Bad Apple!!の動画を再生する!実際にはパラパラ漫画です

2.4インチの大画面 OLED SSD1309で東方の Bad Apple!!の動画を再生する
2.4インチの大画面 OLED SSD1309で東方の Bad Apple!!の動画を再生する

  2.42inch 128x64 OLED LCD Display module SSD1309で Bad Apple!!を再生します

EPS32の I2Cの SCLの周波数をクロックアップして SSD1306 OLEDの描画を高速化する方法
EPS32の I2Cの SCLの周波数をクロックアップして SSD1306 OLEDの描画を高速化する方法

  OLED SSD1306で I2Cの SCLK周波数をドーピングで高速化して描画速度を爆速にする方法

【技適付き】Freenoveの ESP32-S3-WROOMの Basic Starter Kitを買ってみた、カメラ、SD-Card付きの学習キット
【技適付き】Freenoveの ESP32-S3-WROOMの Basic Starter Kitを買ってみた、カメラ、SD-Card付きの学習キット

  Freenove Basic Starter Kit for ESP32-S3-WROOM Onboard Camera Wireless Python C FNK0084

【技適付き】Freenoveの ESP32-WROVERの Ultimate Starter Kitを買ってみた、カメラ付きの学習キット
【技適付き】Freenoveの ESP32-WROVERの Ultimate Starter Kitを買ってみた、カメラ付きの学習キット

  Freenove Ultimate Starter Kit for ESP32-WROVER Onboard Camera Wireless Python C FNK0047

LoRa通信を使用してポストに郵便物が投函されるとスマホの LINE宛に通知する IoTの作り方
LoRa通信を使用してポストに郵便物が投函されるとスマホの LINE宛に通知する IoTの作り方

  LoRaを使用した IoT郵便受け LoRa IoT Mailbox Sensor with LINE Messaging API

ESP32で Slackに「勤怠管理」メッセージをワンボタン操作で投稿する方法
ESP32で Slackに「勤怠管理」メッセージをワンボタン操作で投稿する方法

  Slackの勤怠チャンネルに毎日毎日毎日毎日 手動で投稿するのが馬鹿らしいので ESP32で作った

ESP32-S3で SPIを使う時に Arduinoでエラーが出た話
ESP32-S3で SPIを使う時に Arduinoでエラーが出た話

  ESP32 S3 SPI error VSPI was not declared in this scope

ESP32の I2Sで MCLKが必須の CS4344 DAC Audioを使用する方法
ESP32の I2Sで MCLKが必須の CS4344 DAC Audioを使用する方法

  ESP32で MCLKが必須の CS4344が問題無く使えます

ESP32で SPI接続の Color LCD ST7735S 160x80px IPSを動かす方法
ESP32で SPI接続の Color LCD ST7735S 160x80px IPSを動かす方法

  ESP32 SPI IPS Color LCD ST7735S tutorial

【ソースコード有】ESP32で I2C接続の OLED SSD1306 128x64pxを動かす方法
【ソースコード有】ESP32で I2C接続の OLED SSD1306 128x64pxを動かす方法

  ESP32 I2C OLED SSD1306 tutorial

ESP32-WROOMを購入したらヘンテコ技適マークもどきの基板が届いた話
ESP32-WROOMを購入したらヘンテコ技適マークもどきの基板が届いた話

  Fake ESP32-WROOM Ignore FCC ID and Strange TELEC mark




[HOME] | [BACK]
リンクフリー(連絡不要、ただしトップページ以外は Web構成の変更で移動する場合があります)
Copyright (c) 2023 FREE WING,Y.Sakamoto
Powered by 猫屋敷工房 & HTML Generator

http://www.neko.ne.jp/~freewing/hardware/oled_ssd1306_speed_up_drawing/