Arduinoの関数を日本語で調べようとすると、たどり着くのは2010年前後で更新が止まったページか、英語の公式ドキュメントです。関数名と意味は分かっても、実際どれくらい時間がかかるのか、どちらを使えばいいのかまでは書かれていません。

この記事は、Arduinoの関数を分類から引ける一覧にしたうえで、UNO R3とUNO R4の実機で測った値を並べたものです。載せているのは、数字で確かめたものだけです。digitalWrite()は1回4.980マイクロ秒。millis()が1回で進むのは1.024mstone()を鳴らすと特定のピンのPWMが止まります

だいき現役の車載組み込みソフトエンジニア車載マイコンのソフトウェアを業務でC言語を使って開発しています。応用情報技術者、C言語プログラミング能力検定1級。この記事の数値は、手元のUNO R3とUNO R4 WiFiで実際に測ったものです。測定に使ったスケッチも全文載せています。
この記事で分かること

  • Arduinoの関数を分類から引ける(デジタルI/O、アナログI/O、時間、割り込み…)
  • analogWrite()が出しているのは電圧ではない(実測グラフつき)
  • 各関数が1回あたり何マイクロ秒かかるか(R3・R4の実測)
  • UNO R3とR4で挙動が変わる関数と、その中身

読了 約15UNO R3・UNO R4 WiFiで実測

この記事の使い方

分類から関数を探し、「使いどころ」で判断し、詳しく知りたいものだけリンク先へ進んでください。

関数はArduino公式のLanguage Referenceと同じ分類で並べています。公式ドキュメントと行き来しても迷わないようにするためです(公式は英語です)。

そのうえで、他のリファレンスに無い情報を2つ足しました。

実測値1回あたりの時間UNO R3とR4で1,000回呼び、空ループぶんを引いた平均です。「この関数は遅いのか」を数字で判断できます。
使い分けどちらを選ぶかdelaymillisのように似た関数が並ぶところで、どちらを使うべきかを書いています。
R3 / R4ボードによる違いUNO R4はマイコンが別物です。挙動が変わるものにだけ印を付けています。付いていないものは同じです。

Arduinoで書いているコードがそもそも何の言語なのかは、Arduinoの言語は何?C言語・C++との違いを現役組み込みエンジニアが解説で説明しています。

デジタルI/O|ピンをHIGH/LOWで扱う

ピンを出力か入力に決めて、HIGHかLOWを書く・読む。どのスケッチにも必ず出てくる3つです。
pinMode(pin, mode)
ピンを OUTPUTINPUTINPUT_PULLUP のどれかに設定する。setup()で1回だけ。書き忘れると入出力が動かない
digitalWrite(pin, value)R3 4.980 µs / R4 0.669 µs
出力に設定したピンを HIGH(5V)か LOW(0V)にする。LEDやリレーのオンオフ。速さが要るならレジスタを直接書く(下の実測)
digitalRead(pin)R3 3.324 µs / R4 1.127 µs
入力ピンが HIGHLOW かを読む。スイッチやセンサの出力を読む
動かないときに最初に見るのは pinMode
pinMode()を書かないと、ピンは入力のままです。digitalWrite()を呼んでもLEDは光らず、コンパイルは通るのでエラーも出ません。配線を疑う前にここを見てください。
digitalWrite と直接ポート操作を1,000回ずつ測る

pinMode(8, OUTPUT);

unsigned long start = micros();
for (uint16_t i = 0; i < 1000; i++) {
    digitalWrite(8, HIGH);
}
Serial.println((micros() - start) / 1000.0, 3);

/* 同じ動作をレジスタ直接で。D8=PORTBの0ビット目 */
/* AVR(UNO R3)だけ。R4では動かない */
start = micros();
for (uint16_t i = 0; i < 1000; i++) {
    PORTB |= _BV(PB0);
}
Serial.println((micros() - start) / 1000.0, 3);

digitalWrite が「遅い」と言われる理由|実測で89倍

digitalWrite()は、渡されたピン番号から毎回テーブルを引いて、どのレジスタのどのビットかを調べ、割り込みを止めてから書き込みます。安全な代わりに手間がかかります。

レジスタを直接書けば同じ動作が終わります。UNO R3で両方を1,000回ずつ呼んで測りました。

