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

arduino

2017年3月23日 (木)

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

Blue PillでIchigoJamのような、BASICインタプリタを動かしたいと思い、
vintagechipsさんが公開している豊四季タイニーBASICを動かしてみました。

関連情報
   ・電脳伝説 Vintagechips - 豊四季タイニーBASIC確定版

シンプルなプログラムでいい感じです。
対話型 BASICインタプリタの売り、「トライ&RUN」を行うには編集機能がちょっと弱いです。
そこで、IchigoJamのような感じでプログラム入力が出来るよう、ちょっと機能追加しました。

動画はtinyBASIC 起動直後の表示メッセージに行番号とPRINT文付けて
プログラム化してしまう操作デモです。
フルスクリーン編集対応しているからこそ出来る操作です。


MCURSESを使って手を加えただけで、結構イケてる環境にビフォー&アフターしました。
MCURSESスバラシイ..

スクリーン制御が出来ると凝ったプログラムが作れますね。
次のコマンドを追加しました。

CLS,LOCATE,COLOR,ATTR,WAIT

02_2

こんな感じで指定した位置の文字を色を付けて表示出来ます。

修正版を下記にて公開します

・Tamakichi/ttbasic_arduino 豊四季タイニーBASIC for Arduino 修正版 V0.1
  https://github.com/Tamakichi/ttbasic_arduino
まだまだ、ガンガン修正していきます.

Blue Pillでは、USBでのシリアル接続での利用となります。
Arduino MEGAでも動作しました。Arduno UNOはメモリ不足で動きません。

01_2

2017年3月19日 (日)

脈拍パルス(?)センサーを試してみる

Aliexpressで見つけた、脈拍パルス(?)センサー

02_2


到着した部品は、写真見本に比べると若干、状態が悪いです。

Dscn6501

無理やりリード線を捻じ曲げて調整しました。

Dscn6503

Arduinoとの接続は3線(GND、VCC、アナログ入力)のみです。

Dscn6502

スケッチは、Arduinoフォーラムに投稿されているものを使用しました。
https://forum.arduino.cc/index.php?topic=209140.msg1941983#msg1941983



実行して、シリアル出力をArduino IDEのシリアルプロッタでモニターします。

01

測定はこんな感じでよいのかなぁ
赤外線送信部(LED)の受光センサーの間に指を入れます。
LEDの向き、指の位置で測定値がかなり変わってしまいます。

この調整が難しいです。あまり実用的でないです。
プログラムでフィルタリングやなにやら色々とやらないとダメっぽいです。

Dscn6504

赤外線LEDとセンサーとしてフォトトランジスタを使っているようです。

仕組みはよくわかりませんが、
センサーにあてた指を動かすと、測定値が変化するので、
脈打ったときの微妙な指の膨張(または、上下左右の動き)を検出して
心拍としているのでしょうかね。

2017/03/24 追記
脈拍パルス検出は、血中ヘモグロビンの近赤外線吸収の性質を利用しているようです。
(caskazさん、情報ありがとうございます)

下記の記事にてその仕組みが分かり易く解説されています。

参考記事
・ケータイ Watch ケータイ用語の基礎知識「377回:脈拍センサー とは」
  http://k-tai.watch.impress.co.jp/cda/article/keyword/40664.html

このセンサーモジュール、結局は単なる赤外線LEDとフォトトランジスタが乗っている
に過ぎず、特別なことをやっているわけではないですね。
汎用的な部品使って、100円くらいで実装出来そうです。


2017年3月16日 (木)

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

Blue Pillボードのフラッシュメモリの容量の確認

Blue PillボードにはARMマイコン STM32F103C8が搭載されています。

STM32F103C8は、公式にはフラッシュメモリ容量は64kバイトなのですが、
実際には128kバイトのものが流通しているとのことです。

実際にどうなのか、ちょっと確かめてしました。
まずは、搭載マイコンの刻印のチェック



STM32F103C8T6」と明記されています。

データシートにより、このマイコンのフラッシュメモリサイズは64kバイトのはずです。

03

次に、実際にフラッシュメモリサイズを128kバイトと仮定して、
その領域の最終領域のアドレス 0x801FC00 に書き込みを行ってみました。
フラッシュメモリはページ単位(1kバイト)で消去、16ビット単位で書き込みが出来ます。

04

書き込み確認用スケッチ

特定の文字列をフラッシュメモリ上の指定アドレスに書き込んで、その内容を
確認するプログラムです。
64kバイト目のページと128kバイト目のページに異なる文字列を書きこんで、
シリアル接続にて内容を確認します。

//
// FILE stm31_testFlash
// フラッシュメモリ書き込みテスト for Arduino STM32
// 作成日 2017/03/16 by たま吉さん
//

