フォト
2025年3月
            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 31          
無料ブログはココログ

« 2018年6月 | トップページ | 2018年8月 »

2018年7月の7件の記事

2018年7月28日 (土)

アクティブマトリクス蛍光表示管の実験用表示モジュールver.2の動作確認

VFDふぁんさんより、アクティブマトリクス蛍光表示管の実験用表示モジュール
改訂版 MW25616L実験表示モジュールver.2(VF1711) を頂きました。

最近、電子工作のモチベーションが下降気味でしたが、一気にテンションが上がりました!
VFDふぁんさん、ありがとうございます。

こんな感じのモジュールです。

MW25616L_1

初版からは、いくつか改良が施されているようですが、
変更点については、おって確認したいと思います。

MW25616L_2

改訂版は基板の上下にアクリル版が標準装備になったみたいです。
これは有難いです。ショートの防止になります。

実は、初版の利用において、私の不注意で基板の裏に何かが接触してショートし、
破損させてしまいました(その後、VFDふぁんさんに修理して頂き、再稼働出来ました)。

早速、電源を入れてみるとデモスケッチが動きました。

MW25616L_3

コントラスト・輝度が強くで、私のデジカメでは絞らないと綺麗に撮影出来ませんでした。
表示は鮮明で読みやすいです。

豊四季タイニーBASIC for Arduino機能拡張版(+ VFD MW25616L対応)を動かす

早速、「豊四季タイニーBASIC for Arduino機能拡張版(+ VFD MW25616L対応)」
動作確認をしてます。

折角なので、利用出来るまでの手順を復習がてらまとめてみました。

まずは、スケッチの書込みを行います。
プロジェクトの一式を、下記からダウンロードして解凍します。
   ・ttbasic_MW25616L-master.zip

解凍すると次のファイルが得られます。

02

次に、Arduino IDEを起動して上記のフォルダtbasic 内のttbasic.inoを開きます。

03

デフォルトの設定では、アクティブマトリクス蛍光表示管の利用が出来ません。

ttconfig.hの設定の一部を下記のように変更(赤字の項目)します。
// ** 機能利用オプション設定 *************************************************
#define USE_CMD_PLAY   1  // PLAYコマンドの利用(0:利用しない 1:利用する デフォルト:1)
#define USE_CMD_I2C    1  // I2Cコマンドの利用(0:利用しない 1:利用する デフォルト:1)
#define USE_CMD_VFD    1  // VFDモジュールコマンドの利用(0:利用しない 1:利用する デフォルト:0)
#define USE_RTC_DS3231 1  // I2C接続RTC DS3231の利用(0:利用しない 1:利用する デフォルト:1)
#define USE_I2CEEPROM  1  // I2C EEPROM対応(0:利用しない 1:利用する デフォルト:1)
#define USE_SYSINFO    0  // SYSINFOコマンド(0:利用しない 1:利用する デフォルト:0)
#define USE_GRADE      0  // GRADE関数(0:利用しない 1:利用する デフォルト:1)
#define USE_DMP        0  // DMP$関数(0:利用しない 1:利用する デフォルト:1)
#define USE_IR         0  // IR関数(0:利用しない 1:利用する デフォルト:1)
#define USE_ANADEF     1  // アナログピン定数A0~A7orA15(0:利用しない 1:利用する デフォルト:1)
#define USE_SO1602AWWB 0  // 有機ELキャラクタディスプレイ SO1602AWWB(0:利用しない 1:利用する デフォルト:0)

アクティブマトリクス蛍光表示管の利用にはフラッシュメモリ利用域を消費するため、
他の機能を無効にする必要があります。
今回は、利用頻度の少ない、GRADEコマンド、DMPコマンド、IR関数を無効にしました。

スケッチを書き込みます。

01


次にTeraTarmを起動して、MW25616L実験表示モジュールに接続します。

通信条件

   05

端末の設定

   改行コード、漢字-受信、漢字-送信を下記の設定にします。

   06

接続します。何も表示されない場合は、F5キーを押して再表示させてます。

    04