書き方 1回あたり(UNO R3)
digitalWrite(8, HIGH) 4.980 µs
PORTB |= _BV(PB0)(同じピンを直接操作) 0.056 µs
89倍の差がある
1回では5マイクロ秒の違いですが、1万回のループなら約50ミリ秒変わります。逆に言えば、数回のオンオフにdigitalWrite()を使うことは何の問題もありません。ループの中で何万回も叩くときだけ、この差が時間に表れます。

レジスタを直接触る書き方はArduino Lチカのやり方|digitalWriteからレジスタ操作まで練習問題9問で、同じLチカを両方の書き方で試せます。

INPUT_PULLUP は内蔵の抵抗を使う設定

スイッチを読むときINPUTのままだと、スイッチが開いている間のピンの電圧が定まらず、読むたびに値が変わります。INPUT_PULLUPにすると内部の抵抗でピンが5V側へ引き上げられ、スイッチを押したときだけLOWになります。外付けの抵抗が要りません。

この内蔵抵抗の値を、外付けの10kΩとの分圧から逆算して測りました。UNO R3で約37〜44kΩ(5回の測定)。ATmega328Pのデータシートは20〜50kΩと幅のある書き方をしていますが、その範囲に収まっています。同じ個体でも測るたびに数kΩ動くので、設計で当てにする値ではありません。

アナログI/O|電圧を読む・PWMを出す

読むほうは本物のアナログ電圧。書くほうは電圧ではありません。ここを取り違えると動きません。
analogRead(pin)R3 111.792 µs / R4 21.424 µs
アナログピンの電圧を数値で読む。R3は0〜1023(10bit)。可変抵抗・光センサ・温度センサ。他の関数より2桁遅いので、ループで連発すると時間を取られるR3は10bit固定。R4は analogReadResolution() で最大14bitに変えられる
analogWrite(pin, value)R3 8.268 µs / R4 25.121 µs
指定した比率でHIGHとLOWを繰り返す(PWM)。電圧を出す関数ではないLEDの明るさ、モータの速さ。R3で使えるのは3・5・6・9・10・11番ピンR3は0〜255の8bit固定。R4は analogWriteResolution() で12bitまで上げられる。R4のほうが3倍おそい
analogReference(type)
analogRead()の基準電圧を変える。小さい電圧を細かく読みたいとき選べる値がR3とR4で違う
電圧を読んで平均と振れ幅を出す/PWMを出す

long sum = 0;
int lo = 1023;
int hi = 0;

for (uint16_t i = 0; i < 200; i++) {
    int v = analogRead(A0);
    sum += v;
    if (v < lo) {
        lo = v;
    }
    if (v > hi) {
        hi = v;
    }
}

/* 平均電圧 */
double volt = (sum / 200.0) * 5.0 / 1023.0;

/* 振れ幅(LSB)。大きいならノイズを疑う */
Serial.println(hi - lo);

/* PWMを出す。128 でデューティ約50% */
analogWrite(3, 128);

analogWrite は電圧を出す関数ではない

出ているのはHIGHとLOWの繰り返しです。名前がanalogWriteなので「指定した電圧が出る」と読めますが、そうではありません。analogWrite(pin, 128)なら、およそ半分の時間だけ5Vになる波形が出続けます。

テスタで測ると中間の電圧に見えるのは、テスタが平均を表示しているからです。確かめるために、PWMの出力を抵抗とコンデンサで平滑して、平均電圧を実際に測りました。

analogWriteの値を変えたときの波形と、平滑した平均電圧の実測グラフ。値に比例して平均電圧が上がる
図1:左が出ている波形(塗った区間がHIGH)、右がUNO R3で実測した平均電圧。値に比例して上がる
analogWriteの値 実測した平均電圧 5V×値/255(理論値)
0 0.000 V 0.000 V
64 1.266 V 1.255 V
128 2.507 V 2.510 V
192 3.763 V 3.765 V
255 4.995 V 5.000 V

理論値との差は最大0.9%でした。値と平均電圧はきれいに比例します。ただし出ているのはあくまで5Vと0Vの繰り返しで、LEDが暗く見えるのも、モータがゆっくり回るのも、平均の電力が下がるからです。

本物のアナログ電圧が要るとき
オーディオのように滑らかな電圧が必要なら、この記事で使ったように抵抗とコンデンサでローパスフィルタを作るか、DAC(デジタル・アナログ変換器)を積んだボードを使います。UNO R3にDACはありません。

