google-site-verification: google3bd66dd162ef54c7.html

Timer1とFlexiTimer2を使ったバーストパルス発生の実験

 Arduinoを使ったパルスジェネレーターを最近作りましたが、まだまだメモリーに余裕があります。これを遊ばせておくのはもったいないので、もう少し機能を追加したいと思います。

 機能追加の候補としては、バーストパルス発生、サーボテスト信号発生、ストロボスコープ信号の発生です。

 このうちで厄介そうなのはバーストパルスの発生です。現在のパルスジェネレーターは、パルスのONとOFFの時間をTimer1割り込みの値を交互に切り替えることで、タイミングを作っています。(下図参照)

▼パルスの発生原理
ノーマルパルス
 この方法は単純で判り易いのですが、毎回タイマーの設定をやり直さないといけないのでオーバーヘッドが発生するのが難点です。

 この方法のままでバーストパルスを発生させると、オーバーヘッドが累積するため大きな誤差になることが予想されます。この問題を解決するために、もう一つ別のタイマー割り込みを使うことにします。

▼タイマー割り込みを二つ使ったバーストパルスの発生
バーストパルス
 この図のように、バーストの発生周期はFlexiTimer2で、バーストそのものの周期はTimer1で発生させてやれば、タイミング誤差の少ないパルス発生が出来るはずです。

 実はこれをやりたくてFlexiTimer2の使い方を調べていたのですが、制約条件はいろいろあるものの、なんとか思い通りに動かせるようになりました。そこで、早速Timer1と組み合わせてバーストパルスの発生のテストを行ってみました。動作の原理は、上の図の通りです。

▼バーストパルス発生のテストプログラム
/* FlexiTimer2とTimer1の同時使用テスト
* インターバルはFlexiTimer2で、バーストのON/OFF時間はTimerOneで発生
* 2016/3/28 ラジオペンチ
*/

#include <FlexiTimer2.h>
#include <TimerOne.h>
#include <LiquidCrystal.h>

#define led_pin 13

volatile int burstN;
volatile boolean sigFlag;

unsigned long MI; // メインインターバル
unsigned long BI; // バーストインターバル(周期の半分の値をセット
int BPN; // バーストパルス数

void setup() {
pinMode(led_pin, OUTPUT);
pinMode(14, OUTPUT);
MI = 50; // 50*60μs= 3ms
BI = 30;
BPN = 10;
IntervalSet(MI);
}

void loop() {
}

void IntervalSet(unsigned long t) { // メインインターバル設定
FlexiTimer2::stop();
FlexiTimer2::set(t, 0.00006, IRQ_FT2); // 注:40usだとジッタが大きいかも
FlexiTimer2::start();
}

void IRQ_FT2() { // FlexiTimer2の割込み処理
sigFlag = false; // 開始位相はネガ
burstN = BPN; // バーストパルス数設定
Timer1.initialize(BI); // Timer1にバーストインターバルセット
digitalWrite(14, HIGH); // バースト同期パルス発生
Timer1.attachInterrupt(IRQ_timer1); // Timer1割込み開始
digitalWrite(14, LOW);
}

void IRQ_timer1() { // TimerOneの割込み処理
sigFlag = ! sigFlag; // 極性反転して
digitalWrite(13, sigFlag); // ポートへ出力
if (sigFlag == false) { // LOWになっていた場合は
burstN--; // カウンタをデクリメント
if (burstN == 0) {
Timer1.stop(); // Timer1割込み停止
}
}
}
3ms周期で、10発のバーストパルス(デユーテーイ50%、周期60μs)を発生させるプログラムになっています。

▼波形
バースト波形
 横軸は0.5ms/div. 波形の上はバーストパルスで、下は同期信号です。バースト周期3msで10発のパルスが発生しています。

 ということで、何とかバーストパルスが発生できるようになりました。

 記事の最初に書いたサーボテスト信号などは簡単なプログラムを書くだけで作れるはずです。ただ問題は、これらの機能を、どうやって既存のパルスジェネレーターに組み込むかです。

 複数の機能を簡単なボタン層背で使えるようにしないといけませんが、プログラムを組むのが面倒です。さて、どうしたものやら。

Arduinoを使ったパルスジェネレーターの製作、(ソフト解説編)

 少し間が空いてしまいましたが、Arduinoを使ったパルスジェネレーターの製作記事の続きです。今回が最終回で、ソフト解説編です。このパルジェネはほとんどの機能をソフトで作っているので、この記事は実質的には機能解説編になります。

