第20回 analogRead

毎回同じパターンの乱数にならないように、乱数の初期化方法を検討します。

目次

Arduino定番の乱数初期化

最近はあまり機会がないと思いますが、ダイヤル式のAM/FMラジオを聞いたことはありますでしょうか?

最近はスマホでラジオが聴けるアプリがありますが、インターネットでデジタルで配信されていますので音がクリアですよね。

ダイヤル式のAM/FMラジオは、ダイヤルを回して放送局を選びますが、ダイアルが放送局の周波数に合っていないと「ザーッ」という雑音が聞こえてきます。

このような電気的な雑音はノイズと呼ばれています。

ダイヤル式のラジオを聴いたことがない方でも、電子レンジを使っているときにWi-Fiの調子が悪くなったりというような経験はないでしょうか?これも電子レンジから漏れている電気的なノイズ(電磁波)が、Wi-Fiの無線通信に影響してしまっているためです。


このように身の回りには電気的な雑音、つまりノイズがたくさん飛び交っているんです。

乱数とは全然関係ない話から始まってしまいましたが、Arduinoの乱数初期化は、このノイズを利用する方法が定番なんです。

実際にノイズを測定してみると一定の値ではありません。

測定するたびに毎回違う値が測定されます

そこで、Arduinoで乱数を初期化するとき、ノイズの値を読み取って、その読み取った値を randomSeed関数の引数にしてしまおう、というわけです。

Arduinoの乱数初期化の定番は、次のようにノイズを測定して、その値で乱数を初期化する、という方法になります。

randomSeed( 読み取ったノイズの値 );

スケッチにこのように書けば、Arduino Microが動作するたびに毎回違ったパターンの乱数を生成することができるようになります。

それでは、最初にArduinoでノイズを測定する方法を確認します。

ノイズの測定

「ノイズを測定する」と説明しましたが、具体的にはノイズの「電圧」を測定します。

Arduinoには、「電圧を測定する」関数がありますので、この関数を使ってノイズを測定します。

そこで、最初に電圧を測定する関数を確認して、そのあとノイズを測定する仕組みについて説明します。

analogRead関数

Arduinoでは、端子の電圧を読み取るために次の関数が用意されています。

この関数は、端子の電圧をアナログ(analog)で読み取ります。

基礎編パート1でスイッチ接続端子の状態を読み取るとき、digitalRead関数を使いましたよね。

digitalReadは端子の電圧があるかないか、の2つの状態(デジタル)で読み取ります。

一方、analogRead関数は、端子の電圧を連続的に読み取ります。

この関数には3つ注意点があります。

❶ pinMode設定は必要なし

1点目は、この関数を使用する前に、pinModeで端子の設定をする必要はない、という点です。

Arduinoボードの動作開始直後は端子の設定は電圧を読み取る設定になっています。具体的にはpinMode(端子名, INPUT);の状態になっています。

そのため、pinModeで端子の設定をする必要はありませんが、スケッチで明示的にpinMode(端子名, INPUT);と書いても問題ありません。

❷ 使用できる端子は限られる

2点目は、analogRead関数が使える端子は限られている、という点です。

具体的には、端子名が「A」から始まっている端子でanalogReadが使えます。

端子名の「A」は「Analog」の意味です。analogReadで電圧を連続的に読み取れますよ、というイメージですね。

❸ 電圧は0〜1023の値で読み取る

3点目は、読み取った電圧の値は「V(ボルト)」ではない、という点です。

普通は「電圧を読み取る」という場合「1.5ボルト」などのように単位はボルトです。

analogReadは、「何V」と読み取るわけではなく、「0 〜 1023」の数字で読み取ります。

実際の電圧は1023が5Vに対応しています。例えば読み取った数字が1023の約半分の512であれば、そのときの電圧は5V半分の約2.5Vということになります。


このようにanalogRead関数を使用するとピンの電圧を読み取ることができるわけですが、もう一つ非常に重要なことがあります。

何も接続されていない端子の電圧

現在のブレッドボードの回路は、次のようにA1端子やA2端子には何も接続されていません。

A1・A2ピン

この何も接続されていないA1やA2端子の電圧をanalogReadで読み取ると、そのときの数字はいくつになると思いますか?

何も接続されていないのだから、0V、つまり読み取った数字は0という感じがしますよね。