PWMの周波数はピンによって違う(R3)

UNO R3でPWMを出せるのは3・5・6・9・10・11番ピン(基板に「〜」が付いています)。このピンは内部で複数のタイマに割り当てられていて、担当するタイマが違うと周波数も違います。同じanalogWrite(128)を出して測りました。

ピン UNO R3 UNO R4
D3 493.10 Hz 491.16 Hz
D5 981.35 Hz 491.16 Hz

R3ではピンによって周波数が倍違います。モータが可聴域で唸る、LEDがちらついて見えるといった症状は、この違いで説明が付くことがあります。R4ではどちらも同じ491Hzで、この差はありません。

時間|待つ・測る

delay()は止まります。止めたくないならmillis()です。
delay(ms)
指定したミリ秒だけ待つ。待っている間に他のことをしないときだけ。その間ほかの処理は進まない(割り込みは動く)
delayMicroseconds(us)
マイクロ秒単位で待つ。センサの信号タイミングを作るとき。1µsの指定は効かない(下の実測)
millis()R3 2.008 µs / R4 0.249 µs
起動からの経過ミリ秒を返す。止めずに周期処理を作る。unsigned long約49.7日で0に戻る1回で進む時間がR3は1.024ms、R4は約1.000ms(下の実測)
micros()R3 3.928 µs / R4 2.041 µs
起動からの経過マイクロ秒を返す。短い時間を測る。約71.6分で0に戻る。R3では4µs刻み

delay と millis の使い分け

判断はこれだけ
待っている間に他のことをしないならdelay()、するならmillis()。LEDを1秒ごとに点滅させるだけならdelay()で足ります。点滅させながらボタンも見たいならmillis()です。

millis()で周期処理を書くときは、前回の時刻を覚えておいて引き算で判定します。

1秒ごとに処理する(delayで止めない書き方)

unsigned long last = 0;

void loop(void)
{
    unsigned long now = millis();

    if ((now - last) >= 1000) {
        last = now;
        /* ここに1秒ごとの処理 */
    }

    /* ここは止まらずに毎回まわる */
}

now >= last + 1000 ではなく (now - last) >= 1000 と書くのは、49.7日で0に戻ったときにも正しく動かすためです。引き算なら桁が一周しても差は正しく求まります。

周期処理そのものの練習はArduinoで一定周期の処理を作る|millisとタイマ割り込みの練習問題にまとめています。

millis() は1.000msずつ進まない(R3)

millis()の値が1つ進む実際の時間を測ったところ、UNO R3で1024.0マイクロ秒でした。1.000msではありません。

理由はArduinoのソースに書いてあります。AVR版のコア(wiring.c)では、時間を数えているタイマが一周するのが1024µsで、そのたびにmillis()を1進めています。

コアの定義 意味
MICROSECONDS_PER_TIMER0_OVERFLOW 1024 µs タイマが一周する実時間
MILLIS_INC 1 一周ごとに1進める
FRACT_INC / FRACT_MAX 3 / 125 端数を貯めて、約42回に1回だけ2進める

つまり1歩は1.024msですが、42回に1回2進む補正が入るので、長い目で見た平均は1.000msになります。数秒の周期処理で困ることはありませんが、1歩を1msとして積算すると少しずつずれることは知っておいてください。UNO R4では999.7〜1000.0µsで、この補正は要りません。

delayMicroseconds(1) は何もしない

指定値と実際の待ち時間を、50回ずつ呼んで測りました。

指定 UNO R3の実測 UNO R4の実測
1 µs 0.24 µs 2.14 µs
10 µs 10.00 µs 11.66 µs
100 µs 109.52 µs 105.64 µs
1000 µs 1007.52 µs 1045.68 µs

R3で1µsの指定が待ち時間にならないのは仕様です。コアのソースif (us <= 1) return;と書かれていて、1以下なら何もせずに返ります。コメントには「関数を呼ぶ手間だけで約1µs使うから」と理由も書かれています。

1µs前後の待ちが要る場面では、この関数を当てにしないのが正解です。なお100µsの指定だけ約1割長くなりましたが、原因は特定できていないため、実測値だけを載せます。

外部割り込み|待たずに拾う