▼電源投入前
電源OFF
 右下のロータリーエンコーダと3つのタクトスイッチを組み合わせて操作を行います。

▼動作中
動作中の画面
 電源を入れるとEEPROMから設定値を読み出してパルス出力を開始します。液晶には出力中の波形のパラメーターを表示します。

 画面左上のNは、パルスの極性を表わしており、Nはネガという表示です。(ポジなら P 表示)
 0.04ms/0.08msの表示は、左側の数字がパルス幅、右側がパルス周期を表わしています。
 下段左はパルスのデューティ比、下段右はパルス周波数です。

 パルス幅と周期は 99.99ms、999.9ms、9.999s の3つのレンジで表示します。実際に出力されるパルスはこの表示と一致しています。つまり、レンジ切り替えなどで端数が出た場合は表示の分解能で丸めています。

 なお、時間設定の最小分解能は0.02ms。パルス周期-パルス幅 =< 0.02ms という条件で入力可能な値を制限しており、制限を越えた設定は無視するようになっています。Arduinoの時間の最小ステップは4μsで、これを丸め誤差無しで表示するために20μを最小分解能としました。四捨五入して10μs分解能にする手も考えたのですが、気持ち悪いのでやめました。

▼表示例
動作中
 時間の最小分解能は0.02msなので、このレンジでの最大パルス幅は99.98msです。(99.99msは設定出来ない)。なお、パルス周期は999.9msになっています。

▼値の変更
早送り
 ロータリーエンコーダー(以下、略してダイヤル)で設定値の変更を行います。設定出来るのはパルス幅と周期で、ダイヤルを早く廻すと数値が早送りされます。またFastボタンを押しながらダイヤルを廻すと爆速で値が変化します。なお、値の変更はレンジをまたいでシームレスに行うことが出来るので、直感的な操作が可能です。

▼ダイヤルで変更する値の選択
パルス幅/周期切り替え
 Pw/Ppボタンを押すと下段の表示がこのように変わり、現在の変更対象を表示します。この表示はパルス幅の変更モードになっていることを表わしています。

 ダイヤルを廻すと、変更対象を変えることが出来ます。

パルス幅/周期切り替え
 この表示はパルス周期変更モードです。↑で変更対象の数値を示しています。

▼出力極性変更
N/P
 Poralityボタンを押す度にパルス巣の極性が反転します。設定状態は液晶の左上に P/N の文字で表示。

▼設定値の保存
EEPROMに設定保存
 Fastを先に押してPw/Ppボタンを同時に押すと現在の設定(パルス幅/周期/極性)をEEPROMに保存します。ここで保存した内容は次回電源ONの際に読み出されて反映されます。

▼デューティ50%設定
Duty50%設定
 Fastを先に押してPoralityを同時に押すと、デューティが50%となるようにパルス幅を変更されます。なお、20μsステップの値しか設定出来ないので、周期のぴったり半分のパルス幅にならない場合があります。(Ex:周期100μsの場合のパルス幅は50μsでは無く60μsが設定される)

 Tips:ダイヤルを廻してパルス周期を小さくしようとしても、パルス幅以下に小さくすることは出来ません。こういう場合は、Pw/Ppボタンでパルス幅を先に小さくしないといけません。でもデューティ50%ボタンを押せば、パルス幅をすばやく半分に出来るので、場合によってはこっちの方が便利です。

 全プログラムはこちら (拡張子がtxtになっています)

 以下、プログラムの主要部についてポイントを解説します。