#define FLASH_PAGE_SIZE        1024
#define FLASH_START_ADDRESS    ((uint32)(0x8000000))

#include <string.h>
#include "stm32_hexedit.h"
#include "TFlash.h"

uint8_t str1[] = "1234567890A";
uint8_t str2[] = "abcdefghij";

void Arduino_putchar(uint8_t c) {
  Serial.write(c);
}

char Arduino_getchar() {
  char c;
  while (!Serial.available());
  return Serial.read();
}

uint32_t adr0 = FLASH_START_ADDRESS + FLASH_PAGE_SIZE *  63;
uint32_t adr1 = FLASH_START_ADDRESS + FLASH_PAGE_SIZE *  127;

void setup() {
  Serial.begin(115200);
  while (!Serial.isConnected()) delay(100);
  setFunction_putchar(Arduino_putchar); 
  setFunction_getchar(Arduino_getchar); 
  initscr();

  // フラッシュメモリ書き込みテスト
  TFlash.unlock();
  TFlash.eracePage(adr0);
  TFlash.write((uint16_t*)adr0, str1, strlen((char*)str1));
  TFlash.eracePage(adr1);
  TFlash.write((uint16_t*)adr1, str2, strlen((char*)str2));
  TFlash.lock();
}

void loop() {
  // 64kバイトフラッシュメモリ最終ページの参照
  clear();
  hexedit2 (adr0, false);

  // 128kバイトフラッシュメモリ最終ページの参照
  clear();
  hexedit2 (adr1, false);  
}

スケッチを実行して確認すると、64kバイト目のページ、128kバイト目のページに
それぞれちゃんと書き込めていました。

ページ63、64kバイト目のページへの書込みの確認

06

ページ127、128kバイト目のページへの書込みの確認

05

ということで、STM32F103C8T6 ですが128kバイト利用出来ます。
ただし、生産ロット・時期により64kバイトの可能性もあります。

