Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
逆引きArduino

はじめに

Arduinoでのプログラムに関して、こんなときにはどうすればいいのかという、いわゆる逆引きの観点で一覧を作成中です。内容を見るには、小項目をクリックしてください。リファレンスの目次と大差ないかもしれませんが…

Arduinoのバージョンについてはもはや追跡不能なので、特定のバージョンでは動作しない可能性もあります。

プログラムは断片だけのものがあり、これだけではコンパイルできないものもあります。注意してください。

Arduino ソフトウェア

Arduinoソフトウェアを入手する

Arduinoソフトウェアは、Arduinoのサイトダウンロードページから入手することができます。

Arduino Dueに対応したバージョンは、Uno用のソフトウェアをインストールした後、追加インストールします。

Arduinoソフトウェアをインストールする

Arduinoソフトウェアのインストールのページを参照してください。

ボードメニューにArduino Dueが表示されない/AVRアーキテクチャ以外のArduinoを使う

Arduino-1.6.2以降からは、AVR用のツールチェーン以外は、追加インストールを行う必要があります。Arduinoソフトウェアのインストールのページを参照してください。

"avrdude: stk500_getsync(): not in sync: resp=0x00" と表示されてマイコンボードへの書き込みに失敗する

(Arduino UNOの場合)デジタルピンの0番や1番にデバイスを接続している場合に表示されたときは、接続を解除してからマイコンボードへの書き込みを行ってください。

デジタルピンの0番と1番はシリアル通信に利用されていて、これは、USB経由での通信にも利用されています。このため、0番ピンや1番ピンにデバイスが接続されているとスケッチのアップロードに失敗することがあります。

他にもいろいろ理由があるようですので、事例があれば教えてください。

"avrdude: stk500_getsync(): not in sync: resp=0x30" と表示されてマイコンボードへの書き込みに失敗する

(Arduino UNOの場合)適切なシリアルポートが選択されていない可能性があります。Arduino ソフトウェアのメニューの、"ツール > シリアルポート" からArduinoが接続されているポートを選択してください。

Arduino用のドライバがインストールされていない場合は、そもそも選択肢に適切なシリアルポートが現れないので、インストールのページを参考にして、ドライバをインストールしてください。

この例は、このページをご覧いただいた方から情報をいただきました。ありがとうございました。

Arduinoソフトウェアのフォントの大きさを変えたい

Arduinoソフトウェアのメニューから、「ファイル > 環境設定」 を選択し、「エディタの文字の大きさ」を変更し、Arduinoソフトウェアを再起動します。

フォントの大きさだけでなく、フォントそのものを変えたい場合は、設定ファイルを変更します。

スケッチをコンパイルするときの詳細情報を知りたい

Arduinoソフトウェアのメニューから、「ファイル > 環境設定」 を選択し、「より詳細な情報を表示する」の「コンパイル」にチェックを入れた後、スケッチをコンパイルします。

デジタル入出力ピン

デジタル入出力ピンの入出力モードを変更する

Arduinoでデジタルピンを利用する場合は、入出力モードを正しく設定しておく必要があります。入出力モードの設定には、pinMode()を利用します。第二引数にINPUTを設定すると入力モードに、OUTPUTを設定すると出力モードになります。また、INPUT_PULLUPという値を設定すると、内蔵のプルアップ抵抗が有効になります。

const int inputPin = 10;
const int outputPin = 11;

pinMode(inputPin, INPUT);  /* inputPin を入力モードに設定する */
pinMode(outputPin, OUTPUT); /* outputPin を出力モードに設定する */

デジタル入出力ピンから入力する

pinMode()でデジタルピンを入力モードにしてから、digitalRead()で値を読み取ります。digitalRead()は、HIGHもしくはLOWを返します。アナログピンもデジタルピンとして利用できます。アナログの0番ピンはA0、1番ピンはA1、という風に変数が定義されています。

const int digitalPin = 10;
int val;