試しに次のプログラムをコピー&ペーストして実行してみます。
10 VCLS
20 VMSG 10,"CL-VFD実験用表示モジュールVer2登場!"
30 VSCROLL 256,5
40 WAIT 300
50 GOTO 20

07

問題なく動作しました。

Dscn8487

実際に動いている様子です。



問題無く動いているようです。
実際は、もっと鮮明で綺麗な表示なのですが、
私のデジカメの動画撮影機能では、綺麗に撮影出来ませんでした。

もうすこし、動作確認を続けてみます。

2018年7月25日 (水)

プロペラでVT100端末の実装

SNSにてシリアル・ターミナル コンソールをマイコンで実装する話題があり、
「そういえば、プロペラマイコンでVGA+PS/2キーボード構成が簡単に実装出来るなぁ」
と思い出し、やってみました。
久しぶりのプロペラマイコン利用です。

Dscn8443

ゼロからの自作は無理なので、mikrocontroller.netVT100-Terminal (VGA+PS2)
にて紹介されている、「RETROCOMPUTING VT100 VIDEO TERMIAL」を
利用して実装しました。

ソースや回路図等は上記フォーラムに掲載されているリンク先から入手しました。
  ・https://www.mikrocontroller.net/svnbrowser/avr-cp-m/trunk/VT100/


回路図を見ると、キーボードやVGA周りの実装は、
プロペラのデータシートに掲載されているデモボードと同じようです。
比較的少ない部品で実装出来ました。

Dscn8440

一応、これで640x480ドット 80x25文字、 64色同時表示のVGA表示が出来ます。
キーボードはPS/2インタフェースとなります。
シリアル接続のターミナル コンソール VT100相当として利用出来ます。

動作確認に、Arduino Uno互換機にて「豊四季タイニーBASIC for Arduino機能拡張版」
動かしてみました。

本来はパソコン上でTeraTarmの利用を想定しているのですが、
果たして使い物になるでしょうか?


動作確認の様子


予想以上にまともに動作しました。
表示もNTSCとは異なり、非常に綺麗です。

カラー表示も出来ました。
意外と使えそうです。

他にもプロペラを使った類似のものが公開されているので、もう少し調べてみます。

2018/07/28 補足

開発環境はPropeller IDEを利用しています。
こんな感じのツールです。言語としてはSPINというインタプリタ言語が標準です。

Ide

出回っているプログラムは、旧開発環境のPropeller Toolを使っている場合があります。
その場合、Propeller IDEではコンパイルに失敗する場合があります。
Propeller Toolもインストールして、Propeller IDEのライブラリパスの設定にて、
Propeller Toolの付属ライブラリにパスを通す必要があります。


関連記事
Propeller始めました(8) スピーカーで音を出してみる (2015.11.30)
Propeller始めました(3) VGA出力を試してみる (2015.11.14)
Propeller始めました(2) TV出力を試してみる (2015.11.12)

2018年7月14日 (土)

Arduino用漢字フォントライブラリ SPI フラッシュメモリ版の更新

先日の「Arduino用漢字フォントライブラリ SDカード版のSdfat対応」繋がりで、
「Arduino用漢字フォントライブラリ SPI フラッシュメモリ版」を更新しました。
ベースとなっている「Arduino用 SPI接続フラッシュメモリW25Q64ライブラリ」も更新しました。

 

更新ライブラリ
    ・Arduino用漢字フォントライブラリ SPI フラッシュメモリ版
      https://github.com/Tamakichi/Arduino_exfonts

      修正点
          - SPIバスの共有対応
          - Arduino STM32環境での複数のSPIポート利用に対応
          - SPIクロック速度の任意設定対応

    ・Arduino用 SPI接続フラッシュメモリW25Q64ライブラリ
      https://github.com/Tamakichi/Arduino-W25Q64    
          - SPIバスの共有対応
          - Arduino STM32環境での複数のSPIポート利用に対応
          - SPIクロック速度の任意設定対応

    上記2つのライブラリはArduino(Atmega328)、Arduino STM32で利用可能です。

早速、先日の「吾輩は猫である」の漢字フォントを
SDカードからSPI接続フラッシュメモリ(W25Q64)に置き換えてみました。

置き換後の様子

 

