■Arduino / ubuntuとシリアル通信してみた -2-

Arduino / ubuntuとシリアル通信してみた」で試してみたターミナルコマンド cu による LED の PWM 制御を、少し改良してみました。

前回は数字を 3つ入力すると LED の明るさが変わるというものでした。これを 0 から 255 までの数字を入力して Enter を押すと、その値に従って LED の明るさを変えるようにしたいと思います。

そのために、桁数の異なる数字をシリアル通信で Arduino に送り、数値として PWM 制御に渡す必要があります。少しググってみたのですが、シリアル通信で数値を送るには、文字列を数値に替えるだけでなく、桁数の違いをどう処理するかなど、けっこう面倒な雰囲気です。単純な処理なのでもっと簡単な方法がないかなと探していたところ、こんなチュートリアルを見つけました。

 

String to Int Function

The toInt() function allows you to convert a String to an integer number.

In this example, the board reads a serial input string until it sees a newline, then converts the string to a number if the characters are digits. 

 

ドンピシャです。文字列を数値に変換する、まさに求めていた内容ですよ。

toInt() は String オブジェクトの関数です… よくわかりません (^_^;) でもこうやって使うとこういう結果が出るということは、簡単に理解できます。なお、このチュートリアルにあるサンプルコードは、IDE のスケッチ例「08.String」に「StringToint」として附属しています。

 

さて、そのサンプルコードを参考にした、cu から送信するデータに従って 3番ピンに接続した LED を PWM 制御するスケッチです。

 

  1. // PWM control LED from cu 2019/09/09 meyon
  2. const int ledPin = 3;
  3. String inStr = "";
  4. void setup()
  5. {
  6.   pinMode(ledPin, OUTPUT);
  7.   Serial.begin(9600);
  8. }
  9. void loop()
  10. {
  11.   while(0 < Serial.available()) {
  12.     int inChar = Serial.read();
  13.     Serial.write(inChar);
  14.     if(isDigit(inChar)) {
  15.       inStr += (char)inChar;
  16.     }
  17.     if(13 == inChar) {
  18.       int ledBri = inStr.toInt();
  19.       ledBri = constrain(ledBri, 0, 255);
  20.       analogWrite(ledPin, ledBri);
  21.       Serial.print("¥nBrightness: ");
  22.       Serial.println(ledBri);
  23.       inStr = "";
  24.     }
  25.   }
  26. }

 

16行目は cu へのエコーです。Serial.write() はデータを byte 型で出力するので、受け取った数字の ASCII コードをそのまま cu へ送り返し、入力した数字を表示させることができます。

18行目で入力データが数字かどうかを判断し、数字なら文字列として inStr に追加していきます。

22〜25行目。入力データが Enter の場合 ASCII コード (13) が届きますので、それまでに届いた文字列を数値に変換して 3番ピンへアナログ出力します。

数値は 0〜255 の範囲に制限し、256 以上の場合は 255 としています。int 型なので 32767 を超えると出力は 0 になってしまいますね。また、入力に数字以外が含まれていると無視されます。数字だけを抽出して制御値になります。

 

これでかなりエクセレントになった気がします (^_^;)

 


■Arduino / ubuntuとシリアル通信してみた

ubuntu と Arduino 間でシリアル通信してみます。

ubuntu では cu コマンドを利用します。まずはインストール。

 

$ sudo apt install cu

 

起動・接続は以下。切断・終了は「~.」ですが、Arduino で受信したデータを送り返すスケッチを動かしていると ~. も折り返されてくるだけで終了できません (^_^;) 正しい方法かどうかわからないのですが、Ctrl+c 押した後に ~. で終了できました。

 

$ cu -s 9600 -l /dev/ttyACM0

 

シリアルモニタとは動作が異なります。

シリアルモニタでは入力した文字列が「送信」ボタンを押すことで一括して送られましたが、cu では入力ごとに即送信され、たとえば「A」キーを押すとすぐに「65」が返ってきます。

 

そこで、数字を 3つ入力するとその値で LED の明るさを制御するようなスケッチを書いてみました。

LED は 3番ピンに繋ぎますが、最大で 20mA 程度流しますので LED 駆動用にトランジスタを 1個入れてます。まぁ毎度の回路です。電源は USB を刺しているので、Arduino の 5V 出力を利用しました。

 

  1. const int ledPin=3;
  2. void setup() {
  3.   pinMode(ledPin, OUTPUT);
  4.   Serial.begin(9600);
  5. }
  6. void loop() {
  7.   while(0 < Serial.available()) {
  8.     int data = Serial.read();
  9.     Serial.println(data);
  10.   
  11.     static int val[3];
  12.     static int i=0;
  13.     val[i]=data-48;
  14.     i++;
  15.     if(2<i) {
  16.       int bri = val[0]*100+val[1]*10+val[2];
  17.       bri = constrain(bri, 0, 255);
  18.       Serial.print("Brightness: ");
  19.       Serial.println(bri);
  20.       analogWrite(ledPin, bri);
  21.       i=0;
  22.     }
  23.   }
  24. }

 

Arduino へ書き込んだら IDE を終了します。

cu を起動し接続。数字を 3つ押すごとに LED への出力が変化します。「255」 と押すと LED が 100% で点灯します。「128」なら 50% 、「000」で消灯です。入力値が 255 を超える場合は 255 にしますが、contrain() なんて便利な関数があったので使ってみました。

受信したデータは 48 を引くことで数値に変換しています。3つデータを受けたら 3桁の数字に計算しているだけですが、このあたりがどうもエクセレントじゃないですよねぇ (^_^;)

 


■Arduino シリアル通信 Serial.read() を試してみる

Arduino シリアル通信 Serial.print() を試してみる」で、Arduino からシリアルモニタへデータを送るときはすべて文字列として扱っていることがわかりました。文字は ASCII コードとしてシリアルポートへ送られ、シリアルモニタ側では受け取った ASCII コードに対応した文字を表示しています。

 

では、シリアルモニタから Arduino へ送信する場合はどうでしょうか。簡単なスケッチで試してみます。

 

  1. void setup() { 
  2.   Serial.begin(9600); 
  3. void loop() { 
  4.   if(Serial.available() > 0) { 
  5.     int data = Serial.read(); 
  6.     Serial.println(data, DEC); 
  7.   } 

 

シリアルモニタから「A」と送信すると、

 

65
10

 

と表示されました。

シリアルモニタ側で「A」と入力すると、文字「A」の ASCII コード (65) がシリアルポートへ送られるはずです。

Arduino 側では、Serial.read() で読み取ったデータを数値変数 data に代入し、Serial.println() で 10 進数のフォーマットで出力しています。そこで表示されたのが「65」ということは、Serial.read() がシリアルポートから送られてきた ASCII コードそのものを読み取っている、ということです。

 次に表示されている「10」は、シリアルモニタから送信されるときに付加された改行コード LF のASCII コードです。シリアルモニタでは、入力されたデータは「送信」ボタンを押したときに改行コードを付加してまとめて送出されています。

 

ということはですよ、入力した文字をそのまま表示させるには、読み取った ASCII コードを文字に変換すればいいんじゃないですか?

 

  1.     char data = Serial.read();
  2.     Serial.println(data);

 

スケッチの一部を上のように書き換えます。シリアルモニタで「A」と入力すると、

 

A

 

と出力されました。ASCII コード (65) を文字変数 data に代入すると、その中身は文字「A」となりますね。そーゆーことです (^_^;)

ところで、「ABC」と入力するとどうなりますか?

 

A
B
C

 

となってしまいます。スケッチをちょっと工夫しましょう。

 

  1. void loop() {
  2.   if(Serial.available() > 0) {
  3.     char data = Serial.read();
  4.     if(10 == data) {
  5.       data = '¥n';
  6.     }
  7.     Serial.print(data);
  8.   }
  9. }

 

末尾に改行コード LF がついてくるのですから、LF のときだけ改行するようにしました。これで出力は、

 

ABC

 

となりました。

 

  1. Arduino → シリアルモニタでは、データは文字列としてそのまま送信される。
  2. シリアルモニタ → Arduino では、データは文字列として送られ、Serial.read() は文字の ASCII コードを読み取る。

 

これでシリアル通信の状況がわかりました。わかってしまえば、まぁなんてことないですよねぇ (^_^;) たぶん、ね。

 


■Arduino シリアル通信 Serial.print() を試してみる

前回の「Arduino シリアル通信を試してみる (文字列)」で、シリアルモニターから送られるデータは ASCII コードになっているらしいとわかったわけですが、じゃ Arduino からシリアルモニタへ出力するときはどうなってるのか、調べてみます。

 

Reference の Serial.print() を確認してみました。

 

Prints data to the serial port as human-readable ASCII text. This command can take many forms. Numbers are printed using an ASCII character for each digit. Floats are similarly printed as ASCII digits, defaulting to two decimal places. Bytes are sent as a single character. Characters and strings are sent as is.

 

データは ASCII コードで出力されます。数字も各桁ごとに ASCII コードになるようですね。浮動小数点数も同じですが、小数点以下 2桁になるとのこと。

バイト形式は一つの文字として送るということなので、この場合は数字のようにバラバラにはならないということでしょうか。あとで確認してみましょう。

文字と文字列は「そのまま」です。

 

では、実際に確認してみます。ちなみに Serial.println() はデータの末尾に改行コード (CR+LF) を付加する関数で、それ以外は Serial.print() と同じです。

文字列を送ってみます。

 

  1. Serial.println("hoge");

 

シリアルモニタには「hoge」とそのまま表示されます。内部的には ASCII コードで送るのでしょうが、文字形式は「そのまま」の文字として出力されるということです。

変数ではどうでしょうか。

 

  1.   char str[]="Hello world!";
  2.   Serial.println(str);

 

変数の場合も意図したように、そのまま「Hello world!」と表示されました。

 

数字形式で試してみます。

 

  1.   int num=65;
  2.   Serial.println(num);

 

シリアルモニタには「65」と表示されました。この場合は「65」が文字列としてそのまま送信されたということなのでしょう。

浮動小数点数を送ってみましょう。

 

  1.   float num=12.345678;
  2.   Serial.println(num);

 

「12.34」と小数点以下 2桁になって表示されました。

 

数字では表示フォーマットを指定できます。

 

  1.   int num=65;
  2.   Serial.println(num, BIN);

 

この場合は「1000001」と 2進数形式で表示されます。あくまでも文字列として扱っているのですから、BIN を指定した場合は改行コードを含んで 9 バイト送信しているということになるのでしょうか。

なお、浮動小数点数の場合はフォーマットで小数点以下の桁数を指定できるようです。

 

バイト形式を試してみましょう。バイト形式はひとつの文字として送るということでした。

 

  1.   byte b=B01000001;
  2.   Serial.println(b);

 

「65」と表示されました。B01000001 は 10 進数の「65」を表していますので、文字列「65」を送信したってことですね。

では、これはどうでしょうか。

 

  1.   byte b='A';
  2.   Serial.println(b);

 

文字「A」をバイト形式にして送信すると、シリアルモニタには「65」と表示されました。「A」の ASCII コードの値「65」が送信されたということですね。

フォーマットを指定してみます。

 

  1.   byte b='A';
  2.   Serial.println(b, BIN);

 

今度は「1000001」と表示されました。「65」が 2進数形式で送信されたということです。

 

さてと、こうしてみてくるとなんだか様子がわかってきました。Serial.print() 、Serial.println() では、

 

  1. 文字も数字も改行コードもすべて ASCII コードにして送信する。シリアルモニタ側では、受け取った ASCII コードに該当する文字を表示する。
  2. 文字形式はそのまま送信する。数字は桁ごとの文字列として送信する。
  3. バイト形式はひとつの値として扱い、その値を文字列として送信する。

 

ってことです。わかってしまえば当たり前のことですけど、どうやら俺は、数字と ASCII コードを混同してわけわからなくなってしまうようです。

次はもう一度、シリアルモニタから Arduino へ送る場合を試してみたいと思います。

 


■Arduino シリアル通信を試してみる (文字列)

以前「Arduino シリアル通信でLチカを試す」で Arduino とパソコン間のシリアル通信を試してみたことがありました。でも、やってみたらできたって感じでよくわかっていなかったので、今回はもう少しちゃんと調べてみようと思います。

 

まず、Reference の Serial.available() にあるサンプルスケッチで試してみましょう。

 

  1. int incomingByte = 0; // for incoming serial data
  2. void setup() {
  3.   Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
  4. }
  5. void loop() {
  6.   // reply only when you receive data:
  7.   if (Serial.available() > 0) {
  8.     // read the incoming byte:
  9.     incomingByte = Serial.read();
  10.     // say what you got:
  11.     Serial.print("I received: ");
  12.     Serial.println(incomingByte, DEC);
  13.   }
  14. }

 

シリアルインターフェースから受信したデータをそのまま送り返すスケッチです。シリアルモニタを開いて試してみました。

 

入力 出力
123 I received: 49
I received: 50
I received: 51
I received: 10
ABC I received: 65
I received: 66
I received: 67
I received: 10

 

シリアルモニタから「123」と入力して送信すると、出力のように表示されます。ここで、あれ?と思っちゃう。そのまま送り返しているはずなのに、違うじゃん (^_^;)

よーく考えてみると、文字「1」の ASCII コードは「49」、文字「2」は「50」です。入力したデータが ASCII コードとしてやり取りされている、ということですね。

最後の「10」は改行コードの LF を表します。シリアルモニタでは送信ボタンをクリックすると LF が送られるんです。シリアルモニタの右下の「LFのみ」を「CRのみ」とすると改行コード CR の「13」が表示されます。

 

シリアルモニタから入力された文字列は Arduino に送られてシリアルバッファに格納されます。Serial.read() を実行するとバッファの最初の 1 バイトが読み取られます。そして最初の 1 バイトはクリアされる (のじゃないかと思います) 。Serial.read() を繰り返すたびに 1 文字ずつ読み取っていき、読み取れるデータがなくなったら次のデータを待ちます。

そんな仕組み。間違っているかもしれません。間違っていたら笑ってやってください。

 

ところで、これを文字列にしようと思ったら 1 文字ずつ配列変数に格納するなどしなくてはいけません。Serial.available() で文字数を取得して配列変数を定義し、1 文字ずつそこに格納していくって感じかな。難しくはないけれど、面倒くさい (^_^;)

 

文字列を簡単に読み取れないのかなぁとググってみたら、Serial.readString() という関数があることがわかりました。ただ、この関数はタイムアウト (デフォルトは 1000ms) で終了するので、処理が遅いのが欠点。実際試してみると、データを送信して 1 秒後に表示されます。

そこで、Serial.readStringUntil( terminator ) 関数を使うことにしました。terminator に '¥n' を指定すると、改行コードを受信した時点で終了してくれます。

 

作り直したスケッチはこんな感じ。変数名やコメントは変えてないので、ちょっと違和感もありますが、ご容赦。

 

  1. String incomingByte; // for incoming serial data
  2. void setup() {
  3.   Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
  4. }
  5. void loop() {
  6.   // reply only when you receive data:
  7.   if (Serial.available() > 0) {
  8.     // read the incoming byte:
  9.     incomingByte = Serial.readStringUntil('¥n');
  10.     // say what you got:
  11.     Serial.print("I received: ");
  12.     Serial.println(incomingByte);
  13.   }
  14. }

 

これで入力した文字列と同じ文字列が出力に表示されるようになりました。

 


■Arduino CPUファン (PWM)を回してみた

体調があまり良くなくて、競技プログラミングのある問題のアルゴリズムを考えつつ、ぼんやりする日が続いてます。しかし、暑い。でもエアコンが寒い。そんな感じ。で、ファンを回してみようと思った。なんだかな〜 (^_^;)

 

使っていない CPU 冷却ファンがあったので、これを回してみましょう。

 

ファンから出ている線は 4 芯です。ググってみると、4 芯のものは PWM で回転数制御ができるんだとか。電源に 12V を与えておき、control に 25KHz の PWM 信号を入れてやるとそのデューティー比によって回転数が変化する。また、sense には 1 回転あたり 2 パルス (デューティー比 50%) の信号がオープンコレクタで出てくる。らしい。

このファン自体の仕様はわからなかったのですが、汎用品のようですのでたぶん同じでしょう。

 

ということで、回路図。Arduino Nano 互換品を使いました。


画像キャプション

 

ジャンクのサーミスタがあったので、これで温度を検出し回転数を制御することにします。

このサーミスタはレーザープリンタの定着部の部品で 200℃ 程度の計測がベストなんですけど、まぁテキトーに使いましょ。この定数での入力値は、約 26℃ で 830 、約 32℃ で 780 ぐらいになりました。 

 

Arduino の Vin に 12V を入れていますが、USB をつないだまま 12V を落としたときに 5V が逆流するのを防ぐためにダイオードを入れています。実験中は電源を切ることがよくありますのでね。

 

control は 3.3V 推奨なので、ツェナーダイオードで降圧しています。

control の電流の実測値は吐き出しで約 0.6mA でした。Arduino の 3 番ピンが HIGH (5V) のとき、ツェナーダイオードには R2 を介して約 11mA 流れ、control 側の HIGH レベルは 3.3V になります。3 番ピンが LOW (0V) のとき R2 の電圧降下は 0.09V ですので、control 側は問題なく LOW レベルに落ちます。

 

sense はオープンコレクタなのでプルアップしています。内部プルアップでも構わないのですが、プルアップを明示的にするために抵抗を入れることにしています。まぁ好みです。

 

さて、問題の PWM ですが、Arduino の PWM は 490Hz か 980Hz ですのでこのままでは使えません。そこで Arduino で 25KHz PWM を作る方法をググってみると、けっこうたくさんヒットします。なるほどっポンと膝を叩いた、参考にさせていただいたサイトはこちらです。ありがとうございます。

 

趣味関係のメモ帳

Arduinoで遊ぶページ

 

そしてできあがったスケッチは以下。

 

/*  FAN Controller   2019.08.15 meyon
 *   
 *  Timer2 Mode5 PhaseCorrect PWM
 *  Clock 16MHz / 8 = 2MHz
 *  PWM countuency 25KHz
 *  OCR2A = 2MHz / 25KHz / 2 = 40
 *  OCR2B = dutyRatio ratio 0 ~ 40
 *  PWM output PD3
 */

 

volatile long riseTime = millis();
volatile long prevTime = millis();

 

void setup() 
{
  pinMode(3, OUTPUT);
  TCCR2A = _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(WGM22)  | _BV(CS21);
  OCR2A = 40;
  OCR2B = 0;

 

  pinMode(2, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), sense, RISING);

 

  Serial.begin(115200);
}

 

void loop() 
{
  static const int thermMax = 780;
  static const int thermMin = 830;
  static const float gradients = 40.0/(thermMax-thermMin);

 

  int thermValue = analogRead(A0);
  float dutyRatio = (thermValue-thermMin)*gradients;

  if (0>dutyRatio) {
    dutyRatio=0;
  } else if (40<dutyRatio) {
    dutyRatio=40;
  }

 

  OCR2B = (int)dutyRatio;


  long cycle=riseTime-prevTime;
  long rpm=30000/cycle;
  if (100<millis()-riseTime) {
    rpm=0;
  }

 

// data monitor
  Serial.print(thermValue);
  Serial.print("¥t");
  Serial.print(dutyRatio);
  Serial.print("¥t");
  Serial.print(rpm);
  Serial.print("¥n");

}

 

void sense()
{
  prevTime=riseTime;
  riseTime=millis();
}

 

setup() のなかで PWM の動作を規定しています。

WGM20 、WGM22 をセットすることで Mode 5「PWM Phase Correct」になります。COM2B1 はデジタルピン 3 番の PWM 出力を規定します。CS21 は分周比を 8 としています。OCR2A で TOP を 40 にすることで、出力周波数が 16000 / ( 8 * 40 * 2 ) = 25KHz になります。

 

OCR2B は 3 番ピンの比較レジスタで、これを 0 〜 40 にすることでデューティー比 0 〜 100% の PWM を発生させることができます。

サーミスタ入力を 830 〜 780 とし、その範囲でデューティー比を 0 〜 40 (0 〜 100%) として OCR2B を設定します。

 

2 番ピンは sense を入力し、パルスの立ち上がりで関数 sense() を呼び出しています。これによりパルスの周期を計測して回転数を算出しますが、100msを超えて割り込みが無いときは回転数を 0 としています。

 

ちなみに、ビットアクセスマクロ _BV() は、指定したビットをセットし、他をクリアします。| でつなぐことで複数ビットをセットしています。

指定したビットのみセットするには、たとえば DDRD |= _BV(PD3) のようにします。これは DDRD = DDRD | _BV(PD3) ということですね。PD3 のビットのみセットし、他は変更しません。 指定したビットのみクリアするには DDRD &= ~_BV(PD3) とします。DDRD = DDRD & ~_BV(PD3) ということです。

 


■電源ユニットに端子板を付ける

余っていたパソコンの電源ユニットを処分しようとしたんだけど、ふと、これ電源装置として使えるよなぁと思ったわけ。で調べてみました。

 

出力は +12V が二系統、+5V、+3.3V が各一系統と -12V 一系統。その他にスタンバイ用の +5VSB があります。

+5VSB は電源 ON で常に 5V が出力されていますが、他は出力していません。PS_ON という信号線を LOW に落とすと冷却ファンが回りだし、すべての出力が ON 。出力が ON になると PWR_OK が HIGH になります。

その他に 3.3Vsence というラインがありますが、これは +3.3V の出力検出用で、マザーボードに挿入するメインコネクタで +3.3V 出力に繋がっていました。

 

参考:ニプロン 製品情報 電源事典 Q&A Q. ATX電源_EPSのピンアサインについて教えて下さい。

 

 

電源ユニットからは、マザーボードや周辺機器を接続するために、さまざまな形のコネクタが付いたハーネスがたくさん出ています。これらを取り外して、出力と制御用に端子板を取り付けることにします。


電源ユニット

 

 

カバーを開けると、基板の出力部周辺はこんな感じ。同じ場所から何本もの電線が束になって出ていることがわかります。


出力線のようす

 

 

基板をひっくり返してみるとこんな感じ。大量のハンダが盛られていて、外し甲斐がありそうです (^_^;)


基板裏面のようす

 

 

電線を取り外しましたが、けっこう大変な作業でした。ハンダを取るとき、周辺の抵抗器などのハンダまで溶かしたり、パターンをショートしたりしないように注意が必要です。

信号線など一本だけ出ているところはそのまま残しています。


電線を取り外した基板

 

 

新たに出力線をハンダ付けし、ケースに戻します。


出力線を取り付け

 

 

ケースに端子板取付用の穴あけをします。ボール盤があればもっときれいにあけられるんですけどねぇ (言い訳 ^_^;)


ケースに端子板用の穴あけ

 

 

端子板を取り付け、出力線をハンダ付けして組み立てたら、完成です。


完成

 

 

端子板は以下のようになっています。3.3Vsence は No.4 端子に接続してあります。

 

No. 1 2 3 4 5 6 7 8 9 10
出力 12V(1) 12V(2) 5V 3.3V GND GND PS_ON 5VSB PWR_OK -12V

 

今回は 10pin の端子板を取り付けたのですが、これだと GND 端子が二つしか取れません。GND 端子は余裕があったほうが使いやすいので、12pin の端子板にしたほうがよかったかなと思っています。あと、端子板の表示をつけないといけませんね。

 


■ArduinoでFizzBuzzしてみた

俺はプログラミングもほとんど初心者に等しいので、そっちの方をもう少し勉強しようと思ってます。

Arduino スケッチは C 言語をベースにしているということなので、C 言語も合わせてやっていきましょう。てなことを考えながら、とりあえず何か書いてみるなら FizzBuzz かなとか (^_^;)

 

FizzBuzz とは、まぁ誰でも知っていると思いますけど、50 まで数えて 3 の倍数のときアホになるというやつです。… じゃぁなくて、100 までカウントして 3 の倍数のとき Fizz 、5 の倍数のとき Buzz、さらに 3 と 5 の倍数のときは FizzBuzz と表示させるといったプログラムのことです。こいつを Arduino でやってみます。

ちょうど 7セグメント LED を使ってみていたので、スタートボタンを押したら 0 から 99 までカウント表示させることにしましょう。3 の倍数のときは FZ 、5 の倍数では BZ 、3 と 5 の倍数のときは FB と表示させることにします。

 

回路図は以下です。特段変わったところはありません。

Arduino の D9 にタクトスイッチをつなぎ、これを ON するとカウントをスタートさせます。

アノードコモンの 7 セグメント LED 表示器が 2個手元にありますので、これをシフトレジスタ SN74LS164 で表示させます。デジットドライブはこれも手持ちの 2N3906 を使用しています。2SA1015 とかでもいいですね。FET でもかまいません。持っているものを適当に使いましょう。でも Arduino で直接駆動するのはちょっと無理があります。

シフトレジスタと LED の接続は、7ビットのデータを LSBFIRST (最下位ビットから送出) させるつもりなので、Qa の出力を未接続にしています。このあたりはプログラムと整合させないとうまく表示できませんので注意します。

 


DizzBuzz_回路図

 

 

スケッチのフローチャートです。

スイッチの ON を検出して 0 から 99 までカウントします。それを 7 セグメント LED で 2 桁表示させます。十の位が 0 のときは表示しないようにしています。2 桁を 1 ミリ秒ずつ表示して 300 回繰り返すので、約 600 ミリ秒ごとにカウントしていくことになります。

その値の倍数かどうかは剰余が 0 かどうかを判断します。3 と 5 の倍数ということは 15 の倍数ということです。ここが FizzBuzz の肝ですね。それぞれの判定結果に従って、表示する値を変えています。

ん〜、なんかもっとすっきりできそうな気がするけど、いまの俺のレベルではこんなもんでしょ (^_^;)

 


FizzBuzz_フローチャート

 

 

スケッチです。

じつは、関数の引数とか戻り値とか初めて使ってみたんですよ (^_^;)

 

// Fizz Buzz on Arduino  2019.06.16 meyon

 

/*  スイッチを押下すると 0 から 99 までカウントする。
    3 の倍数のとき FZ 、5 の倍数のとき BZ 、
    15 の倍数のとき FB と表示させる。
*/

 

const byte segmentData[14] = {
  B1111110, // 0
  B0110000, // 1
  B1101101, // 2
  B1111001, // 3
  B0110011, // 4
  B1011011, // 5
  B1011111, // 6
  B1110000, // 7
  B1111111, // 8
  B1111011, // 9
  B1000111, // F
  B0011111, // B
  B1101100, // Z
  B0000000  // blank
};

 

const int startPin = 9;
const int driverPin[2] = {10, 11};
const int clockPin = 12;
const int dataPin = 13;

 


int judgeNumber(int value) {
  int result;
  if(value == 0) {
    result = 0;
  } else if(value % 15 == 0) {
    result = 15;
  } else if(value % 5 == 0) {
    result = 5;
  } else if(value % 3 == 0) {
    result = 3;
  } else {
    result = value;
  }
  return result;
}

 


void displayNumber(int value) {
  int digit[2];
  if(value == 15) {
    digit[0] = 11;        // B
    digit[1] = 10;        // F
  } else if(value == 5) {
    digit[0] = 12;        // Z
    digit[1] = 11;        // B
  } else if(value == 3) {
    digit[0] = 12;        // Z
    digit[1] = 10;        // F
  } else {
    digit[0] = value % 10;
    if(value / 10 == 0) {
      digit[1] = 13;      // 10 digit is blank
    } else {
      digit[1] = value / 10;
    }
  }

 

  int j=0;
  while(++j < 300) {      // Display cycle
    for(int i=0; i<2; i++) {
      shiftOut(dataPin, clockPin, LSBFIRST, ~segmentData[digit[i]]);
      digitalWrite(driverPin[i], LOW);
      delay(1);
      digitalWrite(driverPin[i], HIGH);
    }
  }
  return;
}

 

 

void setup() {
  pinMode(startPin, INPUT);
  pinMode(driverPin[0], OUTPUT);
  pinMode(driverPin[1], OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  digitalWrite(driverPin[0], HIGH);
  digitalWrite(driverPin[1], HIGH);
}

 


void loop() {
  if(digitalRead(startPin) == LOW) {
    for(int i=0; i<100; i++) {
      int value = judgeNumber(i);
      displayNumber(value);
    }
  }
}

 

 

ついでにブレッドボードです。

7 セグメント LED がピンが左右についている品物なので 2桁並べて置けません。ご愛嬌です。

回路図にはありませんが、IC の電源には 0.1μF のコンデンサをつけてます。またブレッドボードの電源ラインには 10μF 程度の電解コンデンサを置くようにしています。
 


FizzBuzz_ブレッドボード

 

 

電源が写真にはありませんが、12V の AC アダプタの出力から 3 端子レギュレータで 5V を作っています。Arduino の電源は 12V を Vin に供給しています。ただ、USB ケーブルを挿したまま AC アダプタを切っちゃうと Vin から 5V が逆流してしまうので、12V ラインにダイオードを入れて阻止しています。まぁ小ネタですけど。

 


■Arduino Nano 互換機を買ってみた

Amazon で「HiLetgo® 3個セット Mini USB Nano V3.0 ATmega328P CH340G 5V 16M マイクロコントローラーボード Arduinoと互換」を購入しました。結果、何の問題もなく期待通り動作してくれました。

 

廉価な互換機ということなので、Uno との違いもさることながら、正規品との違いも確認しておきたいとググってみたのですが、どうもなにやら良い印象がない (^_^;) 

レビューでも数個に 1個は動作しないとか、USB シリアル変換チップ CH340 用のドライバをインストールしないといけないとか、その CH340 が偽物であるとか、Old Bootloader を選択しないと書き込みできないとか… 使ってみたというサイトを見ても同様の情報が多く、また中国からの配送なので時間がかかるとか、まぁ導入時は多少苦労するかなと覚悟をしていました。

 

が、そんな懸念は一切無用。Ubuntu 18.04.2 LTS のパソコンで、次の設定で動作しています。

 

ボード:Arduino Nano

プロセッサ:ATmega328P

シリアルポート:/dev/ttyUSB0

 

CH340 のドライバのインストールは不要です。ブートローダも Old ではないほうで良いので、新しいものになっているようです。ちなみに CH340 の真贋の判断基準となるとか言われているマークは底の平らな大きな丸でした。見た目も気になるようなところは何もなく、粗悪な安物といった感じはありません。


Arduino nano 互換機

 

USB ケーブル (A to miniB) とその他  1点とともに、注文から約 24時間で到着しました。さすが Amazon's Choice です。寸法はわかっていましたが、実物は思っていた以上に小さく感じました。A コネクタと変わらないですね。

 

まず、3個すべてに Blink を書き込んで正常に動作することを確認しました。

付属のピンヘッダを取り付けて、以前作った 3x3x3 LED Cube につないでみました。これも全く問題なく動作しています。このサイズの基板で実装できそうですね。


3x3x3 LED Cube with 互換機

 

 


■ファンクションボタンの検出

最近よく見かける、上下左右にメニューボタン、中央に決定ボタンがあるような、なんていうのか名前を知らないのですが、ファンクションボタンを作ってみました。

 

タクトスイッチ 6個を「up」「down」「right」「left」「set」「reset」のファンクションボタンとし、どのボタンが押されたかを検出します。今回の肝は電圧分圧方式でのボタン検出で、押したボタンによって入力電圧が変化するという仕組み。ググると考え方や抵抗値の計算方法が出てきますので参考にしてください。

二つ以上のボタンを同時に押した場合は優先順位がありますので、今回は「reset」を最優先のボタンとして並べてみました。

計算上の分電圧と実際の入力値は異なりますので、Arduino の入力値を実測してボタン判断のしきい値を決めています。

どのボタンが押されたかを表示するために、手元にあった 7 セグメント LED (アノードコモン) をシフトレジスタで表示させています。シフトレジスタは前に LED キューブで使っていた SN74LS164 です。こいつはストレージレジスタがありませんので、ドライブ用にトランジスタ 2N3906 を付けました。これによりデータ送信中は LED を表示させないようにしています。セグメントの電流は 7mA 程度、2N3906 は最大コレクタ電流 200mA ですので十分使えます。

 

回路図です。


function-switch 回路図

 

ブレッドボードはこんな感じ。実際はこれを時計方向に 90° 回して実験していますので、右上のタクトスイッチは上下左右に 4 個のボタンと左下に reset 、右下に set という配置になってます。まぁどんな並びでも関係ないんですけど (^_^;)  押したボタンに応じて数字が表示されるという単純なものです。


function-switch ブレッドボード

 

 

フローチャートとスケッチです。ボタン判断のしきい値がフローチャートとスケッチで異なっていますが、最終的にはスケッチの値にしています。

 


画像キャプション

 

// function-switch 2019.06.06 meyon

 

byte segmentData[7] = {
  B1111110, // 0
  B0110000, // 1
  B1101101, // 2
  B1111001, // 3
  B0110011, // 4
  B1011011, // 5
  B1011111  // 6
};

int driverPin = 11;
int clockPin = 12;
int dataPin = 13;
int displyNumber = 0;


int buttonPressed() {
  int inputVoltage = analogRead(A0);

  if (inputVoltage > 947) {
    return 0; //off
  } else if (inputVoltage > 779) {
    return 1; //set
  } else if (inputVoltage > 602) {
    return 3; //up
  } else if (inputVoltage > 436) {
    return 5; //right
  } else if (inputVoltage > 268) {
    return 4; //down
  } else if (inputVoltage > 90) {
    return 6; //left
  } else {
    return 2; //reset
  }
}


void displyButtonNo() {
  digitalWrite (driverPin, HIGH);
  shiftOut(dataPin, clockPin, MSBFIRST, ~segmentData[displyNumber]);
  digitalWrite (driverPin, LOW);
  return;
}


void setup() {
  pinMode (driverPin, OUTPUT);
  pinMode (clockPin, OUTPUT);
  pinMode (dataPin, OUTPUT);

  Serial.begin(9600);
}

 

void loop() {
  Serial.println(analogRead(A0));
  
  if (buttonPressed() != 0) {
    displyNumber = buttonPressed();
  }
  displyButtonNo();
}

 

例によって入力値の確認のためにシリアル出力を使ってます。

シフトレジスタもドライブ用トランジスタも LOW で点灯になります。これ勘違いするとすべてがおかしくなってしまいますので (^_^;)

 


| 1/5PAGES | >>

■calendar

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     
<< September 2019 >>

■search this site.

■recommend

毎日貯まるポイントサイト ECナビ

■recommend

* 楽天ROOM *

■Twitter

■recommend

■recommend

■selected entries

■categories

■archives

■recent comment

■recent trackback

■links

■profile

■others

■mobile

qrcode

■powered

無料ブログ作成サービス JUGEM