Arduinoで本格的なテレビゲームを作れちゃいそう。。。

HDMIコネクタを使用した画像出力

PICやPICOに搭載されているRP2040などのマイクロコントローラーを使用して、NTSCやVGA信号を生成する製作例はいろいろと報告されていますが、HDMIコネクタで使用できるDVI信号を作成する例はほかにみたことがありません。

ハードウェア的にはRP2040に実質的にHDMIコネクタのみを追加し、ソフトウェアのみでDVI信号を生成する製作例 PicoDVI を見てびっくりしました。PicoDVIの機能は驚くことにソフトウェア的に実現されており、ハードウェア的には単にマイクロコントローラーの端子から、HDMIコネクタに信号線を接続しただけで実現されています。PICOに使用されているRP2040の潜在能力や、PicoDVIを開発したLuke Wren氏の能力や熱意(執念、狂気?)には、ただただ感嘆するばかりです。

PicoDVIを使用する際には、定格では133MHzのRP2040のクロックを252 MHzで動かしている点には注意が必要です。(おいたが過ぎるように感じますが、チップが問題なさげに動いているところもびっくりです。どんだけマージンがあんねん…)

映像出力を行うプログラムを実行すると、オーバークロックの影響で、通常は10mA以下の消費電流が50mA程度に激増しており、チップが少し暖かくなります。周辺温度にもよりますが、チップ内の温度センサーで計測すると、40-50度程度になるようです。この温度を見る限りは、チップ内の回路がすぐさま熱損傷を受けるということはなさそうですね。


Arduinoでのプログラミング

ライブラリのインストールとサンプルスケッチの実行

Arduino IDEのライブラリマネージャで[picodvi]を検索すると、[PicoDVI - Adafruit Fork]というライブラリが見つかるので、これをインストールします。

[ファイル]->[スケッチ例]->[PicoDVI - Arafruit Fork]の中にサンプルスケッチが複数納められているので、それらを選択して実行してみるとよいでしょう。

PicoDVIでどのような表示ができるのか大まかに確認したいのであれば、[16bit_hello]を実行してみるといいでしょう。また、ちょっとかわいい?のを試してみたいのであれば、[aquarium]を実行してみるといいかもしれません。

サンプルスケッチを実行する場合には、その最初の部分に以下に示すような記述があり、使用するボード等によって修正が必要な場合があります。

DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);

DVIGFX8がDVIGFX16やDVIGFX1になっている場合や、DVI_RES_320x240p60の数値部分が違う記述になっている場合がありますが、基本的なスタイルは変わりません。この記述のdisplay()の最後の引数を使用するボードにより必要に応じて変更します。

display()引数の変更が必要ないRP2040-UNO-HDMI
display()引数を[pico_sock_cfg]に変更するPICO-HDMI-PLUS

RP2040-UNO-HDMIを使用する場合は、サンプルスケッチのdisplay()の最後の引数は[adafruit_feather_dvi_cfg]のままで構いませんが、PICO-HDMI-PLUSを使用する場合には、以下のように[pico_sock_cfg]に書き換えてください。

DVIGFX8 display(DVI_RES_320x240p60, true, pico_sock_cfg);

PicoDVIライブラリの使用法は、以下のページでも紹介されているので、参考にするとよいでしょう。

スケッチの基本構成

最も基本的で単純なスケッチを作成して見ましょう。スケッチは以下のような構成になります。

  • フレームバッファの確保
  • フレームバッファの初期化
  • 画面の初期化
  • 文字や画像の描画

この構成で最小限の記述のスケッチを作成した例を以下に示します。

#include <PicoDVI.h>

// 16ビットのフレームバッファの確保
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);

void setup() {
  // フレームバッファの初期化
  display.begin();
  // 画面の初期化:全面を白色に
  display.fillScreen(0xFFFF);
  // 画像の描画:赤い丸を描画
  display.fillCircle(160, 120, 80, 0xF800);
}

void loop() {
}

なお、以降のスケッチも含め、display()の最後の引数は使用する開発ボードに合わせ、必要に応じて書き換えてください。

このスケッチでは日の丸が表示されます。:-)

fillScreen()やfillCircle()の最後の引数は色を指定する値で、0xFFFFは白、0xF800は赤を示しています。

このスケッチ例は絵を一度描いたらそれで終わりでとても単純ですが、画面に絵を表示する機能は完全に果たしています。この例では、画面を白で初期化(塗潰す)した上で、1つの赤い丸を書く処理だけが記述されていますが、点、線、円、四角など、表現したい絵に必要な多数の構成要素を書く処理を書き並べることで、このスケッチの構成で任意の絵を描くことができます。 

アニメーションへの対応

紙に描く絵であればこれで終わりですが、コンピュータの画面に描く絵では、一般的には画面の絵を繰り返し描き換えながら、アニメのように絵を動かすことが求められます。そのような場合には、スケッチの構成は先ほどとは少し異なり、以下に示すように絵の描き換えに対応できるように、絵を繰り返し描く構成に変更する必要があります。

  • フレームバッファの確保
  • フレームバッファの初期化
  • 繰り返し
    • 画面の初期化
    • 文字や画像の描画
    • ダブルバッファの場合には画面の切り替え