◆メインループ
void loop() {
duty50(); // ボタンが押されていたらパルス幅を50%デューティにする
EepSave(); // ボタンが押されていたらEEPROMへ設定内容を保存する
setPorarity(); // パルス極性設定
mode = ModeSelect(); // ロータリーエンコーダー設定する対象の選択(パルス幅or周期)
X = ReadEnc(); // ロータリーエンコーダーを読む
switch (mode) {
case 0: // パルス幅設定
IncStep = DecideStep(t1);
t1 += X * IncStep;
t1 = ResoADJ(t1); // 有効数字を補正
if (t1 < 40) { // t1(パルス幅)が40μs以下なら
t1 = 40; // t1の下限は40μs
}
if ((t2 - t1) < 40) { // t2とt1の差が40μs以下なら
t1 = t2 - 40; // t1の上限はt2から40μs下
}
fix4Disp(1, 0, t1); // パルス幅表示
break;

case 1: // パルス周期設定
IncStep = DecideStep(t2);
t2 += X * IncStep;
t2 = ResoADJ(t2); // 有効数字を補正
if ((t2 - t1) < 40) { // t2とt1の差が40μs以下なら
t2 = t1 + 40; // t2の下限はt1より40μs上まで
}
if (t2 > 10000000UL) {
t2 = 9999000UL; // t2の上限は9.999s
}
fix4Disp(9, 0, t2); // パルス周期表示
break;
}

t3 = t2 - t1; // OFF時間計算

if (X == 0) { // エンコーダーが変化していなかった場合だけ
duty1000 = (t1 * 100) / (t2 / 10); // t1*1000/t2ではオーバーフローするので、
DispDuty(duty1000); // デューティ値を表示
freq100 = 100000000 / t2;
DispFreq(freq100); // 周波数を表示
} // ここを毎回動かすとエンコーダーのレスポンスが悪化
}
 このループで操作の検出とパルス出力条件の変更および表示を行っています。ループを廻る速度が遅いとロータリーエンコーダーの変化を取りこぼしてしまいます。そこで、更新が必要な箇所だけ表示の更新を行っています。

◆ロータリーエンコーダー読取り部
int ReadEnc() {             // ロータリーエンコーダーの読取り
static byte bp = 0;
int R = 0;
static unsigned long LastChangeTime;
unsigned long TimeDelta;
bp = bp << 1;
if (digitalRead(ECA) == HIGH) { // A相の状態を
bp |= 0x01; // bpの末尾に記録
}
bp = bp << 1;
if (digitalRead(ECB) == HIGH) { // B相の状態を
bp |= 0x01; // bpの末尾に記録
}

bp = bp & 0x0F; // 下位4ビット残して上位を消す
if (bp == 0b0111) { // このビットパターンと一致していたら
R = 1;
}
if (bp == 0b1011) { // このビットパターンと一致していたら、
R = -1;
}
if (R != 0 ) {
TimeDelta = millis() - LastChangeTime;
if (TimeDelta < 50) { // すごく短い間隔で操作されていたら
R *= 20; // 20倍速
} else if (TimeDelta < 70) { // 短い間隔だったら
R *= 5; // 5倍速
}
LastChangeTime = millis();
}

if (digitalRead(8) == LOW) { // 早送りボタンが押されていたら
R *= 50; // 更に50倍速
}
return R;
}
 ビットパターンの一致を見てダイヤルの操作を検出する方式になっています。後ろの方が早送りの処理ですが、操作感を良くするにはもう少しチューニングした方が良さそうです。また、ロータリーエンコーダーの種類によっても最適な条件は変わりそうです。

◆パルス発生部 
//  タイマー割り込みでパルス出力
void T1Isr() {
if (PosiPulse == true) {
PORTB |= B00100000; // sbi PORTBのビット5(D13ピン)
} else {
PORTB &= B11011111; // cbi (digitalWriteよりこの方が高速)
}
Timer1.setPeriod(t1); // パルス幅セット
Timer1.attachInterrupt( T2Isr ); // タイムアップでT2Isr
}

void T2Isr() {
if (PosiPulse == true) {
PORTB &= B11011111; // cbi
} else {
PORTB |= B00100000; // sbi
}
Timer1.setPeriod(t3); // 次のパルス
Timer1.attachInterrupt( T1Isr ); // タイムアップでT1Isrに戻る
}
 ここが心臓部で、パルス発生はこのルーチンで行っています。

 ポートを直接操作しているのは、少しでも高速化したかったためです。ビットパターンで直接操作するのは移植性が悪くなると言われているので、この書き方はちょっと邪道かも知れません。

 それと、こんなふうにキャッチボールするプログラムではなく、定周期割込みを常に発生させておき、その割り込みを使ってパルス幅の割り込みを起動するようなプログラムを作ればパルス周期の精度が上がるはずです。このあたりはもう少し工夫する余地があるかもしれません。

◆まとめ
 マイコンを使った物はソフトを書くだけでいろいろな機能を作り込む事が出来ます。使用頻度の低い機能はボタンの押し方の組み合わせで起動させるようにすると、少ないボタンの数でいろいろな機能を選択出来るようになります。ソフト開発の最後はこういうおまけの機能を作りこむのですが、これをやってる時が一番楽しい時期です。

 ということで、とても長い記事になってしまいましたが、これでArduinoを使ったパルスジェネレーター作りの話は終わりです。

 なお、一連の記事はパルスジェネレーターのカテゴリにまとめました。