ボタンやセンサの変化を、loop()の順番を待たずに拾う仕組みです。
attachInterrupt(num, fn, mode)
ピンの変化で指定した関数(ISR)を呼ぶ。短いパルスや、待っていられない入力を拾うR3は2番・3番ピンだけ。R4はD0〜D13の14本すべて(実測)
detachInterrupt(num)
割り込みを外す。対で書く
digitalPinToInterrupt(pin)
ピン番号を割り込み番号に変換する。番号を直接書かないために使う。ボードが変わっても動く
interrupts() / noInterrupts()
割り込み全体を許可・禁止する。禁止している間はmillis()も進まないので、必要な数行だけを挟む
割り込みで時刻だけ記録する(ISRの中は短くする)

volatile unsigned long isr_time_us = 0;
volatile bool isr_fired = false;

void on_rise(void)
{
    /* 記録だけ。Serial.print は呼ばない */
    isr_time_us = micros();
    isr_fired = true;
}

void setup(void)
{
    pinMode(2, INPUT);
    attachInterrupt(digitalPinToInterrupt(2), on_rise, RISING);
}

割り込みは「すぐ」ではない

ピンが変化してから割り込み関数の中に入るまでの時間を測りました。UNO R3で11.20マイクロ秒(20回中20回成功)。この値には信号を出すためのdigitalWrite()(4.98µs)が含まれるので、割り込みそのものの応答は約6.2µsです。UNO R4は5.00µsでした。

loop()の途中で割り込みが入り、ISRを実行して戻るまでの時間軸。応答は実測11.20マイクロ秒
図2:loop()の途中に割り込み、ISRを実行して続きへ戻る。ピンの変化からISRに入るまでが11.20µs(UNO R3の実測)

割り込みの中でやってはいけない3つ

ISRは短く書く

  • delay()を呼ばない。割り込みの中では時間を数えるタイマが進まず、戻ってこなくなります
  • Serial.print()を呼ばない。理由は下の実測を見てください
  • 共有する変数にvolatileを付ける。付けないと、コンパイラが「変わらない」と判断して古い値を使い続けることがあります

「ISRは短く」と言われても、どれくらいから問題になるのかは書かれていないことが多いので、測りました。2kHz(周期500µs)のパルスを200ms入れて、期待される400回のうち何回拾えるかを、ISRの中に入れる処理の長さを変えて調べています。

ISRの長さ 数えられた回数(期待400回) 取りこぼし
0 µs(数えるだけ) 401 回 0.2%
100 µs 401 回 0.2%
300 µs 401 回 0.2%
600 µs 268 回 33.3%
900 µs 201 回 50.0%
境目は「割り込みの周期」
周期500µsに対し、ISRが500µsを超えたところから落ち始めます。900µsでは1回おきに落として50%です。そしてSerial.print()で10文字送るコストは850µs(次の節の実測)。ISRの中で1回呼ぶだけで、この割り込みの3〜5割を取りこぼす計算になります。

割り込みを使った周期処理はタイマ割り込みの練習問題、消費電力と絡めた使い方はArduinoの低消費電力化ドリルで扱っています。

拡張I/O|音を出す・パルスを測る

音を出すtone()と、パルスの長さを測るpulseIn()。どちらもタイマを使うので、PWMと取り合いになります。
tone(pin, freq)
指定した周波数の矩形波を出す(圧電ブザーなど)。音を鳴らす。同時に鳴らせるのは1ピンだけR3では鳴らしている間、D3のPWMが止まる(下の実測)。R4では止まらない
noTone(pin)
tone()を止める。止めるまで鳴り続けるので対で書く
pulseIn(pin, value)
パルスの長さをマイクロ秒で返す。超音波センサの距離測定など。この記事のPWM周波数もこれで測っている
shiftOut() / shiftIn()
1ビットずつ送る・受ける。シフトレジスタでピンを増やすとき
pulseIn でPWMの周波数を測る/tone で鳴らす

pinMode(2, INPUT);
analogWrite(3, 128);
delay(50);

unsigned long high_us = pulseIn(2, HIGH, 100000UL);
unsigned long low_us  = pulseIn(2, LOW, 100000UL);

/* D3 なら 493.10 Hz */
double freq = 1000000.0 / (double)(high_us + low_us);
Serial.println(freq, 2);
analogWrite(3, 0);

/* 止めるまで鳴り続ける。noTone と対で */
tone(8, 440);
delay(500);
noTone(8);