スクロールが発生しない文字サイズでは、SDカード版よりもかなり早くなっています。
(表示速度の違いについては、追って追記します^^; )

今回の動作チェックでは、BluePillボードの代わりにRobotDyn製の互換ボードを利用しました。

Dscn8422

BluePillボード(右)と比べるとRobotDyn製互換ボードは、幅が狭いので、
ブレッドボードを有効活用出来ます。

SPIバスの共有動作チェックする際、BluePillボードでは片側が1列しか確保出来ないため、
仕方なく置き換えました。

Dscn8421

RobotDyn製互換ボードは、BluePillボードよりも品質が良く、
ブートローダ付きの製品もあります。
ただし、お値段がBluePillボードよりもちょっと高いです。
(単品購入だと、送料込みでBluePillボードの3倍!!)

STM32F103C8T6, STM32 bootloader compatible for Arduino IDE or STM firmware,
ARM Cortex-M3 Minimum System Development Board

02

送料がかかるので、RobotDyn製の他のパーツとセットで購入が良いと思います。

 

2018年7月11日 (水)

Arduino用漢字フォントライブラリ SDカード版のSdfat対応

以前作成した「Arduino用漢字フォントライブラリ SDカード版」をSdfatライブラリ対応しました。
Arduino標準のSDライブラリを使うよりもパフォーマンスが向上します。

 
  ・Arduino用漢字フォントライブラリ SDカード版
   https://github.com/Tamakichi/Arduino-KanjiFont-Library-SD

Arduino STM32でも利用可能です。BluePillボードでグラフィック液晶モジュールで
日本語表示をやってみました。

      Dscn8411


動いている様子


タッチスクリーンの競合等の動作チェックのため、簡単なお絵描き機能を付けています。
フォントを逐次、SDカードから読んでいるのでさすがに表示は遅いです。

スクロールはグラフィック液晶モジュールから1ラインづつ読み込んだデータを
上にフォント幅分、ずらした位置に描画するここで実装しています。
フォントデータをメモリ上に乗せられないため、先日の高速処理は行えません。
もし、フレームバッファ150kバイト確保できるマイコンボードなら、
マイコンボード上でスクロールして転送するなどして高速に出来るのですが、
BluePillでは、これが限界ですね。

スケッチ

//
// フォントライブラリ利用サンプル
// 作成 2018/07/10 by たま吉さん
//

#include <sdfonts.h>
#include <Adafruit_GFX_AS.h>     
#include <Adafruit_ILI9341_STM.h>
#include <XPT2046_touch.h>

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

// タッチスクリーンCSピン
#define TS_CS  PA3 

// タッチスクリーン領域
#define TS_MINX 600
#define TS_MINY 440
#define TS_MAXX 3460
#define TS_MAXY 3460

#define MY_SPIPORT  2   // SPIポートの指定 1:SPI ,2:SPI2

//
// ※SdFatを使う場合は、sdfontsConfig.hのSDFONTS_USE_SDFATに1を設定し、
//   SdFatまたは、SdFatEX型のグローバルオブジェクトSDを用意すること
//

// 利用するSDオブジェクトの定義
#if SDFONTS_USE_SDFAT == 1
  #include <SdFat.h>
  #if ENABLE_EXTENDED_TRANSFER_CLASS == 1
    SdFatEX  SD(MY_SPIPORT);
  #else
    SdFat    SD(MY_SPIPORT);  
  #endif
#else
  #include <SD.h>
#endif

SPIClass  SPI_2(2); // タッチスクリーン用SPI

// タッチスクリーン制御用
XPT2046_touch ts(TS_CS, SPI_2); // Chip Select pin, SPI port

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

// スクロールアップ
void scrollUp(uint16_t y0,uint16_t h,uint16_t bg) {
  uint16_t sc_w = tft.width();
  uint16_t sc_h = tft.height();
  uint16_t buf[sc_w];

  for (uint16_t y=h+y0; y < sc_h; y++) {  
    tft.readPixels(0, y, sc_w-1, y, buf);
    tft.setAddrWindow(0, y-h, sc_w-1, y-h);
    tft.pushColors(buf, sc_w, 0);
  }
  tft.fillRect(0, sc_h-h, sc_w-1, h, bg);
}

