公開日:2020/10/10
title.png

13th Season


#13-1 TFTシールドにBMPを表示する

 今回はいよいよ、DUEとTFTシールドにSDカードI/Fを接続して、SDカードに保存したBMPファイルを読み込み、TFTに表示してみたいと思います。
 そもそも、TFTシールドにはSDカードI/Fが搭載されているので、UNO+TFTシールドの時にSDカードへアクセスできるか?と思ったのですが、基板を追いかけて、SDカードソケットの端子電圧を測定してみたところ、どうもUNOの5V出力が直結されているようでした。
 SDカードは3.3V定格なので、ちょっと使用するのは躊躇したのです。

※詳しくは「#10-9 QVGA TFT液晶を使う」をご参照ください。

 DUEを入手した後、DUEは3.3V定格なのでようやくTFTシールドのSDカードI/Fを使えるかな?と思いきや、今度は、TFTシールドのSDカード(SPI)端子位置にDUEのSPI端子が引き出されていないというショック。

※詳しくは「#12-1 DUE+TFTシールド」をご参照ください。

 と、言う事で、TFTシールドのSDカードI/Fは使わずに、自作のSDカードI/Fを接続したいと思います。自作SDカードI/Fについてはこちら。SDライブラリで使用可能な、市販のSDカードI/Fでも使用可能です。

SDカードI/Fを接続する

 まず、SDカードI/FにはSDライブラリで使用可能な市販のモジュール(またはピッチ変換ソケットなど)を用意してください。UNO用に販売されているSDカードI/Fの中には、5V I/Oを前提としたものがあるので、3.3Vで直結可能なタイプを選んでください。
 このページでは「#6-5 microSDカードI/Fを自作する」で作り方を説明したSDカードソケット(下図)を使います。

ar65_sdsock.jpg

 ご覧の通り、microSDカードをフルサイズのSDカードに変換する変換ソケットに、ピンヘッダを半田付けしただけのシンプルなピッチ変換ソケットとなっています。ピンヘッダをブレッドボードに挿して配線することが出来るので、変換ソケットに挿入したmicroSDカードにアクセスすることが出来ます。
 DUEでSDカードI/Fを使う方法については「#12-6 DUEにSDカードI/Fを接続する」にて解説済みです。

 DUEのSPI端子位置は以下図でご確認ください。

a126_spipin.jpg

 この(↑)ICSPピンヘッダですが、DUEにTFTシールドを装着するとTFTシールドの下に隠れてしまうため、普通のデュポンケーブルでは引き出すことが出来ません。こちらを参照の上、デュポンケーブルを加工してTFTシールドの下からSPI端子を引き出す必要があります(下左図)。

a113_wire.jpga113_wire2.jpg

 上右図は、UNOの時の実施例です。DUEでも同様な状態になるはずです。
 引き出す必要があるのは3本(SCK,MOSI,MISO)なので、少なくとも3本のケーブルを加工する必要があります。

電源の確保

 実は、微妙に難題だったのが「電源の確保」でした。
 SDカードは3.3V定格なので、3.3Vの電源を供給する必要があります。DUEには3.3Vの電源出力がピンソケットに引き出されているため、通常なら、ピンソケットから3.3Vを引き出してSDカードに供給します。ところが、DUEの3.3Vピンソケットは1個しかないにもかかわらず、その1個はTFTシールドのピンヘッダが挿さっていて、使用することが出来ません。
 当のTFTシールドは基板上に5V → 3.3Vの降圧レギュレータを持っていて、自前で3.3V電源を生成しているので、DUEの3.3Vは使っていないのですけどね……。

 ちょっと面倒ですが、外部に降圧電源回路(下図)を使い、DUEの5Vから3.3Vを生成してSDカードに供給しました。

a131_powmodl.jpg

 まとめると、DUEとSDカードI/F、外部電源モジュールの接続表(ネットリスト)は以下の様になります。

Arduino DUE 接続 電源モジュール 接続 SDカードI/F
D22(SS) 1 CS
D75(MOSI) 2 DI
GND GND 3 VSS
  3.3V 4 VDD
D76(SCK) 5 SCLK
GND GND 6 VSS2
D74(MISO) 7 DO
5V 5V  

 DUEには3.3Vピンソケットは1個しかないのですが、5VのピンソケットはDUEで追加になった2列ピンソケットに2本引き出されています。ICSPピンヘッダにも1本出ているので、どこからでも5Vを供給することが出来ます。恐らく、同じフォームファクタのArduino MEGA(5V定格)と互換になっているためだと思いますが、3.3V定格のDUEに、こんな沢山の5V端子は必要ないのですよ……(1^_^;)。