tone() の周波数は高いほどずれる

指定 UNO R3の実測 UNO R4の実測
440 Hz 445.6 Hz(+1.28%) 442.7 Hz(+0.61%)
1000 Hz 1019.4 Hz(+1.94%) 1010.1 Hz(+1.01%)
4000 Hz 4219〜4329 Hz(+5.5〜8.2%) 4132.2 Hz(+3.31%)

どちらのボードも高い周波数ほど誤差が大きくなります。タイマの分周が整数なので、周波数が上がるほど刻みが粗くなるためです。4000HzのR3だけ幅で書いたのは、測るたびに+5.5%と+8.2%の間で変わったからです。R4は3回とも4132.2Hzで動きませんでした。音階を正確に出したい用途では、この誤差を見込んでおく必要があります。

tone() を鳴らすとPWMが止まる(R3)

UNO R3のtone()は内部でタイマを1つ占有します。そのタイマはanalogWrite()のPWMにも使われているため、音を鳴らしている間だけ特定のピンのPWMが止まります

D3とD5にanalogWrite(128)を出したまま、別のピン(D8)でtone()を鳴らして測りました。

ピン tone前 tone中 結果
D3 493.1 Hz 出力が止まった 同じタイマを使っている
D5 982.3 Hz 996.0 Hz 影響なし

出力そのものが止まります。周波数が変わるのではありません。「音を鳴らしながらLEDの明るさを変えたら、そのLEDだけ消えた」という現象は、これで説明が付きます。音と一緒にPWMを使うなら、D3以外のピンを選んでください。UNO R4では両方とも影響を受けませんでした。

通信|Serialとその仲間

動かない原因を探すとき、最初に使う道具です。ただし遅い。
項目 何をするか 使いどころ
Serial PCとの文字のやり取り 変数の中身を見る、動作の記録を残す
Wire(I2C) 2本の線で複数のセンサとつなぐ 温湿度センサ、液晶など
SPI 高速な同期通信 SDカード、ディスプレイなど

Serial.begin()で速度を決め、シリアルモニタ側も同じ速度にします。ここが食い違うと文字化けします。

シリアルを開いてから送る

Serial.begin(115200);

/* R4はUSB直結。開くまで待つ */
while (!Serial) {
    ;
}

/* F() で文字列をフラッシュへ置く */
Serial.println(F("start"));

Serial.print は850µsかかる

115200bpsで10文字を送るコストを測ったところ、UNO R3で850.0µs、UNO R4で868.5µsでした。10文字=100ビットぶんの送信時間(約868µs)とほぼ一致します。通信の速さそのものが上限になっているということです。

この数字が問題になる場所
1回850µsは、前の節で測った「ISRが600µsを超えると33%取りこぼす」の外側です。デバッグのつもりで割り込みの中にSerial.print()を1行入れると、拾いたかった信号を3〜5割落とします。ISRではフラグを立てるだけにして、表示はloop()側でやるのが定石です。

シリアル通信の使い方そのものはArduinoのシリアル通信入門|Serial.printとデータ受信の練習問題にまとめています。

数学・ビット・文字・乱数

計算そのものより、map()がR3で39µsかかることのほうが実務では問題になります。
分類 関数 ひとこと
数学 abs() constrain() map() max() min() pow() sq() sqrt() map()は範囲の変換。R3では39.460µsかかる(整数の割り算を含むため)。R4は0.657µsで60倍速い
ビットとバイト bit() bitRead() bitWrite() bitSet() bitClear() highByte() lowByte() レジスタを触り始めると毎回使う
文字 isDigit() isAlpha() isSpace() ほか 受信した文字の判定に使う
乱数 random() randomSeed() そのままだと毎回同じ並びになる。種を変える必要がある

map()がR3で40µs近くかかるのは意外に思われるかもしれませんが、8bitのマイコンには割り算の回路がありません。センサ値を毎回map()で変換するループでは、これが積み上がります。

ビット操作の考え方はC言語のビット演算|マスク・シフトの練習問題で扱っています。

読んだ値を0〜100に直す/ビットを立てて読む

/* 0〜1023 を 0〜100 へ直す */
int raw = analogRead(A0);
int pct = map(raw, 0, 1023, 0, 100);

/* 範囲外を丸める */
pct = constrain(pct, 0, 100);