実は、何も接続されていない端子の電圧は不定なんです。少なくとも0Vではありません。

電子回路に慣れていないと、ちょっと???と思われるかもしれませんが、どこにも接続されていないピンや電線の電圧は決まっていないんです。

では、どのぐらいの電圧になるのかというと、、、測定するたびに電圧の値は変わるんです。

これは、ノイズなどの外部の影響を受けてしまうためです。

ということで、実際にA1端子の電圧を読み取ってみましょう。

ノイズを測定してみよう

A1端子はどこにも接続されていませんので、analogReadで読み取った値は不定になります。

そこで次のスケッチを確認して、実際にA1端子の電圧(analogReadで読み取った値)を確認してみます。

/*
 * 何も接続されていないA1端子の電圧を読み取る
 */

void setup() {
  // シリアルモニタ初期設定
  Serial.begin(9600);
  while(!Serial){  
  }
}

void loop() {
  //0.2秒ごとに読み取った電圧値をシリアルモニタ/シリアルプロッタに表示
  Serial.println( analogRead(A1) );
  delay(200);
}

シリアルモニタで確認すると、次のようになりました。

なんて言われても、数字の羅列だとよく分かりませんよね。

そこで、この数値をグラフとして表示してくれる機能があるので、グラフにしてみましょう!

Arduino IDEウィンドウの右上に、グラフのようなアイコンがあります。

クリックすると、次のようなグラフで表示するウィンドウが表示されます。

このような感じで変化していることがわかると思います。


数値が変化するのを確認したら、次はブレッドボードの置いてある位置を変更してみてください。位置を変更するとノイズの状況が変わるので、数値が変化するのがわかると思います。

さらに、Arduino Microに触れないように、ブレッドボードを手のひらで覆ってみたり、顔を近づけてみたりしてみてください。波形が乱れたりするのが確認できると思います。

このように、身の回りで発生しているノイズは場所によっても、周りの環境によっても、また時間によっても変わってきます。(太陽の影響も受けますので、昼と夜で違うこともあります)


このように何も接続されていない端子の電圧をanalogReadで読み取ると、適当な数字を取得できることがわかりました。

乱数の初期化にはこのように毎回異なる数字になることが期待されるanalogReadを取得した値を使います。

ということで、今までの内容をまとめると、Arduinoの乱数の初期化は定番のスケッチは次のようになります。

randomSeed( analogRaed(A1) );  // 何も接続していないA端子の電圧値を初期値にする

再度、乱数発生スケッチ

それでは、前回の乱数発生スケッチに対して、乱数初期化を追加して動作を確認しましょう。

random関数で乱数を発生させる前に、randomSeed関数で乱数を初期化すればOKです。

スケッチは次のように作成してみました。

/*
 * random関数の動作確認用スケッチ
 *   randomSeedで定番の乱数初期化をする
 */

void setup() {
  // シリアルモニタ初期設定
  Serial.begin(9600);
  while(!Serial){  
  }

  // 乱数の初期化
  randomSeed( analogRead(A1) );

  // 乱数を30個生成してシリアルモニタに表示する
  for(uint8_t count=0; count<30; count++){
    Serial.println( random(10) );
  } 

}

void loop() {

}

乱数はanalogReadでいいのでは?

analogRead関数を使って接続されていないピンの電圧を読み取ると、適当な数字が取得できることがわかりました。

ところで、random関数では乱数を求めるのに計算していましたので、乱数を初期化する必要がありました。

こんなややこしいことをするのであれば、analogReadで数字を取得して、それを乱数としてしまえばよいのではないか、という気もします。


確かに何も接続していない端子をanalogReadで読み取った値は乱数っぽいですが、残念ながら乱数には使えません。

乱数は、全ての数字が均等な確率でランダムに出現する必要があるためです。。

先ほどの私の環境で動作確認したシリアルプロッタのグラフを見ると、増えたり減ったりというパターンの規則性がありますよね。これはおそらく何か周期的な信号を受信してしまっているためだと思います。

また、ノイズといっても全ての数字が均等な確率で出てくるとも限りません。

ということで、Arduinoの世界では、analogReadで初期化の数字を決めて、randomSeedで乱数の初期化、randomで乱数を取得するのが一般的になっています。


