google-site-verification: google3bd66dd162ef54c7.html

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を使ったパルスジェネレーター作りの話は終わりです。

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

コメントの投稿

管理者にだけ表示を許可する

こんにちは!

いつも的確で貴重なアドバイスをいただき感謝申し上げます。
奇遇にも、昨夜全自動洗濯機の分解情報を捜し求めて辿り着いたのが、ラジオペンチさんのブログだったのです。
しかもナショナル・ニューロファージィー!
これ以上の情報はありません。感謝です。

re:こんにちは!

ロートレーさん、今晩は

全自動洗濯機の洗濯槽外しをやられるのでしょうか。私の情報が役に立てば幸いです。ロートレーさんならいろんな工具をお持ちなので、構造さえ判っていれば楽勝かと。

あと、そちらのブログに書いたULPのシナ合板の件、詳しい解説ありがとうございます。てっきり翼面に使うのかと思っていました。早トチリですみません。

作りました。

どうも、ご無沙汰しております。
と言っても、いつも楽しみにブログは拝見させてもらってます。
久しぶりに電子工作してみました。
パルスジェネレータを持っていなかったので、なんか得をした気分です。
いつもながら、作成説明が分かりやすいので問題なく作れました。 昨日、完成したばかりなのでこれからいろいろ遊んでみたいです。
乾電池2本で作ったので、DCDCコンバータHT7750Aとarduinoマイコンからのスパイクノイズとノコギリ状のノイズがちょっと気になります。
このノイズをある程度平滑にするのって難しいのですか。
lcdのコントラストにダイオードを使うテクニックは便利ですね。コストダウンも出来て素晴らしい。

re:作りました。

hiroさん、お久しぶりです。

そうやって同じ物が出来たよ、という話を聞くと嬉しいです。

電池2本から昇圧されたんですね。ノイズは電源間のデカップリングコンデンサを増やすのが常套手段ですが、アースの配線の引き回し方でも問題が出ます。
電池のマイナスとロジック系のGNDがHT7750AのGNDピンのところで一点アースになるように配線するといいのですが。

ノイズ対策

アースラインを基板全体を囲むように外周に這わしていました。
いろいろな所からアースが取れるし、アース強化するだろうと思っていたのですが、最悪なことをしていたんですね。
どおりで、ADコンバーターなどブレッドボードでテストした方が数値が落ち着いていることが多々あったのも納得します。 早速、配線の見直ししてみます。
アドバイスしてもらって助かりました。これから気をつけなくては。
進歩ない初心者、丸出しで恥ずかしい。

パルスジェネレーターの波形を見ていたら、P1msec.9.18~9.20msecやP10msec・18.18~18.20msecやP100msec.108.1~108.2msec
となにかパルスがズレるのですが、
特に、私には何も影響はないのですが、パルス幅が変わるので気になりしまた。
0.04msec 8.22~8.24secの時が、大きな誤差でした。

プログラムを理解してないので、ただ気になったことを書いてみました。処理上当たり前な事を、書いていたとしたらごめんなさい。

これから、雷の季節に備えて、去年秋月で買った雷センサーをもう一度作り直してみようと思います。
ノイズエラーや誤作動などうまく動作していなかった。
アースの件を、教えていただいたので、やる気が出てきました。上手くいくと良いな。

re:ノイズ対策

アースの件については「一点アース」などのキーワードで検索するといろいろ解説が出てくると思います。
このあたりは回路図で表現しずらいですが、配線する時に電流の流れを意識して接続するようにしています。もちろん大きな電流が流れるとこだけですが。

あと、特定の値を通過する時にパルスの時間変化が滑らかになっていない件、こちらでも再現しました。
これは想像ですが、パルスを作るのに割り込みを使っているのですが、その割り込みとArduinoがシステムとして使っている割り込みとのタイミングの関係で、処理時間にムラが出ちゃっているのだと思います。
これはちょっと簡単には解決出来そうにないです。実は、この記事↓
http://radiopench.blog96.fc2.com/blog-entry-653.html
はそういう現象の解決にもなる方法なんですが、その後進展していません。

ノイズ対策は難しい

あれから、回路を見直しデカップリング゛・1点アースを目標に作り直しました。
完全な1点では無いのですが、だいぶノイズが少なくなりました。もっと欲が出てきてしまいどうしたらもっと平滑になるか調べてたらインダクタとコンデンサでローパスフィルタで改善すると書かれていたので、計算がわからないので適当に試してみましたが、逆にノイズが増えたり脈打ったりで、ノイズ対策は本当に難しいとつくづく思いました。
一般に言うスイッチングノイズって、消せるのですか?
マイコンノイズ対策のテクニックの記事あったら良いです。

パルス幅のズレの件は了解しました。
割り込みが関係していたのですね。

re:ノイズ対策は難しい

hiroさんおはようございます。

コイルを入れるのは電源のノイズ対策として有効だと思います。ただ部品の選定がけっこう難しいように思います。インダクタンス、直流抵抗、通過電流、コアの周波数特性、漏洩磁束などです。

HT7750AなどのDCDCコンではある程度のリップルが残るのは仕方ないと思います。それに負荷が軽くても重くてもリップルが多くなる特性だったような気がします。

高級オーディオ機器にスイッチング電源が使われないのはそんな理由ではないかと思います。
カレンダー
04 | 2017/05 | 06
- 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 - - -
プロフィール

ラジオペンチ

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

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