フォト
2025年4月
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      
無料ブログはココログ

« しばらく、ブログを休みます。 | トップページ | 次はSTM32ボードを積極的に使ていきたい(6) - Lチカ »

2017年2月16日 (木)

次はSTM32ボードを積極的に使ていきたい(5) - NTSCビデオ出力

やっと落ち着いてきました。 ブログを再開します^^
(NAVERとは徹底的に戦うつもりでしたが、時間の経過とともにどうでもよくなってきました)

安価なSTM32ボードでNTSCビデオ出力に挑戦、取りあえず表示出来ました。
プログラムの作成は、Arduino STM32環境を利用しました。

Dscn6446

解像度は取りあえず、モノクロ 224x192ドットです。
文字を表示するだけのデモプログラムです。

Dscn6444


回路図

01

端子A7: 映像信号出力
端子B1: 同期信号出力
GND : GND

抵抗値の計算は面倒なので、スマホのHandyCalcで計算しました。
連立方程式を入力すれば、解いてくれるので非常に便利です。

02

式のr1が端子A7: 映像信号出力に接続する抵抗、
式の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
  ・KOUSAKUさんのHP 建築発明工作ゼミ2008
    「Arduino ビデオ信号/テレビ画面に出力」
    「Arduino ビデオ信号/バウンドするドット」



スケッチ(プログラム)
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のパルスで生成しています。

04

映像信号出力
前半に水平同期パルスを入れて、9.4usから映像出力を行っています。
水平同期パルスは0V or 0.3Vのパルス、映像信号は0.3V (黒) or 1.0V(白)です。
3.3Vを抵抗で分圧した後、合成して出力しています。

05

映像信号の出力は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経由での直後のスケッチ書き込みが自動では
行えなくなります。


次にスケッチ書込みを行う場合は、「マイコンボードに書き込んでいます」の表示が
されたタイミングで、リセットボタンを押すと書き込みが出来ます。

03

« しばらく、ブログを休みます。 | トップページ | 次はSTM32ボードを積極的に使ていきたい(6) - Lチカ »

arduino」カテゴリの記事

表示器制御関連」カテゴリの記事

ARM」カテゴリの記事

STM32」カテゴリの記事

コメント

たま吉さん様お帰りなさい!

暫く5月25日の記事の追記を見させていただいていたのですが、
やはりというかとうとうというかブログ再開になりました。ありがと
うございます。

ブログをお休みになっている間、インタプリタを幾つか試していた
のですが何れも元のプログラムが良くできていて簡単に移植がで
きました。中でも、Maple mini用 Espruino はそのままST-LINKで
焼け一応動いているようです。Arduino IDE では、上部アドレスも
EEPROM としてアクセスできているようで、STM32F103CBT6 と
同じようにフラッシュメモリは128Kバイト使える!?

また再開された記事で勉強させていただきます。

くまさん

コメントありがとうございます。
Maple mini用 Espruinoいいですね。私も動かしてみます。

くまさん

Espruino、Blue Pillボードで動作しました。
これはいいですね。
有益な情報ありがとうございます。

別のBlackPillボードでは動きませんでした。
フラッシュメモリサイズが64kバイトのようです。

コメントを書く

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: 次はSTM32ボードを積極的に使ていきたい(5) - NTSCビデオ出力:

« しばらく、ブログを休みます。 | トップページ | 次はSTM32ボードを積極的に使ていきたい(6) - Lチカ »