Arduinoを使ったパルスジェネレーターの製作、(ハード解説編)

 Arduinoを使ったパルスジェネレーターの製作の話、今回はハードウェア解説編です。

 なお、詳しい仕様や機能は、この後のソフト解説編で説明予定ですが、出力可能なパルス周期は80μs~9.999sとなっています。

▼回路図(クリックで別窓に拡大)
Arduinoでパルスジェネレーターの回路図
 できるだけソフトで処理してハードは簡単にする方針で作ったのですが、表示や操作系などが入ると結構ややこしい回路になってしまいます。なお、プリント基板を取り外せるようにするためのコネクタもあるのですが、この回路図では表現していません。シリアル書込み用のコネクタも準備しておきました。ここは標準的なピン配置になっているはずですが、私はこのケーブルで使う予定です。

▼出力波形
CMOSとアナログ出力
 最高周波数(パルス幅40μs、周期80μs)に設定した状態です。上がCMOS出力、下がアナログ出力波形です。

 CMOS出力を50Ωで終端しているので、振幅が本来のロジックレベルである5Vから4Vに下がっています。この波形の立ち上がり部分はこのスケールでは見えないくらい高速です。

 アナログ出力は、ピンジャックからシールド線で取り出した先にオシロの10:1プローブを接続して測定しています。出力インピーダンスが高く負荷容量も大きいので立ち上がり時間が遅くて、1μs以上かかっています。でもオーディオ用としては、むやみに速いより、これくらいの立ち上がり時間の方が、変な問題が出なくて安全だと思います。

▼CMOS出力の立ち上がり波形
CMOS出力の立ち上がり速度
 立ち上がり時間は5ns以下になっています。波形の肩の段付きは送信側のインピーダンスが合っていないのが原因で発生しているような気がします。IC出力と直列に40Ω程度の抵抗を直列に入れてインピーダンスを整合させたほうがいいのかもしれません。なお、測定に使ったオシロの帯域は400MHzで、信号はプローブなどは使わず同軸ケーブルで直接入力のBNCコネクタに接続し、オシロ内部で50Ω終端しています。ということで測定系の立ち上がり時間などの誤差の影響は、この波形に現われていないはずです。

 あと、もっと立ち上がりを高速にするために、出力用のICを74AC04に差し替えても面白そうです。

 ところで、これはパルスジェネレーターとして使うので設定値に対する実際の出力の時間精度が気になります。そこで測定してみました。

▼ユニバーサルカウンタで出力パルスの周期測定
周期の設定精度確認
 パルス周期10ms設定をユニバーサルカウンターで測定すると、10.000573msと出ました。うん、極めて優秀な性能、と思っていたらそうでもありませんでした。

▼パルス周期の精度
周期の設定誤差カーブ
 パルス周期設定を変えた時の実際の出力波形の誤差です。なお、これはユニバーサルカウンタでアベレージングした値です。また、パルスのデューティは50%に設定しています。

 周期が10ms以上では誤差は少ないのですが、10ms以下になると誤差が急激に増加しています。グラフに示していませんが、0.1msでは0.8%もの誤差が発生していました。これはパルスのON/OFF時間をそれぞれソフトのタイマー割り込みで作っているので、切り替えに伴うオーバーヘッドなどが見えてしまっているのだと思います。つまり、設定値より実際のパルスの周期が長くなってしまっていますが、これはArduinoの環境で動かしている限りは改善することは難しそうです。

 ちなみに、以前作ったLEDストロボでは定周期割込みで時間間隔を作っていたので、もっと精度が高かったです。但しこのプログラムでは、パルス幅を割り込みを使わないで作っているので、今回のパルスジェネレーターのような高速パルスの発生は無理です。というかパルスの発生はかろうじて出来ても、同時に表示などを行うことが出来ません。

▼パルスのジッタ
パルスのジッタ
 前の写真と同じ波形をうんと明るく表示したものです。良く見ると4μsくらいのジッタがあります(矢印部)。Arduinoでは時間の粒度が4μsステップなのでこの現象は避けられません。ということで、位相雑音が問題になるような用途には、このパルスジェネレーターは使えません。

 ということで、このパルスジェネレーターには弱点があります。そのあたりは予想していた通りなのですが、実際にどの程度の誤差があるのかをちゃんと把握しておいて使うことが大事だと思います。

 以上でハードウエアの解説は終わりです。次回はソフトウエアの解説を予定しています。
カレンダー
03 | 2017/04 | 05
- - - - - - 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 - - - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード