フォト
2017年5月
  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 31      
無料ブログはココログ

« 2016年12月 | トップページ | 2017年3月 »

2017年2月の4件の記事

2017年2月28日 (火)

次はSTM32ボードを積極的に使ていきたい(7)

「次はSTM32ボードを積極的に使ていきたい(5)」でやったNTSCビデオ出力を改良しました。
水平同期パルス、垂直同期パルスをPWM出力を使って実装しました。
これで、利用する割り込み処理を少し減らせました。

02

画面は縦を192ドットから216ドットに広げました。 

03

同期信号はPWM出力に変更したため、利用ピンをA1に変更しました。

01

このNTSCビデオ出力はライブラリ化して、GitHubの方に登録&公開しました。
  ・Arduino STM32用 NTSCビデオ出力ラブラリ
   https://github.com/Tamakichi/ArduinoSTM32_TNTSC

ただし、このライブラリはフレームバッファ内のデータをただ表示するだけの機能しかありません。
別途描画処理を行う上位のライブラリが必要となります。 


2017/03/03 追記

画面解像度を5パターン対応しました。

https://github.com/Tamakichi/ArduinoSTM32_TNTSC の公開版は
V2.2にバージョンアップしました。

02

添付のサンプルスケッチも差し換えました。
画面解像度を動的に変更します。

05

04

2017年2月22日 (水)

久しぶりのPICマイコン、ちょっとハマった

「PIC24FJ64GB002を使ったUSBホストの実験 5」の記事を更新しました。

USB-MIDI変換モジュールの製作にて、
システムエクスクルシブメッセージの処理に不具合があり、修正しました。
動作確認にために、amazonで購入した「USB MIDIケーブル」を使ったのですが、
中途半端に動く製品のため、ハマりました。

03

通常のMIDIパケットは問題ないのですが、私が一番動作確認したい
システムエクスクルシブメッセージだけが正しく送信できません。

04

プログラムの不具合修正の動作確認に、パソコンMIDI演奏ソフトを使っていたのですが、
USB-MIDIパケット => MIDIシリアルデータの変換において、
システムエクスクルシブメッセージを送ると、その中のデータ抜けが発生します。
どうも、システムエクスクルシブメッセージ中に特定のコードがあると
発生するようです。

02

まあ、価格が300円なのであまり文句はいえませんが..

2017年2月19日 (日)

次はSTM32ボードを積極的に使ていきたい(6)

Arduino STM32環境 Blue PillボードでのLチカの3連発

基本に戻って、Lチカ(1秒間隔でLEDをチカチカ)のメモです。
3つの方法についてのメモです。

Dscn6448

回路図
01

  流れる電流は4.0mAくらいにしています。
   I = (3.3v - 2.0 v) / 330Ω × 1000 = 3.94 mA  (順方向電圧降下を2.0vとする)

   ※LEDを駆動する端子は訳アリでPA8にしています。

(1)delay()を使った方法

#define LED_PIN  PA8  // TIMER1 1CH用出力ピン

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
 digitalWrite(LED_PIN, HIGH);
 delay(1000);
 digitalWrite(LED_PIN, LOW);
 delay(1000);
}

LED_PINの出力をHIGH、LOWと変化させてLEDを点滅させています。
loop()で無限ループさせているので、他に処理が出来ません。

そこで、次にタイマー割り込みを使ってみます。


(2)タイマー割り込みを使った方法

#define LED_PIN  PA8  // TIMER1 1CH用出力ピン

uint8_t sw = LOW;

void handle_timer() {
  if (sw == LOW) {
    sw = HIGH;
  } else {
    sw = LOW;    
  } 
  digitalWrite(LED_PIN, sw);
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Timer1.pause();                   // タイマー停止
  Timer1.setPrescaleFactor(7200);   // システムクロック 72MHzを10kHzに分周
  Timer1.setOverflow(10000);        // 最大値を1秒に設定
 
  Timer1.attachInterrupt(           // 割り込みハンドラの登録
      TIMER_UPDATE_INTERRUPT,       // 呼び出し条件は、カウンターオーバーフロー更新時
      handle_timer                  // 呼び出す関数
    );  

  Timer1.setCount(0);               // カウンタを0に設定
  Timer1.refresh();                 // タイマ更新
  Timer1.resume();                  // タイマースタート
}