動作確認で利用した、フラッシュメモリ書き込みは色々と使えそうなので
ライブラリ化しました。

   ・Arduino STM32用 内部フラッシュメモリ書き込みライブラリ
     https://github.com/Tamakichi/ArduinoSTM32_TFlash

     まだ、ドキュメント等は未作成です。

     動作確認したスケッチはそのまま、ライブラリのサンプルとして入れています。
     別途、mcursesライブラリ(https://github.com/ChrisMicro/mcurses)が必要です。

 

2017年3月14日 (火)

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

TVoutライブラリと互換性の高いライブラリ

「次はSTM32ボードを積極的に使ていきたい(7)」で作成した、
「Arduino STM32用 NTSCビデオ出力ラブラリ」の上位層のライブラリを作成しました。

Arduino用のTVoutライブラリと互換性の高いAPIを実装しました。
TVoutのソースの一部を利用しています。

02

GitHubの方に登録&公開しました。
  ・Arduino STM32 TVoutライブラリ - TTVout
   https://github.com/Tamakichi/ArduinoSTM32_TVout


サンプルスケッチの動作の様子です
(だだし、古いバージョンのものです)




回路図(接続図)は次のような感じとなります。

01
使い方等については、公開ページの説明を参照願います。


ライブラリの実装において、ARM cortex-M3のビットバンド機能を使ってみました。
メモリ上のデータをビット単位で効率よく操作する仕組みです。

具体的には、1ビット毎にワードアドレス(32ビット)を割り付け、
そのアドレスへの読み書きでビット単位の読み書きが出来る仕組みです。

ビット操作を行うドット描画部分関数sp()に適用しました。
このsp()は、直線や円等の描画にも利用しています。
以下がその関数の実装部分です。

//
// ドット描画
// 引数
//  x:横座標
//  y:縦座標
//  c:色 0:黒 1:白 それ以外:反転
//
static void inline sp(uint16_t x, uint16_t y, uint8_t c) {
#if BITBAND==1
  if (c==1)
    _adr[_width*y+ (x&0xf8) +7 -(x&7)] = 1;
  else if (c==0)
    _adr[_width*y+ (x&0xf8) +7 -(x&7)] = 0;
  else 
    _adr[_width*y+ (x&0xf8) +7 -(x&7)] ^= 1;
#else
  if (c==1)
    _screen[(x/8) + (y*_hres)] |= 0x80 >> (x&7);
  else if (c==0)
    _screen[(x/8) + (y*_hres)] &= ~0x80 >> (x&7);
  else
    _screen[(x/8) + (y*_hres)] ^= 0x80 >> (x&7);
#endif
}

BITBANDが1の場合、ビットバンドを利用してフレームバッファのビットを操作します。
BITBANDが1でない場合は、ビットバンドを利用しません。かわりにビットシフト等にて
ビット操作を行います。

上記ソースでビットバンドを利用している場合と利用していない場合で
フレームバッファのアドレスが_adr、_screenといった具合に異なるのは、ビットバンドでの
アドレス操作が32バイト単位のため、型変換とアドレス変換を行っているためです。
    _adr = (volatile uint32_t*)(BB_SRAM_BASE + ((uint32_t)_screen - BB_SRAM_REF) * 32);

画面224x216ドットを1点ずつ点を描画して全面白色処理を行った場合、
ビットバンド機能を利用すると、全体で10%くらいパフォーマンスが向上しました。
10%は大きいです。

このビットバンド機能、ARMでもCortex-M3、Cortex-M4しかないようです。

2017年3月12日 (日)

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

Arduino STM32環境 Blue PillボードでPWMを使った単音演奏

01

Blue Pillボード(STM32F103C8)でPWMを使った「こいのぼり」の単音演奏をやってみました。
音の出力には圧電スピーカー(圧電サウンダ)を使いました。
ゲームなんかの効果音には使えますね。

演奏の様子
(ちょっと音が小さいです)


回路図

02

スケッチ

「こいのぼり」の演奏部分はスイッチサイエンスさんのサンプルスケッチを流用させていただきました。
SWICHI SCIENCE MAGAZINE - Arduinoで童謡「鯉のぼり」を流してみよう

/*
 *  Arduino STN32
 *  Stm32ボード(STM32F103C8T6)で PWMで任意の周波数を生成する
 *  作成日 2017/01/17 by たま吉さん

 *  説明
 *  ・PB9端子の圧電スピーカーを接続することで任意の周波数を音を出す
 *  ・Timer4 チャンネル 4 を利用しているため、PWM出力ピンはPB9固定

 *  参考にした情報
 *  ・Topic: DUE PWM Frequency (Read 51962 times) 
 *    https://forum.arduino.cc/index.php?topic=131323.15
 *    
 *  サンプル作成に参考にしたサイト
 *  ・Arduinoで童謡「鯉のぼり」を流してみよう
 *    http://mag.switch-science.com/2015/04/29/gwprojact_koinobori/
 */

//
// 音出し
// 引数
//  pin     : PWM出力ピン (現状はPB9固定)
//  freq    : 出力周波数 (Hz) 15~ 50000
//  duration: 出力時間(msec)
//
const int pwmOutPin = PB9;  // PWM出力ピン
void _tone(uint8_t pin, uint16_t freq, uint16_t duration = 0) {
  if (freq < 15 || freq > 50000 ) {
     _notone(pin);
  } else {
    uint32_t f =1000000/(uint16_t)freq;
    Timer4.setPrescaleFactor(72); // システムクロックを1/72に分周
    Timer4.setOverflow(f);
    Timer4.refresh();
    Timer4.resume(); 
    pwmWrite(pin, f/2);  
    if (duration) {
      delay(duration);
      Timer4.pause(); 
    }
  }
}

//
// 音の停止
// 引数
// pin     : PWM出力ピン (現状はPB9固定)
//
void _notone(uint8_t pin) {
    Timer4.pause();  
}

//HardwareTimer pwmtimer(2);
#define PIN pwmOutPin
void setup() {  
  pinMode(pwmOutPin, PWM);
  _tone(pwmOutPin,440, 100);
  _tone(pwmOutPin,880,100);  
  delay(500);

  _tone(PIN, 330, 150);
  delay(150);
  _tone(PIN, 294, 150);
  delay(150);
  _tone(PIN, 262, 300);
  delay(300);
  _tone(PIN, 294, 300);
  delay(300);
  _tone(PIN, 330, 300);
  delay(300);
  _tone(PIN, 440, 300);
  delay(300);
  _tone(PIN, 392, 150);
  delay(300);
  _tone(PIN, 330, 150);
  delay(150);
  _tone(PIN, 330, 150);
  delay(150);
  _tone(PIN, 330, 300);
  delay(300);
  _tone(PIN, 294, 150);
  delay(150);
  _tone(PIN, 262, 150);
  delay(150);
  _tone(PIN, 294, 300);
}

void loop() {
  
}


Arduino STM32では音を出すためのAPIとしてtone()関数があります。
  参考:  ・Arduino for STM32 - tone() and notone() have been added to the libmaple core

使ってみると音がちょっと汚いです。濁ったような音です。
原因はタイマー割り込みを使って、出力をオン・オフしているためです。
他の様々な割り込みの影響により、タイミングがずれたりして濁った音になっています。
(USBシリアル、SysTicの時間処理等)

また、このやり方だとCPU負荷もそれなりにかかります。
今後のビデオ出力を使ったゲーム作成なんかに使うにはちょと問題ありです。

そこで、PWM出力に置き換えて綺麗な音が出るようにしました。
音が出ている間はCPU負荷がかかりません。

問題点としては、利用するピンが固定となることです。標準のtone()では任意のピン
を使って音が出せます。

2017年3月 5日 (日)

Arduinoで使えるcursesライブラリ

Arduino等の組み込みマイコン系で
「シリアル経由でLinuxのviエディタみたいな表示や制御をしたい、cursesが使いたい!」
と思い、調べたところ、
機能縮小版のMCURSESなるもを見つけました。

本家サイトMCURSESでは、Arduino用ではないものの、AVRマイコン、STM32マイコン
には対応しているのでArduinoへの移植を試みていたのですが、
既に移植した方がおられました(下記)。

・ChrisMicro/mcurses
  https://github.com/ChrisMicro/mcurses

Arduino用にライブラリ化(サンプルスケッチ付き)されて公開されています。
ダウンロード&ライブラリ登録してサンプルスケッチを動かしてみました。

フルスクリーン16進ダンプ表示のバイナリエディタ

01

ArduinoのSRAM上のデータを編集することが出来るバイナリエディタです。
矢印キーでカーソル移動できます。上限、下限に達すると画面がスクロールします。
カーソル位置のデータの修正が可能です。
TABキーで16進数表示部とASCII表示部の行き来が出来ます。

文字に色を付けて表示するデモ

02

文字に色を付けたりすることが出来ます。

MCURSESで利用可能なAPI関数を見ると、
残念ながらWINDOW(本家cursesはマルチウィンドウ機能がある)は使えないようです。
まあ、バッファにメモリを消費しますから無理でしょう。
WINDOWが使えると、Windowsのようなドロップダウンメニュー、ポップアップメニュー
っぽいのが簡単に実装できるのですが..

それでも、ちょっとしたメニュー画面や計測データのリアルタイム表示、簡易グラフ表示
なんかは出来そうです。

フルスクリーンテキストエディタっぽいのが欲しいと思い探してみると、
下記のフォーラムにて
  ・MCURSES - Mini Curses Bibliothek für Mikrocontroller
   http://www.mikrocontroller.net/topic/227312
フルスクリーンテキストエディタsmed.cが公開されていました。
Linux用のコードっぽいですが、強引に動かしてみました。

03

取りあえず、、フルスクリーンにて文字入力が出来ました。
だたし、BS,DELキーで文字削除できません。
そこそこ動くので、実装の参考にはなりそうです。

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月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月 7日 (水)

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

ここ数日、STM32ボードをArduino環境にて使うことにこだわって調べています。
その状況をこのタイトルのシリーズにてしばらく続けていきます。

Blue Pillボードの設計ミス (= USB経由のスケッチ書き込みの原因)

私が使っているSTM32F103C8T6搭載ボード、Arduino STM32のサポートフォーラムでは
Blue Pillと呼ばれています。

01

このボードには何種類かあるようですが、設計ミスがあるようです。
この件に関しては、フォーラムの 「Red Pill or Blue Pill?」にて議論されています。

具体的にはUSBのD+のプルアップ抵抗が本来は1.5kΩであるべきところを、
10kΩの抵抗が実装されています。
これが原因でUSB経由では、うまく書きこめない場合があるようです。

私の所有するBlue Pillボードも、調べてみると該当するD+のプルアップ抵抗が
10KΩになっていました。

03

私のパソコンではUSB書込みが出来ますが、パソコンの相性でたまたま書き込みが
出来ていた可能性があります。
(利用しているパソコン、Aliexpressで買ったので相性いいのかも^^)

注意:Blue PillボードでUSB経由で書き込みを可能にするには、ブートローダーを
        自分で書き込む必要があります。 フォーラムの次の記事に目を通すことをお勧めします。

          ・FAQ's and links - Please read if you are new
           http://www.stm32duino.com/viewtopic.php?f=2&t=873

ブートローダーを書き込んでもUSB経由でスケッチの書込みが出来ない場合は、
このR10抵抗を1.5kΩに付け替えるか、この抵抗に並列に1.8KΩの抵抗を接続
具体的にはPA12端子 - 3.3V間に1.8KΩの抵抗を接続すると良いようです。
(情報はフォーラムのこのあたり)


使えるライブラリ

STM32_arduinoのダウンロードサイトのWikiの情報はちょっと古いようです。
実際に使えそうなライブラリはフォーラムのWorking / ported librariesを見た方が良いです。

より以前の記事一覧