ところで、普通のPCやスマホなどはどのように乱数を初期化しているのでしょうか?

これはプログラマによりますが、例えば現在の時刻をミリ秒で取得して、その時刻の数字を使用して乱数を初期化したりしています。また、

他にもセンサの値が取得できる状況であればセンサの値を使用して乱数の初期化を行うことも可能です。

ということで、世の中、乱数を発生させるのには意外に苦労していますね。

ミニチャレンジ課題

課題

乱数の初期化は、randomSeed関数、乱数を生成するにはrandom関数いうことがわかりましたので、ミニチャレンジ課題に挑戦してみましょう!

ミニチャレンジ課題

ブレッドボードにLEDが4個ありますので、LEDおみくじを作ってみたいと思います。
動作としては、スイッチを押すとどれか1個のLEDが点灯するようにします。
点灯したLEDにより次のような結果のおみくじにしたいと思います。

LEDの色結果
大吉
中吉

解答例

動作としては、スイッチが押されたことを検知したら、4つの状態をランダムに選んで、結果に応じてLEDを制御する、という内容になります。

スイッチの検知はSwitchライブラリを使ってみました。pollメソッドでスイッチ状態を確認し、pushedメソッドの戻り値が真であればおみくじの処理を行います。

毎回同じ結果にならないように、乱数はrandomSeed( analogRead(A1) );によりシードを初期化します。

おみくじの4つの状態は、random(4);で0〜3の数値を発生して、それぞれの値に応じて結果をLEDで表現することにします。

結果は0〜3ですので、それぞれ、大吉、中吉、吉、凶に対応させて、switchで処理を切り分けるようにしました。この処理は関数にしています。

4個のLEDの処理は、それぞれのおみくじ結果で同じような処理になりますので関数にしてみました。

/*
 * Arduino入門 基礎編パート2  第20回
 *   ミニチャレンジ課題解答例
 */

// ライブラリヘッダファイル
#include <avdweb_Switch.h>

// スイッチの接続端子
#define SWITCH_HIDARI A5 // 左側スイッチ

// LEDの接続端子
#define BLUE_LED   12
#define GREEN_LED  8
#define YELLOW_LED 6
#define RED_LED    4

// スイッチクラスのインスタンスを生成
Switch hidariSwitch(SWITCH_HIDARI); // 左側スイッチのインスタンス

// おみくじ結果のLED点灯関数
//   引数でそれぞれのLEDのON/OFFを指定する
//   戻り値: なし
//   引数: HIGH/LOW             青色           緑色            黄色           赤色
void controlOmikujiLed(uint8_t blue, uint8_t green, uint8_t yellow, uint8_t red) {
  digitalWrite(BLUE_LED, blue);
  digitalWrite(GREEN_LED, green);
  digitalWrite(YELLOW_LED, yellow);
  digitalWrite(RED_LED, red);
}

void setup() {
  // スイッチ接続ピンの設定
  pinMode(SWITCH_HIDARI, INPUT_PULLUP);

  // LED接続端子の設定
  pinMode(BLUE_LED,   OUTPUT);
  pinMode(GREEN_LED,  OUTPUT);
  pinMode(YELLOW_LED, OUTPUT);
  pinMode(RED_LED,    OUTPUT);

  // 乱数初期化
  randomSeed( analogRead(A1) );
}

void loop() {
  // 左側スイッチ状態の調査
  hidariSwitch.poll();
  
  // スイッチが押されたら、乱数(0〜4)を発生してLEDを制御
  if( hidariSwitch.pushed() ) {
      switch( random(4) ) {
        case 0: controlOmikujiLed(HIGH, LOW,  LOW,  LOW ); break;  // 大吉・青色LED
        case 1: controlOmikujiLed(LOW,  HIGH, LOW,  LOW ); break;  // 中吉・緑色LED
        case 2: controlOmikujiLed(LOW,  LOW,  HIGH, LOW ); break;  // 吉・黄色LED
        case 3: controlOmikujiLed(LOW,  LOW,  LOW,  HIGH); break;  // 凶・赤色LED
      }
  }
}

更新履歴

日付内容
2021.11.8新規投稿
2025.2.17端子名の説明内容変更
Arduino IDE2対応
ミニチャレンジ課題解答例追加
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次