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

arduino

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の値が 得られるわけですが、タッチスクリーンは液晶の描画範囲の外側も含んでいます。

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

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

2018年6月23日 (土)

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

Arduino STM32 最新版 でのグラフィック液晶利用調査

以前、調査・検証した「次はSTM32ボードを積極的に使ていきたい(13) - グラフィック液晶」の再調査です。

マイコンボードとして、Blue Pillボード(STM32F103C8T6搭載)を使います。
グラフィック液晶モジュールは、コントローラーにILI9341(SPI接続版)をつかった製品を使います。
開発環境としては、Arduinoを利用します。

Dscn8316

ここ数ヶ月で利用しているArduino STM32モジュールにかなりの仕様変更がありまして、
コントローラーにILI9341を使った液晶モジュール利用環境にも影響がありました。
そのため、改めて動作確認を行いました。


影響する仕様変更点

Adafruit_GFX_ASライブラリの実装形態の変更

(概要)
  コントローラーILI9341用ライブラリAdafruit_ILI9341_STMは描画処理等に、
  Adafruit_GFX_ASライブラリを利用しています。

  Adafruit_GFX_ASは従来は、Adafruit_GFXライブラリをSTM32環境に修正したものが
  提供されていましたが、Adafruit_GFXからの派生クラスとして実装する形態に変更されました。

(影響)
  別途、Adafruit_GFXを各自でインストールする必要があります。

(メリット)
  従来は、Adafruit_GFXの古いバージョンがベースだったため、グラフィック液晶関連では
  Arduinoと同等の機能を利用することが出来ませんでしたが、この修正により利用出来る
  ようになりました。

それでは、最新版のArduino STM32モジュールを使った環境にて、
手持ちのグラフィック液晶モジュールの動作確認を行ってみることにします。

Adafruit_GFXライブラリのインストール
ライブラリマネージャにてAdafruit_GFXを検索し、最新版をインストールします。
Photo

この方法でインストールすると、ライブラリ\librariesに配置されるライブラリの
フォルダ名がarduino_981956となってしまいました。
これだと分かりにくいので、Arduino IDEを一旦終了し、Adafruit_GFXに変更して
Arduino IDEを再起動して利用しました。

サンプルスケッチ「stm32_graphicstest」を使った動作確認

Blue PillボードのSPIバスは2つありますが、ここではSPI1を使います。
SPI1は次のピンを利用します。
  PA4 : NSS
  PA5 : SCK
  PA6 : MISO
  PA7 : MOSI

液晶モジュールとの結線は次のような感じです。

・結線図

01

・結線表

02

スケッチの読み込みと修正

メニューのスケッチ例から
Adafruit_ILI9341_STMのサンプルスケッチ「stm32_graphicstest」を開きます。

#include "SPI.h"
#include "Adafruit_GFX_AS.h"
#include "Adafruit_ILI9341_STM.h"

#define TFT_CS  PA0                  
#define TFT_RST PA1
#define TFT_DC  PA2
            
//Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC);
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial.begin(115200);
  delay(3000);
  Serial.println("ILI9341 Test!"); 
 
  tft.begin();

RESETをBlue Pillのリセットと連動させる場合はBlue PillのRSTへの接続でもOkです。
その場合は、TFT_RSTを-1にするか、コメントアウトしているコンストラクタの方を
利用して初期化します。

スケッチを書き込むと、液晶モジュールにはトップ画像のデモ画面は表示されます。


・SPI2を利用したい場合


SPI2は次のピンを利用します。
  PB12 : NSS2
  PB13 : SCK2
  PB14 : MISO2
  PB15 : MOSI2

方法としては、
  ①SPI.setModule(2)にて、SPI2に切り替える
または、
  ②SPI2のインスタンスを生成し、Adafruit_ILI9341_STMクラスのメンバ関数
         void  begin(SPIClass & spi, uint32_t freq=48000000);
     を使う
でSPI2を利用することが出来ます。