// 指定位置に1文字表示
void mputc(uint16_t x, uint16_t y, uint8_t* buf, uint16_t fg, uint16_t bg) {   
  uint16_t w = SDfonts.getWidth();
  uint16_t h = SDfonts.getHeight();
  int16_t byteWidth = (w + 7)>>3;
  uint8_t byte = 0;

  // フォントの描画
  tft.setAddrWindow(x, y, x+w-1, y+h-1); // 描画領域の設定
  for(int16_t j=0; j<h; j++, y++) {
    for(int16_t i=0; i<w; i++) {
      byte = (i & 7) ? byte<<1 : buf[j * byteWidth + (i>>3)];
      tft.pushColor((byte & 0x80) ? fg : bg);
    }
  }
}

// 指定位置に文字列表示
void mprint(uint16_t x, uint16_t y, char* str, uint16_t fg, uint16_t bg) { 
  uint8_t buf[MAXFONTLEN]; 
  int16_t   len,x0 = x, y0 = y;
  char* pUTF8 = str;

  SDfonts.open();   // フォントのオープン
  while ( pUTF8 = SDfonts.getFontData(buf, pUTF8) ) {  // フォントの取得
    mputc(x, y, buf ,fg, bg);
    if (x + SDfonts.getWidth()*2 < tft.width()) {
      x += SDfonts.getWidth()+1;  
    } else {  
      x = x0;
      if (y + SDfonts.getHeight()*2 < tft.height()) {
        y += SDfonts.getHeight()+2;
      } else {  
        scrollUp(y0, SDfonts.getHeight()+2, bg);
      }
    }
  }      
  SDfonts.close();   // フォントのクローズ
}

void setup() {
  Serial.begin(115200);
  delay(1000);
#if SDFONTS_USE_SDFAT == 0
  SPI.setModule(2);
#endif

  // フォント管理の初期化
  if(!SDfonts.init(PB0)) {                
    Serial.println(F("sdfonts init error"));
    exit(1);
  }
  Serial.println(F("test sdfonts liblary"));
  
  ts.begin();
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK); 
}

static const char* text =
   "吾輩は猫である。名前はまだ無い。"
   "どこで生れたかとんと見当がつかぬ。"
   "何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。"
   "吾輩はここで始めて人間というものを見た。"
   "しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。"
   "というのは時々我々を捕つかまえて煮にて食うという話である。"
   "しかしその当時は何という考もなかったから別段恐しいとも思わなかった。"
   "ただ彼の掌のひらに載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。"
   "掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始めであろう。"
 ;

void loop() {
  //7種類のフォントサイズで文字列表示
  for (uint8_t i =0 ; i <7; i++) {
    tft.fillScreen(ILI9341_BLACK);
    SDfonts.setFontSizeAsIndex(i);
    mprint(2, 2, (char*)text, ILI9341_WHITE, ILI9341_BLACK);

    // テキスト表示後、3秒間タッチスクリーンを使って描画出来る
    // (SDfontsとタッチスクリーンのSPIバス共有利用の動作確認)
    uint32_t t = millis() + 3000;
    while(millis() < t) {
      TS_Point p = ts.getPoint();
      p.x = tft.width() - map(p.x, TS_MINX, TS_MAXX, 0, tft.width());
      p.y = tft.height() - map(p.y, TS_MINY, TS_MAXY, 0, tft.height());

      if (p.x >=0 && p.x < tft.width() && p.y >=0 &&  p.y < tft.height()) {
         tft.fillCircle(p.x, p.y, 3, ILI9341_RED);
      }       
    }
  }
}

表示速度の改善方法としては、
Arduino用漢字フォントライブラリ SPI フラッシュメモリ版」への置き換えで
かなり改善出来ると思います。これについてもちょっとやってみます。
Arduino Unoでも同じこと出来るかも試したいと思います。

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にこのテキスト描画を採用しようと思います。

2018年7月 4日 (水)

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

Sdfatライブラリを使った画像ロード&表示時間の改善

前回からの続きです。

2018/07/07 内容を大幅修正しました

今回はTFT、SDカード、タッチスクリーンを同時利用しました。

