デジカメ弐号機 第3回 DISPLAYのSPI制御
2022.11.04
YouTubeでポイントを説明しています。画像をクリックすると再生できます。
今回は、Adafruit TFT LCD Display を制御します。
一般的には、Adafruit GFXライブラリを用いて、SPI通信制御を行い、簡単なコードで表示が可能なのですが、
このライブラリを使用してしまうと、SPIバスを占有してしまうのか、ArduCamカメラのSPI通信を妨害していまいます。
そこで、直接SPIコマンドを叩いて、ディスプレイを制御します。
デジカメ弐号機では、スクウェア写真、正方形画像として撮影するので、被写体を移すディスプレイも縦横240ドットの正方形ディスプレイを使用します。
■Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789 (BLUE TAB)
Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789
マイコンボード(Adafruit QT Py ESP32-S2)とディスプレイとの接続は下記の通りです。
| QT Py | - | ST7789 |
| 3.3V | - | Vin |
| | | 3V3: output from the onboard regulator |
| GND | - | GND |
| SCK | - | SCK |
| | | MIS |
| MOSI | - | MOS |
| A2 | - | TCS: SPI chip select |
| | - | RST(-1) |
| A1 | - | D/C: SPI data or command |
| | | SCS: SD card chip select |
| | | LIT: PWM input for the backlight control |

画像を表示して、SPI制御を確認するのですが、単体テストの際には、SDカード内の画像データファイルからの読込みは行わず、画像配列から読み込みます。
ニーア オートマタ PLAY ARTS改 <ヨルハ 二号 B型 DX版> PVC製 塗装済み可動フィギュア
テスト画像(YoRHa2B.tga)にはヨルハ2Bさんを使いました。
この画像データをSDカードモジュールに挿して読み込んでもよいのですが、ST7789の単体テストを行いたかったので、
TGA画像を配列データに変換して、プログラムに取り込む形にします。
下記のスクリプトでは、TGAフォーマット画像を16進表記の配列に変換します。
bin2hex.php
<?php
$fname = $argv[1];
if(!($file_size = filesize($fname))) return;
list($file_name,$file_type) = explode('.',$fname,);
if (!($infp = fopen($fname,"rb"))) return;
$outfile = $file_name.'.h';
$outfp = fopen($outfile,"w");
fwrite($outfp, "const unsigned long ".$file_type."_len = ".$file_size.";\n");
fwrite($outfp, "const unsigned char ".$file_type."_data[".$file_size."] = {\n");
$i = 0; $cnt = 0;
while (!feof($infp)) {
$byte = fread($infp, 1);
$hexChar = '0x'.bin2hex($byte);
if ((++$cnt)==$file_size) {
fwrite($outfp,$hexChar."\n};\n");
break;
} else {
if ((++$i)%16) {
fwrite($outfp,$hexChar.",");
} else {
fwrite($outfp,$hexChar.",\n");
}
}
}
fclose($outfp);
fclose($infp);
?>
$ php bin2hex.php YoRHa2B.tga
ラズベリーパイのコマンドラインから、PHPスクリプトを実行すると、YoRHa2B.h ファイルが生成されます。
$ vi src/YoRHa2B.h
const unsigned long tga_len = 172818;
const unsigned char tga_data[172818] = {
0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0x00,0xf0,0x00,
0x18,0x20,0x23,0x1d,0x1e,0x23,0x1d,0x1e,0x24,0x1e,0x1f,0x25,0x1f,0x20,0x25,0x1f,
0x20,0x25,0x1f,0x20,0x25,0x1f,0x20,0x24,0x1e,0x1f,0x26,0x20,0x21,0x26,0x20,0x21,
0x26,0x20,0x21,0x25,0x1f,0x20,0x25,0x1f,0x20,0x24,0x1e,0x1f,0x24,0x1e,0x1f,0x24,
0x1e,0x1f,0x25,0x1f,0x20,0x25,0x1f,0x20,0x25,0x1f,0x20,0x26,0x20,0x21,0x26,0x20,
~~~~~~~~~~~~~~~~~~~~~~
0x67,0x5f,0x60,0x62,0x5a,0x5b,0x5f,0x57,0x58,0x60,0x58,0x59,0x63,0x5b,0x5c,0x66,
0x5e,0x5f,0x60,0x58,0x59,0x5e,0x56,0x57,0x5b,0x53,0x54,0x5c,0x54,0x55,0x5e,0x56,
0x57,0x5e,0x56,0x57,0x5b,0x53,0x54,0x58,0x50,0x51,0x60,0x58,0x59,0x60,0x58,0x59,
0x5f,0x57,0x58,0x5c,0x54,0x55,0x59,0x51,0x52,0x5a,0x52,0x53,0x5e,0x56,0x57,0x62,
0x5a,0x5b
};
ディスプレイのSPI制御に関しては、ゆるプロさんのサイトに分かりやすく纏められています。
また、サイトからダウンロードできるソースコードも利便性が非常に優れています。

