Arduino のサンプルスケッチにある「LED Bar Graph」を作ります。
が、僕達はもう、データをシリアルにして出力ピンを減らすことが簡単にできるようになっていますね (^_^;) 「D/A コンバータを使って Arduino から正弦波を出力する (1) (2) (3)」でも、D/A コンバータの入力部のシフトレジスタに Arduino からシリアルデータを送っていました。仕組みは同じです。
今回は、10 個の LED をシフトレジスタで制御する Bar Graph を作ります。そして、4-20mA の電流で送られてきた信号を、Bar Graph で表示させてみましょう。
回路図
一気にすべての回路です。細かいのでわかりにくいですが、難しい回路ではありません m(_ _;)m
本来なら、伝送路の両端に保護回路とか付けるのですが、ないです。いろいろな部分の誤差を考慮するとか、ないです (;´Д`)
デジタル値をアナログに変換して、4-20mA 電流信号として送り、再びデジタル値に変換してレベルメーターに表示させるという、昔、何かの教科書でみたような仕組みを再現してみたって話ですな (^_^;)
回路図の上部分の Arduino1 で正弦波データを生成し、D/A コンバータ IC1 でアナログの正弦波を出力しています。
IC2 、IC3 は定電流回路で、D/A コンバータからの電圧信号を 4-20mA の電流信号に変換しています。
下側部分が今回追加した Bar Graph の回路。
受信した 4-20mA 電流信号を 100Ω の抵抗で受けているので、電圧は 0.4-2V です。それをオペアンプ IC3 で 1-5V に増幅しています。250Ω で受ければそのまま 1-5V になりますが、オペアンプの増幅回路を試してみたかった、だけです。まぁ入力インピーダンスを低くするためには増幅回路が必要になりますから。
本格的にやるなら、ここは「計装オペアンプ」ってのを使うんですケド。もちろん部品箱に、ないです。ちなみに、オペアンプ IC3 は、レール・ツー・レールっていうやつで、ジャンク品です。
1-5V の電圧信号は Arduino2 のアナログピンで受け、A/D コンバータでデジタルデータに変換、シフトレジスタ IC5 、IC6 に送ります。シフトレジスタの出力は 8 ビットなので、10 個の LED を制御するために 2 個つないでいます。1 つ目のシフトレジスタから溢れたデータは Q7′ から出てきますので、これを 2 つ目のシフトレジスタへ送ります。
シフトレジスタの出力は LED ドライブ用のトランジスタを介して LED を点灯させます。
正弦波を発生させるスケッチ
Arduino1 の正弦波を発生させるスケッチです。「D/A コンバータを使って Arduino から正弦波を出力する (3)」で作ったスケッチと基本は同じですが、周波数が高いと Bar Graph の表示が追いつきませんから、周波数を 0.5Hz にしてあります。
- // Sine wave oscillator 2020.12.19 by meyon230
- #include <SPI.h>
- void setup() {
- SPI.begin();
- SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
- }
- void loop() {
- float f = 0.5;
- unsigned long t = micros();
- byte AO = 0x08;
- float y = (sin(TWO_PI*f*t*0.000001)+1.0)*0.5;
- byte DI = y * 204+51;
- digitalWrite(SS, LOW);
- SPI.transfer(AO);
- SPI.transfer(DI);
- digitalWrite(SS, HIGH);
- }
11 行目が周波数の指定です。0.5Hz になっています。
16 行目では、出力電圧を 1-5V にするように換算しています。
変更点はこの二点です。
シフトレジスタを使った Bar Graph のスケッチ
Bar Graph を制御する Arduino2 のスケッチは以下です。
- // Bar Graph using Shift Register 2020.12.29 by meyon230
- int inputPin = A0;
- int serPin = 11;
- int srclkPin = 13;
- int rclkPin = 10;
- void setup() {
- pinMode(serPin, OUTPUT);
- pinMode(srclkPin, OUTPUT);
- pinMode(rclkPin, OUTPUT);
- }
- void loop() {
- int inputValue = analogRead(inputPin);
- int ledLevel = map(inputValue, 0, 1023, 0, 11);
- int ledCount = pow(2, ledLevel)-0.9;
- digitalWrite(rclkPin, LOW);
- shiftOut(serPin, srclkPin, MSBFIRST, ledCount >> 8);
- shiftOut(serPin, srclkPin, MSBFIRST, ledCount);
- digitalWrite(rclkPin, HIGH);
- }
21行目で最初の 8 ビットを、22行目で次の 8 ビットを送出しています。リファレンスにも書いてあるやりかたです。
これまでに何度もやったことのあるスケッチですので、特に説明することは、ないです。が…
ちょっと説明が必要なのは、18行目の pow() 関数、でしょうか。
べき乗 pow() を使う
スケッチの 18 行目では、点灯させる LED の数をビットの 1 の数にしています。
たとえば LED 5 個を点灯させるビット列は「0000 0000 0001 1111」です。10 進数では「31」、これは ( 25 – 1 ) に相当します。LED 8 個を点灯させるなら ( 28 – 1 ) = 255 、ビット列では「0000 0000 1111 1111」です。
この計算を行なうために、べき乗 pow() を使いました。
式どおりにスケッチを書くと、
- int ledCount = pow(2, ledLevel)-1.0;
ってことになるのですが、なぜかこいつが想定したように計算されません。
試しに int ledLevel = 5; として計算すると、pow( 2 , ledLevel ) = 32.0000 となります。間違い、ないです。
ところが、16 行目の map() で計算した値を使うと、pow( 2, ledLevel ) = 31.9999… となるんです。なぜですか?
俺にはわかりません (^_^;) でも、事実そうなります。int 型にすると 31 になっちゃう。
左図は、シリアルモニタで出力した pow( 2, ledLevel) の計算結果です。
それならそれでもいいじゃん。と思いきや、図にはないですが、pow( 2, 0 ) = 1.0000… 、pow( 2, 1 ) = 2.0000… となるから質が悪い。
map() から出力した数値はたしかに 5 なんです。map() の戻り値は整数で、小数は切り捨てされる、とリファレンスには書いてあります。
でも、本当は何かが違う。んでしょうねぇ、きっと (;´Д`)
とゆーことなので。
pow() から 1.0 引いちゃうと結果がおかしくなるので、0.9 引くことにします。pow( 2, 5 ) – 0.9 = 31.09999… なので、int 型にすると 31 。
期待した答えになりました。
ブレッドボードのようす
左から、電源回路、Arduino1 と D/A コンバータによる正弦波発生部、定電流回路です。オシロスコープは D/A コンバータの出力波形を表示しています。
青白のワイヤが伝送路のつもり。
右側のブレッドボードの下側、変換基板に載っているのがレール・ツー・レールのオペアンプです。
その上の Arduino2 が A/D コンバータとして働き、右の IC 2 個がシフトレジスターです。さらにその右がドライバのトランジスタと、10 個の LED が並んでいます。
一番右に見える黒い筐体は電源装置です。これは「電源ユニットに端子板を付ける」で作ったパソコンの電源ユニットを改造したものです。いろいろ活躍してくれているのですが、いかんせん、ちょっと図体が大きくてねぇ (^_^;)