センサはいろいろな種類がありますが、どんなものにも誤差が生じます。この誤差をできるだけ小さくするために、キャリブレーション(校正、調整)を行います。例えば、ライントレースカーの場合だと、床材が異なると反射率も異なるので、その環境下でデータを取って、制御閾値を調整する必要があります。このようなセンサデータを処理プログラムする方法を紹介します。
起動時にキャリブレーションする方法
起動時時に実行される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);
}

コメントを残す