はじめに
私が今までかじって来たESPマイコンのArduinoライブラリについて紹介したいと思います。
これらの記事が、どなたかの何らかに貢献できれば幸いです。👍
ESP_8_BIT_composite Library
今回は、ESP_8_BIT_compositeライブラリについて簡単に説明します。
https://github.com/Roger-random/ESP_8_BIT_composite.git
概要
ESP32マイコンのI2Sピン(GPIO25)からコンポジット信号(NTSC or PALフォーマット)を出力できます。
Adafruit GFX Library描画ライブラリを使用しています。
アナログTVやデジタルTVの黄色いRCAコネクタに入力すると、アナログ映像(NTSC)でフォント、アニメーションGIF、BITMAP、線、円、ポリゴンなどを表示させることができます。4:3表示で解像度は480i(インターレース)です。カラー表示ができます。(8bitカラー:256色)
お試し環境
このライブラリは次の環境でビルドを行い動作を確認しました。
- ソフト
- VSCode
- PlatformIO(VSCode拡張機能)
- arduino-esp32 ライブラリ
- ハード
- M5 ATOM Lite(ESP32-PICO)
動作の様子
テレビはメルカリで買ったSHARP LC-13S4(3000円)です。ATOM LiteのG25ポートをTVのRCA端子(映像)に接続しました。(言わずもがなGNDも接続しています)
サンプルコード
riraosan/ESP_8_BIT_composite
フォントを表示させるために、ESP_8_BIT_GFX
クラスをEfontWrapper
クラスから派生しています。
Adafruit_GFXライブラリのフォント以外のフォント(日本語、韓国語、etc)が使いたい方はこちらをお使いください。
本家Roger-random/ESP_8_BIT_composite.git
にはプルリクエストを出していません。
tanakamasayuki/efont
EfontWrapperクラスは私がefontライブラリへプルリクエストを出してマージしてもらったものです。よろしければどうぞ。
#もしかすると、こちらのライブラリのEfontWrapperクラスはコンパイルエラーになるかもしれません。そのときはriraosan/efont.git
(fork)のほうを使ってください。
riraosan/ESP_8_BIT_composite_Sample
こちらは私がmodifyしたサンプルになります。よろしければご参考になさってください。
test_efont
ブランチのほうにefontを使ったサンプルアプリをアップしています。
platformio.ini
を見ればライブラリの依存関係がわかると思います。
SHARP製液晶アナログTVの電源をONするための、赤外線リモコンライブラリをおまけでくっつけてます。要らなければ削除してください。
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <AnimatedGIF.h>
#include <ESP_8_BIT_GFX.h>
#include "non_4b_gif.h"
#include <IRremoteESP8266.h>
#include <IRsend.h>
const uint16_t kIrLed = 12; // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRsend irsend(kIrLed); // Set the GPIO to be used to sending the message.
// Create an instance of the graphics library
ESP_8_BIT_GFX videoOut(true /* = NTSC */, 16 /* = RGB565 colors will be downsampled to 8-bit RGB332 */);
AnimatedGIF gif;
// Vertical margin to compensate for aspect ratio
constexpr int margin = 10;
constexpr int _gif_offset_x = (256 - 180) / 2 - 10;
constexpr int _text_offset_y = 112;
constexpr int _text_offset_x = _gif_offset_x - 10;
// Draw a line of image to ESP_8_BIT_GFX frame buffer
void GIFDraw(GIFDRAW *pDraw) {
uint8_t *s;
uint16_t *d, *usPalette, usTemp[320];
int x, y;
usPalette = pDraw->pPalette;
y = pDraw->iY + pDraw->y; // current line
s = pDraw->pPixels;
if (pDraw->ucDisposalMethod == 2) // restore to background color
{
for (x = 0; x < pDraw->iWidth; x++) {
if (s[x] == pDraw->ucTransparent)
s[x] = pDraw->ucBackground;
}
pDraw->ucHasTransparency = 0;
}
// Apply the new pixels to the main image
if (pDraw->ucHasTransparency) // if transparency used
{
uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
int x, iCount;
pEnd = s + pDraw->iWidth;
x = 0;
iCount = 0; // count non-transparent pixels
while (x < pDraw->iWidth) {
c = ucTransparent - 1;
d = usTemp;
while (c != ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent) // done, stop
{
s--; // back up to treat it like transparent
} else // opaque
{
*d++ = usPalette[c];
iCount++;
}
} // while looking for opaque pixels
if (iCount) // any opaque pixels?
{
for (int xOffset = 0; xOffset < iCount; xOffset++) {
videoOut.drawPixel(pDraw->iX + x + xOffset, margin + y, usTemp[xOffset]);
}
x += iCount;
iCount = 0;
}
// no, look for a run of transparent pixels
c = ucTransparent;
while (c == ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent)
iCount++;
else
s--;
}
if (iCount) {
x += iCount; // skip these
iCount = 0;
}
}
} else {
s = pDraw->pPixels;
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
for (x = 0; x < pDraw->iWidth; x++) {
videoOut.drawPixel(_gif_offset_x + x, margin + y, usPalette[*s++]);
}
}
} /* GIFDraw() */
void setup() {
irsend.begin();
delay(1000);
irsend.sendPanasonic(0x555A, 0xF148688B);
delay(10);
irsend.sendPanasonic(0x555A, 0xF148688B);
delay(2000);
videoOut.begin();
videoOut.copyAfterSwap = true; // gif library depends on data from previous buffer
videoOut.fillScreen(0);
videoOut.waitForFrame();
gif.begin(LITTLE_ENDIAN_PIXELS);
}
void loop() {
if (gif.open((uint8_t *)non_4b_gif, 2252208, GIFDraw)) {
while (gif.playFrame(true, NULL)) {
videoOut.setTextSize(1);
videoOut.setTextColor(0xf800, 0x0000);
videoOut.printEfont(" By using ", _text_offset_x - 12, _text_offset_y + 16 * 2);
videoOut.setTextColor(0xFFFF, 0x03e0);
videoOut.printEfont("ESP_8_BIT_composite Library ", _text_offset_x - 12, _text_offset_y + 16 * 3);
videoOut.setTextColor(0xFFFF, 0x001f);
videoOut.printEfont("AnimatedGIF Library ", _text_offset_x - 12, _text_offset_y + 16 * 4);
videoOut.setTextColor(0xFFFF, 0xf800);
videoOut.printEfont("EfontWrapper Library ", _text_offset_x - 12, _text_offset_y + 16 * 5);
videoOut.waitForFrame();
}
videoOut.waitForFrame();
gif.close();
}
}
気をつけること
- 色数は256色です。NTSCフォーマットですので、テレビによって発色が異なります。そのような前提でNTSCフォーマットは設計されているようです。(個人的解釈)
- releaseモードでビルドすること。Debugモードでビルドすると、垂直同期信号が不安定になり画像が細かい縦揺れを起こします。
- このライブラリだけでは音はでません。コンポジット信号を出力する目的でI2Sチャンネルを一つ使用しています。ESP32から音を出すにはPWM信号を他のGPIOから出力してください。もしくは、残りのI2Sチャンネルを初期化して、内蔵DACからモノラル音声を出力してください。MP3等の音声ファイルはSPIFFSを使って内蔵フラッシュかSDカードから読み込めばよいと思います。
さいごに
おもしろいESP32ライブラリがありましたら教えて下さい。よろしく!
追記2021-10-18
応用例?
Weather Stationのデータをメルカリで買ったアナログ液晶テレビ(3000円)の大画面で確認してみる。
画質と発色は悪いけどね。これでいいんじゃないの?👍
(了)