近頃は、見様見真似の C++ クラスもどきスケッチを書いていますが、Arduino IDE に叱られながらも、だんだんと雰囲気をつかみつつある meyon さんであります。なんと、これまでチンプンカンプンだった C++ の教科書が、少しずつフムフムになっていますので、それなりに成長しているのではないかな (^_^;)
ならばもう一度、教科書をめくりなおしてみようかと。
もちろん、目的は Arduino のスケッチを書くこと。しかし、C++ の教科書には Arduino のアの字もでてきません。そこで頼りになるのはグーグル先生なんだけど……。ググってでてくるのは、すでにクラスを理解したプログラミング先達たちが、Arduinoでクラスを使うときはこうするんだよ、という方向性のものばかり。
そうじゃなくてさ。
俺は C も C++ もわからない。Arduino のスケッチを書くために、必要に応じて学んできただけ。クラスなんて言葉も、つい最近知ったばかり。だから、Arduino のスケッチって〜のは setup() と loop() を書いてさぁ〜 って感じで、クラスって〜のは class Hoge {} って書くんだぜ〜 って感じの話が聞きたいわけさ。
そんな俺。ならば自分で、Arduino で C++ クラスを使うために学習してみる。
というシリーズに、なったらいいな。途中でコケたら、ごめんなさい m(__)m
回路図
Arduino を動かすことが目的ですから、何らかの入出力が不可欠です。最初は簡単に L チカをやってみましょう。
今回も Arduino Nano Every を使いました。毎度の L チカ回路。LED は 2 個つないであります。12 番と 13 番を使っていますので、内蔵 LED も一緒に L チカします。
抵抗器は 3.3KΩ にしています。高輝度 LED ですので、1mA も流せば十分光ります。
それに、Arduino Nano Every の I/O ポートって、いがいと電圧降下するんですよ。あんまり電流とらないほうが、吉です。
クラスの宣言
では、スケッチを書いていきましょう。最初に行なうのは、クラスの宣言です。
以前にもやった L チカするためのスケッチ Flash をクラスにしてみます。クラス名は Flasher。クラスの中に、L チカ Flash を行なうために必要な変数や関数をまとめていきます。
クラスの宣言の書き方。
- class Flasher {
- };
このクラスの宣言は、からっぽです。最後の } のあとのセミコロンをお忘れなく。
このなかにまず、必要な変数を置きます。必要な変数は、LED をつなぐピン番号 ledPin、点灯時間 onTime、消灯時間 offTime、LED の状態 ledStat、前回の更新時刻 prevMillis です。
クラスのなかに置くこれらの変数を、メンバ変数といいます。
- class Flasher {
- public:
- byte ledPin;
- int onTime;
- int offTime;
- byte ledStat;
- unsigned long prevMillis;
public: は「アクセス指定子」です。省略すると private: になります。その働きは、また今度。
次は、L チカ Flash に必要な関数を宣言します。これは、メンバ関数といいます。
今回は、ふたつの関数を用意しました。ひとつは LED の点滅を行なう関数 update()。もうひとつは、LED がつながっている I/O ピンのモード pinMode() を設定する関数 attach() です。
- void attach();
- void update();
- };
以上が、クラスの宣言。このクラスのメンバはこいつらだよ、という内容表明ですね。
メンバ関数の定義
クラスの宣言の中では、メンバ関数も宣言しただけです。メンバ関数の実際の処理について定義しておかなければなりません。ここでは、クラスの宣言の外で定義してみましょう。
まずは attach() です。LED がつながっている I/O ピンのモード設定を行ないます。
- void Flasher::attach() {
- pinMode(ledPin, OUTPUT);
- }
メンバ関数は、:: 演算子 (スコープ解決演算子) を使って、どのクラスのメンバ関数かを指定します。
内容は、いつも setup() でやっている pinMode() の設定です。
つぎは、じっさいに LED の点滅を行なう update() です。
- void Flasher::update() {
- unsigned long currMillis = millis();
- if((HIGH == ledStat) && (onTime < currMillis - prevMillis)) {
- ledStat = LOW;
- prevMillis = currMillis;
- digitalWrite(ledPin, ledStat);
- }
- else if((LOW == ledStat) && (offTime < currMillis - prevMillis)) {
- ledStat = HIGH;
- prevMillis = currMillis;
- digitalWrite(ledPin, ledStat);
- }
- }
LED を onTime だけ点灯し、offTime 消灯するスケッチ Flash の本体部分です。内容は難しくはないですね。詳細は過去記事を参照下さい。
これで、クラスの宣言がすべてできました。
オブジェクトの生成
クラスっていうのは、変数の「型」なんですね。そこで、クラス Flasher 型の変数 led1 を定義する、それを「オブジェクトの生成」と言います。L チカ Flash を実行するためのもろもろが、led1 という変数の中にまとめて格納されている。この、オブジェクトの生成で作られた変数を「インスタンス」と呼びます。
- Flasher led1;
これで、クラス Flasher を利用することができるようになりました。
クラスを利用する
では、インスタンス led1 を使って、L チカ Flash してみましょう。
- void setup() {
- led1.ledPin = 12;
- led1.onTime = 100;
- led1.offTime = 400;
- led1.ledStat = LOW;
- led1.prevMillis = 0;
- led1.attach();
- }
まずは setup() です。
ふつうはここで、LED がつながれた I/O ピンのモード設定 pinMode() を行ないますね。クラス Flasher ではメンバ関数 attach() が、それを行ないます。
35行目。LED1 がつながっている I/O ピンは 12 番ですので、変数 led1 のメンバ変数 ledPin にその値 12 を代入します。メンバ変数は、. 演算子 (ドット演算子) でつないで led1.ledPin と表記します。
そして、41 行目でメンバ関数 led1.attach() を呼び出しています。
36〜39行目は、ledPin と同様に、インスタンス led1 に必要なメンバ変数の値を代入しています。
これらのメンバ変数はアクセス指定子を public: としたので、このようにクラスの宣言の外から直接、値を代入することができます。アクセス指定子を private: にすると、外から直接、値を代入することができなくなります。
- void loop() {
- led1.update();
- }
続いて loop()。
ここではもう、メンバ関数 led1.update() を呼び出して、L チカするだけです。
このスケッチを Arduino Nano Every に書き込めば、12 番ピンにつながれた LED1 が Flash します。
うまくいきましたね?
では、13 番ピンにつながれた LED2 も Flash するようにしてみましょう。
2 つ以上のオブジェクトの生成をする
オブジェクトの生成はいくつでもすることができます。もうひとつの LED を点滅させたければ、2 つ目のオブジェクトの生成をすればよいです。
- Flasher led1;
- Flasher led2;
32 行目までのスケッチは全く同じです。Flasher クラスの宣言はそのまま利用します。
33行目で、2 つ目のオブジェクトの生成を行ない、インスタンス led2 を作っています。
- void setup() {
- led1.ledPin = 12;
- led1.onTime = 100;
- led1.offTime = 400;
- led1.ledStat = LOW;
- led1.prevMillis = 0;
- led1.attach();
- led2.ledPin = 13;
- led2.onTime = 350;
- led2.offTime = 350;
- led2.ledStat = LOW;
- led2.prevMillis = 0;
- led2.attach();
- }
setup() では、led1 と同様に、led2 に必要な値を代入し、led2 の attach() を呼び出します。これで、13 番ピンも OUTPUT モードになり、点灯時間、消灯時間の設定ができました。
- void loop() {
- led1.update();
- led2.update();
- }
最後に、led2 の update() も呼び出すように追加します。
これで、2 つの LED が、同時に、独立して、点滅するようになりました。
スケッチ – クラスの宣言
できあがったスケッチです。
- class Flasher {
- public:
- byte ledPin;
- int onTime;
- int offTime;
- byte ledStat;
- unsigned long prevMillis;
- void attach();
- void update();
- };
- void Flasher::attach() {
- pinMode(ledPin, OUTPUT);
- }
- void Flasher::update() {
- unsigned long currMillis = millis();
- if((HIGH == ledStat) && (onTime < currMillis - prevMillis)) {
- ledStat = LOW;
- prevMillis = currMillis;
- digitalWrite(ledPin, ledStat);
- }
- else if((LOW == ledStat) && (offTime < currMillis - prevMillis)) {
- ledStat = HIGH;
- prevMillis = currMillis;
- digitalWrite(ledPin, ledStat);
- }
- }
- Flasher led1;
- Flasher led2;
- void setup() {
- led1.ledPin = 12;
- led1.onTime = 100;
- led1.offTime = 400;
- led1.ledStat = LOW;
- led1.prevMillis = 0;
- led1.attach();
- led2.ledPin = 13;
- led2.onTime = 350;
- led2.offTime = 350;
- led2.ledStat = LOW;
- led2.prevMillis = 0;
- led2.attach();
- }
- void loop() {
- led1.update();
- led2.update();
- }
まとめ
C++ クラスは、通常、クラスの宣言を別々のファイルに分割して書きます。ひとつはヘッダファイル hoge.h で、もうひとつはソースファイル hoge.cpp というふうに。ヘッダファイルにはメンバ変数とメンバ関数が、ソースファイルにはメンバ関数の定義が、書かれます。そのことを知っていれば、先達たちが書かれたプログラムを理解しやすくなると思います。
けれどもここでは、ファイルを分割せずにひとつのファイルに書きました。Arduino で俺が書く、分割コンパイルなどとゆー概念もない初心者の俺が書く程度のスケッチなら、とりあえずそれで十分じゃないだろうか、と思っています。少なくとも当面は、このようにしておこうかと。
今回は、クラスを宣言する第一歩目を学習しました。次は、クラスメンバへのアクセス制限について、学んでみようと思います。