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

STM32

2018年8月20日 (月)

Arduino STM32のUSB HIDインタフェースの新しいブートローダー (2)

記事「Arduino STM32のUSB HIDインタフェースの新しいブートローダー」を書いた時点では
Arduino IDEのメニューにてHIDブートローダ経由書き込み指定が出来なかったのですが、
先日の更新後のArduino STM32パッケージをインストールして確認したところ、
メニューに組み込まれていました。

そこで、実際にHID経由でのスケッチ書き込みを試してみました。

09_2


試してみたボード
  ・Blue Pillボード(写真 左)
  ・Bkack Pillボード(写真 中)
  ・RobotDym Blue Pill互換ボード(写真 右)

  Dscn8723

まずはHID用のブートローダを書き込む必要があります。
HID用ブートローダは、本来は下記のサイトが公式サイトになるのですが、
rogerclarkmelbourne/STM32_HID_Bootloader
まだ、Blue Pill用のものしか置いていないようです。若干古い可能性があります。

当面は、オリジナル版開発者の下記のサイトから入手した方が良さそうです。
https://github.com/Serasidis/STM32_HID_Bootloader

ダウンロードして解凍します。
HIDブートローダは、
  STM32_HID_Bootloader\bootloader_F1\bootloader_only_binaries\の中のファイルを利用します。

  ・Blue Pillボード、RobotDym Blue Pill互換ボード
    hid_generic_pc13.bin を利用
  ・Bkack Pillボード
    hid_generic_pb12.bin を利用

ST-Linkを使ってHIDブートローダを書き込みます。

  Dscn8724

ツールは、ST-LINK Utilityを使いました。   

  14

  HIDブートローダのファイルをロードして書き込みます。
  15


  書き込み後、パソコンにUSB経由で接続します。 
  この時、ボードマイコン(ここではBlue Pill)のBOOT1を1に設定してから、
  パソコンに接続します。  LEDが激しく点滅します。
  (この手順が分からす、なかなか書込み出来ませんでた。試行錯誤のため、
    手順としては間違っている可能性があります。)


  Dscn8725

  つなげると、パソコン(Windows 10)でSTM32F HID Bootlooader が認識されました。
  10

デバイスを調べてみると、次のような感じで登録されています。

11

試しに、Lチカスケッチを書き込んでみます。

17

問題無く書き込めました。
マイコンボードのBOOT1を0側にして、リセットボタンを押します。
これで、書き込んだスケッチが実行されます。

再度スケッチを書き込むには、 マイコンボードのUSB接続を抜き、
BOOT1を1にして、パソコンに接続します。
なぜか、USBの接続を一旦抜いてからでないとダメでした。
このあたりは、ちょっと面倒です。
もしたら、手順として間違っている可能性があるため、もう少し試してみます。

HIDブートローダのメリットとしては、
ブートローダが8Kバイトから4Kバイトに小さくなり、利用出来るフラッシュメモリの領域が
4kバイト増えた点と、ドライバーのインストールが不要な点です。

手順(もし、今回のやり方が正しいとすれば)的には、HIDブートローダ経由は、
BOOTピンの設定変更とボートの抜き差しがちょっと面倒です。

USBはCDC(シリアル通信)としても使うので、
HIDブートローダを有効にするために、BOOT1の設定が必要みたいです。

現時点では、HIDブートローダの書込みはWindowsしか対応していないようです。

関連記事
Arduino STM32のUSB HIDインタフェースの新しいブートローダー

2018年8月19日 (日)

TV出力ライブラリ更新を豊四季Tiny BASIC for Arduino STM32に適用する

先日、修正したArduino STM32用のTV出力ライブラリ
豊四季Tiny BASIC for Arduino STM32に適用し、コマンドにてNTSC信号の
横位置と縦位置を補正出来るようにしました。

以前入手した3.5インチのTFT LCDモジュール(下記の製品)を使った場合、 
  Podofo 3.5" TFT LCD Display RGB LCD Display Module Kit
  01

デフォルトでは、表示画像の上部が欠けてしまい、どうすることも出来ませんでした。

Dscn8698

Dscn8701

修正ライブラリを組み込み、コマンドにて補正出来るようにしました。
  コマンド
    CONFIG 0,垂直同期補正,横位置補正, 縦位置補正


補正後の画面

Dscn8705

補正した値は、SAVECONFIG コマンドにて内部フラッシュメモリに保存することで
再起動しても状態を保ちます。

補正の様子

ついでに、以前試したブラウン管テレビでの補正も試してみました。

