SPRESENSEで７インチモニターを制御しようとした話（未完）

# 7インチディスプレイの概要
ひょんなことから、７インチのディスプレイを入手しました。ディスプレイのコントローラはFTDI製でSPIで制御できるものです。

![DSC_1806.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/c399c3e4-e2f8-4c92-f8cc-0a176485dcbc.jpeg)
https://ja.newhavendisplay.com/7-0-inch-premium-eve2-resistive-tft-module/

もともとはArduino系のボードで制御するものなのですが、SPRESENSEでも制御できるんじゃね？という軽い考えで試してみました。

# ディスプレイのIO
[データシート](https://newhavendisplay.com/content/specs/NHD-7.0-800480FT-CSXV-T.pdf)を見ると、コネクタは20ピンですが基本的にはSPIのみ。タッチもついているようなのですが、I2Cのラインはなさそうです。SPIは3.3V系。バックライトは3.3Vもしくは5.0V電源です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/108c4d20-3ce3-fdd8-956b-6d62665f8ad5.png)

# ディスプレイに必要な電力
バックライトに必要な電力を見ると、3.3Vでは760mA（2.5W）、5.0Vでは440mA（2.2W）です。いずれにしてもUSBからの給電では厳しそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/dab545eb-38e1-2555-c53a-9a2a5ad336af.png)


# 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
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/f3959d38-97f5-4696-a274-f90150b77ef2.png)


ただ、あとで気づいたのですが裏面にはIPのピンヘッダがあるのでそちらを活用したほうが簡単ですね。かさばるのが嫌な場合は、上記FFC変換基板を活用するとよいと思います。

![DSC_1890.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/b615c021-ed55-62c8-2333-c3f38ee81ebc.jpeg)

結線は次のようにしました。バックライトの電源は安定化電源から供給します。GPIO0とGPIO1は念のため接続していますが、不要かもしれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/023de033-aec7-2085-19a2-d1255f9221d7.png)

実際に結線した様子はこちらになります。

![DSC_1805.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/d7c38bf4-859d-1af0-243e-76c57b6db084.jpeg)


# SPRESENSE の Arduinoコード
[プログラマガイド](https://www.ftdichip.com/Support/Documents/ProgramGuides/FT81X_Series_Programmer_Guide.pdf)を頼りに次のようなスケッチを書いてみました。

```c++
#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のモードをいじくっているうちにようやく表示されました。

![DSC_1807.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/3830a3b5-d3b9-02ca-a327-0a4091574e4b.jpeg)

しかし、かなり動作が不安定です。これはソフトの問題というよりはハードの問題のようです。フレキシブルケーブルが長すぎるのか、私の半田がいまいちなのか…。近いうちにDIPのコネクタで再チャレンジしてみようと思います。


# 使ってみた感想
このディスプレイは、GPUというだけあって、フォントを内蔵していますし様々な描画命令も備えているようで、SPI経由で命令を伝えるだけでグラフィック処理ができます。タッチもサポートしているので、GUIを作るには便利そうですね。ライブラリを準備すれば、かなり高度なことができそうです。