#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. 全ソース
/*
+---------------------+ +------------------------------------------+
| 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);
}
#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;
#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を実装してくれないかなぁ...
これ全画面になるのに
Lovyanさんからご助言頂き 全画面版を作成中です
ちょっといい感じ
次回【第2話】をお楽しみに