次はSTM32ボードを積極的に使ていきたい(5) - NTSCビデオ出力
やっと落ち着いてきました。 ブログを再開します^^
(NAVERとは徹底的に戦うつもりでしたが、時間の経過とともにどうでもよくなってきました)
安価なSTM32ボードでNTSCビデオ出力に挑戦、取りあえず表示出来ました。
プログラムの作成は、Arduino STM32環境を利用しました。
解像度は取りあえず、モノクロ 224x192ドットです。
文字を表示するだけのデモプログラムです。
回路図
端子A7: 映像信号出力
端子B1: 同期信号出力
GND : GND
抵抗値の計算は面倒なので、スマホのHandyCalcで計算しました。
連立方程式を入力すれば、解いてくれるので非常に便利です。
式のr2が端子B1: 同期信号出力に接続する抵抗です。
r3は接続先モニターの内部抵抗で、75Ω(固定値)としています。
抵抗を使って3.3Vを分圧します。
3.3Vが白レベルが1V、同期信号レベルが0.3Vとなる様に抵抗を決定します。
求められた値に近い抵抗を使っています。
ビデオ信号生成方法及び抵抗の計算方法等については、次の情報を参考にいたしました。
大変参考になりました(感謝!)。
・nekosanさんのHP: 「PIC AVR 工作室 - ビデオ表示のツボ」
http://picavr.uunyan.com/making_p_ntsc.html
・ChaNさんのHP: 「S-170A NTSCビデオ信号タイミング規格の概要」
http://elm-chan.org/docs/rs170a/spec_j.html
スケッチ(プログラム) GitHubGist: Tamakichi/stm32_video.ino
Arduino IDE 1.8.1+ArduinoSTM32環境を利用しています。
別途美咲フォントライブラリ Arduino-misakiUTF16 が必要です。
// // Arduino STM32 NTSCビデオ出力 サンプル V2.0 // Blue Pillボード(STM32F103C8)にて動作確認 // 最終更新日 2017/02/17 たま吉さん // #include <SPI.h> #include <misakiUTF16.h> // 美咲フォントライブラリ #define gpio_write(pin,val) gpio_write_bit(PIN_MAP[pin].gpio_device, PIN_MAP[pin].gpio_bit, val) #define CLK PB1 // 同期信号出力ピン #define DAT PA7 // 映像信号出力ピン #define SC_WIDTH 224 // 横解像度 #define SC_HIGHT 192 // 縦解像度 #define SYNC(V) gpio_write(CLK,V) // 同期信号出力 #define VRAMSIZE (224*192/8) // ビデオ表示フレームバッファサイズ int count=1; // 走査線を数える変数 uint8_t vram[VRAMSIZE]; // ビデオ表示フレームバッファ uint8_t* ptr; // ビデオ表示フレームバッファ参照用ポインタ uint8_t flgHsync = 0; // 水平同期信号出力フラグ uint8_t flgVideo = 0; // 映像信号出力フラグ //垂直同期信号 inline void vsync(){ SYNC(0); delay_us(27); SYNC(1); delay_us(5); } // フォント描画 void drawFont(int x, int y, uint8_t* font) { uint8_t* ptr = &vram[y*28*8+x]; for (int i=0; i<8; i++) { *ptr = *font; ptr+=28; font++; } } // 文字列描画 void drawText(int x, int y, char* str) { uint8_t fnt[8]; while(*str) { if (x>=28) break; if (! (str = getFontData(fnt, str)) ) { Serial.println("Error"); break; } drawFont(x,y ,fnt); x++; } } // 画面クリア void cls() { memset(vram, 0, VRAMSIZE); } // DMA用割り込みハンドラ(データ出力をクリア) void DMA1_CH3_handle() { while(SPI.dev()->regs->SR & SPI_SR_BSY); SPI.dev()->regs->DR = 0; } // DMAを使ったデータ出力 void SPI_dmaSend(uint8_t *transmitBuf, uint16_t length) { dma_setup_transfer( DMA1,DMA_CH3, // SPI1用DMAチャンネル3を指定 &SPI.dev()->regs->DR, // 転送先アドレス :SPIデータレジスタを指定 DMA_SIZE_8BITS, // 転送先データサイズ : 1バイト transmitBuf, // 転送元アドレス : SRAMアドレス DMA_SIZE_8BITS, // 転送元データサイズ : 1バイト DMA_MINC_MODE| // フラグ: サイクリック DMA_FROM_MEM| // メモリから周辺機器、転送完了割り込み呼び出しあり DMA_TRNS_CMPLT // 転送完了割り込み呼び出しあり ); dma_set_num_transfers(DMA1, DMA_CH3, length); // 転送サイズ指定 dma_enable(DMA1, DMA_CH3); // DMA有効化 } // タイマー割り込みハンドラ(走査線の処理) void handle_video() { SYNC(0); flgHsync = 0; flgVideo = 0; if(count>=3 && count<=5){ //垂直同期信 vsync(); vsync(); } else if (count >35 && count <227) { flgHsync = 1; //水平同期信号出力有効 flgVideo = 1; //映像信号出力有効 } else { flgHsync = 1; //水平同期信号出力有効 } count++; if(count>262) { count=1; ptr = vram; } } // 水平同期信号出力 void handle_hsync() { if (flgHsync) SYNC(1); } // ビデオ用データ表示(ラスタ出力) void handle_vout() { if (flgVideo) { SPI_dmaSend((uint8_t *)ptr, 28); ptr+=28; } } void setup(){ // ジッター防止のためタイマー割り込み優先度を上げる nvic_irq_set_priority(NVIC_TIMER2, 0); // 割り込み優先レベル設定 pinMode(CLK,OUTPUT); // 同期信号出力ピン // SPIの初期化・設定 SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE3); SPI.setClockDivider(SPI_CLOCK_DIV16); SPI.dev()->regs->CR1 |=SPI_CR1_BIDIMODE_1_LINE|SPI_CR1_BIDIOE; // 送信のみ利用の設定 // SPIデータ転送用DMA設定 dma_init(DMA1); dma_attach_interrupt(DMA1, DMA_CH3, &DMA1_CH3_handle); spi_tx_dma_enable(SPI.dev()); ptr = vram; /// タイマ2の初期設定 Timer2.pause(); // タイマー停止 Timer2.setPrescaleFactor(3); // システムクロック 72MHzを24MHzに分周 Timer2.setOverflow(1524); // カウンタ値1524でオーバーフロー発生 63.5us周期 // +0.0us 更新時呼び出し 割込みハンドラの登録 Timer2.attachInterrupt(TIMER_UPDATE_INTERRUPT, handle_video); // +4.7us 水平同期信号出力用 割り込みハンドラ登録 Timer2.setCompare(1, 112); Timer2.setMode(1,TIMER_OUTPUTCOMPARE); Timer2.attachInterrupt(1, handle_hsync); // +9.4us 映像出力用 割り込みハンドラ登録 Timer2.setCompare(2, 225); Timer2.setMode(2,TIMER_OUTPUTCOMPARE); Timer2.attachInterrupt(2, handle_vout); Timer2.refresh(); // タイマーの更新 Timer2.resume(); // タイマースタート // 画面表示 cls(); drawText(3,3,"■■STM32ボードでNTSC出力テスト■■"); drawText(7,6,"ねこにコ・ン・バ・ン・ワ"); drawText(5,8,"解像度は224x192ドットです"); drawText(6,12,"まだまだ色々と調整中です^^"); } void loop(){ }
スケッチ(プログラム)の解説 (2017/02/18 追記・更新)
取りあえず、映像は表示出来たのですがかなり簡略化しています。
NTSCの仕様には完全には準拠していません。
NTSCビデオ信号の処理について
1画面は水平走査線262本で構成されています。
その走査線を先頭から番号を付けて1番から262番とすると
プログラムでは
走査線 3番 ~ 5番 で垂直同期信号の出力
走査線 35番 ~227番 で映像信号出力
上記以外は何も表示しない映像信号出力
を行っています。
垂直同期信号の出力
本来なら前置等価パルス3本、垂直同期パルス3本、後置等価パルス3本の
合計9本の走査線構成となりますが、前置・後置等価パルスは省略しています。
下記の垂直同期パルスを三回、走査線3本分のみを出力して同期を行っています。
(実際はdelay_us()を使った手抜きを行い、実際はちゃんと出ていません)
同期信号は0v or 0.3vのパルスで生成しています。
映像信号出力
前半に水平同期パルスを入れて、9.4usから映像出力を行っています。
水平同期パルスは0V or 0.3Vのパルス、映像信号は0.3V (黒) or 1.0V(白)です。
3.3Vを抵抗で分圧した後、合成して出力しています。
映像信号の出力はSPIを使っています。出力は72MHzのシステムクロックを1/16分周し
4.5MHzの速度で1ライン分28バイトを出力しています。
この場合、1クロック幅は0.22usとなり、映像出力期間としてさ有効な52.6us(実際は
この幅はとれない)は236ドット分となります。
実際は、224ドットしかとれませんでした。
システムクロック 72MHzは、NTSCビデオ出力に使うには相性が悪いです。
分周クロックが6~8MHZくらいを作れれば理想の256ドットくらいに出来るのですが..
SPIのクロック分周は2,4,6,8,16,32..としか出来ません。
1/8分周で1ライン448ドットは可能ですが、微妙に解像度が高くて使いにくいです。
SPIの送信バッファへのデータ補充はDMA転送を使ってします。
DMAは大変便利です。CPUの処理と切り離して勝手にやってくれます。
Arduino STM32にはSPI.dmaSend()というSPI出力をDMA経由で行うAPIがあるのですが、
このAPI、関数内で転送完了待ちのポーリング(ループ)を行っており折角のDMAの利点
が半減してしまうので、自作しました。
転送完了の割り込み通知にて映像信号をOFFにしています。
(これをしないと最後の出力データがHIGHの場合、画面が崩れます)
映像出力用のクロックは、Timer2を利用しています。
システムクロック72MHzを1/3分周して24MHzのクロックとして利用しています。
24MHzですと、走査線幅63.5usは1524クロック分となります。
主要タイミングのクロックは次のようになります。
走査線 63.5us 1524
水平同期パルス幅 4.7us 112
映像出力開始 9.4us 225
各種同期信号、映像信号出力は上記のタイミングで処理を行い、
3つのタイミングで割り込み関数を呼び出しています。
・走査線・垂直同期用 : handle_video()
・水平同期用 : handle_hsync()
・映像信号出力用 : handle_vout()
タイマー機能は流石にARMだけあって高機能です。
タイマーの使い方については下記のサイトを参考にしました。
LeafLabs Documentation
・Timers
・HardwareTimer
・libmaple APIs timer.h
NTSCビデオ出力については、もうちょっと機能強化してArduinoのTVoutのような
ライブラリにしようと思います。
2017/02/17 更新
ジッター防止及びUSB経由書き込み不全は、タイマー割り込みの優先度を上げることで
対応出来ました。スケッチを修正しました。(注意)
スケッチは、ジッター防止のためUSBシリアル通信を停止しています。
=> Serial.end();
これを実行すると、Arduino IDEからUSB経由での直後のスケッチ書き込みが自動では
行えなくなります。
次にスケッチ書込みを行う場合は、「マイコンボードに書き込んでいます」の表示が
されたタイミングで、リセットボタンを押すと書き込みが出来ます。
« しばらく、ブログを休みます。 | トップページ | 次はSTM32ボードを積極的に使ていきたい(6) - Lチカ »
「arduino」カテゴリの記事
- Arduino IDE+Arduino STM32環境で指定と異なるgccが使われてしまう(2025.01.23)
- Zorin OSでArduino Uno互換機(CH340)が認識しない(2025.01.19)
- Arduino IDE 2.3.4でArduino STM32を利用する(2025.01.12)
- Arduino用 SKK日本語変換ライブラリの開発 その1(2024.12.28)
- NeoPixel(WS2812B)の制御 その5(2024.09.15)
「表示器制御関連」カテゴリの記事
- NeoPixel(WS2812B)の制御 その5(2024.09.15)
- Arduino用 美咲フォントライブラリを更新しました(2024.03.21)
- Raspberry Pi Pico(MicroPython)でLEDドットマトリックスを使ってみる(2024.03.14)
- Raspberry Pi Pico MicroPython用のマルチフォントライブラリ(2023.02.09)
- MicroPython(Raspberry Pi pico)で8x8ドットNeoPixcel文字表示(2023.02.08)
「ARM」カテゴリの記事
- Arduino IDE+Arduino STM32環境で指定と異なるgccが使われてしまう(2025.01.23)
- Arduino IDE 2.3.4でArduino STM32を利用する(2025.01.12)
- PocketGoで遊んでみる(1)(2020.03.24)
- Arduino用 MML文演奏ライブラリの作成 その1(2019.04.01)
- BluePillボードで4桁7セグLEDの制御(2019.03.21)
「STM32」カテゴリの記事
- Arduino IDE+Arduino STM32環境で指定と異なるgccが使われてしまう(2025.01.23)
- Arduino IDE 2.3.4でArduino STM32を利用する(2025.01.12)
- 「Arduino STM32 リファレンス 日本語版」が2万アクセス突破!(2021.03.26)
- SPI接続フラッシュメモリモジュールを入手しました(2020.05.13)
- Arduino STM32でキャラクタ液晶ディスプレイを使う(2019.06.01)
コメント
« しばらく、ブログを休みます。 | トップページ | 次はSTM32ボードを積極的に使ていきたい(6) - Lチカ »
たま吉さん様お帰りなさい!
暫く5月25日の記事の追記を見させていただいていたのですが、
やはりというかとうとうというかブログ再開になりました。ありがと
うございます。
ブログをお休みになっている間、インタプリタを幾つか試していた
のですが何れも元のプログラムが良くできていて簡単に移植がで
きました。中でも、Maple mini用 Espruino はそのままST-LINKで
焼け一応動いているようです。Arduino IDE では、上部アドレスも
EEPROM としてアクセスできているようで、STM32F103CBT6 と
同じようにフラッシュメモリは128Kバイト使える!?
また再開された記事で勉強させていただきます。
投稿: くま | 2017年2月19日 (日) 01時27分
くまさん
コメントありがとうございます。
Maple mini用 Espruinoいいですね。私も動かしてみます。
投稿: たま吉さん(管理者) | 2017年2月19日 (日) 09時27分
くまさん
Espruino、Blue Pillボードで動作しました。
これはいいですね。
有益な情報ありがとうございます。
別のBlackPillボードでは動きませんでした。
フラッシュメモリサイズが64kバイトのようです。
投稿: たま吉さん(管理者) | 2017年2月19日 (日) 10時42分