56【UIAPduino】VIDEO信号を出したにょ①【290円マイコン基板】
抵抗2本で「映像」は作れるか?NTSC信号の物理レイヤ【連載①】
マイコンでビデオ信号を作るということは、ソフトウェアで高精度な可変電圧源をシミュレートすることと同義です。
1. 信号の三値論理(0V / 0.3V / 1.0V)
NTSCコンポジット信号は、単なるON/OFFのデジタル信号ではありません。以下の3つの電圧状態を、GPIOの組み合わせ(簡易抵抗DAC)で作り出します。
同期 (Sync): 0V
PC4をLOW、SPI(PC6)もLOWにする。
黒 (Black): 約0.3V
PC4をHIGH、SPIをLOWにする。この0.3Vが「映像の基準(ペデスタルレベル)」となります。
白 (White): 約1.0V
PC4をHIGH、SPIをHIGH(1)にする。
この「0.3V」という中途半端な電圧を、1kΩと470Ωの分圧で作るのがこの回路のキモです。
2. 時間軸の解像度:1クロックの重み
CH32V003を48MHzで動かしている場合、1クロックは約 20.8ns です。
NTSCの1行(水平走査期間)は 63.5μs ですが、テレビの同期回路は非常にシビアです。
水平同期パルス (4.7μs ± 0.1μs)
もし処理の都合でこのパルスが 5.0μs に伸びたり、4.4μs に縮んだりすると、テレビ側は「水平周波数が狂った」と判断し、画面を暗転させたり(信号ロスト)、表示位置を左右にガタつかせたりします。
ジッタの排除
C言語の for ループは、ループの終端で「条件比較」と「ジャンプ」を行います。このジャンプ命令が実行されるタイミングが、直前の命令の並びやパイプラインの状態によって1〜2クロック変動するだけで、画面には「波」となって現れます。
3. 難所:SPIとDMAの「同期」
映像データ(ドット)を出すにはSPIを使いますが、ここに落とし穴があります。
バックポーチの固定: 水平同期パルスが終わってから映像を出し始めるまでの「待ち時間」が1クロックでもズレると、画面全体が左右に微振動します。
DMAのバス競合: DMAがVRAMからデータを読み出す際、もしCPUが別のメモリ操作をしていると、DMAの読み出しが1クロック待たされることがあります。これが「ドットの泣き(太さが変わる)」の原因です。
対策: 描画中は while(DMA1_Channel3->CNTR > 0); でCPUをビジーウェイトさせ、バスをDMAに明け渡すのが最も安定します。
4. 垂直同期 (VSync) の「嘘」
実は、今回実装しているのは厳密なNTSC(インターレース)ではなくノンインターレース(プログレッシブ) という、ファミコンなどのレトロゲーム機で使われていた手法です。
通常、NTSCは1フレームを「奇数フィールド」と「偶数フィールド」の2回に分けて送りますが、マイコンでは面倒なので、常に同じタイミングの同期パルスを送り続けます。
テレビ側はこれを「240p」というプログレッシブ信号として解釈し、ビシッと安定した静止画として表示してくれます。
実機
コード抜粋
noInterrupts();
while (1) {
gameLoop();
updateGame();
for(int i=0; i<3; i++){ syncStart(); DELAY_CYCLES(1400); *GPIO_C_BSHR=(1<<(4+16)); DELAY_CYCLES(25); *GPIO_C_BSHR=(1<<4); }
for(int i=0; i<3; i++){ while(!(TIM2->INTFR&TIM_UIF)); TIM2->INTFR=0; *GPIO_C_BSHR=(1<<(4+16)); DELAY_CYCLES(1350); *GPIO_C_BSHR=(1<<4); DELAY_CYCLES(100); *GPIO_C_BSHR=(1<<(4+16)); DELAY_CYCLES(1350); *GPIO_C_BSHR=(1<<4); }
for(int i=0; i<3; i++){ syncStart(); DELAY_CYCLES(1400); *GPIO_C_BSHR=(1<<(4+16)); DELAY_CYCLES(25); *GPIO_C_BSHR=(1<<4); }
for (int line = 9; line < 100; line++) {
syncStart();
DELAY_CYCLES(400);
}
for (int line = 0; line < VRAM_H; line++) {
syncStart();
DELAY_CYCLES(220);
DMA1_Channel3->CFGR &= ~0x01;
DMA1_Channel3->MADDR = (uint32_t)&vram[line * VRAM_W];
DMA1_Channel3->CNTR = VRAM_W;
DMA1_Channel3->CFGR |= 0x01;
while(DMA1_Channel3->CNTR > 0);
DELAY_CYCLES(20);
SPI1->DATAR = 0x00; // 黒固定
}
for (int line = 100 + VRAM_H; line < 262; line++) {
syncStart();
DELAY_CYCLES(400);
}
}
}
まとめ:マイコンから見たビデオ信号
ビデオ信号を作るということは、マイコンをナノ秒単位で正確に電圧を切り替える精密機械 として扱う作業です。ロジックの正しさよりもいかにCPUの気まぐれ(実行サイクルの変動)を抑え込むか が全てと言っても過言ではありません。
次回予告
左右のげじげじを止めたいなぁと
i2c で通信出来れば夢のvideoコンバータに....(夢
投稿者の人気記事





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