構文

このセクションでは、Arduino言語の構文などについて解説します

構文のサブセクション

基礎構文

setup()

この関数はプログラムが実行されたあとに一度だけ呼び出されます

この関数の中には pinMode など最初に一度だけ行えばよい処理を記述します

// 2番ピンをアウトプットとして指定する
void setup() {
    pinMode(2, OUTPUT);
}

loop()

この関数は setup() が呼び出された後に名前の通り繰り返し実行されます

プログラムの主な処理内容はこの関数の中に記述します

// 1秒おきに4番ピンに接続されたブザーを440Hzで鳴らす
void loop() {
    tone(4, 440);
    delay(1000);
    noTone(4);
    delay(1000);
}

変数

変数とは、値を格納できる箱のようなものです

実際には、変数を宣言することによって、指定された型の分だけメモリ空間を確保し、そのメモリ空間へのアドレスに対して変数名のエイリアスをつけることが行われています

宣言

Arduino言語では以下のようにして変数を宣言します

型 任意の変数名;

具体的には以下のようになります

// int型のvariableという変数を宣言する
int variable;

同一の型であれば複数の変数を同時に宣言することも可能です

// int型の first, second, thirdの3つの変数を一行で宣言する
int first, second, third;

変数の宣言時に指定する型によって格納可能な値の大きさが変わります
もっともよく使う int 型であれば、基本的には-2の15乗から、2の15乗-1まで格納することができます

ものコンでは、符号付整数を格納できる int や、真偽値を格納できる bool をよく使います
某H先生はint型のことを実数型と教えてきましたが、それは間違っているので注意してください

intbool 以外にも様々な型がArduino言語には存在します
公式リファレンスの型一覧
これらの型はケースバイケースで使い分けます

値の代入

変数には = を用いて値を格納することができます
変数に値を格納することを、代入といいます

// int型の変数variableに1を代入する
int variable;

variable = 1;

また、変数の宣言と同時に値を代入することもできます

// int型のvarという変数を宣言し、1を代入
int var = 1;

変数を用いた演算

変数は式の一部に用いることができます

int one = 1;
int two = 2;

// oneとtwoの足し算の結果をsumに代入
int sum = one + two;

スコープ

宣言した変数や関数が有効な範囲のことを、スコープと呼びます

int max = 123;

void setup() {
    // ここでも max は使える
    int foo = 0;
}

// ここでは foo は使えない

void loop() {
    // ここでも max は使える
    // ここでは foo は使えない
}

このように、変数には宣言したブロック( {} で囲まれた部分)より外では使うことができないというルールがあります

この使用できる範囲のことをスコープと呼びます

また、同一のスコープの中では同じ名前を変数につけることはできません

int hoge = 1;

void setup(){
    int hoge = 3; // ここで上書きされてしまう
}

演算子

演算子を用いることで様々な演算を行うことができます

Arduino言語には様々な演算子がありますが、このセクションでは算術演算子、比較演算子、論理演算子と一部の複合演算子の説明を行います

算術演算子

数値を計算することのできる演算子です

算術演算子例の演算結果意味
+1 + 23足し算
-2 - 11引き算
*2 * 24掛け算
/10 / 52割り算
%3 % 21割り算の余り
=a = 5aは5代入

複合演算子

変数に対して代入と計算を同時に行うことのできる演算子です
複合演算子を使うことでプログラムを簡潔に記述することができます

複合演算子意味
++a++a = a + 1
a–a = a - 1
+=a += 2a = a + 2
-=a -= 2a = a - 2
*=a *= 2a = a * 2
/=a /= 2a = a / 2
%=a %= 2a = a % 2

++ (インクリメント)と -- (デクリメント)は ++a のように、変数の前に置くこともできますが、意味が変わります

int a = 0;
int b = 1;

a = b++; // a = 1, b = 2 となり、代入=>インクリメント となる

a = ++b; // a = 2, b = 2 となり、インクリメント=>代入 となる

上記の例のように、インクリメントやデクリメントをした変数の値をほかの変数に代入する場合に、代入結果が変わることに注意してください