Windows BMPファイル形式

 WindowsのBMPファイルは、基本的には非圧縮のビットマップデータを直接保存するタイプの画像データファイル形式です。フォーマットとしては、色々な圧縮方式を適用可能な仕様になっているのですが、その圧縮形式に対応したソフトウェアでしか取り扱い出来ないファイルになってしまうため、あまり使われません。
 さらに、非圧縮のデータ形式なので、基本的にファイルサイズが大きく、写真などの保存用としても適しません。BMPの存在意義は、異なる環境間で容易に画像データのやり取りをする事と言っても過言ではありません。
 だって、JPEGなんて、そんなに難しいファイル形式ではないけれども、Arduinoで自力でローダ/セーバを作ろうと思ったら骨が折れるでしょう?でも、BMPファイルは非圧縮なので、ファイル形式を知ってさえいれば、簡単にインプリできます。

 BMPファイル形式を以下表に示します。

BMPファイル サイズ 内容 ロード時対応
  BITMAPFILEHEADER
  bfType 2 BMPヘッダ識別子'BM' 'BM'以外ならエラー
bfSize 4 ファイルサイズ 無視
bfReserved1 2 リザーブ1 無視
bfReserved2 2 リザーブ2 無視
bfOffBits 4 画像データの先頭ファイルポジション  
BITMAPINFOHEADER
  biSize 4 sizeof(BITMAPINFOHEADER) 無視
biWidth 4 画像横サイズ  
biHeight 4 画像縦サイズ ≦0(トップダウンBMP)ならエラー
biPlanes 2 プレーン数 1以外ならエラー
biBitCount 2 ピクセルのビット数 16/24/32以外ならエラー
biCompression 4 圧縮形式 BI_RGB(=0)以外ならエラー
biSizeImage 4 画像データサイズ 無視
biXPelsPerMeter 4 横方向解像度 無視
biYPelsPerMeter 4 縦方向解像度 無視
biClrUsed 4 カラーテーブルの数 無視
biClrImportant 4 重要なカラーテーブルの数 無視
uncompressed bit map data
  16 bit/pixel 2 [MSB]I(1),R(5),G(5),B(5)[LSB] bfOffBits位置から始まって、
biSizeImageバイト繰り返し
24 bit/pixel 3 [MSB]R(8),G(8),B(8)[LSB]
32 bit/pixel 4 [MSB]I(8),R(8),G(8),B(8)[LSB]

 ヘッダフィールドとその意味については上表の通りです。
 ビットマップデータとしては、16/24/32ビットに対応することにします。各ピクセル値は、2/3/4バイトの整数で、上位ビット(MSB)からI→R→G→Bの順で並んでいます(上表中カッコ内数字はビット数)。Iはリザーブビットで、未使用です(無視する)。ちょっと注意すべきなのは、データの並び順です。
 I→R→G→Bの順で、かつリトルエンディアン(LE)なので、ファイル内のバイトの並び順は、B→G→R→Iです!

※だから素直にビッグエンディアン(BE)にしなさいってば(1^o^)/!

 今回のプログラムでは一応対応しておきますが、16ビットBMPファイルは正式には対応していない形式らしいです。また、32ビットBMPにも対応しておきますが、32ビットBMPファイルが見つからなかったので未検証となりますm(1__)m。

 尚、BMPファイル内のピクセルデータは、画像の下端ライン→上端ラインの順で記録されています(ボトムアップ)。ライン内のピクセル配置は、左端ピクセル→右端ピクセルです。どういう訳か、ラインの記録順番が直感的な順番とは逆になっているので注意です。
 更に、ライン内のデータ数は4の倍数に揃えておくことが要求されています。1ピクセル当たりのバイト数psiz、横方向の画像サイズbmp_widとした場合、ライン内のデータ数lsiz=(bmp_wid×psiz+3)/4×4……すべて整数演算、となります。
 もし、bmp_wid×psiz≠lsizだった場合には、lsizに到達するまでダミーのデータ(0〜3[バイト])が書き込まれています。

プログラムの設計

 BMPファイルの仕様は解りました。あと、TFT画面へのマッピング仕様を決めておく必要があります。TFTシールドは横240×縦320ドット表示なのですが、BMPファイルには様々な画像サイズが有ります。これをどうやってTFTシールドに表示するか?
 ここで作成するプログラムでは以下の様にしたいと思います。

 これだけ決めておけば、前節のフォーマットに従ってデータを読み出して表示して行くだけですので、詳細説明は省略します。完成プログラム(load_bmp.ino)でご確認ください。


 TFTシールドを縦画面/横画面で使用した場合のグラフィックデータ(ピクセル)の描画ですが、TFTシールドのLCDドライバチップILI9328が持っている、以下図のような機能を使うと比較的簡単に実現することが出来ます。