①の方式は安定版(RR20170323)でのみ利用可能です。
最新版では①と②の方法が可能ですが、②の方法は何となくバグがあるようです。
SPI2を使っているのに、SPIオブジェクトの設定が変更されてしまうような気がします。
(SPIにSPI2の値がセットされてしまう。これについては調査中です)

結線図および結線表のピンは、
   A5 => B13
   A6 => B14
   A7 => B15
に変更となります。

ちなみに、SPI1とSPI2ではMAXの転送速度が異なります。
SPI1ではシステムクロックの1/2(36MHz)がまで出せますが、SPI2ではその半分となります。

SPI2を利用する際のスケッチの修正(SPI2のインスタンス生成による対応)
#include "SPI.h"
#include "Adafruit_GFX_AS.h"
#include "Adafruit_ILI9341_STM.h"

#define TFT_CS  PA0                  
#define TFT_RST PA1
#define TFT_DC  PA2
            
//Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC);
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);

SPIClass SPI_2(2);
void setup() {
  Serial.begin(115200);
  delay(3000);
  Serial.println("ILI9341 Test!"); 
 
  tft.begin(SPI_2);


次に、従来は古いAdafruit_GFXがベースだったために使えなかった、
フォントFreeFontを試してみます。

スケッチ(SPI2を使っています)
#include "SPI.h"
#include "Adafruit_GFX_AS.h"
#include "Adafruit_ILI9341_STM.h"
#include <Fonts/FreeMonoBoldOblique12pt7b.h>
#include <Fonts/FreeSerifBold12pt7b.h>
#include <Fonts/FreeSansBoldOblique12pt7b.h>

#define TFT_CS  PA0                  
#define TFT_RST PA1
#define TFT_DC  PA2

Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST);
SPIClass SPI_2(2);

void setup() {
  Serial.begin(115200);
  tft.begin(SPI_2);  
  tft.fillScreen(ILI9341_BLACK);
  
  tft.setCursor(0, 8);
  tft.setTextColor(ILI9341_WHITE);  
  tft.setFont(&FreeMonoBoldOblique12pt7b); 
  tft.println("Hello World!");

  tft.setCursor(0, 38);
  tft.setTextColor(ILI9341_YELLOW);  
  tft.setFont(&FreeSerifBold12pt7b); 
  tft.println("Hello World!");

  tft.setCursor(0, 60);
  tft.setTextColor(ILI9341_RED);  
  tft.setFont(&FreeSansBoldOblique12pt7b); 
  tft.println("Hello World!");
}

void loop() {

}

実行結果

Dscn8341

公式サイトにも説明がありますが、
FreeFontは表示位置がカーソル指定とずれるためちょっと使いにくいです。
等幅、プロポーショナルフォント、ボールド、イタリック等使い分けることが出来ますが、
それほど使用頻度は無いと思われます。


さて、利用しているこのグラフィック液晶モジュール、

Dscn8324

タッチパネルとSDカードスロットも搭載しています。
ピンもTFTとは独立してそれぞれ、SPIインタフェース対応の接続ピンがあります。
これらの機能も使い切ってみたいと思います。


2018年6月18日 (月)

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

現在、Arduino for STM32のフォーラムの下記の
USB HIDインタフェースの新しいブートローダーに注目しています。
Small USB HID bootloader for BluePill and other STM32F10X devices

簡単にまとめると次のような感じです。
・USB HIDインタフェースの4Kバイト弱のブートローダーを別途サポートを予定
・OSへのドライバーインストール不要でスケッチの書込みが可能となる。
・フラッシュメモリの領域が4kバイト増加する。
・USB HIDブートローダ実行のトリガーはBOOT1ピンの状態で判定(の予定)

最新のArduino STM32の環境にも徐々にUSB HIDインタフェース関連の機能が
組み込まれている状況です(ただし、メニュー設定での利用は出来ない)。

USB HIDブートローダも公開されています。
rogerclarkmelbourne/STM32_HID_Bootloader
Serasidis/STM32_HID_Bootloader(オリジナル版)

豊四季Tiny BASIC for Arduino STM32をUSB HIDインタフェースの新しいブートローダー
に切り替えようと思い、4月から提供待ち状態です。

