マイコンをはじめよう ~サンプルコードを理解する~

IoT

サンプルコードを読み解こう

前回、M5StampS3を無事、動作させることができました。

それでは自分でプログラムを書きましょう!

と言いたいところですが、いかんせんプログラミング言語を理解しておりません。

どうやらこのArduino IDEでの開発では、Arduino言語というものを使うそうです。

これはC言語を簡単にして初心者向けになっているらしい。ありがたい。

そんなわけで、サンプルコードを読み解くことでどんな書き方をすればいいのか学んでいきます。

サンプルコードの解読

ゴリゴリにコメントを入れてみました

#include <FastLED.h>      // FastLEDのライブラリを呼び出す

// コードの意味を分かりやすくするためにそれっぽいピン番号にそれっぽい名前をつける
#define PIN_BUTTON 0      // PIN_BUTTONという名前に0という値を設定する (あとで0ピンだよと解釈される)
#define PIN_LED    21     // PIN_LEDという名前に21という値を設定する
#define NUM_LEDS   1      // NUM_LEDSという名前に1という値を設定する


// ledsという配列をCRGB型で定義する。NUM_LEDSは1と定義したので、1個だけ要素がある配列ができる
// これはFastLEDの仕様なのであまり深くは考えない
CRGB leds[NUM_LEDS];

// 符号なしの整数(unit8_t)でLEDの状態を記録する用の変数を用意する。
// led_ihはFastLEDでレインボーな感じにするものなので、何を意味するかは深く考えない方がいい
uint8_t led_ih             = 0;
uint8_t led_status         = 0;

// 色の名前を入れた配列を作る
String led_status_string[] = {"Rainbow", "Red", "Green", "Blue"};

// 起動時のセットアップをする関数
void setup() {
    // シリアル通信をする
    Serial.begin(115200);             // シリアル通信の速度を決める(ボーレートと呼ばれるもの)
    Serial.println("StampS3 demo!");  // シリアル通信の表示をする

    pinMode(PIN_BUTTON, INPUT);       // ボタンのポートを入力モードに設定する

    FastLED.addLeds<WS2812, PIN_LED, GRB>(leds, NUM_LEDS); //FastLEDに、このLEDをコントロールするよ、と渡す
}


// LEDを点灯させるプログラム
void loop() {
    // 変数 led_statusの値(0~3)によって点灯状態を変える
    switch (led_status) {
        case 0: // led_statusが0ならレインボーモード
            leds[0] = CHSV(led_ih, 255, 255);
            break;
        case 1: // led_statusが1なら赤
            leds[0] = CRGB::Red;
            break;
        case 2: // led_statusが2なら緑
            leds[0] = CRGB::Green;
            break;
        case 3: // led_statusが3なら青
            leds[0] = CRGB::Blue;
            break;
        default:
            break;
    }
    FastLED.show(); // 上記のswitch文で決めたleds[0]の状態を実際のLEDに反映させる
    led_ih++;       // led_ihの値を+1する
    delay(15);      // 15ms待つ

    // スイッチの状態を確認してled_statusの状態を変える
    if (!digitalRead(PIN_BUTTON)) { //PIN_BUTTONの状態をみて、Highでない(Low)なら下の処理をする
        delay(5);
        if (!digitalRead(PIN_BUTTON)) { //もう一度、上と同じ判定をする (たぶん、ちゃんと押しているのか見ている)
            led_status++;   // led_statusの値を+1する
            if (led_status > 3) led_status = 0; // もしled_statusが3より大きい(4)の場合は0に戻す
            while (!digitalRead(PIN_BUTTON))  // PIN_BUTTONの状態をみてHighではない(Low)なら何もしない状態を続ける
                ;   // 何もしない
            Serial.print("LED status updated: ");   // ボタンが離されたらシリアルに変わったよ、と連絡する
            Serial.println(led_status_string[led_status]);  // シリアル通信でled_statusの状態を連絡する
        }
    }
}

雑で申し訳ないのですが、だいたい雰囲気は伝わったでしょうか?

次にM5StampS3を今後使っていくにあたって、大事なポイントをおさえておきます

大事なポイント

プログラムを書くルールの中で基本的な部分を解説します。だいたいここら辺を押さえておけば8割方なんとかなります。たぶん。

#include ライブラリ

こちらは、このライブラリを使いますよ宣言です。これが無いとライブラリで提供される便利機能が使えません

#define 名前 = 本当の値

これは「本当の値」のままだとプログラムを書く上でやりづらいので、適当な「名前」を割り当てる、という操作です。

#define PIN_BUTTON 0

