【ESP Arduinoライブラリのご紹介】ESP_8_BIT_composite

# はじめに

私が今までかじって来たESPマイコンのArduinoライブラリについて紹介したいと思います。
これらの記事が、どなたかの何らかに貢献できれば幸いです。👍

# ESP_8_BIT_composite Library

今回は、ESP_8_BIT_compositeライブラリについて簡単に説明します。

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

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

# 概要

ESP32マイコンのI2Sピン（GPIO25）からコンポジット信号（NTSC or PALフォーマット）を出力できます。
[Adafruit GFX Library](https://github.com/adafruit/Adafruit-GFX-Library.git)描画ライブラリを使用しています。
アナログTVやデジタルTVの黄色いRCAコネクタに入力すると、アナログ映像（NTSC）でフォント、アニメーションGIF、BITMAP、線、円、ポリゴンなどを表示させることができます。４：３表示で解像度は480i（インターレース）です。カラー表示ができます。（8bitカラー:256色）

# お試し環境

このライブラリは次の環境でビルドを行い動作を確認しました。

- ソフト
    - VSCode
    - PlatformIO（VSCode拡張機能）
    - arduino-esp32 ライブラリ
- ハード
    - M5 ATOM Lite（ESP32-PICO）

# 動作の様子

テレビはメルカリで買ったSHARP LC-13S4（3000円）です。ATOM LiteのG25ポートをTVの[RCA端子（映像）](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%9D%E3%82%B8%E3%83%83%E3%83%88%E6%98%A0%E5%83%8F%E4%BF%A1%E5%8F%B7)に接続しました。（言わずもがなGNDも接続しています）

https://www.instagram.com/reel/CTpTXEwggaP/?utm_source=ig_web_copy_link

https://www.instagram.com/p/CTpXG4FvYxa/?utm_source=ig_web_copy_link

# サンプルコード

## riraosan/ESP_8_BIT_composite

フォントを表示させるために、`ESP_8_BIT_GFX`クラスを`EfontWrapper`クラスから派生しています。
Adafruit_GFXライブラリのフォント以外のフォント（日本語、韓国語、etc）が使いたい方はこちらをお使いください。
本家`Roger-random/ESP_8_BIT_composite.git`にはプルリクエストを出していません。

https://github.com/riraosan/ESP_8_BIT_composite.git

## tanakamasayuki/efont
EfontWrapperクラスは私がefontライブラリへプルリクエストを出してマージしてもらったものです。よろしければどうぞ。
＃もしかすると、こちらのライブラリのEfontWrapperクラスはコンパイルエラーになるかもしれません。そのときは`riraosan/efont.git`(fork)のほうを使ってください。

https://github.com/tanakamasayuki/efont.git

## riraosan/ESP_8_BIT_composite_Sample
こちらは私がmodifyしたサンプルになります。よろしければご参考になさってください。
`test_efont`ブランチのほうにefontを使ったサンプルアプリをアップしています。
`platformio.ini`を見ればライブラリの依存関係がわかると思います。
SHARP製液晶アナログTVの電源をONするための、赤外線リモコンライブラリをおまけでくっつけてます。要らなければ削除してください。

https://github.com/riraosan/ESP_8_BIT_composite_Sample

```c++:main.cpp
#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ライブラリがありましたら教えて下さい。よろしく！:pray:

# 追記2021-10-18

応用例？

Weather Stationのデータをメルカリで買ったアナログ液晶テレビ（3000円）の大画面で確認してみる。
画質と発色は悪いけどね。これでいいんじゃないの？👍
![246153462_2110460712440513_4760729400736712406_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103572/68689674-4959-b709-2123-f64ae667db40.jpeg)


（了）