【それM5StickCでも出来るもん】【第1話】ATOMDisplayっぽいのをESP_8_BIT_composite(ビデオ出力)にLovyanGFXを絡めて作る話

#1. はじめに

最近ATOMDisplayが発売されちょっとしたブームとなっている

https://shop.m5stack.com/products/m5stack-atom-psram-lcd-display-driver-kit

でもこれって M5Atom専用なんですよねぇ
って事で他のESPシリーズであれば作れそうなものをちょっと考察
ESP_8_BIT_composite の画面データに LovyanGFX で作成されたデータを
いい感じで転送出来れば....

https://github.com/Roger-random/ESP_8_BIT_composite

あぁ LovyanGX の構造が判らない..
ん？SDカードに画面のスクショをとった事例があったような?
WEBを検索するといつものlang-shipさんのところにたどり着く

https://lang-ship.com/blog/work/lovyangfx-8-screenshot/

#2. ESP_8_BIT_composite を読み解く

複雑すぎてわからない...が
ビデオ出力系は動くのは判っているんで触らずにいると
2次元の配列としてアクセス出来る事が判明

``` 
void sendVideo()
{
  uint8_t **videoFb = video.getFrameBufferLines();
  uint16_t srcPos = 0;
  for (int y = 0; y < videoHeight; y++) {
    memcpy(&videoFb[y][0], &_videoBuf[srcPos], videoWidth);
    srcPos += videoWidth;
  }
  video.waitForFrame();
  videoFb = NULL;
}

```
#3. DAC_CHANNELの罠

ESP32 関連は DAC_CHANNEL_1 (G25) DAC_CHANNEL_2 (G26) が固定でアサインされていて
ESP_8_BIT_composite.cpp では　もちろん DAC_CHANNEL_1 固定でした...
以下の 3か所を修正して USE_DAC_CH で切り替えが出来るように仕込む
*　M5StickC は G25 がPINとしてアクセス出来ません

```
 ESP_8_BIT_composite.cpp:25   #define USE_DAC_CH (DAC_CHANNEL_2)
 ESP_8_BIT_composite.cpp:116  dac_output_enable (USE_DAC_CH);
 ESP_8_BIT_composite.cpp:687  dac_output_disable(USE_DAC_CH);
```

#4. LovyanGFXの画面キャプチャ
lang-shipさんのご忠告通り readRectでしかも 豪勢に40Lineまでキャプチャする
*　lcd height 80ドットは video height 3回分なのですが 20fpsから10fpsまで下がります
*　LovyanGFXは video出力しないと 120fpsまで出力されますので速い速い

```
void lcdCapture()
{
  uint16_t lines = 40;  // lcd captire lines 1..40
  uint8_t copyCnt = 1;  // lcd copy cnt      1.. 3
  uint8_t pxls[lcdWidth * lines];

  bool swap = lcd.getSwapBytes();
  uint16_t dstPos = 0;

  lcd.setSwapBytes(true);
  for (int c = 0; c < copyCnt; c++) {
    for (int y = 0; y < lcdHeight; y += lines) {
      lcd.readRect(0, y, lcdWidth, lines, pxls);
      uint16_t srcPos = 0;
      for (int i = 0; i < lines; i++) {
        memcpy(&_videoBuf[dstPos], &pxls[srcPos], lcdWidth);
        srcPos += lcdWidth;
        dstPos += videoWidth;
      }
    }
  }
  lcd.setSwapBytes(swap);
  sendVideo();
}

```
#5. 全ソース