この例だと、ボタンはGPIO0(ジーピーアイオーゼロ)というところにつながっているので0と指定するのですが、プログラム上で「0をあれこれする!」と書くよりは「ボタンをあれこれする!」と書いた方が見やすいですよね?

変数の定義

サンプルプログラムで「uint8_t led_ih = 0」となっているところです。

uint8_tというのは「8bitの符号なし整数」という意味で0~255の数字を入れることができます。

そういうled_ihという変数を、まずは0をいれておいて定義しています。

ほかにも文字列や、小数点がある数字など色々なタイプがありますので、それは都度調べて使っていきます。

配列

String led_status_string[] = {“Rainbow”, “Red”, “Green”, “Blue”};にあたる部分です。

これはled_status_stringという文字列(String)を格納できる配列を用意して、その中にRainbowなど、計4つの値を入れています

[]の中には格納番号を書きます。

この場合、led_status_string[1]と指定すると”Red”という値を取り出せます。

※ プログラムはだいたい0から始まるので1といった場合は2個目の要素となります。

関数

void setup()と書かれている部分です。

関数はその中に書かれた一連の動作のセットです。

その時の宣言に、戻り値 変数名(引数)というような書き方をします。

例えば整数(int)の足し算をする機能を書きたかったら

int add(int x, int y){
    int answer = x + y;
    return answer;
}

と書きます。そして以下のように使います

int a = 1;
int b = 2;
int c = add(a, b);

解説していきます。

まず、関数の定義のほうからです。

int add(int x, int y)と書いています。

これは「xとyの2つの引数を受けとって、int型の値を返すaddという関数です」という意味です。

プログラムの中を見ると、answerという変数にx+yの結果をいれてreturnしています。

このreturnで返されるのが戻り値です。

そしてその使い方でa=1, b=2と定義し、それらをadd()にいれて計算結果をもらいます。

この場合、計算された3がcに入ります。

では、最初に戻ってvoid setup()とはなんぞや、という話ですが、

voidは何もない、という意味です。

同じく()も何も入力しない、という意味です。

なので引数も戻り値もない関数setupというわけです。

setup()とloop()

本サンプルに出てくるこの2つの関数はArduino言語の中で特殊な位置づけでして、意味が決められています。そしてどんなプログラムにも必ず登場します。

基本はこの2つの関数の中に色々書いていくことになります。

setup()

起動時に一度だけ実行する関数です。

名前の通り初期のセットアップを行います。

サンプルでいうとピンの定義とかそういうところですね。

loop()

Arduino(今回はM5StampS3)はこの中に書かれたプログラムを延々と繰り返して実行します。

例えば、

void loop(){
    Serial.println("こんにちは");
}

と書くと、ひたすら全力で「こんにちは」と言い続けるプログラムが完成します。こわいですね。

次に大事なポイント

上記までは、おそらく知っておかないと動かすことすらままならない、というレベルの話でした。

ここから取り上げるのは場合によっては使ったり使わなかったりするけど、まぁものすごい頻度で出現しますよ、というレベル感です

プログラムを作るための基本パーツ、と言うとしっくりくるかもしれません。

if

英語のままですね。「もし~なら」ということです。

if (!digitalRead(PIN_BUTTON)) {と出てきたやつです。

もう少し簡単にしましょう

if (a=1) {
     Serial.println("こんにちは")
}

もしaという変数が1ならば「こんにちは」と言う、という意味になります。

aが1以外だと何も言ってくれません。

その時になにか言って欲しければ次のように記載します。

if (a=1){
    Serial.println("こんにちは")
} else {
    Serial.println("ごきげんよう")
}

a=1でないときは「ごきげんよう」といってくれます。

elseは「そうでなければ」という意味です。

for, while, switch

ifと並んでよくつかわれる関数群です。詳細は書きませんが名前は覚えておいてください。

switchはサンプルにも出てきた、条件に応じて処理を分ける関数です。ifの複雑版みたいなイメージでしょうか。

whileはその条件に当てはまる限りその処理を続ける、という感じです。

forは決められた範囲での繰り返し処理です。たとえば書類をどさっと渡され、これ処理しておいて、という感じです。

digitalRead/digitalWrite

まずは、ここまでしれっと流してきましたがピンのHigh/Lowについて触れておきます。

コンピュータが1と0で計算している、というのは聞いたことがあると思います。

これが現実世界ではどういうことかというと、電圧が高いか低いか、ということになります。

もう少しいうと、M5StampS3は動作電圧が3.3Vなので、3.3Vか0Vということです。

つまり、1/0 = High/Low = 3.3V/0V、全部同じ意味となります。


digitalReadは、if文の中で登場した関数です。

if (!digitalRead(PIN_BUTTON)) {

この赤字部によって、PIN_BUTTONの状態を知ることができます。

押されていたら0が、押されていなかったら1が返ってきます。

なんでONが0でOFFが1なのかというと、冒頭で説明した電圧がスイッチONで0V、OFFで3.3Vになっているためです。

スイッチONが1っぽいじゃん、と思われるかもしれませんが、電気的に見た場合に通常はON=0Vのほうがメリットがあるため世の中の多くのスイッチがそういう設定になっています。

PIN_BUTTONはインプットモードにしていたので、そのピンの状態を読むため、Readとなります。

逆に、例えばあるピンの状態を変えたいとしたらピンはアウトプットモードにして、digitalWriteを使って1を書き込めばそのピンがHigh(3.3V)になる、という使い方です。

こういうガジェットは外の回路とのやり取りが肝なので、これらのコマンドは押さえておく必要があります

!

こちらもif文に登場した子です。

if (!digitalRead(PIN_BUTTON)) {

これはnot的な意味です。

TRUE/FALSE(yes/no, 真/偽)という論理値と呼ばれるタイプの変数に使います。

例えばこう使います。

a = TRUE

b = !a

そうするとbにFALSEが入ります。

if文の例では「PIN_BUTTONではなかったら」という意味になります。

これだと???となってしまいますのでちょっと説明します。

まずはif文についてif(xxxxx){のxxxxxについては、TRUE/FALSEのどちらかが入ります。

これは1/0と読み替えてもいいです。なんでTRUE=1かって?それはそういう慣習です。

そしてif文はxxxxxがTRUE (1)なら、if文の中身を実行しますよ、という意味です。

続いて、digitalRead(PIN_BUTTON)ですが、これはスイッチの状態を取得しています。

スイッチが押されていたらdigitalRead(PIN_BUTTON)の値は0(=FALSE)となります。

しかしこれだと、スイッチが押されているときにif文の中身を実行したいのに実行してくれません。

そこで!で反転させてTRUEにしている、というわけです。

delay()

これは処理を待つための命令です。

例えば、delay(1000)で1000ミリ秒=1秒待ちます。

これは回路を扱うので現実世界の反応速度みたいなものを待つ必要があります。

そうでないと回路の動作が置いてきぼりになってプログラムだけ勝手に進んでおかしくなってしまいますので、適切にdelayを入れていきます。

インクリメント

led_status++と書いてある++の部分です。

これは+1する、という意味です。それなりに使います。

逆に–と書くデクリメントというのものあります。

こちらは逆に-1する、という意味です。

Serial.println()

これはデバッグ用です。

シリアル通信で()内に書いたことを送ってくれます。

とあるプログラムを作って意図通り動いているか確認するのって、外観からは難しいですよね?

そこで、プログラムのところどころに今の状態をシリアル通信で送ってくれることで、ちゃんと動いてるな、と確認できるわけです。

番外編: FastLED

これもデバッグ用に近いです。

パソコンから切り離して本番動作させているときはシリアル通信が見れません。

そんな時、動作状態をLEDで表現しておけばちゃんと動いているかが確認できます。

FastLED自体を理解するのがメンドイのですが、せっかく内蔵のLEDをいい感じに扱えるようにしてくれているので利用しない手はないかな、と思います。

理解せずとも↓のコードをコピペして必要なところ(Red)だけ書き換えて使えばよくわからないままでも使えます。

M5StampS3のデバッグ用にシンプルにしたサンプルです。

#define PIN_LED    21
#define NUM_LEDS   1
CRGB s3_led[NUM_LEDS];

void setup(){
    FastLED.addLeds<WS2812, PIN_LED, GRB>(s3_led, NUM_LEDS);
}
void loop(){
    s3_led[0] = CRGB::Red;    //このRedを他の色名に変えることで色を変えられます
    FastLED.show(); 
}

NUM_LEDSは定義せず1としてしまってもいいのですがFastLEDの慣習にならってdefineしておきます。

最初の方で変数名と値を定義し、LEDを格納する配列s3_ledを用意しています。

これはFastLEDに決められた形なので、使うLEDが1個でも配列を使います。

で、setup()内で「このLEDを使いますよ」とFastLEDさんに渡して、

loop()内で色を決めてFastLED.show()で実行しています。

色についてはRedのほか、Blue、GreenはもちろんAquaとかBrownとか色々なものが定義されているので使ってみてください。

Blackにしてみたら消灯しました。

最後に

いかがでしたでしょうか?

もし初心者の方もちょっとは使える気になってもらえたらうれしいです。

私も調べながら書いていたのですが、なんとなくできる気になりました。

言っても私も初心者なので間違い等あればご指摘いただけるとありがたいです。

ここまで読んでいただき、ありがとうございます。

コメント

タイトルとURLをコピーしました