69[SPRESENSE 2026]TVに出力したにょ[CVBS]
CVBS on SPRESENSE ― 5年越しのアナログビデオ出力
はじめに
SPRESENSE で CVBS(コンポジットビデオ)信号を生成し、ブラウン管テレビやアナログモニターに映像を映すことに成功しました。
「SPRESENSE CVBS」をキーワードに検索しても、日本語・英語問わずまとまった実装例が見当たりません。おそらく世界初の実装です。
この記事では、実装に至るまでの経緯と、コードの仕組みを解説します。
ここまで 5年かかった ― CVBS on SPRESENSE
最初にSPRESENSE(Sony の ARM Cortex-M4F マイコンボード)でアナログビデオ出力に挑戦し始めたのは約5年前のことです。
なぜ難しいのか
CVBS(NTSC)信号には厳格なタイミング要件があります。
| 項目 | 値 |
|---|---|
| 水平周波数 | 15.734 kHz |
| 垂直周波数 | 59.94 Hz |
| 総ライン数 | 262ライン(フレームあたり) |
| 水平周期 | 約 63.5 µs |
1ラインごとに同期パルス・映像信号をマイクロ秒単位の精度で生成し続けなければなりません。割り込みの遅延やキャッシュの影響で信号が乱れ、何度も画面が崩れてきました。
ブレークスルー
SPRESENSE のタイマー割り込みを 60 µs 固定で刻むことで 16.09 kHz の安定した水平同期を実現。さらに ARM の インラインアセンブリ NOP ループで映像ピクセルのタイミングを精密に合わせることで、ついに安定した映像出力を達成しました。
アーキテクチャ ― MainCore / SubCore1
SPRESENSE の最大の特徴の一つがマルチコア(MP)機能です。本実装ではこれを最大限に活用しています。
┌─────────────────────────────┐
│ SPRESENSE │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ MainCore │ │SubCore1 │ │
│ │ │ │ │ │
│ │ CVBS信号 │ │ シリアル │ │
│ │ 生成専念 │ │ 通信など │ │
│ └────┬─────┘ └──────────┘ │
│ │ 共有VRAM │
└───────┼─────────────────────┘
│
D6: SYNC_PIN
D7: VIDEO_PIN
│
┌────▼────┐
│ RCA端子 │
└─────────┘
MainCore(SubCore1.hpp / CVBS信号生成)
MainCore はビデオ信号生成に完全に専念します。loop() は 1ライン分の処理を延々と繰り返すだけです。
void loop() {
int line;
WAIT_LINE(line); // タイマー割り込みで次のラインを待つ
PROCESS_LINE(line); // そのラインの信号を出力
}
SubCore1(MainCore.hpp)
SubCore1 はシリアル通信など MainCore を邪魔しない処理を担当します。現在はシンプルに MP.begin(1) で起動するのみです。
void setup() {
Serial.begin(115200);
MP.begin(1);
}
ビルド振り分け
エントリポイント 3rd03.ino は #ifdef SUBCORE でコアを自動判別します。1つのスケッチで MainCore / SubCore1 両方をビルドできる、SPRESENSE らしいスマートな構成です。
#ifdef SUBCORE
#include "SubCore1.hpp"
#else
#include "MainCore.hpp"
#endif
マクロ多用 ― タイミングを守るための設計
CVBS 生成でもっとも重要なのはタイミングの正確さです。関数呼び出しのオーバーヘッドすら信号の乱れにつながるため、本実装はクリティカルな処理をすべてマクロで記述しています。
ポート直接操作
digitalWrite() は遅すぎます。portOutputRegister() でポートアドレスを直接取得し、1命令で出力します。
volatile uint8_t *portSync = portOutputRegister(digitalPinToPort(SYNC_PIN));
volatile uint8_t *portVideo = portOutputRegister(digitalPinToPort(VIDEO_PIN));
#define SYNC_LO (*portSync = 0)
#define SYNC_HI (*portSync = 1)
#define VIDEO_LO (*portVideo = 0)
#define VIDEO_HI (*portVideo = 1)
NOP ディレイ(インラインアセンブリ)
C の delayMicroseconds() では精度が足りません。ARM の nop 命令を正確なループで刻みます。
#define NOP_DELAY(n) do { \
uint32_t count = (n); \
__asm__ volatile ( \
"1: nop \n\t" \
"subs %0, %0, #1 \n\t" \
"bgt 1b" \
: "+r"(count) : : "cc" \
); \
} while (0)
1バイト 8ビット展開マクロ
1ピクセル = 1ビットの映像データをピンに送り出します。ループを使わず8ビットをマクロで完全展開することで、毎ビット同じサイクル数を保証します。
#define VIDEO_BIT(b, bit) (*portVideo = (((b) & (bit)) ? 1 : 0))
#define SEND_BYTE(b) do { \
VIDEO_BIT(b, 0x80); VIDEO_BIT(b, 0x40); \
VIDEO_BIT(b, 0x20); VIDEO_BIT(b, 0x10); \
VIDEO_BIT(b, 0x08); VIDEO_BIT(b, 0x04); \
VIDEO_BIT(b, 0x02); VIDEO_BIT(b, 0x01); \
} while(0)
ライン種別の振り分け
262ライン全体を NTSC 規格どおりに振り分けます。
#define PROCESS_LINE(line) do { \
if ((line) < 3) { DO_EQUALIZING; } \ // 等化パルス
else if ((line) < 6) { DO_VSYNC; } \ // 垂直同期
else if ((line) < 9) { DO_EQUALIZING; } \ // 等化パルス
else if ((line) < 21) { DO_BLANK; } \ // ブランク
else if ((line) < 21 + VRAM_H) { DO_VIDEO((line)-21); } \ // 映像
else { DO_BLANK; } \ // ブランク
} while(0)
VRAM 構成
解像度: 192 × 168 ピクセル(1bit/pixel)
VRAM: 24 バイト × 168 ライン = 4,032 バイト
volatile 修飾された共有 VRAM を MainCore / SubCore1 の両コアから参照できます。
3×5 ピクセルフォント(Font35.hpp)
テキスト表示用に 3×5 の極小フォントを自作しています。1文字を uint16_t 15ビットで表現するコンパクトな設計です。
static const uint16_t font3x5[94] = {
...
0b111101111101101, // 'A'
0b110101110101110, // 'B'
...
};
実際の画像
映像出力の画面キャプチャです。
画面には以下が表示されています。
CVBS ON SPRESENSE
[0] 192 X 168
カウンタ([0]〜[9])がインクリメントされ、映像がリアルタイムで更新されていることを確認できます。
さいごに
5年越しの挑戦がようやく実を結びました。
SPRESENSE の強みであるマルチコア・高精度タイマー・ARM Cortex-M4F の速度を組み合わせることで、外部チップなしの純粋なソフトウェア CVBS 生成を実現できました。
今後の展望としては以下を考えています。
- グレースケール対応(抵抗ラダーによる多値出力)
- SubCore1 からの動的描画
- PAL 対応
コードは近日 GitHub と elchika に公開予定です。何か質問があればコメントどうぞ!
いつもご清聴ありがとうございます
投稿者の人気記事
![[SPRESENSE 2026]TVに出力したにょ[CVBS]](https://res.cloudinary.com/elchika/image/upload/t_elchika_article_cover/v1/user/878d4a9c-c79b-489e-8802-a7be8ac2f070/article/b420a75a-50d0-4ea5-8481-91204f286f1b/q7eyhsovzkmgcgekumq4.png)




-
chrmlinux03
さんが
前の水曜日の12:22
に
編集
をしました。
(メッセージ: 初版)
-
chrmlinux03
さんが
前の水曜日の12:27
に
編集
をしました。
ログインしてコメントを投稿する