センサはいろいろな種類がありますが、どんなものにも誤差が生じます。この誤差をできるだけ小さくするために、キャリブレーション(校正、調整)を行います。例えば、ライントレースカーの場合だと、床材が異なると反射率も異なるので、その環境下でデータを取って、制御閾値を調整する必要があります。このようなセンサデータを処理プログラムする方法を紹介します。
起動時にキャリブレーションする方法
起動時時に実行されるsetup関数内で、基準となるラインを計測して、それをもとに閾値やヒステリシスを設定します。
ヒステリシスについて、簡単に説明します。例えば、照度センサの閾値を500に設定して、500以下だとLEDを点灯、501以上だとLEDを消灯するプログラムを作るとします。照度センサから入力される値は、常に微小に上下しているため、500や501といった値が入ってきます。すると、LEDは点灯と消灯を高速に繰り返してしまいます。街灯はそのような事が起きませんよね?それは、LEDを点灯させる閾値を450、LEDを消灯させる閾値を550というように設定し、センサの値が451~549のときはLED制御をしないように不感帯を設けます。このように、状態によって異なる閾値を持つことをヒステリシスと言います。
1.平均値から決める
1秒間の平均値を計算し、その平均値を閾値とします。ヒステリシスは、0と閾値の距離の10%、または1023と閾値の距離の10%とします。
//www.stemship.com //2019.12.14 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン #define LED_PIN 6 //LED接続ピン // 状態を示す定数 const int INIT = 0; const int LEDOFF = 1; const int LEDON = 2; // 状態 int curState = INIT; //現在の状態 int nextState = INIT; //次の状態 // 閾値 int threshold = 0; // ヒステリシス int hys = 0; void setup() { //ピンの設定 pinMode(LED_PIN, OUTPUT); //シリアル通信設定 Serial.begin(9600); //キャリブレーション実施 runCalibration(1000); //1000ms } void loop() { int sensorVal = analogRead(SENSOR_PIN); //状態を遷移させる if (sensorVal > (threshold + hys)) { nextState = LEDOFF; } else if (sensorVal < (threshold - hys)) { nextState = LEDON; } //状態が変わったときに、LEDのON/OFFを切り替える if ((curState != LEDON) && (nextState ==LEDON)) { digitalWrite(LED_PIN, HIGH); Serial.print("Switch OFF->ON, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } else if ((curState != LEDOFF) && (nextState == LEDOFF)) { digitalWrite(LED_PIN, LOW); Serial.print("Switch ON->OFF, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } //状態を更新する curState = nextState; //次のループまで100ms待つ delay(100); } //キャリブレーションを実行する void runCalibration(int time) { int count = 0; int sensorVal = 0; long sumVal = 0; float average = 0; // 起動時してから<time>ms間のセンサ値を合計する while (millis() < time) { sensorVal = analogRead(SENSOR_PIN); sumVal += sensorVal; count++; delay(1); } //平均値を計算 average = sumVal / count; //平均値から閾値とヒステリシスを決める threshold = int(0.9 * average); if (threshold > 511) { hys = (1023 - threshold) / 10; } else { hys = threshold / 10; } //計算結果を表示 Serial.print("count = "); Serial.println(count); Serial.print("sumVal = "); Serial.println(sumVal); Serial.print("average = "); Serial.println(average); Serial.print("threshold = "); Serial.println(threshold); Serial.print("hys = "); Serial.println(hys); }
2.最小最大値から決める
5秒間の最小値と最大値を測定し、それを動作範囲の幅と考えます。例えば、ライントレースカーであれば、ユーザーが起動時に床とラインをセンスさせてあげます。閾値は、最小・最大の中央値とします。ヒステリシスは、閾値から最小または最大値までの距離の10%とします。
このキャリブレーションの場合は少し注意が必要です。ユーザーが起動時のキャリブレーション期間中に、動作範囲を設定してあげないと、正常な閾値設定ができず、動作がおかしくなってしまいます。その場合は、予め設定した値でどうさせたり、動作範囲が狭すぎる場合はキャリブレーションが失敗していることをユーザに音や光で知らせるようにすれば分かりやすいと思います。
//www.stemship.com //2019.12.14 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン #define LED_PIN 6 //LED接続ピン // 状態を示す定数 const int INIT = 0; const int LEDOFF = 1; const int LEDON = 2; // 状態 int curState = INIT; //現在の状態 int nextState = INIT; //次の状態 // 閾値 int threshold = 0; // ヒステリシス int hys = 0; void setup() { //ピンの設定 pinMode(LED_PIN, OUTPUT); //シリアル通信設定 Serial.begin(9600); //キャリブレーション実施 runCalibration(5000); //5000ms } void loop() { int sensorVal = analogRead(SENSOR_PIN); //状態を遷移させる if (sensorVal > (threshold + hys)) { nextState = LEDOFF; } else if (sensorVal < (threshold - hys)) { nextState = LEDON; } //状態が変わったときに、LEDのON/OFFを切り替える if ((curState != LEDON) && (nextState ==LEDON)) { digitalWrite(LED_PIN, HIGH); Serial.print("Switch OFF->ON, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } else if ((curState != LEDOFF) && (nextState == LEDOFF)) { digitalWrite(LED_PIN, LOW); Serial.print("Switch ON->OFF, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } //状態を更新する curState = nextState; //次のループまで100ms待つ delay(100); } //キャリブレーションを実行する void runCalibration(int time) { int minimum = 1023; int maximum = 0; int sensorVal = 0; // 起動時してから<time>ms間のmin/maxを取得する while (millis() < time) { sensorVal = analogRead(SENSOR_PIN); minimum = min(minimum, sensorVal); maximum = max(maximum, sensorVal); Serial.println(sensorVal); delay(1); } //最小値と最大値から閾値ヒステリシスを決める threshold = minimum + (maximum - minimum) / 2; hys = (maximum - threshold) / 10; //計算結果を表示 Serial.print("minimum = "); Serial.println(minimum); Serial.print("maximum = "); Serial.println(maximum); Serial.print("threshold = "); Serial.println(threshold); Serial.print("hys = "); Serial.println(hys); }
一定時間ごとにキャリブレーションする方法
動作環境が一定であれば、起動時のキャリブレーションだけで問題有りませんが、動作環境が刻々と変化するような場合だと、問題が出てきます。例えば、屋外だと気温や照度は一定では有りませんよね?このような場合は、一定時間ごとにキャリブレーションを実行して、閾値やヒステリシスを更新する必要があります。
//www.stemship.com //2019.12.14 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン #define LED_PIN 6 //LED接続ピン // 状態を示す定数 const int INIT = 0; const int LEDOFF = 1; const int LEDON = 2; // 状態 int curState = INIT; //現在の状態 int nextState = INIT; //次の状態 // 閾値 int threshold = 0; // ヒステリシス int hys = 0; // キャリブレーションのインターバル時間[ms] const unsigned long CAL_INTERVAL = 5000; //平均値計算用のカウンタ int count = 0; //平均値計算用の合計値 long sumVal = 0; //前回キャリブレーションを実施した時間 unsigned long lastCalTime = 0; void setup() { //ピンの設定 pinMode(LED_PIN, OUTPUT); //シリアル通信設定 Serial.begin(9600); } void loop() { //センサの値を取得 int sensorVal = analogRead(SENSOR_PIN); //現在の時刻を取得 unsigned long nowTime = millis(); //センサ取得値を積算してカウンタを更新する。 sumVal += sensorVal; count++; //状態を遷移させる if (sensorVal > (threshold + hys)) { nextState = LEDOFF; } else if (sensorVal < (threshold - hys)) { nextState = LEDON; } //状態が変わったときに、LEDのON/OFFを切り替える if ((curState != LEDON) && (nextState ==LEDON)) { digitalWrite(LED_PIN, HIGH); Serial.print("Switch OFF->ON, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } else if ((curState != LEDOFF) && (nextState == LEDOFF)) { digitalWrite(LED_PIN, LOW); Serial.print("Switch ON->OFF, "); Serial.print("sensorVal = "); Serial.println(sensorVal); } //一定時間が経過したらキャリブレーションを実行する if ((nowTime - lastCalTime) > CAL_INTERVAL) { runCalibration(); sumVal = 0; count = 0; lastCalTime = nowTime; } //状態を更新する curState = nextState; //次のループまで100ms待つ delay(100); } //キャリブレーションを実行する void runCalibration() { //countが0の場合はエラーとなるため、その場合はキャリブレーションをしない。 if (count < 1) { return; } //平均値を計算 float average = sumVal / count; //平均値から閾値とヒステリシスを決める threshold = int(0.9 * average); if (threshold > 511) { hys = (1023 - threshold) / 10; } else { hys = threshold / 10; } //計算結果を表示 Serial.print("count = "); Serial.println(count); Serial.print("sumVal = "); Serial.println(sumVal); Serial.print("average = "); Serial.println(average); Serial.print("threshold = "); Serial.println(threshold); Serial.print("hys = "); Serial.println(hys); }
コメントを残す