void loop() {

}

Timer1はあらかじめ用意されているTimerオブジェクト変数です。
Arduino STM32では標準でタイマー割り込み機能(API)が提供されています。
Blue PillボードではTimer1、Timer2、Timer3、Timer4の4つが利用出来ます。

1秒間隔で割り込み関数を呼び出すための設定として、
   分周設定    :   7200分の1
   最大値設定 :  10,000カウント
を行っています。

システムクロック72MHzを7200分の1に分周した10kHzをタイマーのクロックとして
設定しています。
10kHzは0.0001秒に1回、カウンターの値をカウントアップします。
そのカウント値が10,000に達した時に、UPDATEイベントが発生するように指定します。
0.0001秒× 10,000 = 1.0秒 ですね。

Timer1.attachiInterupt()で割り込み処理を行う関数を設定しています。
引数のTIMER_UPDATE_INTERRUPT は、カウンター値を0に戻す更新時に
呼び出す指定で、2つ目の引数に指定した関数を呼び出します。

呼び出されるhanle_timer()でLEDの制御を行っています。
attachiInterupt()では、TIMER_UPDATE_INTERRUPT他に別途、4つの比較値(レジスタ)
に一致した条件での割り込み関数を呼び出すことも出来ます。
ですので、1つのタイマーで複数の異なるカウント値で割り込み処理が可能です。

(3)PWM出力を使った方法

#define LED_PIN  PA8  // TIMER1 1CH用出力ピン

void setup() {
  pinMode(LED_PIN, PWM);            // LED_PINをPWMに設定
  Timer1.pause();                   // タイマー停止
  Timer1.setPrescaleFactor(7200);   // システムクロック 72MHzを10kHzに分周
  Timer1.setOverflow(10000*2);      // 周期を2秒に設定
  pwmWrite(LED_PIN, 10000);         // PWMパルス幅を1秒に設定

  Timer1.setCount(0);               // カウンタを0に設定
  Timer1.refresh();                 // タイマ更新
  Timer1.resume();                  // タイマースタート
}

void loop() {

}

PWMを使っってLEDを点滅させています。
PWMもタイマー制御機能の一部です。

前述(1)、(2)はCPUがLEDの点灯・消灯制御を行っていましたが、
この方法ではタイマーが勝手にLEDの制御を行ってくれます。
CPUの負荷は0%ですね。

制約が少々あります。
任意のI/Oピンを制御することは出来ません。
TimerにはPWM出力を行うピンが割り当てられています。
LED_PINに指定したPA8はTimer1で利用出来るI/Oピンです。
(正確にはTimer1のCH1に割り当てれれているI/Oピン)

PWM出力の設定として、周期とパスル幅の設定が必要ですが、
周期は
  Timer1.setPrescaleFactor(7200);
  Timer1.setOverflow(10000*2);

で2秒周期の設定を行っています。

パルス幅は
  pwmWrirte(LED_PIN, 10000);
で周期の半分の幅1秒を設定しています。

この設定で、2秒周期のうち1秒間はHIGH,残り1秒間はLOWとなり
LEDを点滅させることが出来ます。

Timer1は同時に4つのPWM出力が可能です。
ただし、周期は同じでパスル幅のみ別々に設定が可能です。

タイマーは他にTimer2、Timer3、Timer4が独立して利用出来ます。
BluePillボードにて各タイマーが制御できるI/Oピンについては、
下記のサイトが参考になります。
・stm32duono Blue Pill
  http://wiki.stm32duino.com/index.php?title=Blue_Pill
  (特にpinoutの情報)

  pinoutのPDFをダウンロードしてパウチして利用すると便利です。

  Dscn6449


2017年2月16日 (木)

次はSTM32ボードを積極的に使ていきたい(5)

やっと落ち着いてきました。 ブログを再開します^^
(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

« 2016年12月 | トップページ | 2017年3月 »