ゆるく楽しむプログラミング&電子工作 240x240 カラーTFT液晶 ST7789
ここからソースコードをダウンロードして、ST7789-M5Stamp-TEST3.ino をベースに必要な部分を抜粋して作り替えます。
上記サイト内で使用している 240x240ドット・カラーTFT液晶にはCS(Chip Select)ピンがなく、SPI接続できるのは、このデバイス1つに限られています。
そこで、スレーブとなるデバイスを指定するコードを追記します。
$ vi src/ST7789_SPI.ino
#include <SPI.h>
#include "YoRHa2B.h"
#define SPI_TFT_CS A2 // Chip Select
#define SPI_DC A1 // Data/Command
#define SPI_RST -1 // RESET
#define TFT_WIDTH 240
#define TFT_HEIGHT 240
//#define TFT_HEIGHT 320
#define ST77XX_BLACK 1
typedef struct {
uint8_t idlength;
uint8_t colourmaptype;
uint8_t datatypecode;
uint8_t colourmaporigin[2];
uint8_t colourmaplength[2];
uint8_t colourmapdepth;
uint8_t x_origin[2];
uint8_t y_origin[2];
uint8_t width[2];
uint8_t height[2];
uint8_t bitsperpixel;
uint8_t imagedescriptor;
} TGA_HEADER;
typedef struct {
uint8_t B;
uint8_t G;
uint8_t R;
} TGA_BGR;
TGA_BGR *BGRs;
typedef struct {
uint32_t Width;
uint32_t Height;
} IMAGE_INFO;
IMAGE_INFO *img;
#define HBYTE(u) ((u >> 8) & 0xFF)
#define LBYTE(u) (u & 0xFF)
SPISettings settingsST7789 = SPISettings(20000000, MSBFIRST, SPI_MODE3);
// TFTにコマンドを送信
void tftSendCommand(uint8_t command) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
}
// TFTにコマンド+1バイトデータを送信
void tftSendCommand1(uint8_t command, uint8_t data1) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
digitalWrite(SPI_TFT_CS, HIGH);
}
// TFTにコマンド+2バイトデータを送信
void tftSendCommand2(uint8_t command, uint8_t data1, uint8_t data2) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
SPI.transfer(data2);
digitalWrite(SPI_TFT_CS, HIGH);
}
// TFTにコマンド+3バイトデータを送信
void tftSendCommand3(uint8_t command, uint8_t data1, uint8_t data2, uint8_t data3) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
SPI.transfer(data2);
SPI.transfer(data3);
digitalWrite(SPI_TFT_CS, HIGH);
}
// TFTにコマンド+4バイトデータを送信
void tftSendCommand4(uint8_t command, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
SPI.transfer(data2);
SPI.transfer(data3);
SPI.transfer(data4);
digitalWrite(SPI_TFT_CS, HIGH);
}
void drawRGBBitmap(uint16_t x, uint16_t y, uint16_t *color565, uint16_t width, uint16_t height) {
uint8_t h, l;
width--;
h = (uint8_t)(width>>8);
l = (uint8_t)(width&0x00ff);
tftSendCommand4(0x2A,x,y,h,l); // Colmun Address
height--;
h = (uint8_t)(height>>8);
l = (uint8_t)(height&0x00ff);
tftSendCommand4(0x2B,x,y,h,l); // Row Address
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(0x2C);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(color565, width*height*2);
digitalWrite(SPI_TFT_CS, HIGH);
digitalWrite(SPI_DC, LOW); // Command mode
}
// 表示開始ライン設定
void dispStartLine(uint16_t y) {
uint8_t yH = (y >> 8) & 0xFF ;
uint8_t yL = y & 0xFF ;
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(0x37);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(yH);
SPI.transfer(yL);
digitalWrite(SPI_TFT_CS, HIGH);
}
void init_tft() {
pinMode(SPI_TFT_CS,OUTPUT);
pinMode(SPI_DC, OUTPUT);
// pinMode(SPI_RST, OUTPUT);
// digitalWrite(SPI_TFT_CS, HIGH);
// --- HARD Ware Reset
if (SPI_RST >= 0) {
digitalWrite(SPI_RST, HIGH);
delay(500); // VDD goes high at start, pause for 500 ms
digitalWrite(SPI_RST, LOW); // Bring reset low
delay(100); // Wait 100 ms
digitalWrite(SPI_RST, HIGH); // Bring out of reset
delay(500); // Wait 500 ms, more then 120 ms
}
// --- SOFT Ware Reset
tftSendCommand(0x01) ; // SOFTWARE RESET
delay(50);
// --- Initial Comands
tftSendCommand(0x28) ; // Display OFF
delay(500);
tftSendCommand(0x11) ; // Sleep Out
delay(500);
tftSendCommand1(0x3A,0x05) ; // 16Bit Pixel Mode
delay(10);
tftSendCommand1(0x36,B00000000) ; // MX MY MV ML RGB MH x x:縦向き1
tftSendCommand2(0xB6,0x15,0x02) ; // フレームレート設定
tftSendCommand(0x13) ; // Nomal Display Mode
tftSendCommand(0x21) ; // Display Inversion Off
tftSendCommand(0x29) ; // Display ON
// tftSendCommand1(0x36,B10100000);// MX MY MV ML RGB MH x x:横向き1
// tftSendCommand1(0x36,B01100000); // MX MY MV ML RGB MH x x:横向き2
// tftSendCommand1(0x36,B00000000); // MX MY MV ML RGB MH x x:縦向き1
tftSendCommand1(0x36,B11000000); // MX MY MV ML RGB MH x x:縦向き2
dispStartLine(80);
}
uint16_t swap16(uint8_t *pt) {
uint16_t ret;
ret = *pt;
ret |= (*(pt+1)<<8)&0xff00;
return ret;
}
boolean TGA2Pixel(const unsigned char *data, const unsigned long len) {
FILE *stream;
TGA_HEADER header;
if((stream = fmemopen((void*)data, len, "rb"))==NULL) return false;
if (fread(&header,sizeof(TGA_HEADER),1,stream)>0) {
img->Width = swap16(header.width);
img->Height = swap16(header.height);
BGRs = (TGA_BGR *)malloc(img->Width * img->Height * 3);
} else {
return false;
}
long pos = 0;
while(fread(&BGRs[pos],3,1,stream)) pos++;
fclose(stream);
return true;
}
void setup() {
uint16_t rgb565;
uint16_t *color565, *imgpt;
long pos;
SPI.begin(); //SPIを初期化、SCK、MOSI、SSの各ピンの動作は出力、SCK、MOSIはLOW、SSはHIGH
SPI.beginTransaction(settingsST7789);
init_tft();
img = (IMAGE_INFO *)malloc(sizeof(IMAGE_INFO));
TGA2Pixel(tga_data, tga_len);
color565 = (uint16_t*)malloc(240*240*2);
imgpt = color565;
pos = 0;
for(int y=0; y < img->Height; y++) {
for(int x=0; x < img->Width; x++,pos++,imgpt++) {
rgb565 = ((BGRs[pos].R>>3)<<11)|((BGRs[pos].G>>2)<<5)|(BGRs[pos].B>>3);
*imgpt = (rgb565<<8)|(rgb565>>8);
}
}
drawRGBBitmap(0,0,color565,240,240);
}
void loop() {}
ソースコードの要点を説明します。
まずは、画像配列データを、ファイルとして扱うために、fmemopen()関数を用います。
if((stream = fmemopen((void*)data, len, "rb"))==NULL) return false;
次に、TGA画像データのヘッダー部を読み込んで、画像サイズを取得します。
typedef struct {
uint8_t idlength;
uint8_t colourmaptype;
uint8_t datatypecode;
uint8_t colourmaporigin[2];
uint8_t colourmaplength[2];
uint8_t colourmapdepth;
uint8_t x_origin[2];
uint8_t y_origin[2];
uint8_t width[2];
uint8_t height[2];
uint8_t bitsperpixel;
uint8_t imagedescriptor;
} TGA_HEADER;
TGA_HEADER header;
typedef struct {
uint8_t B;
uint8_t G;
uint8_t R;
} TGA_BGR;
TGA_BGR BGR;
typedef struct {
uint32_t Width;
uint32_t Height;
} IMAGE_INFO;
IMAGE_INFO *img;
uint16_t swap16(uint8_t *pt) {
uint16_t ret;
ret = *pt;
ret |= (*(pt+1)<<8)&0xff00;
return ret;
}
FILE *stream;
~~~~~~~~~~~~~~~~
if (fread(&header,sizeof(TGA_HEADER),1,stream)>0) {
img->Width = swap16(header.width);
img->Height = swap16(header.height);
BGRs = (TGA_BGR *)malloc(img->Width * img->Height * 3);
}
TGAヘッダー部に続く画像情報は、BRG各1バイト(BMP画像とは並び順が逆)を1ピクセルとした画素情報が繰り返されます。
これを構造体配列に格納してきます。
long pos = 0;
while(fread(&BGRs[pos],3,1,stream)) pos++;
fclose(stream);
SPI制御の概要
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW);
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH);
SPI.transfer(data1);
SPI.transfer(data2);
・・・・・・
digitalWrite(SPI_TFT_CS, HIGH);
SPI制御コードは、1バイトのコマンドと、複数のパラメタにより構成されます。
コマンド送信時にはD/C(Data Command)をLOWに、パラメタ送信時にはD/CをHIGHにします。
さらに、複数のSPIデバイスを接続する際には、利用するデバイスのCSピンをLOWにし、制御コード送信後に、HIGHに戻します。
画像情報の格納
uint16_t rgb565;
uint16_t *color565, *imgpt;
long pos;
color565 = (uint16_t*)malloc(240*240*2);
imgpt = color565;
pos = 0;
for(int y=0; y < img->Height; y++) {
for(int x=0; x < img->Width; x++,pos++,imgpt++) {
① rgb565 = ( (RGBs[pos].R>>3)<<11)
|((RGBs[pos].G>>2)<<5)
|(RGBs[pos].B>>3);
② *imgpt = (rgb565<<8)|(rgb565>>8);
}
}
drawRGBBitmap(0,0,color565,240,240);
①1ピクセルRGB各1バイトの情報を、R(赤)上位5ビット、G(緑)上位6ビット、B(青)上位5ビットの16ビットにして、565形式の色情報に変換します。
②リトルエンディアンに置き換えて、メモリ上に格納します。
画像表示
画像表示は、Adafruit GFXライブラリー関数 drawRGBBitmap()の書式に合わせています。
void drawRGBBitmap(uint16_t x, uint16_t y, uint16_t *color565,
uint16_t width, uint16_t height) {
uint8_t h, l;
width--;
h = (uint8_t)(width>>8);
l = (uint8_t)(width&0x00ff);
tftSendCommand4(0x2A,x,y,h,l); ← X方向の表示範囲指定
height--;
h = (uint8_t)(height>>8);
l = (uint8_t)(height&0x00ff);
tftSendCommand4(0x2B,x,y,h,l); ← Y方向の表示範囲指定
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW);
SPI.transfer(0x2C); ← ビットマップモードを指定
digitalWrite(SPI_DC, HIGH);
SPI.transfer(color565, width*height*2); ← 画像データ転送
digitalWrite(SPI_TFT_CS, HIGH);
digitalWrite(SPI_DC, LOW);
}
CSピンをLOWにして、スレーブとして、TFT LCD DISPLAY を指定しています。
D/CをLOWにして、ビットマップモード・コマンドを送信、
D/CをHIGHに戻して、1画像分のデーターを纏めて転送しています。
データ送信終了後にHIGHに戻しています。
$ pio run -t upload

プログラムを実行すると、瞬時に画像が表示されます。
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|