7インチディスプレイの概要
ひょんなことから、7インチのディスプレイを入手しました。ディスプレイのコントローラはFTDI製でSPIで制御できるものです。
https://ja.newhavendisplay.com/7-0-inch-premium-eve2-resistive-tft-module/
もともとはArduino系のボードで制御するものなのですが、SPRESENSEでも制御できるんじゃね?という軽い考えで試してみました。
ディスプレイのIO
データシートを見ると、コネクタは20ピンですが基本的にはSPIのみ。タッチもついているようなのですが、I2Cのラインはなさそうです。SPIは3.3V系。バックライトは3.3Vもしくは5.0V電源です。
ディスプレイに必要な電力
バックライトに必要な電力を見ると、3.3Vでは760mA(2.5W)、5.0Vでは440mA(2.2W)です。いずれにしてもUSBからの給電では厳しそうです。
SPRESENSEとの結線
SPRESENSEでは20ピンのフラットケーブルをDIPに変換してくれる変換基板があったのでそれを使ってみました。
Moechando FPC FFC変換基板 1.0mmピッチ 20ピン 適合FPC FFCフラットケーブル
https://item.rakuten.co.jp/moechando/9z-e042-wsdi/?s-id=ph_pc_itemimage
ただ、あとで気づいたのですが裏面にはIPのピンヘッダがあるのでそちらを活用したほうが簡単ですね。かさばるのが嫌な場合は、上記FFC変換基板を活用するとよいと思います。
結線は次のようにしました。バックライトの電源は安定化電源から供給します。GPIO0とGPIO1は念のため接続していますが、不要かもしれません。
実際に結線した様子はこちらになります。
SPRESENSE の Arduinoコード
プログラマガイドを頼りに次のようなスケッチを書いてみました。
#include <SPI.h>
#include "FT81_GPU.h"
#define CS_PIN (4)
#define PDN_PIN (8)
uint16_t FT_DispWidth = 800;
uint16_t FT_DispHeight = 480;
uint16_t FT_DispHCycle = 928;
uint16_t FT_DispHOffset = 88;
uint16_t FT_DispHSync0 = 0;
uint16_t FT_DispHSync1 = 48;
uint16_t FT_DispVCycle = 525;
uint16_t FT_DispVOffset = 32;
uint16_t FT_DispVSync0 = 0;
uint16_t FT_DispVSync1 = 3;
uint16_t FT_DispPCLK = 2;
uint16_t FT_DispSwizzle = 0;
uint16_t FT_DispPCLKPol = 1;
uint16_t FT_DispCSpread = 0;
uint16_t FT_DispDither = 1;
uint8_t rd8(uint32_t addr) {
uint8_t value;
digitalWrite(CS_PIN, LOW);
SPI.transfer(addr >> 16);
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(0); //Dummy Read Byte
value = SPI.transfer(0);
digitalWrite(CS_PIN, HIGH);
return value;
}
uint16_t rd16(uint32_t addr) {
uint16_t value;
uint16_t byte0;
uint16_t byte1;
digitalWrite(CS_PIN, LOW);
SPI.transfer(addr >> 16);
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(0); //Dummy Read Byte
byte0 = SPI.transfer(0);
byte1 = SPI.transfer(0);
value = (byte1 << 8) | byte0;
digitalWrite(CS_PIN, HIGH);
return value;
}
uint32_t rd32(uint32_t addr) {
uint32_t value;
uint32_t byte0;
uint32_t byte1;
uint32_t byte2;
uint32_t byte3;
digitalWrite(CS_PIN, LOW);
SPI.transfer(addr >> 16);
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(0); //Dummy Read Byte
byte0 = SPI.transfer(0);
byte1 = SPI.transfer(0);
byte2 = SPI.transfer(0);
byte3 = SPI.transfer(0);
value = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
digitalWrite(CS_PIN, HIGH);
return value;
}
void wr8(uint32_t addr, uint8_t value) {
digitalWrite(CS_PIN, LOW);
SPI.transfer(0x80 | (addr >> 16));
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(value);
digitalWrite(CS_PIN, HIGH);
}
void wr16(uint32_t addr, uint16_t value) {
digitalWrite(CS_PIN, LOW);
SPI.transfer(0x80 | (addr >> 16));
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(value & 0xFF);//LSB first
SPI.transfer((value >> 8) & 0xFF);
digitalWrite(CS_PIN, HIGH);
}
void wr32(uint32_t addr, uint32_t value) {
digitalWrite(CS_PIN, LOW);
SPI.transfer(0x80 | (addr >> 16));
SPI.transfer(highByte(addr));
SPI.transfer(lowByte(addr));
SPI.transfer(value & 0xFF);//LSB first
SPI.transfer((value >> 8) & 0xFF); // 2Byte
SPI.transfer((value >> 16) & 0xFF); // 3Byte
SPI.transfer((value >> 24) & 0xFF); // 4Byte
digitalWrite(CS_PIN, HIGH);
}
void host_command(uint8_t cmd) {
digitalWrite(CS_PIN, LOW);
SPI.transfer(cmd);
SPI.transfer(0);
SPI.transfer(0);
digitalWrite(CS_PIN, HIGH);
}
void setup() {
Serial.begin(115200);
pinMode(PDN_PIN, OUTPUT);
digitalWrite(PDN_PIN, HIGH);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
delay(100);
/* MCU_SPI_CLK_Freq(<11MHz); //use the MCU SPI clock less than 11MHz */
SPI.begin();
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
Serial.println("SPI open");
/* Initialization Sequence from Power Down using PD_N pin */
digitalWrite(PDN_PIN, LOW);
delay(100);
digitalWrite(PDN_PIN, HIGH);
delay(100);
Serial.println("DISPLAY Power On");
host_command(FT_GPU_ACTIVE_M);
uint8_t chipid;
do {
chipid = rd8(REG_ID);
delay(100);
} while (chipid != 0x7C);
Serial.println("chipid = " + String(chipid, HEX));
Serial.println("DISPLAY Settings");
wr16(REG_HCYCLE, FT_DispHCycle);
wr16(REG_HOFFSET, FT_DispHOffset);
wr16(REG_HSYNC0, FT_DispHSync0);
wr16(REG_HSYNC1, FT_DispHSync1);
wr16(REG_VCYCLE, FT_DispVCycle);
wr16(REG_VOFFSET, FT_DispVOffset);
wr16(REG_VSYNC0, FT_DispVSync0);
wr16(REG_VSYNC1, FT_DispVSync1);
wr8(REG_SWIZZLE, FT_DispSwizzle);
wr8(REG_PCLK_POL, FT_DispPCLKPol);
wr16(REG_HSIZE, FT_DispWidth);
wr16(REG_VSIZE, FT_DispHeight);
wr16(REG_CSPREAD, FT_DispCSpread);
wr16(REG_DITHER, FT_DispDither);
/* write first display list */
/*
wr32(RAM_DL+0, CLEAR_COLOR_RGB(0,0,0));
wr32(RAM_DL+4, CLEAR(1,1,1));
wr32(RAM_DL+8, DISPLAY());
*/
wr8(REG_DLSWAP, DLSWAP_FRAME); //display list swap
wr8(REG_GPIO_DIR,0x80 | rd8(REG_GPIO_DIR));
wr8(REG_GPIO,0x080 | rd8(REG_GPIO)); //enable display bit
wr8(REG_PCLK,5); //after this display is visible on the LCD
delay(100);
/* MCU_SPI_CLK_Freq(<30Mhz);//use the MCU SPI clock upto 30MHz */
SPI.endTransaction();
SPI.end();
Serial.println("Change SPISettings");
SPI.begin();
SPI.beginTransaction(SPISettings(28000000, MSBFIRST, SPI_MODE0));
do {
chipid = rd8(REG_ID);
delay(100);
} while (chipid != 0x7C);
Serial.println("chipid = " + String(chipid, HEX));
const static uint8_t CMD_SIZE = 4;
const static uint16_t V_START = 110;
const static uint16_t V_HIGHT = 60;
int n = 0;
int c = 0;
wr32(RAM_DL + CMD_SIZE*(c++), CLEAR(1, 1, 1)); // clear screen
wr32(RAM_DL + CMD_SIZE*(c++), BEGIN(BITMAPS)); // start drawing bitmaps
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(220, V_START+V_HIGHT*n, 31, 'S')); // ascii S in font 31
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(244, V_START+V_HIGHT*n, 31, 'P')); // ascii P
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(268, V_START+V_HIGHT*n, 31, 'R')); // ascii R
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(292, V_START+V_HIGHT*n, 31, 'E')); // ascii E
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(316, V_START+V_HIGHT*n, 31, 'S')); // ascii S
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(340, V_START+V_HIGHT*n, 31, 'E')); // ascii E
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(364, V_START+V_HIGHT*n, 31, 'N')); // ascii N
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(388, V_START+V_HIGHT*n, 31, 'S')); // ascii S
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(412, V_START+V_HIGHT*n, 31, 'E')); // ascii E
wr32(RAM_DL + CMD_SIZE*(c++), END());
wr32(RAM_DL + CMD_SIZE*(c++), COLOR_RGB(22, 22, 412)); // change colour to dark blue
wr32(RAM_DL + CMD_SIZE*(c++), POINT_SIZE(320)); // set point size to 20 pixels in radius
wr32(RAM_DL + CMD_SIZE*(c++), BEGIN(FTPOINTS)); // start drawing points
wr32(RAM_DL + CMD_SIZE*(c++), VERTEX2II(192, V_START+23+V_HIGHT*n, 0, 0)); // red point
wr32(RAM_DL + CMD_SIZE*(c++), END());
wr32(RAM_DL + CMD_SIZE*(c++), DISPLAY()); // display the image
// wait for FIFO empty
while(rd16(REG_CMD_READ) != rd16(REG_CMD_WRITE));
Serial.println("Display Strings done");
delay(1000);
SPI.endTransaction();
SPI.end();
Serial.end();
}
void loop() {
/* put your main code here, to run repeatedly: */
}
動作結果
プログラムを書き込んで見たところ、何度かリセットをして、SPIのモードをいじくっているうちにようやく表示されました。
しかし、かなり動作が不安定です。これはソフトの問題というよりはハードの問題のようです。フレキシブルケーブルが長すぎるのか、私の半田がいまいちなのか…。近いうちにDIPのコネクタで再チャレンジしてみようと思います。
使ってみた感想
このディスプレイは、GPUというだけあって、フォントを内蔵していますし様々な描画命令も備えているようで、SPI経由で命令を伝えるだけでグラフィック処理ができます。タッチもサポートしているので、GUIを作るには便利そうですね。ライブラリを準備すれば、かなり高度なことができそうです。