比較演算子

二つの値を比較することができる演算子です

主に if文 で使用します

比較演算子意味
==a == baとbが等しいか
!=a != baとbが等しくないか
<a < baはb未満か
>a > baはbより大きいか
<=a <= baはb以下か
>=a >= baはb以上か

演算の結果は bool で返ってくるので変数に代入することも可能です

bool var = a == b;

論理演算子

真偽値(boolean)を扱うことができる演算子です
論理演算を行います

複数の条件を組み合わせた分岐処理を作成したいときなどに使用します

論理演算子意味
!!aNOT
&&a && bAND
||a || bOR
bool t = true;
bool f = false;

bool not = !t; // false
bool and = t && f; // false
bool or = t || f; // true

また、これらの演算子を用いた式は () でまとめることができます

bool exam = (true && false) || (false || true); // true
int n = 5 * (9 + 1); // 10

条件分岐

条件分岐文は、その名の通り、ある条件を評価し、処理を分岐させる文です

例えば、センサーの値や変数の値によって処理を変えたいときに使用します

if文

与えられた条件によって処理内容を変える文です

条件は bool 型で与えます
各種スイッチなどの入力値を与えたり、 比較演算子論理演算子 と入力値を組み合わせたりして条件を与えます

else if 句によって一つ目の条件に合致しない場合、別の条件を与えて分岐させることもできます
else if 句は複数回連続して記述することが可能です

また、else 句によってどの条件にも合致しない場合の処理を記述することもできます

if (a != b) {
    // aがbと等しくない場合の処理
}

if (a == b) {
    // aがbと等しい場合の処理
} else {
    // aがbと等しくない場合の処理
}

if (a == b) {
    // aがbと等しい場合の処理
} else if (a < b) {
    // aがbと等しくないかつ、aがbより小さい場合の処理
} else if (a != 42) {
    // aがbと等しくないかつ、aがbより小さくなく、aが42ではない場合の処理
} else {
    // 上の3つの条件すべてに当てはまらない場合の処理
}

if (a == b && a != c) {
    // aがbと等しいかつ、 aがcと等しくない場合の処理
}

switch文

与えられた変数がどの値かによって処理を変える文です

if文 は条件で分岐するのに対し、 switch 文 は一つの変数がどの値かという点で処理を分岐します

case 句の後に分岐させる場合の定数を書きます

処理の終わりには必ず break 文を書きます
break 文がない場合、次の break 文までに記述されている処理すべてが行われます

default 句の後にはどの条件にも当てはまらない場合に実行される内容を書きます

int x = 1;

switch (x) {
    case 1:
        // xが1の時の処理
        break;
    case 2:
        // xが2の時の処理
        break;
    case 3:
    case 4:
        // x が3か4の時の処理
        break;
    default:
        // どの条件にも当てはまらないときの処理
        break;
}

反復処理

同じような処理を繰り返して実行するようなときには繰り返し文を使用します

繰り返し文を使用することで、プログラムの行数を削減し、プログラムを記述する手間を削減することができます。
また、簡潔なプログラムを書くことにもつながります

for文

任意の処理を任意の回数繰り返して実行することができる文です

繰り返しの条件は文頭で宣言する変数によって行います
この変数をfor文の中で使用することもできます

break 文が処理中にあった場合、ループを強制的に抜け出します
continue 文が処理中にあった場合には、 continue 文以降の処理を一度スキップしてループを続けます

for (変数の初期化; ループ継続条件; 変数の増加もしくは減少処理) {
    // 繰り返す処理
}

// iが0から9まで10回繰り返す
for (int i = 0; i <= 9; i++) {
    // 10回繰り返される処理
}

while文

任意の処理を条件が真になるまで繰り返して実行する文です
条件は if文 と同じように指定します

break 文が処理中にあった場合、ループを強制的に抜け出します
continue 文が処理中にあった場合には、 continue 文以降の処理を一度スキップしてループを続けます

// 22番ピンのスイッチの値がHIGHの間何かしらの処理を行う

const int SWITCH_PIN = 22;

while (digitalRead(SWITCH_PIN) == HIGH) {
    // 何かしらの処理
}

for-each文

for-each文は for文 を拡張した構文です

配列の要素をひとつづつ取り出して、その値を処理したいときに使用します

for(配列の値の型 内部変数名: 配列名) {
    // 処理
}

通常の for文 でそのような処理を記述する場合、以下の例のように、配列の長さを事前に知っている必要があります

int length = 5;
int array[length] = {0, 1, 2, 3, 4};

for(int i = 0; i < length; i++) {
    // iがlengthを超えた場合バグが発生する
    tone(4, array[i] * 100);
    delay(1000);
    noTone(4);
}

上の例を for-each文を利用して記述した例を下に示します

int length = 5;
// lengthはここしか使用しない
int array[length] = {0, 1, 2, 3, 4};

for(int i: array) {
    tone(4, array[i] * 100);
    delay(1000);
    noTone(4);
}

この構文は主にピンの初期化などに有効です

int OUTPUT_PINS = {4, 5, 6, 12, 13, 14, 15};

for(int i: OUTPUT_PINS) {
    pinMode(i, OUTPUT);
}

関数

プログラムを書いているときに、同じ処理を何度も別の場所で行うことがよくあります
例えば、ステッピングモーターを回すなどです

処理の行数が少なかったり、使用頻度が少ないものであれば、コピーペーストでもよいかもしれません
しかし、行数が多かったり、何度も行う処理をコピーペーストで書いてしまうと長く、読みづらいプログラムになってしまうので、関数としてまとめて宣言し、それを呼び出すようにします

また、動作が似ている処理を抽象化して、関数にすることでコードを書く手間を大幅に削減できます
7セグメントを点灯させる際に、各数字ごとに別の関数を用意する必要はありません
どの数字を点灯するかを引数に取り、数値によって動作を変えればよいのです

宣言

返り値の型 関数名(引数の型 仮引数) {
    処理
}

返り値とは処理の結果の値のことです
例えば、整数の足し算をする関数なら int 型を指定します
また、返り値がない場合(処理だけ行う)ときは void 型を指定します

引数は、関数の動作に必要な変数のことです
引数がいらない場合は省略できます
, で区切って複数個指定することもできます

// 整数同士の足し算を行う関数
int add(int a, int b) {
    return a + b; // return句で返り値を返すことができる
}

// 呼び出せる
int i = add(5, 10);
// クロック書き込みを行う関数
void clock() {
    digitalWrite(5, HIGH);
    delay(5);
    digitalWrite(5, LOW);
}

また、名前が同じでも引数が違う複数個の関数を宣言する(オーバーロード)こともできます

// 5msで固定のクロック
void clock() {
    digitalWrite(5, 1);
    delay(5);
    digitalWrite(5, 0);
}

// 秒数を指定できるバージョン
void clock(int ms) {
    digitalWrite(5, 1);
    delay(ms);
    digitalWrite(5, 0);
}

スコープ

関数にも変数と同様にスコープの概念があるので、宣言の際には気を付けましょう

配列

配列は値を複数格納し、ひとまとめにすることができます
配列は、変数を連続的にしたものと考えてください
変数が電車の一車両、配列が電車全体と思ってください

実際には、指定された型のサイズ*長さ分だけのメモリ空間をメモリ上に確保します
値の読み書きを行う際には、その空間の先頭アドレスに型のサイズ*インデックス文を加算したアドレスを参照します

宣言

宣言の際には、型名、配列名、 [] の中に配列の長さ(何個値を格納するか)を指定して宣言します

型名 配列名[長さ];

宣言と同時に値を代入する場合は長さの指定を省略することもできます

int array[] = {1, 2}; // 長さは2

値の代入には通常の変数と同様に = を使用します
{} で囲み、 , で値を区切ります

int array[3] = {0, 1, 2};

配列内の値がすべて同じ場合はこのように代入することも可能です

// すべて0の場合
int array[5] = {0};

また、宣言と代入を別々に行うこともできます

インデックスを指定することで代入ができますが、インデックスは0から始まるので注意してください

int array[3];

array[0] = 1;
//    ↑ インデックス
array[1] = 7;
array[2] = 5;

値を取り出す際も同じようにインデックスを指定します

int array[5] = {1, 2, 3, 4, 5};

// 0番目の値を取り出す(値は1)
int first_num_of_array = array[0];

二次元配列

配列の中に配列を代入をすることもできます

変数を点、配列をx方向のみの一次元と捉えたうえで、配列を値に持つ配列はx-y平面として捉えることができるので、二次元配列と呼びます

宣言規則は通常の配列と同じですが、変数名の後に [] を2つつける必要があります

// 最深部の値の型が int、外側の配列の長さ4、内側の長さ5の二次元配列
int two_dim_array[4][5] = {
    {1, 1, 34, 0, 12},
    {128, 1, 1, 0, 3},
    {0, 134, 2, 5, 0},
    {1, 8, 0, 1, 15}
};

二次元配列は 7セグメント や、 ステッピングモーター の表示・回転パターンを表現するためによく使用します

定数

定数とは、一度しか代入できない変数のことです

例えば7セグメントの点灯パターンやピン番号など、プログラム実行中に書き換えることのない変数(コンパイル時に値が決まっている)は、頭に constexpr を付けることで、再代入を防ぐことができます

constexpr int PIN_NUM = 0;
PIN_NUM = 10;  // 再代入のため、エラーが発生する

また、ファイルの最上部に #define 定数名 値 と記述することでも定数宣言が可能です(マクロ置換)

#define PI 3.14

float ans = 5.0 * 5.0 * PI;

ただし、#defineは単なる文字列の置き換えであるということに注意する必要があります
基本的にはconstexprを使うようにしましょう

static ローカル変数

static修飾子は、関数、変数の宣言時につけることによって、様々な効果を変数に付与することができます

ただし、ほとんどの効果はものづくりコンテストレベルでは使用しないので、 このページでは、 static ローカル変数についてのみ解説します

宣言

{
    static int var = 0;
}

ブロック内(if, ループ, 関数宣言, etc…)で static をつけて変数を宣言することで、 static ローカル変数として宣言することができます

通常、ブロック内で内容を保存するような変数(カウンターなど)を宣言するときには、以下のように、スコープ外で変数を宣言することが多いでしょう

// 通常の while ループのカウント
int count = 0;
while(digitalRead(22) == HIGH) {
    count++;
    delay(1000);
    printf("%d", count);
}

static 修飾子を使うことで、以下の例のようにすることができます

while(digitalRead(22) == HIGH) {
    static int count = 0;
    count++;
    delay(1000);
    printf("%d", count);
}

特徴

static ローカル変数は以下の特徴があります

  1. 再宣言されない
  2. 内容が保存される
  3. スコープ外からは参照できない

再宣言されない

static ローカル変数は一度宣言された後、再宣言されません

while(digitalRead(22) == HIGH) {
    int count = 0;
    count++;
    delay(1000);
    printf("%d", count);
}

上で出した例のcountを通常の変数にした例です

この場合、ループが何度も回るたびに、countは0として再宣言されてしまいます

static ローカル変数にすることで、再代入を防ぐことができます

内容が保存される

staticをつけることで内容が保存されます

一度宣言された static ローカル変数は内容が保存され続けます

while(digitalRead(22) == HIGH) {
    static int count = 0;
    count++;
    delay(1000);
    printf("%d", count);
}

上記の例では、countは最初に0が代入された後、+1されていきます

スコープ外からは参照できない

// 通常の while ループのカウント
int count = 0;
while(digitalRead(22) == HIGH) {
    count++;
    delay(1000);
    printf("%d", count);
}

count += 100; // 意図しないスコープから変数にアクセスできてしまう

通常の宣言では上記のような問題が起こりますが、 static ローカル変数にすることでそれを防ぐことができます