/* 0ビット目を1にして、読み返す */
uint8_t flags = 0;
bitSet(flags, 0);
bool on = bitRead(flags, 0);

型・定数・修飾子

intの大きさがボードで変わります。割り込みと共有する変数にはvolatileが要ります。
項目 内容 注意
定数 HIGH / LOWINPUT / OUTPUT / INPUT_PULLUPLED_BUILTIN LED_BUILTINは基板上のLED(UNOでは13番)
整数 int long byte unsigned int ほか intの大きさがR3とR4で違う(下の節) |
小数 float double R3ではdoublefloatと同じ4バイト
修飾子 const static volatile volatileは割り込みと変数を共有するときに必須
ユーティリティ sizeof() PROGMEM PROGMEMはR3向け(RAMが2KBしかないため)

配列・構造体・ポインタといったC言語側の話はC言語の配列構造体組み込みでのポインタで扱っています。

手元のボードで型の大きさを確かめる

/* R3: 2 / R4: 4 */
Serial.println((int)sizeof(int));

/* R3: 4 / R4: 4 */
Serial.println((int)sizeof(long));

/* R3: 4 / R4: 8 */
Serial.println((int)sizeof(double));

/* R3: 2 / R4: 4 */
Serial.println((int)sizeof(void *));

UNO R3とUNO R4で違うところ

同じスケッチが両方で動くとは限りません。実測で違いが出た場所をまとめます。

UNO R3のマイコンはAVRのATmega328P(16MHz)、UNO R4はルネサス RA4M1(48MHz)です。互換性を保つよう作られていますが、中身は別物なので変わるところがあります。

速さ

関数 UNO R3 UNO R4
digitalWrite() 4.980 µs 0.669 µs 7.4倍速い
digitalRead() 3.324 µs 1.127 µs 2.9倍速い
analogRead() 111.792 µs 21.424 µs 5.2倍速い
analogWrite() 8.268 µs 25.121 µs 3倍おそい
map() 39.460 µs 0.657 µs 60倍速い
割り込みの応答 11.20 µs 5.00 µs 2.2倍速い

おおむねR4が速いのですが、analogWrite()だけはR4が3倍おそいという結果でした。PWMの設定を書き換えるたびに呼ぶ処理では、ここで時間を取られる可能性があります。

挙動

観点 UNO R3 UNO R4
PWMの周波数 D3=493 Hz/D5=981 Hz(ピンで違う) どちらも491 Hz
tone()中のPWM D3の出力が止まる 止まらない
割り込みに使えるピン D2・D3の2本 D0〜D13の14本
millis()の1歩 1.024 ms(42回に1回2進む補正) 約1.000 ms
delayMicroseconds(1) 何もしない 約2 µs待つ
analogReadの分解能 10bit固定 analogReadResolution()で最大14bit
analogWriteの分解能 8bit固定 analogWriteResolution()で12bitまで

型の大きさ(移植するときに問題になる)

UNO R3 UNO R4
char 1 バイト 1 バイト
int 2 バイト 4 バイト
long 4 バイト 4 バイト
float 4 バイト 4 バイト
double 4 バイト(floatと同じ) 8 バイト
void* 2 バイト 4 バイト

intに32768以上を入れるコードはR3では壊れ、R4では動きます。逆にintのサイズを2バイト前提で書いたコードは、R4で意図しない動きになります。移植するならintではなくint16_tint32_tで書くのが安全です。

R4だけコンパイルが通らないとき

名前がぶつかる・定数が無い
この記事の計測スケッチを書いたときに、R3では通るのにR4だけ落ちたものが2つありました。

  • PIN_ANALOG という変数名が使えない。R4のコアが同じ名前をマクロとして持っているためで、expected ')' before numeric constant というエラーになります。ピン番号を入れる変数には自分用の接頭辞を付けてください
  • NOT_AN_INTERRUPT が定義されていない(AVRのコアにはあります)。使うなら#ifndefで自分で定義しておきます

R3ではRAMが先に尽きる

UNO R3のRAMは2KBしかありません。AVRでは文字列リテラルがRAMに置かれるため、Serial.print("...")を並べるだけで足りなくなります。この記事の計測スケッチも、最初はRAMを3,542バイト(172%)使ってコンパイルが通りませんでした

Arduino IDEが出したエラー