04

テスト用スケッチの起動時間を短縮するために色々と試行錯誤してみました。
(テスト用スケッチは起動後、SDカードから猫画像をロードして表示し、お絵描きが出来ます)

改善策 その1 ・・・・ 2つのSPIバスの割り振りの最適化

TFTにSPI1、SDカード・タッチスクリーンにSPI2を割り当てた方が、
描画のパフォーマンスが向上します。

BluePillボードの仕様として
SPI1ではクロック上限が36MHz、、SPI2ではクロック上限が18MHzとなります。
TFTの表示制御ではSPI1 クロック 36MHzを利用するとパファーマンスが2倍に向上します。

単純な画面塗りつぶしの速度の比較
   fillScreen(ILI9341_BLUE);

  SPI1 システムクロック上限 36MHz設定時: 34.2 ms
  SPI2 システムクロック上限 18MHz設定時: 68.3 ms

一方、SDカードでは18MHz以上では、動作しないため、SPI1を使うメリットがありません。

そこで、グラフィック液晶モジュールのSPIバスの割り当て・結線は次のようにしました。

06



改善策 その2 SDライブりからSdfatライブラリへの変更


Sdfatライブラリは、DMAの利用が出来るため、SDライブラリからSdfatライブラリに
変更することで、パフォーマンスの改善が期待出来ます。

実際にどれくらい、パフォーマンスが改善するかを、確認してみました。
写真の猫画像 320x240ドット(24ビット色 230,454バイト)のロード&表示時間を比較してみました。

なお、Sdfatライブラリは下記から入手しました。
  ・greiman/SdFat -  Arduino FAT16/FAT32 Library
    https://github.com/greiman/SdFat

比較表

05_3
Adafruit_ILI9341_STMライブラリは、前回の不具合対応したものを利用しています。

SDライブラリから、SdFatライブラリに単純に置き換えると、
2,187msかかっていた表示が512msと劇的に改善しました。

また、SdFatのコンフィグレーション定義のSdFatConfig.hのうち、
使用するバッファ容量、拡張機能利用有無に関する設定をいじって性能と
メモリ消費について調べてみました。

変更操作をしたのは、下記の3つの設定です。
  1) ENABLE_EXTENDED_TRANSFER_CLASS
  2) USE_SEPARATE_FAT_CACHE
  3) USE_MULTI_BLOCK_IO

1)の設定を1にすると拡張版のSdfatExクラスを利用することが出来ます。
デフォルトでは無効になっています。
有効にすると、ノーマルの512msから440msに改善しました。
この設定において、SRAM消費量の増加は大したことないので、有効にした方が良いようです。

2)3)はキャッシュ、ブロック数に関する設定です。
デフォルトでは有効になっています。
無効にした場合、512msから520msと若干遅くなりましたが、
その分SRAMの使用量が528バイト減りました。
今回のデモアプリでは、この設定は、無効でも良いかもしれません。

次に、1)を有効にしつつ、2)3)を無効にした場合も測定してみました。
ノーマルの512msから451msと早くなり、SRAM使用量は256バイト増加となりました。
この設定がベストかもしれません。

最後の色のついた測定値は、画像ファイルと表示処理に修正したものです。
DMA転送を利用出来るよう、画像ファイルに16ビットカラーのビットマップ画像を利用しました。
読み込んだファイルは無加工で済むためDMAを使った転送が利用出来ます。
224msと更に改善出来ました。
16ビットカラーのビットマップ画像はWindows的にはサポートされていないので、
gimp2で変換して作成しました。
(標準のペイントツールやビュアーでも閲覧は可能です)

以上の測定から、
SDライブラリからSdfatライブラリへの変更はかなり有益であると思います。
プログラムサイズ、SRAMの使用量の増加もさほど大きくないです。
16ビットカラーのビットマップ画像の利用も効果ありです。

利用したスケッチ
Adafruit_ILI9341_STMライブラリのspitftbitmapとtouchpaintを元に作成しました。