メリットとしては
・Blue Pillを使うにおいて、環境によて、
  ドライバーのインストールやUSBポートの認識にかなり手こずる場合もあり、
  これが軽減されることが期待できます。
・4kバイトのプログラム領域が開くので、コマンドをいくつか追加出来る。
(現状は、領域に空きが無くて機能追加が出来ない状態です)

ただし、BOOT1ピンをプログラム自動起動モード設定に利用していたので、
別のピンに変更する必要があります。

2018年6月11日 (月)

CH340E搭載小型USB-シリアル変換モジュールの調査

AliexpressにてCH340Eを使った小型USB-シリアル変換モジュールを見つけ入手しました。

1PCS CH340E USB to TTL Serial Converter, 5V/3.3V Model:BTE17-06 Alternative CH340G Module
00

1個 $0.53ですが、送料が$1.32かかります。1個だけの注文だと割高になるので5個購入。
5個でも送料は$1.32でした。

このモジュールはaitendoでも販売されているようです。

到着したモジュール
Dscn8178

基板の実装状態は良好な製品です。

GND、5V、TXD、RXD、DTR、3.3Vの6端子があります。
DTRが使えるので、シリアル経由のプログラム書き込みにも利用出来ます。
ポリヒューズが乗っており過電流の対策も施されています。
3.3Vは内蔵レギュレーターで生成しています。

Dscn8188

早速、Arduino Pro mini (5V 16MHz)で書き込みテストを行ってみました。

Dscn8202

Lチカスケッチは問題なく書き込めました。

次に秋月で購入したATmega328P(内部RC 8MHz)への書込みを試してみます。

Dscn8205

書込みでエラー発生。

02

念のため、コマンドラインにて接続チェックをしましたがやはりエラー発生です。

01

ケーブルやリセット回りの実装等を見直すも、原因不明です。
様子的にはDTRによるリセットはちゃんの行われているようですが、
それ以降のデータ通信が上手く行っていないようです。

念のため利用実績のあるCP2102搭載のUSB-シリアル変換モジュールで
書込み確認してみました。

Dscn8209

問題無く書き込み出来ました。何回やってもエラーなしです。

04_2

当然、接続確認も問題なし。

03


CH340E搭載モジュール固有の問題のようです。
Arduino Pro mini (5V 16MHz)では問題なく書き込めていたので、
CH340EがRC発振8MHzの精度の悪さに敏感なためエラーになっているのかもしれません。

CH340E搭載モジュールとCP2102搭載を直結して57600bps設定で通信が出来ることを
確認したので、57600bps設定の通信も問題無く出来ています。

原因については、ロジックアナライザ等を使ってもう少し調べてみることにします。

2018/06/12 追記
ロジックアナライザにて正常書込みが出来るCP2102モジュールと上手くいかない
CH340Eの信号を調べてみました。

CP2012モジュール利用時
06

CH340Eモジュール利用時
05

どうも、CH340EモジュールのDTR信号に問題があるようです。
Atmega328Pは外部リセットピンのLOW→HIGHの立上りでMCU起動の遅延タイマが
スタートし始動しますが、これではブートローダが動く前にデータが送られて処理出来ません。

色々調べると、CH340搭載モジュールの一部にはこれと同様の問題があるようです。
   参考にしたサイト
   ・Arduino Forum Topic: CH340 programmer not auto-resetting (Read 2806 times)
   ・くつしたねこのTinkering,Tinkering and Obliquity - Arduino互換機を作りたい(3)
   ・Memoteki - Arduinoに書き込めない問題を修正する方法[not in sync]

上記のサイトに解決策があり、早速参考にしてみました。
次の回路を付加してCH340EモジュールのDTR出力から短いワンショットパルスを生成します。   

07
Dscn8210

これにより、DTR信号からRESET信号を生成できました。
一番下が生成したRESET信号です。

08

CP2012モジュールのDTRとほぼ同じ信号が生成出来ました。
ただし、これでも書込みがうまくいきません。
もう少し調べてみることにします。

より以前の記事一覧