最大2048バイトのRAMのうち、グローバル変数が3542バイト(172%)を使っていて、
ローカル変数で-1494バイト使うことができます。
data section exceeds available space in board

直し方はF()で文字列をフラッシュ側へ移すことです。

F() でフラッシュへ移す

/* RAMに置かれる */
Serial.println("digitalWrite の実測");

/* フラッシュに置かれる */
Serial.println(F("digitalWrite の実測"));

この置き換えだけで3,542バイト→254バイト(12.4%)まで下がりました。UNO R4はRAMが32KBあるので、この対処は要りません。R3向けのコードにPROGMEMF()が出てくるのは、この2KBという制約のためです。

この数字の測り方(同じことを手元で再現する)

ここに載せた数字は、次のスケッチと配線で測ったものです。同じものが手元にあれば再現できます。

測定は1つのスケッチにまとめてあります。使うのはジャンパ3本(D3とD2、D5とD4、D8とD7)と、ブレッドボードに組む3つの回路(分圧、RCローパス、プルアップ測定)です。あとはArduino IDEで書き込み、シリアルモニタを115200bpsで開くだけです。

測ったもの やり方
関数1回あたりの時間 1,000回呼んでmicros()で挟み、空ループぶんを引く
PWM周波数 出力を別のピンで受けてpulseIn()でHIGHとLOWの長さを測る
平均電圧 1kΩと10µFで平滑してanalogRead()で読む
割り込みの応答 出力をHIGHにする直前のmicros()と、ISRの中のmicros()の差
取りこぼし tone()で2kHzを入れ、ISRの中にdelayMicroseconds()で負荷を入れて数える
ISRの中でSerial.printを試さないでください
「ISRでSerialを使うと危ない」を確かめたくなりますが、AVRでは送信バッファが満杯のとき、割り込みを禁止したまま空くのを待ち続けてボードが固まります。この記事では代わりにdelayMicroseconds()で同じだけ時間を使い、原因である「ISRが長いこと」だけを再現しています。

確認

LEDを1秒ごとに点滅させながら、ボタンが押されたかどうかも見たい。delay()millis()のどちらを使いますか。

解答・解説を見る
millis()を使います。

解説:delay(1000)を書くと、その1秒の間はボタンを読むコードが動きません。前回の時刻を覚えておき、(now - last) >= 1000 で判定すれば、待っている間もループが回り続けます。

よくあるつまずき

動かないpinMode を書いていないコンパイルは通り、エラーも出ません。出力のつもりのピンが入力のままになっています。
値が変割り込みと共有する変数に volatile が無い割り込みで書き換えた値がloop()側に反映されません。
取りこぼす割り込みの中が長い周期を超えると落ち始めます。Serial.print1回(850µs)で3〜5割落とします。
桁あふれmillis() を足し算で比較している約49.7日で0に戻ります。引き算で比べれば問題になりません。
音が邪魔tone() でD3のPWMが止まるR3では同じタイマを使うため。音と一緒にPWMを使うならD3を避けます。
RAM不足R3で文字列を並べすぎているF()でフラッシュへ移します。R4では起きません。

まとめ

Arduinoの関数は、分類さえ分かれば公式ドキュメントからでも引けます。この記事で足したのは実際に測った数字です。

digitalWrite()は1回4.980µs、レジスタ直接なら0.056µsで89倍の差があります。analogRead()は111.792µsで、他の関数より2桁遅い。map()はR3で39.460µsかかります。どれも1回なら気にする値ではありませんが、ループの中で何万回も回ると時間に表れます。

analogWrite()が出しているのは電圧ではなく、HIGHの時間の割合です。平滑して測ると値に比例した電圧になりますが、ピンによって周波数が倍違い(R3のD3とD5)、tone()を鳴らすとD3の出力は止まります

割り込みは11.20µsで応答しますが、ISRが割り込みの周期を超えると取りこぼし始めます。Serial.print()は10文字で850µsかかります。ISRの中で1回呼べば3〜5割落とすという関係です。

そしてUNO R4は、ほとんどの関数でR3より速い一方、analogWrite()だけ3倍おそくintは2バイトから4バイトに変わり割り込みは2本から14本のピンで使えます。R3で当たり前だったF()やPWM周波数の差は、R4では出てきません。

数字が必要になったらこのページに戻ってきてください。測り方も載せてあるので、手元のボードで確かめ直すこともできます。