補正前は、左側が欠けてしまっていました。

Dscn8709

補正して、ちょうどよいポジションで表示出来るようになりました。

Dscn8716

ブラウン管テレビでの表示は、次のような感じです。
試したのは、パナソニック 2001年製造の14インチのテレビです。


文字等の表示は、綺麗とは言えませんが、実用的レベルです。
思っていたよりも、まともに表示出来ています。
久しぶりにブラウン管テレビで映像を見ましたが、これはこれで良いですね。

この更新分は、もう少しテストを行ってから公開したいと思います。

2018年8月 5日 (日)

Arduino STM32用のTV出力ライブラリを更新しました

Arduino STM32用のTV出力ライブラリを更新しました。

01

更新したライブラリ
  ・Arduino STM32 TVoutライブラリ
   https://github.com/Tamakichi/ArduinoSTM32_TVout

   修正内容
     ・横解像度448ドット指定時、ビット・バンディングを使ったドット描画位置がずれる
       不具合の対応を行いました。
       これによりdraw系の関数の描画不具合を解消しました。

      ・draw_rect()、draw_circle()の塗りつぶし指定時に塗りつぶし正しく行えない不具合
       の対応を行いました。

      ・adjust()関数に映像の横表示開始位置補正、縦表示開始位置補正を行うための
       引数を追加し、映像表示位置の調整を行えるようにしました。

  ・Arduino STM32 NTSCビデオ出力ラブラリ
   https://github.com/Tamakichi/ArduinoSTM32_TNTSC

   修正内容
      ・adjust()関数に映像の横表示開始位置補正、縦表示開始位置補正を行うための
       引数を追加し、映像位置の調整を行えるようにしました。

   ※ NTSCビデオ出力ラブラリは、TVoutライブラリの下位レイアのライブラリです。
       NTSCビデオ出力のみを行います。
       TVoutライブラリは、描画等のAPIを提供するライブラリです。

   
従来は利用するモニターによっては、表示する映像が上や左に寄り過ぎて欠けたり
した場合、どうすることも出来なかったのですが、今回の修正で補正を行えるようにしました。

こんな感じで、右側が欠けてしまった場合、

02

関数 adjust(0,8,0) にて、映像を左にずらすことが出来ます。

03

さて、Arduino STM32 TVoutライブラリは、ArduinoのTVoutの描画処理を
ベースにしているのですが、オリジナル版に不具合があり、意外とハマりました。

使い勝手も今一よろしく無いので、ライブラリを2つに分けていたり利点を生かし、
Adafruit_GFXを利用したバージョンを別途用意してそちらを利用しようと考えています。



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月27日 (水)

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

TFT表示制御とタッチスクリーン制御用SPIバスの共有利用

前回の続きです。前回はTFT液晶表示とタッチスクリーンでそれぞれにSPIバスを
割当ていましたが、SPIバスを共有利用する方式に変更しました。

機能としては前回と全く同じです。

結線図
01
結線表

02

ブレットボード上の実装

03


スケッチ

/***************************************************
  This is our touchscreen painting example for the Adafruit ILI9341 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
 ****************************************************/

// 2018/06/25,たま吉さん, XPT2046コントローラー対応
// 2018/06/27,たま吉さん, SPI1のみ利用に修正

#include <SPI.h>
#include <Adafruit_GFX_AS.h>    // Core graphics library
#include <Adafruit_ILI9341_STM.h>
#include <XPT2046_touch.h>

// This is calibration data for the raw touch data to the screen coordinates
#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

// タッチパネルCSピン
#define CS_PIN  PA3 

// 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_TS(1);
XPT2046_touch ts(CS_PIN, SPI_TS); // Chip Select pin, SPI port

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

void setup(void) {
  Serial.begin(115200);
  Serial.println(F("Touch Paint!"));

  tft.begin(SPI);
  tft.fillScreen(ILI9341_BLACK);

  ts.begin();
  Serial.println("Touchscreen started");

  // make the color selection boxes
  SPI.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.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.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.endTransaction();
}

タッチ・スクリーンライブラリ XPT2046_touch は、API利用の都度、SPIの通信条件の
再設定を行ってくれており、他のデバイス利用との共有が出来るのですが、
TFT制御用のライブラリ Adafruit_ILI9341_STM は行ってくれません。
他のデバイスでSPIを利用した後は、TFT制御のためのSPIの設定は正しくない状態に
なります。

