2025年10月15日水曜日

ESP32マイコンでビデオゲーム 1(ビデオ出力回路)

 2年ほど前に作成した昭和レトロのビデオゲームについてまとめます。

 ビデオゲームが出始めたころは、ビデオ出力をテレビに接続してゲームをしていました。WindowsなどのOSが無い時代のパソコンはBASIC言語が主流で、画面をデザインするときには、LINE命令やCIRCLE命令などを使って作成していました。
 

NTSCビデオ出力する

 ESP32マイコンを使い、少ないI/Oでゲームなどができないかと考えていた時に、PICマイコンで「ブレイクアウト(ブロック崩し)」があったことを思い出し、PICマイコンでできたならば、CPUクロック、メモリとも充実しているESP32マイコンでもできるのではないかと思い、開発を始めました。また、テレビ画面やプロジェクタなどの大画面でゲームをやり、みんなで楽しむということもあります。
 検索していると、あすきさんのブログ「試行錯誤な日々」に「ESP32でAVケーブル(RCA、NTSC)を介して画面表示」があり、早速実験しました。この中ではらびやんさんの「画面制御ライブラリLovyanGFX」が使われており、このライブラリはグラフィックスライブラリで、LINE命令、CIRCLE命令などで、画面にグラフィックを描くものです。

 今回使用するNTSCビデオ出力(アナログ信号)が、地上デジタル化して、アナログ信号を扱う入力端子がテレビなどから無くなっていくのではと思っていましたが、安価なDVDプレーヤなどはまだNTSC信号を使っていることからなのか、今でもビデオ入力があります。

 ESP32マイコンでNTSC出力をする場合に、あすきさんのブログにもありますが、1本の信号で、テレビなどに出力できます。
 RCA端子によるビデオ入力には、ビデオ信号の他に、音声信号も入力できますので、もう1ポートを追加して、音声も追加しました。
 ESP32マイコンのビデオ出力は、DACを使って信号を作成しているため、対応しているポート25か、26のみです。
 
 プログラムもサンプルとして拝借をして、実行しました。

#define LGFX_USE_V1
#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device
{
public:

  lgfx::Panel_CVBS _panel_instance;

  LGFX(void)
  {
    { // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();    // 表示パネル設定用の構造体を取得します。

      // 出力解像度を設定;
      cfg.memory_width  = 240; // 出力解像度 幅
      cfg.memory_height = 160; // 出力解像度 高さ

      // 実際に利用する解像度を設定;
      cfg.panel_width  = 208;  // 実際に使用する幅   (memory_width と同値か小さい値を設定する)
      cfg.panel_height = 128;  // 実際に使用する高さ (memory_heightと同値か小さい値を設定する)

      // 表示位置オフセット量を設定;
      cfg.offset_x = 16;       // 表示位置を右にずらす量 (初期値 0)
      cfg.offset_y = 16;       // 表示位置を下にずらす量 (初期値 0)

      _panel_instance.config(cfg);


// 通常は memory_width と panel_width に同じ値を指定し、 offset_x = 0 で使用します。;
// 画面端の表示が画面外に隠れるのを防止したい場合は、 panel_width の値をmemory_widthより小さくし、offset_x で左右の位置調整をします。;
// 例えば memory_width より panel_width を 32 小さい値に設定した場合、offset_x に 16 を設定することで左右位置が中央寄せになります。;
// 上下方向 (memory_height , panel_height , offset_y ) についても同様に、必要に応じて調整してください。;

    }

    {
      auto cfg = _panel_instance.config_detail();

      // 出力信号の種類を設定;
      // cfg.signal_type = cfg.signal_type_t::NTSC;
      cfg.signal_type = cfg.signal_type_t::NTSC_J;
      // cfg.signal_type = cfg.signal_type_t::PAL;
      // cfg.signal_type = cfg.signal_type_t::PAL_M;
      // cfg.signal_type = cfg.signal_type_t::PAL_N;

      // 出力先のGPIO番号を設定;
      cfg.pin_dac = 26;       // DACを使用するため、 25 または 26 のみが選択できます;

      // PSRAMメモリ割当の設定;
      cfg.use_psram = 1;      // 0=PSRAM不使用 / 1=PSRAMとSRAMを半々使用 / 2=全部PSRAM使用;

      // 出力信号の振幅の強さを設定;
      cfg.output_level = 128; // 初期値128
      // ※ GPIOに保護抵抗が付いている等の理由で信号が減衰する場合は数値を上げる。;
      // ※ M5StackCore2 はGPIOに保護抵抗が付いているため 200 を推奨。;

      // 彩度信号の振幅の強さを設定;
      cfg.chroma_level = 128; // 初期値128
      // 数値を下げると彩度が下がり、0で白黒になります。数値を上げると彩度が上がります。;

      // バックグラウンドでPSRAMの読出しを行うタスクの優先度を設定;
      // cfg.task_priority = 25;

      // バックグラウンドでPSRAMの読出しを行うタスクを実行するCPUを選択 (APP_CPU_NUM or PRO_CPU_NUM);
      // cfg.task_pinned_core = PRO_CPU_NUM;

      _panel_instance.config_detail(cfg);
    }

    setPanel(&_panel_instance);
  }
};


LGFX gfx;

void setup(void) {
  gfx.init();

  for (int x = 0; x < gfx.width(); ++x) {
    int v = x * 256 / gfx.width();
    gfx.fillRect(x, 0 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(v, v, v));
    gfx.fillRect(x, 1 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(v, 0 ,0));
    gfx.fillRect(x, 2 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(0, v, 0));
    gfx.fillRect(x, 3 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(0, 0, v));
  }
  delay(1000);
  gfx.drawLine(0,0, gfx.width() - 1, gfx.height() - 1, TFT_GREEN);
  gfx.drawRect(0,0, gfx.width(), gfx.height(), TFT_RED);
  gfx.drawRect(1,1, gfx.width() - 2, gfx.height() - 2, TFT_GREEN);
  gfx.drawRect(2,2, gfx.width() - 4, gfx.height() - 4, TFT_BLUE);
  gfx.fillRect(gfx.width()/2, (gfx.height() >> 3) * 4, gfx.height() / 3, gfx.height() / 3, TFT_PURPLE);
}

void loop(void) {
  gfx.setCursor(20, (gfx.height() >> 3) * 4);
  gfx.setTextSize(0);
  gfx.println("hello at " + String(millis()));
}
 クラス「LGFX」を追加しています。このクラスがNTSCに出力する機能を追加しています。
 接続回路に応じて変更する点は48行目の出力ピンの設定です。
 らびやんさんの画面制御ライブラリ「LovyanGFX」を使用していますので、ライブラリを追加しておきます。

 プログラムを書き込み、出力端子をテレビに接続すると、これだけで出力できました。


0 件のコメント:

コメントを投稿

LEDマトリックス表示装置の設計・製作 9(画像表示)

  LEDマトリックスに画像を表示させるには、いろいろな画像形式を知る必要があります。現在、よく使われるJPG形式やPNG形式は圧縮処理されて保存されています。これに対して、BMP形式やTIFF形式は非圧縮形式で、画像データがそのまま保存されています。このため、ファイルは大きくな...