``` ESPDisplay.ino
/*

  +---------------------+      +------------------------------------------+
  | M5StickC Screen     |      | ESP_8_BIT Screen                         |
  | 160x80              | ==>  | 256 x 240                                |
  | 200fps              |      | 20fps                                    |
  +---------------------+      |..........................................|
                               |                                          |
                               |                                          |
                               |                                          |
                               |..........................................|
                               |                                          |
                               |                                          |
                               |                                          |
                               +------------------------------------------+

  #### caution ####
  edit ESP_8_BIT_composite.cpp:25   #define USE_DAC_CH (DAC_CHANNEL_2)
  edit ESP_8_BIT_composite.cpp:116  dac_output_enable (USE_DAC_CH);
  edit ESP_8_BIT_composite.cpp:687  dac_output_disable(USE_DAC_CH);

*/

#include "Lcd.h"
#include "Video.h"

uint32_t getFps()
{
  static uint32_t psec = 0;
  static uint32_t cnt = 0;
  static uint32_t fps = 0;
  uint32_t sec = 0;

  sec = millis() / 1000;
  ++cnt;
  if (psec != sec) {
    psec = sec;
    fps = cnt;
    cnt = 0;
  }
  return fps;
}

void setup()
{
  lcd.init();
  lcd.setRotation(1);
  lcd.setBrightness(128);
  lcd.fillScreen(TFT_BLACK);
  lcdWidth = lcd.width();
  lcdHeight = lcd.height();
  spr.createSprite(lcdWidth, lcdHeight);
  spr.setTextColor(TFT_WHITE);
//  spr.setFont(&lgfxJapanMincho_16); // Japanese font ready !
  setupVideo();
}

void draw()
{
  uint16_t stp = 8;
  spr.fillScreen(TFT_BLACK);
  for (int x = 0; x < lcdWidth;  x += stp) {
    spr.drawLine(x, 0, x, lcdHeight - 1, TFT_BLUE);
  }
  for (int y = 0; y < lcdHeight; y += stp) {
    spr.drawLine(0, y, lcdWidth - 1, y, TFT_BLUE);
  }
  static uint16_t cx = 0;
  static uint16_t cy = 0;
  static uint16_t x = cx;
  static uint16_t y = cy;
  static uint16_t lx = stp;
  static uint16_t ly = stp;
  if ((x >  lcdWidth) || (x < 0)) lx = lx * -1;
  if ((y > lcdHeight) || (y < 0)) ly = ly * -1;
  x = x + lx;
  y = y + ly;
  spr.fillRect(x+1, y+1, stp-2, stp-2, TFT_RED);
  spr.setCursor(0, 8);
  spr.printf("%3dfps", getFps());
  spr.pushSprite(&lcd, 0,  0);                    // Push to Screen
}

void loop()
{
  M5.update();
  draw();
  lcdCapture();                                   // Capture and send video
  delay(1);
}
```
``` Lcd.h
#define _M5DISPLAY_H_
class M5Display {};

#include <M5StickC.h>
//#include <M5Stack.h>
//#include <M5Atom.h>

#include <LovyanGFX.hpp>
static LGFX lcd;
static LGFX_Sprite spr;

static uint16_t lcdWidth = 0;
static uint16_t lcdHeight = 0;

```
``` Video.h

#include "ESP_8_BIT_composite.h"
ESP_8_BIT_composite video(true /* = NTSC */);

static bool _videoEnable = false;
static const uint16_t videoHeight = 240;
static const uint16_t videoWidth  = 256;
static const uint16_t videoSize = (videoWidth * videoHeight);
static uint8_t _videoBuf[videoSize] = {0};

void setupVideo()
{
  video.begin();
}

void sendVideo()
{
  uint8_t **videoFb = video.getFrameBufferLines();
  uint16_t srcPos = 0;
  for (int y = 0; y < videoHeight; y++) {
    memcpy(&videoFb[y][0], &_videoBuf[srcPos], videoWidth);
    srcPos += videoWidth;
  }
  video.waitForFrame();
  videoFb = NULL;
}

void lcdCapture()
{
  uint16_t lines = 40;  // lcd captire lines 1..40
  uint8_t copyCnt = 1;  // lcd copy cnt      1.. 3
  uint8_t pxls[lcdWidth * lines];

  bool swap = lcd.getSwapBytes();
  uint16_t dstPos = 0;

  lcd.setSwapBytes(true);
  for (int c = 0; c < copyCnt; c++) {
    for (int y = 0; y < lcdHeight; y += lines) {
      lcd.readRect(0, y, lcdWidth, lines, pxls);
      uint16_t srcPos = 0;
      for (int i = 0; i < lines; i++) {
        memcpy(&_videoBuf[dstPos], &pxls[srcPos], lcdWidth);
        srcPos += lcdWidth;
        dstPos += videoWidth;
      }
    }
  }
  lcd.setSwapBytes(swap);
  sendVideo();
}

```
#6. 最後に
いつも欲しい所が既にあるlang-shipさんの[ブログ](https://lang-ship.com/blog/)は最強ですね
またこんな素敵な機能まで実装してくださっている[Lovyanさん](https://twitter.com/lovyan03)のGFXが凄いです
今後ともよろしくお願い申し上げます

#7. おまけ
~~lcdに出力無しで256x240x8bitにアクセスできるfbを実装してくれないかなぁ...
これ全画面になるのに~~
![IMG_4006.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063214/749101ee-4c0b-92a1-3816-7b4faa33acfc.jpeg)

Lovyanさんからご助言頂き 全画面版を作成中です
ちょっといい感じ
次回[【第2話】](https://qiita.com/chrmlinux/items/2281f243f7a07d161b70)をお楽しみに