そこでAdafruit_ILI9341_STM  のAPI利用の前に、SPIの通信条件の設定を
行うよう、修正しました。
具体的には、SPISettings型のSPI通信条件を定義しておき、
    SPISettings TFT_SPISet(SAFE_FREQ, MSBFIRST, SPI_MODE0, DATA_SIZE_16BIT);
この定義を使って、TFT制御の直前に
   SPI.beginTransaction(TFT_SPISet);
を実行して、通信条件を設定しています。
TFT制御終了後は
   SPI.endTransaction();
を実行します(ただし、この命令、内部的には何も行っていません。お飾りです)

次回はこの方式にて、SDカードスロットの併用も試してみたいと思います。

2018年6月25日 (月)

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

グラフィック液晶搭載タッチスクリーン利用の調査

前回の続きです。

グラフィック液晶モジュールのタッチスクリーンの利用を調査しました。

Dscn8344

私が所有しているグラフィック液晶モジュールには、タッチスクリーンを
利用することが出来ます。

裏側のICを調べると、XPT2046というタッチスクリーン・コントローラが搭載されていました。

03

このコントローラーはTI社のTSC2046と同等のようで、探すと日本語のデータシートがあります。

幸いにして、Arduino STM32には標準でXPT2046用のライブラリが提供されています。
  ・Serasidis_XPT2046_touch - A simple XPT2046 Touch Screen library for STM32 micro-controllers
このライブラリを使い、
Adafruit_ILI9341_STMライブラリのサンプルスケッチ touchpaint.inoを修正して
簡単なお絵描きプログラムを作成してみました。

オリジナルのtouchpaint.inoはAdafruit製のグラフィック液晶モジュール用で
コントローラにSTMPE610を使っていることを前提としているので、そのままでは動きません。
コントローラー周りをXPT2046対応に修正しました。

XPT2046はSPIインタフェースで利用しますが、液晶パネルの制御にもSPIを利用してます。
そのため、2つあるSPIバスをそれぞれに割り当てました。
液晶パネルの表示にSPI1、XPT2046にSPI2を利用しました。

接続図
01

結線表

02

スケッチ
/***************************************************
  This is our touchscreen painting example for the Adafruit ILI9341 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
 ****************************************************/

// 2018/06/25,たま吉さん, XPT2046コントローラー対応

#include <SPI.h>
#include <Adafruit_GFX_AS.h>    // Core graphics library
#include <Adafruit_ILI9341_STM.h>
#include <XPT2046_touch.h>

// This is calibration data for the raw touch data to the screen coordinates
#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

// タッチパネルCSピン
#define CS_PIN  PA3 

SPIClass SPI_2(2);
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);
XPT2046_touch ts(CS_PIN, SPI_2); // Chip Select pin, SPI port

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

void setup(void) {
  Serial.begin(115200);
  Serial.println(F("Touch Paint!"));

//  tft.begin(SPI_2);
  tft.begin();
  tft.fillScreen(ILI9341_BLACK);

  ts.begin();
  Serial.println("Touchscreen started");

  // make the color selection boxes
  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;
}

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);

  // 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);
  }
}

スケッチを書き込んで実行すると、簡単なお絵描きが出来ます。
ただし、若干座標位置、描画可能範囲がずれいます。

下記の図は、実行中のシリアル出力です。

各行の後半の括弧内の数値がタッチして検知した座標x、yです。
xの値がマイナスの値になっちゃっています。

04

プログラム内の下記の箇所で、描画範囲部分のスクリーン位置を実測値にて設定しています。
#define TS_MINX 600
#define TS_MINY 440
#define TS_MAXX 3460
#define TS_MAXY 3460

タッチスクリーンでは、x、yの座標と圧力zを12ビットのアナログ値で測定しています。
12ビットなので0~4095の範囲のなります。
定義した上記の値は、描画領域の上下左右の境界部分の値です。

この値を実際の320x240ドットの範囲にスケール変換して座標を得ています。
測定した値が正しくないために、取得した座標にずれが生じてしまっているようです。
もう少し厳密に測定する必要がありそうです。

液晶パネル上に被膜抵抗(タッチスクリーン)が張り付いていて、その上を押すと
0~4095の値が 得られるわけですが、タッチスクリーンは液晶の描画範囲の外側も含んでいます。

個々のモジュールでは、タッチスクリーンを貼った具合(ずれ等)は微妙に異なります。
ですので、個々のモジュール毎に描画の上下限範囲の値を測定する
キャリブレーションが必要となります。

キャリブレーション設定支援のスケッチなんかも必要ですね。

より以前の記事一覧