この構成で作成したスケッチを以下に示します。上記のスケッチと基本的に同じ処理を行っていますが、画面を初期化した上で絵を描く処理がloop()に移され、まだ単一の絵ではありますが、絵を繰り返し書き直す処理が実行されるようになっています。ちなみに、loop()の中で描く絵を毎回変更することにより、アニメの様に、絵が動いて見える表示ができるようになります。

#include <PicoDVI.h>

// 16ビットのフレームバッファの確保
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);

void setup() {
  // フレームバッファの初期化
  display.begin();
}

void loop() {
  // 画面の初期化:全面を白色に
  display.fillScreen(0xFFFF);
  // 画像の描画:赤い丸を描画
  display.fillCircle(160, 120, 80, 0xF800);
  // 1枚の絵を書いたら0.1秒休憩
  delay(100);
}

さて、変更されたスケッチではloop()内で繰り返し絵を描くようになりましたが、同じ絵を繰り返し書いているので、最初のスケッチとスケッチの実行によって得られる画面は同じはずです。しかしながら今度のスケッチの実行結果は、同じ日の丸の絵ではありますが、ちらついて見えるのではないでしょうか。

このように、コンピュータでアニメーションを行うために画面を繰り返し書き直す処理を行うと、画面がちらついて見えるようになるという問題があります。

文字の表示

画面のちらつきを取り除く話の前に少し脱線して、画面に文字を表示する例も示しておきます。

スケッチの構成は、アニメーションに対応するloop()内で繰り返し描画を行うスタイルを採用しています。

#include <PicoDVI.h>

// 16ビットのフレームバッファの確保
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);

void setup() {
  // フレームバッファの初期化
  display.begin();
  display.setFont(); // 文字表示用のフォントの設定:現状ではなくても動く
  display.setTextSize(2); // 文字の大きさをデフォルトの2倍に
  display.setTextColor(0) ; // 文字の色を黒に
}

void loop() {
  // 画面の初期化:全面を白色に
  display.fillScreen(0xFFFF);
  // 文字列の描画:コアの温度を表示
  display.setCursor(0, 0); // 文字の表示位置を指定
  display.print((String)"CORE TEMP: " + analogReadTemp() + "'C") ; // 温度表示
  // 画像の描画:赤い丸を描画
  display.fillCircle(160, 120, 80, 0xF800);
  // 1枚の絵を書いたら0.1秒休憩
  delay(100);
}

日の丸の絵の描き方には同じままなので表示される日の丸は変化しませんが、コアの温度は表示の更新の都度その時の温度を測って表示するので、時間とともに値が変化していきます。

ダブルバッファの利用

メモリ容量の関係でまず8ビットフレームバッファ用にスケッチを書き換え

従来の延長上のシングルバッファ

#include <PicoDVI.h>

// フレームバッファの確保:シングルバッファ
DVIGFX8 display(DVI_RES_320x240p60, false, adafruit_feather_dvi_cfg);

void setup() {
  // フレームバッファの初期化
  display.begin();
  // パレットの設定
  display.setColor(0, 0xFFFF); // 白
  display.setColor(1, 0xF800); // 赤
  display.setColor(2, 0x07E0); // 緑
  display.setColor(3, 0x001F); // 青
}

void loop() {
  // 画面の初期化:全面を白色に
  display.fillScreen(0);
  // 画像の描画:赤い丸を描画
  display.fillCircle(160, 120, 80, millis()/1000 % 4);
  // 1枚の絵を書いたら0.1秒休憩
  delay(100);
}

色の設定がパレット方式に変更

ダブルバッファ使用への切り替え

#include <PicoDVI.h>

// フレームバッファの確保:ダブルバッファを有効に
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);

void setup() {
  // フレームバッファの初期化
  display.begin();
  // パレットの設定
  display.setColor(0, 0xFFFF); // 白
  display.setColor(1, 0xF800); // 赤
  display.setColor(2, 0x07E0); // 緑
  display.setColor(3, 0x001F); // 青
  // パレットの設定を現在裏になっているフレームバッファにも反映する
  display.swap(false, true);
}

void loop() {
  // 画面の初期化:全面を白色に
  display.fillScreen(0);
  // 画像の描画:丸を描画
  display.fillCircle(160, 120, 80, millis()/1000 % 4);
  // フレームバッファの入れ替え
  display.swap() ;
  // 1枚の絵を書いたら0.1秒休憩
  delay(100);
}

フレームバッファの構成

カラーデプス

  • 16ビット
  • 8ビット
  • 1ビット

画面サイズ

  • 320x240
  • 400x240
  • 640x240
  • 800x240
  • 640x480
  • 800x480

描画処理

色指定

図形

文字列


関連製品

RP2040-UNO-HDMI

UNO形状のRP2040開発ボードにHDMIコネクタを搭載し、PicoDVIでPCモニタに画像出力を行うことができる開発ボードです。

PICO-HDMI-PLUS

Raspberri Pi PICO/PICO WにHDMIコネクタとタクトスイッチx4、圧電スピーカーを追加する拡張ボードです。PICOが超小型のテレビゲーム機やPCモニタ出力付きのArduino実験ボードに変身します。