Arduino入門(Lチカ)のその先
Arduino でプログラミングを学習する場合、参考になるプログラム例が Arduino IDE のメニューの File->Examples に多数あります。
これらのプログラム例は、
https://docs.arduino.cc/built-in-examples/
でも公開されています。
なお、Arduino の場合、プログラムは「スケッチ」と読んでいますが、以下では「プログラム」と記しています。
入門プログラム
Arduino入門として、Lチカ(LEDを一定周期でOn/Offさせる)プログラムを紹介、説明する場合が多いと思います。この説明において、Examples->01.Basics->Blink が使われることがほとんどのようで、このようなプログラムです。
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
このプログラムはBasicsなので、これを実用的なプログラムとして使うということではないのだと思います。実際、
Language Reference/Functions/delay()
のNotes and Warningsには、
the use of delay() in a sketch has significant drawbacks.
More knowledgeable programmers usually avoid using delay() for timing events longer than 10s of milliseconds,
とあります。
入門の応用プログラム
LEDのOn/Off以外に何かを実行する、次のようなプログラムを考えてみます。
void loop() {
led_blink();
do_something();
}
void led_blink() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
void do_something() {
;
}
ここでは、LEDのOn/Offをする部分を、別のled_blink()という関数に分離させて、loop()からled_blink()を呼ぶようにしています。同様に、LED On/Off以外を実行するdo_something()という関数を作成し、これもloop()から呼ぶことにします。
led_blink()関数の中で、delay(1000 を実行するところは、1秒経つまで次のステップに進まないので、led_blink() が終わるのに少なくとも2秒は掛かります。そのため、do_something()関数は、2秒以上待つことになります。つまり、delay() を使って経過時間のコントロールをすると、その間は何もできずに待っているだけになってしまいます。
別の入門プログラム
実は、delay()を使わないLチカのプログラム例として、Examples->02.Degital->BlinkWithoutDelay がちゃんとあります。次のようなプログラムです。
const int ledPin = LED_BUILTIN; // the number of the LED pin
int ledState = LOW; // ledState used to set the LED
unsigned long previousMillis = 0; // will store last time LED was updated
const long interval = 1000; // interval at which to blink (milliseconds)
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
// the interval at which you want to blink the LED.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
}
これも、LEDのOn/Offをする部分を、led_blink()という別関数に分離して、do_something()も追加すると、
const int ledPin = LED_BUILTIN; // the number of the LED pin
const long interval = 1000; // interval at which to blink (milliseconds)
void loop() {
led_blink();
do_something();
}
void led_blink() {
static int ledState = LOW; // ledState used to set the LED
static unsigned long previousMillis = 0; // last time LED was updated
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
void do_something() {
;
}
このようにすると、led_blink()関数に入った時点で前回からの経過時間が interval(1秒)未満の場合は 何も実行せずに終了する(return する)ので、LED 点滅間隔の時間経過を待つことなく、次の do_something() 関数が実行されます。
なお、Examples では ledState, previousMillis は関数外で宣言(グローバル変数宣言)されていましたが、上記 led_blink() 関数でしか参照されないので、static 宣言して led_blink() 関数の中に入れています。
応用(スイッチのチャタリング回避)プログラム
スイッチ入力を読み取る場合、スイッチのチャタリングによってスイッチOn/Offが短時間で繰り返され、スイッチ入力によって動作を切り替えている場合、頻繁に動作が切り替わってしまいます(チャタリングについては参考文献参照)。
この影響を除去するプログラムでの工夫として、次のような例を見ることがあります。(sw入力部を sw_input() 関数として loop() から分離しています。)
int sw = 0;
void loop() {
void sw_input();
}
void sw_input() {
int swNow = digitalRead(swPin); // read sw input
delay(10); // wait 10ms
if (digitalRead(swPin) == swNow) { // if sw input continue
sw = swNow; // no bouncing
}
}
Basics の Lチカプログラムを参考にして、スイッチがチャタリング(英語では bouncing)を生じているかも知れない時間(この場合は 10ms としている)待って、値が同じであればチャタリングは収まっていると判断しています。
しかし、何もせずに 10ms 待っているのは実用的ではないでしょう。
実際、Examples には、Examples->02.Digital->Debounce には、delay() を使わずにチャタリングを回避する例が示されているので、それを使って sw_input() を書き換えると、
int buttonState; // the current reading from the input pin
unsigned long debounceDelay = 50; // the debounce time
void loop() {
sw_input();
}
void sw_input() {}
static int lastButtonState = LOW; // the previous reading
static unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
int reading = digitalRead(buttonPin);
// If the switch changed, due to noise or pressing:
if (reading != lastButtonState) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// it's been there for longer than the debounce
// delay, so take it as the actual current state:
// if the button state has changed:
if (reading != buttonState) {
buttonState = reading;
}
}
lastButtonState = reading;
}
Examples では、lastButtonState と lastDebounceTime は関数外で宣言(グローバル変数宣言)されていますが、スイッチ読み取り関数でしか参照しないので、上記 sw_input() の中で static 宣言しています。