/***************************************************
  This is our Bitmap drawing example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

// SDカード用ライブラリの選択
#define USE_SDFAT 1 // 0:SDライブラリ利用, 1:SdFatライブラリ利用

// SdFatライブラリ利用時は下記の設定を好みで行う
// SdFatConfig.h:
//   ENABLE_EXTENDED_TRANSFER_CLASS (1の場合、SdFatEXを使う) 
//   USE_SEPARATE_FAT_CACHE
//   USE_MULTI_BLOCK_IO

#include <SPI.h>
#if USE_SDFAT == 1
  #include <SdFat.h>
  #if ENABLE_EXTENDED_TRANSFER_CLASS == 1
    SdFatEX  SD(2);
  #else
    SdFat  SD(2);  
  #endif
  #define SPI_SPEED SD_SCK_MHZ(18)
#else
  #include <SD.h>
#endif

#include <Adafruit_GFX_AS.h>      // Core graphics library
#include <Adafruit_ILI9341_STM.h> // Hardware-specific library
#include <XPT2046_touch.h>

// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.  For Arduino Uno,
// Duemilanove, etc., pin 11 = MOSI, pin 12 = MISO, pin 13 = SCK.

// タッチスクリーン領域
#define TS_MINX 600
#define TS_MINY 440
#define TS_MAXX 3460
#define TS_MAXY 3460

// タッチスクリーンCSピン
#define TS_CS  PA3 

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

SPIClass  SPI_2(2); // タッチスクリーンで共有

// タッチスクリーン制御用
XPT2046_touch ts(TS_CS, SPI_2); // Chip Select pin, SPI port

// TFT制御用
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);
//SPISettings TFT_SPISet(SAFE_FREQ, MSBFIRST, SPI_MODE0, DATA_SIZE_16BIT);

// SDカード選択
#define SD_CS PB0

// Size of the color selection boxes and the paintbrush size
#define BOXSIZE 40
#define PENRADIUS 3
int oldcolor, currentcolor;

// システム情報の表示
void iinfo() {
  char top = 't';
  uint32_t adr = (uint32_t)&top;
  uint8_t* tmp = (uint8_t*)malloc(1);
  uint32_t hadr = (uint32_t)tmp;
  free(tmp);

  // スタック領域先頭アドレスの表示
  Serial.print("Stack Top:"); Serial.println(adr,HEX);
  
  // ヒープ領域先頭アドレスの表示
  Serial.print("Heap Top :"); Serial.println(hadr,HEX);

  // SRAM未使用領域の表示
  Serial.print("SRAM Free:"); Serial.println(adr-hadr,DEC);
}

void setup(void) {
  Serial.begin(115200);
  delay(1000);
  Serial.println((uint32_t)SPI.dev(),HEX);

#if USE_SDFAT == 1
  if (!SD.begin(SD_CS,SPI_SPEED)) {
#else
  if (!SD.begin(SD_CS)) {
#endif
    Serial.println("failed!");
  }

  ts.begin();
  tft.begin();  
  Serial.println((uint32_t)SPI.dev(),HEX);
  //Serial.println(micros(),DEC);
  tft.fillScreen(ILI9341_BLUE);
  //Serial.println(micros(),DEC);
  Serial.println("OK!");
  bmpDraw("CAT.BMP", 0, 0);
  iinfo();
  // make the color selection boxes
  //SPI_2.beginTransaction(TFT_SPISet);
  tft.fillRect(0, 0, BOXSIZE, BOXSIZE, ILI9341_RED);
  tft.fillRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, ILI9341_YELLOW);
  tft.fillRect(BOXSIZE * 2, 0, BOXSIZE, BOXSIZE, ILI9341_GREEN);
  tft.fillRect(BOXSIZE * 3, 0, BOXSIZE, BOXSIZE, ILI9341_CYAN);
  tft.fillRect(BOXSIZE * 4, 0, BOXSIZE, BOXSIZE, ILI9341_BLUE);
  tft.fillRect(BOXSIZE * 5, 0, BOXSIZE, BOXSIZE, ILI9341_MAGENTA);

  // select the current color 'red'
  tft.drawRect(0, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
  currentcolor = ILI9341_RED;
  //SPI_2.endTransaction();

}

void loop() {
  // Retrieve a point
  TS_Point p = ts.getPoint();

  if ( (p.z <=800) || (p.z > 3000))
    return;
   
 int16_t tmp = p.x;
  p.x = p.y;
  p.y = tmp;
  
  Serial.print("Z = "); Serial.print(p.z);
  Serial.print(" X = "); Serial.print(p.x);
  Serial.print(" Y = "); Serial.print(p.y);

  //SPI_2.beginTransaction(TFT_SPISet);

  // Scale from ~0 ~ TS_MAXX to tft.width using the calibration #'s
  p.x = tft.width() - map(p.x, TS_MINX, TS_MAXX, 0, tft.width());
  p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());

  Serial.print("("); Serial.print(p.x);
  Serial.print(", "); Serial.print(p.y);
  Serial.println(")");

  if (p.y < BOXSIZE) {
    oldcolor = currentcolor;

    if (p.x < BOXSIZE) {
      currentcolor = ILI9341_RED;
      tft.drawRect(0, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    } else if (p.x < BOXSIZE * 2) {
      currentcolor = ILI9341_YELLOW;
      tft.drawRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    } else if (p.x < BOXSIZE * 3) {
      currentcolor = ILI9341_GREEN;
      tft.drawRect(BOXSIZE * 2, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    } else if (p.x < BOXSIZE * 4) {
      currentcolor = ILI9341_CYAN;
      tft.drawRect(BOXSIZE * 3, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    } else if (p.x < BOXSIZE * 5) {
      currentcolor = ILI9341_BLUE;
      tft.drawRect(BOXSIZE * 4, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    } else if (p.x < BOXSIZE * 6) {
      currentcolor = ILI9341_MAGENTA;
      tft.drawRect(BOXSIZE * 5, 0, BOXSIZE, BOXSIZE, ILI9341_WHITE);
    }

    if (oldcolor != currentcolor) {
      if (oldcolor == ILI9341_RED)
        tft.fillRect(0, 0, BOXSIZE, BOXSIZE, ILI9341_RED);
      if (oldcolor == ILI9341_YELLOW)
        tft.fillRect(BOXSIZE, 0, BOXSIZE, BOXSIZE, ILI9341_YELLOW);
      if (oldcolor == ILI9341_GREEN)
        tft.fillRect(BOXSIZE * 2, 0, BOXSIZE, BOXSIZE, ILI9341_GREEN);
      if (oldcolor == ILI9341_CYAN)
        tft.fillRect(BOXSIZE * 3, 0, BOXSIZE, BOXSIZE, ILI9341_CYAN);
      if (oldcolor == ILI9341_BLUE)
        tft.fillRect(BOXSIZE * 4, 0, BOXSIZE, BOXSIZE, ILI9341_BLUE);
      if (oldcolor == ILI9341_MAGENTA)
        tft.fillRect(BOXSIZE * 5, 0, BOXSIZE, BOXSIZE, ILI9341_MAGENTA);
    }
  }
  if (((p.y - PENRADIUS) > BOXSIZE) && ((p.y + PENRADIUS) < tft.height())) {
    tft.fillCircle(p.x, p.y, PENRADIUS, currentcolor);
  }
  //SPI_2.endTransaction();
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print(F("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.color565(r,g,b));                  
          } // end pixel
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

16ビットカラーのビットマップ画像対応ではbmpDraw()関数を次のように修正しています。

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[320*2];       // pixel buffer (R5+G6+B5 per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print(F("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      //if((bmpDepth == 16) && (read32(bmpFile) == 0)) { // 0 = uncompressed
      if((bmpDepth == 16)) { // 0 = uncompressed
        read32(bmpFile);
        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 2 + 2) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          bmpFile.read(sdbuffer, w*2);
          tft.pushColors((&sdbuffer[0]), w,1);
                              
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}

描画の高速化の過程で、SDカードの初期化(begin())が失敗してしまうことに悩まされました。
特にノートPCにUSB接続して利用すると頻発しました。
GNDとVCC間にパスコンを入れたら安定しました。

さて、TFTの描画に関しての高速化については、もう少し何とかしたいと思うのですが、
Blue Pillボードを使った描画において、次のデモ動画を見つけました。


グラフィック液晶のインタフェースはSPIではなく8ビットパラレル接続のようです。
SDカードからデータを読み込みながら表示でここまで高速に表示できるんですね。

2018年7月 1日 (日)

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

Adafruit_ILI9341_STM ライブラリの不具合?でハマる


前回からの続きです。TFTとSDカードの併用を試したのですが、ちょっとハマりました。

Adafruit_ILI9341_STM ライブラリを使て、SDカードからビットマップ画像ファイルをロードして
表示してみたのですが、Adafruit_ILI9341_STM ライブラリにはバグがあるようです。
安定版にはなかったバグです。

ここ数日、そのバグに悩まされてしまいました。

01

SPIポートの同時に2つ利用(SPI1とSPI2)する場合で、
TFT液晶ディスプレイの制御にSPI2を使うと、SPI(SPI1)オブジェクトの値が
書き換えられてしまい、SPIオブジェクトが利用出来なくなりました。

原因は、Adafruit_ILI9341_STM ライブラリの参照型で利用するSPIオブジェクトを
メンバ変数として利用しているためでした。

Adafruit_ILI9341_STMのインスタンス生成時、利用するSPIポートを初期値として
クラス定義内にて

private:
  SPIClass & mSPI = SPI;
としています。
利用するSPIポートを変更(例えばSPI2)する場合、SPIClassのインスタンスを生成し
それをメンバ関数 begin()に指定することで行う仕様なのですが(次の手順)、
  Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);
  SPIClass  SPI_2(2);
  tft.begin(SPI_2);

この時、begin()関数の内部では、
  void Adafruit_ILI9341_STM::begin(SPIClass & spi, uint32_t freq)   {
     mSPI = spi;

ってなことをやってます。

一見良さそうですが、mSPIは参照型であり、SPIオブジェクトの別名にすぎません。
すなわち、
   SPI = spi;
と等しく、spiはSPI_2を参照しているので
   SPI = SPI_2;
と等価、同じ型のオブジェクトの代入演算はコピーが代入されるので、
SPIはSPI_2の内容に書き換えられてしまいます。
これでは、SPIオブジェクトを使っている通信・制御が不能となってしまいます。

対策としては、SPIオブジェクトを使う前に通信条件等を再設定します。
その分、処理に時間がかかるため、画像表示の処理は遅くなってしまいました。

スケッチ(サンプルのspitbitmapを修正して利用しています)
/***************************************************
  This is our Bitmap drawing example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/



#include <Adafruit_GFX_AS.h>    // Core graphics library
#include "Adafruit_ILI9341_STM.h" // Hardware-specific library
#include <SPI.h>
#include <SD.h>

// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.  For Arduino Uno,
// Duemilanove, etc., pin 11 = MOSI, pin 12 = MISO, pin 13 = SCK.

#define TS_MINX 600
#define TS_MINY 440
#define TS_MAXX 3460
#define TS_MAXY 3460

// 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);
SPISettings TFT_SPISet(SAFE_FREQ, MSBFIRST, SPI_MODE0, DATA_SIZE_16BIT);
SPIClass  SPI_2(2);
// SDカード選択
#define SD_CS PB0

void setup(void) {
  Serial.begin(115200);
  delay(1000);
  Serial.println((uint32_t)SPI.dev(),HEX);
  tft.begin(SPI_2);
  Serial.println((uint32_t)SPI.dev(),HEX);
  tft.fillScreen(ILI9341_BLUE);

  SPI.setModule(1);
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
  }

  Serial.println("OK!");
  bmpDraw("CAT.BMP", 0, 0);
}

void loop() {
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print(F("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            SPI.setModule(2);
            tft.pushColor(tft.color565(r,g,b));
            SPI.setModule(1);                  
          } // end pixel
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

SDカードの利用にはArduino(UNO)用のSDライブラリを利用しています。
とりあえず、Arduino STM32のフォーラムにて本件のバグを連絡しました。

Adafruit_ILI9341_STM ライブラリを自分で修正する場合、コンストラクタにて
利用するSPIを指定する感じの対応かな。

グラフィック液晶に関してはしばらく調査を続けます。

« 2018年6月 | トップページ | 2018年8月 »