pinMode(digitalPin, INPUT);  /* pin を入力モードに設定する */
val = digitalRead(digitalPin); /* val には HIGH か LOW が代入される */

pinMode(A0, INPUT); /* A0 を入力モードに設定する */
val = digitalRead(A0); /* val には HIGH か LOW が代入される */

デジタル入出力ピンに出力する(HIGH/LOW)

pinMode()でデジタルピンを出力モードにしてから、digitalWrite()で値を出力します。出力できるのは、HIGHもしくはLOWです。

const int digitalPin = 10;

pinMode(digitalPin, OUTPUT); /* pin を出力モードに設定する */
digitalWrite(digitalPin, HIGH); /* pin にHIGHを出力する */

デジタル入出力ピンに出力する(PWM)

pinMode()でデジタルピンを出力モードにしてから、analogWrite()で値を出力します。これによりPWM出力を行うことができます。PWM出力することができるのは、(Arduino Unoの場合)3、5、6、9、10、11の6つのピンです。5番ピンと6番ピンに出力されるPWMの周波数は約977Hz、他のピンに出力されるPWMの周波数は約490Hzです。

const int digitalPin = 3;

pinMode(digitalPin, OUTPUT); /* pin を出力モードに設定する */
analogWrite(digitalPin, 128); /* デューティ比 128/255 のPWM出力 */

プルアップ抵抗を有効にする

pinMode()でデジタルピンを入力モードにしてから、digitalWrite()でHIGHを出力します。こうすることにより、内蔵の20kΩのプルアップ抵抗を有効にすることができます。

const int digitalPin = 10;

pinMode(digitalPin, INPUT); /* pin を入力モードに設定する */
digitalWrite(digitalPin, HIGH); /* pin にHIGHを出力する */

Arduino 1.0.1以降では、pinMode()だけでプルアップ抵抗を有効にすることができます。Arduino 1.0以前ではコンパイルすることはできません。

const int digitalPin = 10;

pinMode(digitalPin, INPUT_PULLUP); /* pin のプルアップ抵抗を有効にする */

押しボタンスイッチが押されたことを検出する

デジタルピンに押しボタンを接続して、その押しボタンが押されているかどうかを検出するには、デジタルピンの値を読み取ります。

利用する回路によってスケッチが異なるので、今回は、図に示すような回路を前提としたスケッチ例を記述します。

まず、プルアップ抵抗を接続します。この抵抗がないと、押しボタンが押されていないときに入力ピンの値が不定となります。Arduino(ATmega328P)には、プルアップ抵抗が内蔵されているので今回はそれを用います。この回路では、押しボタンを押していないときに入力ピンの値を読み取るとHIGHになり、押しボタンを押しているときに読み取るとLOWになります。

あとは、入力ピンの値を読み出すし、読み出した値によって必要な処理を行います。

以下に、押しボタンを押したときに内蔵のLEDを点灯させるスケッチの例を示します。押しボタンをデジタルの2番ピンに接続し、内蔵のLED(13番ピン)を制御します。

/* 押しボタンを接続するピン */
const int inputPin = 2;

