2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-04

#1. はじめに

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

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

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

#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さんのブログは最強ですね
またこんな素敵な機能まで実装してくださっているLovyanさんのGFXが凄いです
今後ともよろしくお願い申し上げます

#7. おまけ
lcdに出力無しで256x240x8bitにアクセスできるfbを実装してくれないかなぁ...
これ全画面になるのに

IMG_4006.jpg

Lovyanさんからご助言頂き 全画面版を作成中です
ちょっといい感じ
次回【第2話】をお楽しみに

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?