a131_amid.png
※ILI9328データシートから引用

 MCUから次々とピクセルデータを書き込んだ時、ILI9328内のアドレスカウンタを書き込み矩形領域内で自動的に更新するのですが、その更新方法が上図の中から選択可能となっています。
 この設定ビット、AMビットとI/DビットはEntry Modeレジスタ(アドレス0x03)に割り当たっており、筆者のプログラムでは通常、AM=0、I/D=0b11に設定されています(下コード)。

  // Entry Mode
  tft_write (0x03,0x0030);


※ビットの割り当てについてはILI9328のデータシートで確認して下さい。

 これ(↑、AM=0、I/D=0b11)は、TFTシールドを縦画面で使って、上端ライン→下端ラインへ、ライン内の描画順は左端ドット→右端ドットの順でピクセルデータを書き込む場合の設定です。ここでもし、I/D=0b10に変更して、全く同じ順番でピクセルデータを書き込むと、書き込んだデータの左右が反転して描画されます。
 同様にAMビットの設定と組み合わせることで、左右鏡像、上下鏡像、90°毎の回転などが比較的容易に実現できる機能です。

 この機能を使ってTFTシールドを横長画面(320×240ドット)として使用する場合、AM=1、I/D=0b10として以下の設定を使用すれば良いはずです。

  // Entry Mode
  tft_write (0x03,0x00a8);


 詳細コーディングについては完成プログラム(load_bmp.ino)でご確認ください。

 なお、ILI9328へのアクセス関数、tft_write()関数やtft_read()関数は、以前作ったものをそのまま流用しているので、UNO用の場合分け(#ifdef)が残してありますが、今回のプログラム(load_bmp.ino)はDUE専用です。
 確かにUNO等でも、SPI端子の引き出し(SS端子含む)を工夫すれば使用可能ではあるので、プログラム(load_bmp.ino)をUNO用に改造しさえすれば、使えるとは思います。ただし、プログラム(load_bmp.ino)はsizeof (int)=4が前提になっているので、本当にUNOで実行するためには変数の型など大幅に変更は必要となります。必要に応じて、各位で対応願います。

 いつもの様にシリアルモニタから受け付けるコマンド仕様を以下に示しておきます。

実験

 完成したプログラムを以下にリンクしておきます。

  完成版プログラム:load_bmp.ino

 TFTシールドを装着して、こちらの表に従ってSDカードI/Fを接続したDUEにコンパイル&書き込みしてください。また、SDカードには、PCとカードリーダ/ライタなどを使って適切なBMPファイルを書き込んでおいてください。
 BMPファイルはMicrosoftのペイントソフトウェア等でセーブすることが出来ます。拙作Olive+を使うと、16ビットBMPファイルの作成も可能です(※)。

※16ビットBMP作成方法: Olive+で画像を読み込み、「ツールメニュー ⇒ ウインドウのリサイズ」を選択、表示されたウインドウの「フレームバッファの色数」で「16ビット」を選択、「OK」、「ファイルメニュー ⇒ 保存」でBMPセーブ。尚、「32ビット」を選択すると、24ビットBMPでセーブされます。


 以下、実験中の様子です。

a131_expr.jpg

※上図でTFT画面に表示したデータは、Arduino UNOを拙作Xi-TRACEでモデリングして、レイトレーシングした物です(写真ではなく、CGです(1^−^)v)。「Arduinoをモデリングする」からデータのダウンロードや、その解説をご覧いただけます。


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【bmp,UNO_RAY.BMP】
  DBG: 320x240(24b) rot90=1
  OK: load_bmp(0): 2642807[us]

 ↑320×240ドット24ビットの横長BMPファイルをSDカードから読み出してTFTシールドに表示しました。

  【bmp,ATMG328.BMP】
  DBG: 320x240(16b) rot90=1
  OK: load_bmp(0): 1972803[us]

 今度は、同じサイズで16ビットBMPファイルを表示してみました。「OK:」の行に表示されているのが、読み込んで表示するのに要した時間です。24ビットBMPファイルでは約2.6秒かかりましたが、16ビットBMPファイルでは約2.0秒と、高速に表示出来ています。
 印象としては、だいぶSDカードのアクセスに時間を要しているようで、24ビット→16ビットピクセルになったことでアクセスすべきデータ量が減少し、その分高速に表示ができた模様です。

 BMPファイルの画像サイズを大きくした場合も、アクセスするデータ数自体は変わりませんが、飛び飛びにアクセスするため、読み込むべきSDカードのセクタ数が増加しますので、上の例よりさらに遅くなります。高速に表示したい場合には、TFTシールドに合わせて、予め画像サイズと色数を調整してからSDカードに書き込むのが吉です。


 TFTシールドを入手した時、ホントはすぐにでもSDカードにアクセスして、BMPファイルの表示プログラムくらいまでたどり着く予定だったのだけど、TFTシールドのSDカードI/FにUNOの5V信号が直接印加されているみたいだったので敬遠していて、ここまで来るのにずいぶんと時間がかかってしまいました🐢


btn_arduino.png

btn_prev.png btn_next.png

home