void setup () {
  /* inputPinを入力モードにし内蔵プルアップ抵抗を有効にする */
  pinMode(inputPin, INPUT_PULLUP);
  
  /* LED_BUILTINを出力モードにする */
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop () {
  /* inputPinの値を読み取る */
  int buttonState = digitalRead(inputPin);
  
  /* 押しボタンが押されていたらLOWになる */
  if (buttonState == LOW) {
    /* LED_BUILTINをHIGHにする */
    digitalWrite(LED_BUILTIN, HIGH); 
  } else {
    /* LED_BUILTINをLOWにする */
    digitalWrite(LED_BUILTIN, LOW);
  }
}

デジタルピンの数が足りない

アナログピンをデジタルピンとして利用することができます。Arduino Unoの場合は、A0からA5をデジタルピンの14から19として利用することができます。14とか15という数字を指定しても、A0やA1という変数を指定しても問題ありません。

デジタル入出力を高速化する

Arduinoソフトウェアでは、デジタル入出力を簡単に利用できるようにするため、操作を抽象化しています。このため、速度を少し犠牲にしています。avr-gccでは、チップのレジスタを直接利用することもできます。詳細はこちらのページを見てください。ただし、Arduinoを利用する意味が少なくなるので、利用する際は本当に必要かを検討するのがいいと思います。

また、アナログ出力(PWM出力)ができるピンは、デジタル入出力の際にPWM出力をオフにする操作を行っているので、PWM出力ができないピンよりも少し速度が落ちています。詳細はこちらのページを見てください。

アナログ入力ピン

アナログピンから入力する

analogRead()でアナログピンにかかっている電圧を読み取ることができます。analogRead()が出力するのは、0から1023までの整数です。実際の電圧は、参照電圧により異なります。

(Arduino Unoの場合)参照電圧は、DEFAULT(5V)、INTERNAL(1.1V)、EXTERNAL(Arefピンにかけた電圧)の3種類から選ぶことができます。analogReference()で参照電圧を設定します。デフォルトは5Vです。それ以外の参照電圧を利用する場合は、analogRead()を呼ぶ前に設定します。すべてのアナログピンの参照電圧が変更されます。また、Arefピンにかける電圧は0Vから5Vの範囲とする必要があります。範囲外の電圧をかけるとArduinoが壊れる可能性があるので注意してください。

const int analogPin = 0;
int val;

analogReference(INTERNAL); /* 参照電圧を1.1Vに設定する */
val = analogRead(analogPin); /* valには0から1023までの値が代入される */

analogRead()が出力する値(val)と実際の電圧(V)との関係は、参照電圧をVrefとすると、以下の通りです。数式を見てわかるように、参照電圧の1023/1024までの値まで測定可能です。

V =val / 1024.0 * Vref

アナログピンはデジタルピンとして利用することもできるので、digitalRead()を使えば、デジタル信号を読み取ることもできます。

アナログ入力ピンをデジタル入出力ピンとして使う

アナログピンはデジタルピンとして利用することもできます。アナログの0番ピンはA0、1番ピンはA1…という風に変数が定義されているので、これらを使うことで、digitalRead()digitalWrite()を使うことができます。

通信インターフェイス

シリアル通信インターフェイスを利用する

Arduinoは、シリアル通信のインターフェイスを標準で持っています。

以下は、Arduino Unoの場合の話です。Arduino Unoでは、USBケーブルもしくはデジタルピンの0番(受信用)と1番(送信用)を使って通信することができます。

Arduino Unoでは、Serialというオブジェクトが既に定義されていて、このオブジェクトを利用します。Serial.setup()で初期化(通信速度の設定)を行います。その後は、available()はread()、write()を使うことで通信を行うことができます。詳細はリファレンスを参照してください。以下に、一番簡単な使い方の例を示します。

void setup() {
  Serial.begin(9600);
}

void loop() {
  int c;

  if (Serial.available() > 0) {
    c = Serial.read();

    Serial.write(c);
  }
}

Arduinoソフトウェアを接続しているときは、右上のSerial Monitorをクリックするとシリアルコンソールが現れます。シリアルコンソールの一番上は、テキストボックスになっていて、Arduinoに対してテキストを送信することができます。下側は、Arduinoから送信されたデータを表示する領域です。上記のプログラムを動作させると、テキストボックスに入力した文字がそのままコンソールに表示されます。

Arduino 1.0では、Serial.print()は、文字ではなくその文字に対応するASCIIコード(例えば、大文字の'A'は、10進数で65です)が表示されます。文字を表示したいときは、Serial.write()を使ってください。

最初にUSBケーブルもしくはデジタルピンの0番と1番と書きましたが、これらは、内部では共通のピンを利用しているようです。このため、デジタルピンの 0番と1番を他のデバイスなどに接続していると、スケッチのアップロードに失敗することがあるので、スケッチをアップロードする際は、デジタルピンの0番 と1番には何も接続しないようにしてください。

Arduino Leonarodではシリアルポートをオープンしてもスケッチを再起動しません。このため、シリアル通信を利用する際には、以下のコードを入れて、シリアルポートが開くのを待つ必要があります。

while (!Serial)

I2Cインターフェイスを利用する

Arduinoは、標準で付属するWireライブラリを利用することで、I2Cインターフェイスにマスターデバイスとしてもスレーブデバイスとしても参加することができます。リファレンスはこちら

Wireライブラリを使ってI2Cインターフェイスを利用する際のピンは決まっていて、多くのボードではSDA(Serial Data Line)はアナログピンの4番(Megaではデジタルピンの20番)、SCL(Serial Clock)はアナログピンの5番(Megaではデジタルピンの21番)に接続します。

Wireライブラリを使う(スケッチにincludeする)と、Wireというオブジェクトが定義されます。このオブジェクトを利用してI2Cインターフェイスに参加します。

マスターデバイスとして参加する

マスターデバイスとして参加するときに以下の操作を行います。

初期化

setup()の中で、begin()を実行することで、初期化を行います。マスターデバイスとして参加する場合は、begin()メソッドへの引数はありません。

void setup () {
  Wire.begin();
}

データ(コマンド)の送信

スレーブデバイスに対してデータ(コマンド)を送信するには、送信の開始(beginTransmission())を実行し、write()メソッドで送信データをキューイングし、最後に、endTransmission()で実際にデータを送信します。

  Wire.beginTransmission(address);  // 送信アドレスを指定する
  Wire.write(data1);
  Wire.write(data2);
  …
  Wire.endTransmission();

データの受信

スレーブデバイスからデータを受信するには、requestFrom()を実行した後、必要なバイト数のデータをread()を使って受信します。requestFrom()を実行後available()を使って、データが利用可能かを調べることもできます。

  Wire.requestFrom(address, num);  // address に対して numバイトのデータを要求する
  if (Wire.available()) {
    Wire.read(); // num回read()を実行する
    Wire.read();
    …
  }

SPIインターフェイスを利用する

Arduinoは、標準で付属するSIPライブラリを利用することで、SPIインターフェイスを利用することができます。リファレンスはこちら

詳細は現在執筆中です。

時間・時刻

時刻を取得する

Arduino単体では現在時刻を取得することはできません。ただし、Arduinoをリセットしてから経過した時間を取得することはできます。Arduinoはこのための関数を2種類用意しています。一つは、millis()でもう一つはmicros()です。名前が示す通り、milis()はミリ秒を、micros()はマイクロ秒を返却します。

unsigned long ms, us;

ms = millis();  // Arduino をリセットしてからの時間(ミリ秒)
us = micros();  // Arduino をリセットしてからの時間(マイクロ秒)

millis()が返す値の型は、unsigned long(32ビット)なので、2^32=4,294,967,296ミリ秒後に0に戻ります。

4294967296ミリ秒 = 4294967.296秒 ≒ 71583分 ≒ 1193時間 ≒ 49.7日です。49.7日以上連続して動作させる場合には注意してください。

時刻情報が必要な場合は、外付けのリアルタイムクロックなどを利用して情報を取得することができます。

一定時間待つ

何もしないで一定時間待つには、delay()delayMicroseconds()のどちらかを使うことで実現できます。delay()ではミリ秒、delayMicroseconds()は指定したマイクロ秒を待つことができます。

delay(100); // 100ミリ秒待つ
delayMicroseconds(100); // 100マイクロ秒待つ

delay()やdelayMicroseconds()は簡単に利用できますが、この関数で待っている間は何もできなくなることに注意してください。

文字列操作

特定の区切り文字で区切られた文字列を解析する

strtok()やstrtok_r()あるいはsscanf()を利用することができます。

strtok()、strtok_r()

C言語の標準ライブラリに、strtok()とstrtok_r()という関数が用意されています。この関数を使うと、特定の区切り文字で区切られた文字列を切り出すことができます。strtok()は、スレッドセーフではないので、複数のスレッドから同時に呼び出すような場合には利用できませんが、Arduinoは(今のところ)マルチスレッドではないのでどちらを使っても問題はないと思います。

strtok()とstrtok_r()の書式は以下のようになっています。

char *strtok(char *s, const char *delim)
char *strtok_r (char *s, const char *delim, char **last)

sは解析対象の文字列、delimは区切り文字列(文字列なので区切り文字を複数指定することができます)、lastはスレッドセーフにするために、次回の呼び出しのための情報を(ユーザプログラム側でに)保存しておく変数です。また、戻り値は、切り出した文字列へのポインタです。切り出した文字列がなくなるとNULLが返ります。

strtok()は、strtok_r()を呼び出すことで実現されています。具体的には、以下のようになっています。

static char *p;

char *
strtok(char *s, const char *delim)
{
    return strtok_r(s, delim, &p);
} 

strtok()とstrtok_r()では、1回目の呼び出しには解析対象文字列を指定しますが、(同一文字列を継続して解析する場合)2回目以降の呼び出しは解析対象の文字列は指定せずNULLポインタを指定します。以下に例を示します。

void setup() {
  char str[80];
  char *lexeme;
  
  Serial.begin(9600);
  
  strcpy(str, "abc:def:ghi");
  
  Serial.println(str);
  for(lexeme = strtok(str, ":"); lexeme; lexeme = strtok(NULL, ":")) { // 1回目はstrを指定。2回目以降はNULLを渡す。
    Serial.println(lexeme);
  }
  Serial.println(str); // もとのstrは破壊されている。
}

void loop() {
}
void setup() {
  char str[80];
  char *lexeme;
  char *last;
  
  Serial.begin(9600);
  
  strcpy(str, "abc:def:ghi");
  
  Serial.println(str);
  for(lexeme = strtok_r(str, ":", &last); lexeme; lexeme = strtok_r(NULL, ":", &last)) { // 1回目はstrを指定。2回目以降はNULLを渡す。

    Serial.println(lexeme);
  }
  Serial.println(str); // もとのstrは破壊されている。

}

void loop() {
}

strtok()とstrtok_r()は、解析対象の文字列を変更してしまうので注意が必要です。このため、文字列定数を指定することはできません。

sscanf()

文字列を解析する別の方法にsscanf()を使う方法もあります。sscanf()の形式は以下の通りです。

int sscanf(const char *s, const char *fmt, ...)

文字列や数値など複数の形式の値を一度の呼び出しで得ることができます。いろいろな使い方ができるので調べてみてください。

void setup() {
  char s1[10], s2[10];
  int i;

  Serial.begin(9600);
  
  sscanf("abc:def:100", "%*s:%*s:%d", s1, s2, &i);
  
  Serial.println(s1);
  Serial.println(s2);
  Serial.println(i);
}

void loop() {
}

整数を文字列に変換する

整数を文字列に変換するときには、sprintf()を使うことができます。sprintf()の形式は以下の通りです。

int sprintf(char *s, const char *fmt, ...)

数値だけではなく、他の文字列などもまとめて変換できるので便利です。こちらもいろいろな使い方ができるので調べてみてください。

int i = 10;
char s[16];

sprintf(s, "i = %d", i);

ただし、Arduino(というかavr-libc)のsprintf()は、浮動小数点数を扱うことはできません。そのかわり、avr-libcでは、dtostre()/dtostrf()という関数が用意されています。次のエントリも合わせてご覧ください。

小数(浮動小数点数)を文字列に変換する

Arduino Uno(というかavr-libc)のsprintf()は、浮動小数点数を扱うことはできません。一方、Arduino Dueのsprintf()は、浮動小数点を扱うことができます。

avr-libcでは浮動小数点数を文字列に変換するために、dtostre()/dtostrf()という関数が用意されています。dtostre()は指数表示、dtostrf()は小数表示です。よりよく使うと思われるdtostrf()の説明をリファレンス形式で示します。

名称

dtostrf()

説明

浮動小数点数を文字列に変換する。

書式

char *dtostrf(double val, signed char width, unsigned char prec, char *s)

引数

val 変換対象の数値
width 小数点や符号を含んだ、最小の表示幅(表示文字数)。負の数を指定すると左詰めとなる。
prec 小数点以下の表示幅(表示文字数)
s 変換後の文字列を格納するメモリ領域(格納するのに必要な十分な領域を用意する必要がある)

戻り値

変換後の文字列を格納する領域(引数で与えたsが返ってくる)。

使用例

void setup() {
  Serial.begin(9600);
  
  char s[16];
  char t[16];
  double val = -12.345678;
  
  dtostrf(val, 5, 1, s);
  Serial.println(s); 
  
  val = 12.345678;
  dtostrf(val, 5, 1, s);
  Serial.println(s); 
  
  sprintf(t, "Val = %s", dtostrf(val, 5, 10, s));
  Serial.println(t);
  
  dtostre(val, s, 3, DTOSTR_ALWAYS_SIGN|DTOSTR_PLUS_SIGN);
  Serial.println(s);
}

void loop() {
}

注意

変換後の文字列を格納する領域は呼び出し側で十分な領域を確保する必要がある。

バージョン

Arduino 1.8.0

文字列を数値に変換する

文字列を数値に変換するには以下のような関数が用意されています。

変換後の数値の形式 関数名 備考
int atoi() strtol()を呼び出して実現している。
long atol() strtol()を呼び出して実現している。
long strtol()
unsigned long strtoul()
double atof() strtod()を呼び出して実現している。
double strtod()

sprintf()で浮動小数点を使う

Arduino(というよりは、avr-libcの)sprintf()では、浮動小数点を利用することはできません。代わりに、dtostre()/dtostrf()という関数が用意されているのでそちらを使ってください。sprintf()と組み合わせて利用することもできます。詳細は小数(浮動小数点数)を文字列に変換するを見てください。

avr-libcのマニュアルを読むと使えるような記述がありますが、残念ながら、Arduino Unoのsprintf()では、%fを使うことができないようです。avr-libcのvfprintf()の中に以下のような処理が入っていました。Arduino Dueでは利用可能です。

if (c && strchr_P (PSTR("EFGefg"), c)) {
    (void) va_arg (ap, double);
    putc ('?', stream);
    continue;
}

このため、float型を文字列には変換できないので注意してください(使っても'?'が表示されるだけです)。

printf()を使ってシリアルコンソールに出力する

avr-libcのマニュアルを呼んでいたら fdev_setup_stream() という関数がありました。ネットをいろいろ調べてみるとprintf()を使ってシリアルコンソールに出力することができることがわかりました。参考サイトはこちら。ただし、スケッチのサイズが大きくなるので注意してください。

#include <stdio.h>

static FILE uartout;

static int uart_putchar (char c, FILE *stream) {
    if (Serial.write(c) > 0) {
      return 0;
    } else {
      return -1;
    }
}

void setup(void) {
  Serial.begin(9600);
  fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
  stdout = &uartout;

  int i = 10;
  char s[] = "abcd";
  printf("i = %d, s = %s\n", i, s);
}

void loop(void) {
}

割込み

ピンの状態が変わったときに関数を起動する

Arduino Unoでは、デジタルの2番ピン(割り込み番号0)と3番ピン(割り込み番号1)の状態の変化に応じて、割り込みを処理することができます。Arduino Megaでは、さらに、21番ピン(割り込み番号2)、20番ピン(割り込み番号3)、19番ピン(割り込み番号4)、18番ピン(割り込み番号5)を使うことができます。

検出できる状態の変化には、以下の4通りがあります。

状態の変化 attachInterrupt()の第3引数
1 ピンがLOWのとき LOW
2 ピンの値が変わったとき CHANGE
3 ピンがLOWからHIGHに変わったとき RISING
4 ピンがHIGHからLOWに変わったとき FALLING

attachInterrupt()という関数を用いて、割り込み時に起動する関数を登録します。登録できる関数は、戻り値も引数も持つことはできません。使い方は、リファレンスを参照してください。

割り込み処理を行っている間(登録した関数が実行されている間)は、さらなる割り込み処理が禁止されるため、割り込み処理をもとに動作しているdelay()やmillis()は動作しません(delay()の場合は、実際には指定した値より多く遅延が発生します。millis()で返される値は増えません)。また、登録した関数内で大域変数を操作するときは、その変数をvolatileと宣言しておかないと、コンパイラの最適化により意図した動作をしない可能性があります。

C言語/C++言語

C言語標準の関数を使う

Arduinoリファレンスを見ると通常のC言語実行系で利用できる関数があまり載っていません。でも、Arduino Unoには、avr-libcがリンクされるため、avr-libcで提供されている関数が利用できます。日本語訳を公開しているページはこちら。AVR用に追加されている関数もあります。

関数を定義する

C言語での関数の構造は以下のようになっています。

返却値の型 関数名 (仮引数の型 仮引数, …) {
 /* 関数の実体 */
}

返却値の型は、配列型以外のオブジェクトかvoid型です。仮引数の型と仮引数の組は、その関数に必要な数だけ書きます(0個以上です)。

プログラム内で同じような処理がたくさんあるときや、定番の処理を関数として定義しておくと便利です。例えば、アナログピンの電圧を返却する関数は以下のように書くことができます。参照電圧が5Vのときに、analogRead()の出力をミリボルト単位に変換します。

int analogReadMillivolt(int pin) { /* この関数は int型を返し、名前はanalogReadMillivolt、int型のpinという仮引数をを一つだけ取る。
    return map(analogRead(pin), 0, 1024, 0, 5000);  /* analogRead()で読み取った値を、ミリボルトに変換した値を返却する。 */
}

関数内の仮引数は、関数内で用意された領域に、その関数の呼び出し元で指定した実引数がコピーされたものです。呼び出された関数内で仮引数を変更しても呼び出した側の変数には影響を与えません。返却値が呼び出し側の関数に戻るだけです。ただし、ポインタを使うと間接的に値を変更することができます。

配列の要素数を取得する

配列の要素数をあとから変える可能性があるときには、配列の要素数を動的に取得できると便利です。sizeof演算子を利用することで配列の要素数を取得することができます。

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

int array_size = sizeof(array) / sizeof(array[0]);

sizeof(array)で、arrayが占めている総バイト数を得ることができます。また、sizeof(array[0])は、配列の要素1個分の大きさです。よって、総バイト数を要素1個当たりの大きさで割ると、配列の大きさ(要素数)を取得することができます。

多次元配列を利用する

C言語では多次元配列を利用することができます。例えばキャラクタ液晶画面などに表示する文字列をそのまま保持することができます。

以下は、16x2の配列を宣言する例です。

char display[2][16];
for(int i = 0; i < 2; i++) {
  for(int j = 0; j < 16; j++) {
    display[i][j] = 'a';
  }
}

イメージは以下の通りです。C言語では、配列の添え字は0から始まることに注意してください。

display[0][0] display[0][1] display[0][2] display[0][3] display[0][4] display[0][5] display[0][6] display[0][15]
display[1][0] display[1][1] display[1][2] display[1][3] display[1][4] display[1][5] display[1][6] display[0][15]

メモリ上は以下のように展開されます。行が優先されて展開されます。

display[0][0]
display[0][1]
display[0][15]
display[1][0]
display[1][1]
display[1][15]

ビット操作

変数内の特定のビットを1にする

ビット単位のOR演算子(|)を使うことで特定のビットを1にすることができます。

ビット単位のOR演算子は、オペランドのどちらかが1のとき、1になる演算子です。つまり、以下のようになります。

0 | 0 → 0
0 | 1 → 1
1 | 0 → 1
1 | 1 → 1

つまり、1ともとのビットとORをとると、そのビットを1にすることができます。一方で、変数は複数のビットから構成されているため、変数内の特定のビットを1にするということは、他のビットは元の値を保持しておく必要があります。上記の演算の左辺をもとの変数と考えると、0とORをとっている1行目と3行目は元の値となっていることがわかります。よって、変数内の特定のビットを1にするには、特定のビットだけを1にし、その他のビットは0の変数とORをとればいいことがわかります。

例えば、変数の第3ビット(右から4番目)を1にするには、変数が8ビットのときは、0b00001000 とORをとればいいということになります。0b00001000は、0b00000001を左に3個シフトした値なので、変数の第nビットを1にするには、1を左にn個シフトした、1<<n とORをとるということになります。

まとめると、変数valの第nビットを1にするには、以下のようにします。

val |= (1 << n);

Arduinoでは、bitSet()というマクロが以下のように定義されており、利用することが可能です。

#define bitSet(value, bit) ((value) |= (1UL << (bit)))

変数内の特定のビットを0にする

ビット単位のAND演算子(&)を使うことで特定のビットを0にすることができます。

ビット単位のAND演算子は、オペランドの両方が1のとき、1になる演算子です。つまり、以下のようになります。

0 | 0 → 0
0 | 1 → 0
1 | 0 → 0
1 | 1 → 1

つまり、0ともとのビットとANDをとると、そのビットを0にすることができます。一方で、変数は複数のビットから構成されているため、変数内の特定のビットを0にするということは、他のビットは元の値を保持しておく必要があります。上記の演算の左辺をもとの変数と考えると、1とANDをとっている2行目と4行目は元の値となっていることがわかります。よって、変数内の特定のビットを0にするには、特定のビットだけを0にし、その他のビットは1の変数とANDをとればいいことがわかります。

例えば、変数の第3ビット(右から4番目)を0にするには、変数が8ビットのときは、0b11110111 とANDをとればいいということになります。0b11110111は、0b00000001を左に3個シフトして全体を反転させた値なので、変数の第nビットを0にするには、1を左にn個シフトして反転させた、~(1<<n) とANDをとるということになります。

まとめると、変数valの第nビットを0にするには、以下のようにします。

val &= ~(1 << n);

Arduinoでは、bitClear()というマクロが以下のように定義されており、利用することが可能です。

#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))

変数内の特定のビットを反転する

ビット単位の排他OR演算子(^)を使うことで特定のビットを反転することができます。

ビット単位の排他OR演算子は、オペランドの両方のビットが異なるときに1になる演算子です。つまり以下のようになります。

0 | 0 → 0
0 | 1 → 1
1 | 0 → 1
1 | 1 → 0

つまり、1ともとのビットと排他ORをとると、そのビットを反転することができます。一方で、変数は複数のビットから構成されているため、変数内の特定のビットを反転するということは、他のビットは元の値を保持しておく必要があります。上記の演算の左辺をもとの変数と考えると、0と排他ORをとっている1行目と3行目は元の値となっていることがわかります。よって、変数内の特定のビットを反転するには、特定のビットだけを1にし、その他のビットは0の変数と排他ORをとればいいことがわかります。

例えば、変数の第3ビット(右から4番目)を反転するには、変数が8ビットのときは、0b00001000 と排他ORをとればいいということになります。0b00001000は、0b00000001を左に3個シフトした値なので、変数の第nビットを反転するには、1を左にn個シフトした、1<<n と排他ORをとるということになります。

まとめると、変数valの第nビットを反転するには、以下のようにします。

val ^= (1 << n);

Arduinoでは、bitSet()やbitClear()とは違い、上記を実現するマクロは定義されていないようです。

ESP-WROOM-32

Arduino core for the ESP32をインストールする



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2011 garretlab all rights reserved.
inserted by FC2 system