Arduino Lチカのやり方|digitalWriteからレジスタ操作まで練習問題7問
この記事では、Arduino Uno R3でLEDを点滅させる「Lチカ」を練習します。この記事を終えると、内蔵LEDを点滅させられ、D13・PB5・レジスタのつながりを自分の言葉で説明できるようになります。
LチカはArduino入門でよく出てくる題材です。けれども、LEDを光らせて終わりにするのはもったいありません。組込みソフトでは、出力したいピンを設定し、必要なビットだけを0と1に変える処理を繰り返し書きます。
この回は、まずpinMode()とdigitalWrite()で動作の流れをつかみます。次に、同じD13の内蔵LEDをDDRBとPORTBのレジスタ操作で点滅させます。C言語ドリルで学んだビット演算が、実機の出力ポートを動かす処理になることを確かめましょう。
<自己紹介>
筆者は現役の車載組込みソフトエンジニアです。Arduinoでもレジスタ設定を触り、実務のマイコン設定を理解する助けにしてきました。このLチカでは、最初の一歩から「なぜ光るのか」まで確認していきます。
目次
この回で学ぶこと
Lチカは、LEDを点滅させるだけの練習です。ただし中では、組込みソフトの基本動作をまとめて使っています。
pinMode()で、ピンを入力ではなく出力に設定するdigitalWrite()で、出力をHIGHとLOWに切り替える- Uno R3のD13はATmega328PのPB5に対応する
DDRBで出力設定、PORTBで出力値を操作する
進める順番はArduinoの書き方を理解する → ピン番号をマイコンの端子名に置き換える → レジスタの1ビットだけを操作するです。途中で分からなくなったら、この3段階のどこにいるかへ戻ってください。
Arduino Uno R3では、基板上の「L」と書かれた内蔵LEDがD13につながっています。内蔵LEDだけを使うなら外部配線は不要です。外付けLEDを使う場合は、必ず抵抗を直列に入れてください。
本記事のAPI版・レジスタ版は、Arduino Uno(ATmega328P)向けにPlatformIOでコンパイル確認しました。実機の点灯確認と動画はまだ追加していないため、手元のUno R3で実行結果を確認しながら進めてください。
準備するもの
| 使うもの | 用途 |
|---|---|
| Arduino Uno R3(またはATmega328P搭載のR3互換ボード) | ATmega328PのDDRB・PORTBを使うため。本記事はUno R4対象外 |
| USBケーブル | PCからスケッチを書き込む |
| LED・抵抗・ジャンパ線(任意) | D13以外のピンでもLEDを光らせたい場合 |
最初は内蔵LEDだけを使う方法で進めてください。外部配線は不要です。外付けLEDをD13につなぐ場合は、図のように220〜330Ω程度の抵抗を直列に入れ、LEDの向きを確認してください。

環境構築がまだの場合は、Arduino IDEのダウンロード〜インストール完全ガイドまたはVS Code+PlatformIOで作るArduino開発環境を先に確認してください。
まずArduino APIでLEDを点滅させる
最初はArduinoの書き方で、D13を出力にしてからHIGHとLOWを1秒ごとに切り替えます。Arduino公式でも、pinMode()でピンの動作を設定し、digitalWrite()でHIGHまたはLOWを書き込む流れが示されています。
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
LED_BUILTINは、ボードに用意された内蔵LEDのピン番号を表す定数です。Uno R3ではD13を指します。delay(1000)は1000ms、つまり1秒待つ処理です。
| 場所 | このコードでしていること |
|---|---|
setup() |
最初に1回だけ、内蔵LEDのピンを出力に設定する |
loop()前半 |
HIGHを書き込み、LEDを点灯側へ切り替えて1秒待つ |
loop()後半 |
LOWを書き込み、LEDを消灯側へ切り替えて1秒待つ。ここまで終わると再びloop()の先頭へ戻る |
この段階では、まず「出力にする」「HIGHで点灯側へ切り替える」「LOWで消灯側へ切り替える」という流れを確認できれば十分です。pinMode()の詳しい役割はArduino公式リファレンスでも確認できます。
次にレジスタを直接触り同じLEDを動かす
ここからは同じD13のLEDを、DDRBやPORTBといったレジスタで操作します。ただし、いきなりDDRBと書いても「それは何?どこから出てきた名前?」となるはず。レジスタ名は暗記するものではなく、データシートから読み取るものだからです。
ここで確認するのが以下データシートになります
「Arduino UNO R3のデータシート」と、「ATmega328Pのデータシート」の2冊を読んで、D13に紐づくレジスタまでたどります。
DDB5とPORTB5にある「5」は、このPB5の5番を表しています。
データシートからレジスタを読み解く
たどる順番は、①Arduino UNO R3のデータシートで「ボードで使用するマイコン」と「D13と紐づくレジスタ」を確かめ、②ATmega328Pのデータシートで「そのレジスタ」を読む、の2段です。
① まずArduino UNO R3のデータシートで、D13に対応するレジスタを確認
手元のボードの正体が書いてあるのは、Arduino UNO R3の公式データシート(SKU: A000066)です。冒頭の「Description」に、こうあります。
The Arduino® UNO R3 is the perfect board to get familiar with electronics and coding. This versatile development board is equipped with the well-known ATmega328P and the ATMega 16U2 Processor.
(訳:Arduino UNO R3は、電子工作とプログラミングに親しむのに最適なボードです。この多用途な開発ボードは、よく知られたATmega328Pと、ATMega 16U2プロセッサを搭載しています。)出典:Arduino® UNO R3 Datasheet(SKU: A000066)「Description」p.1 より原文引用(© Arduino S.r.l.)
つまり、このボードでピンを制御するとは、頭脳であるATmega328Pの入出力を制御することです。だからDDRBのようなレジスタ名は、このArduinoのデータシートには出てきません。Arduinoのデータシートはボードの説明書で、ピン配置・電源・定格までを扱い、レジスタの中身はチップ側のデータシートに委ねているからです。
そのうえで、D13がチップのどの端子につながるかを対応させます。同じデータシートのピン配置図(Pinout)を見ると、デジタルピンD8〜D13がそれぞれPort BのPB0〜PB5に対応し、D13はSCK、そして内蔵LED(LED_BUILTIN)もPB5だと図示されています。

LED_BUILTIN)=PB5が読み取れる。出典:Arduino® UNO R3 Datasheet(SKU: A000066)「5 Connector Pinouts」より該当部分を引用(© Arduino S.r.l.)| Arduinoのピン | マイコンの端子(Port B) |
|---|---|
| D8 | PB0(Port Bの0番ビット) |
| D9 | PB1 |
| D10 | PB2 |
| D11 | PB3 |
| D12 | PB4 |
| D13 | PB5(Port Bの5番ビット) |
この表から、D13=PB5=Port Bの5番ビットだと分かります。ここがレジスタ操作の起点です。
② 次にATmega328Pのデータシートで、Port Bのレジスタとビット構成を見る
PB5はATmega328Pの端子なので、レジスタの中身はATmega328Pのデータシート(Microchip、文書番号7810D–AVR)で確認します。「13. I/O-Ports」章の冒頭(13.1 Overview、p.58)に、ポートごとに3つのレジスタがあることが次のように原文で書かれています(英文はデータシートからの原文引用です)。
Three I/O memory address locations are allocated for each port, one each for the data register – PORTx, data direction register – DDRx, and the port input pins – PINx.
(訳:各ポートには3つのI/Oメモリアドレスが割り当てられ、データレジスタPORTx、データ方向レジスタDDRx、ポート入力ピンPINxに1つずつ対応する。)出典:ATmega328P Datasheet(Microchip, 7810D–AVR)13.1 Overview, p.58 より原文引用(© Microchip Technology)
この PORTx・DDRx・PINx が、Port Bではそれぞれ PORTB・DDRB・PINB になります。実際のレジスタ定義は同じデータシートの「13.4 Register Description」(p.72)にあり、次のようにどれも8ビットで、bit7〜bit0が端子PB7〜PB0に1対1で対応します。

下の表は、この定義からPB5=5番ビット(DDB5・PORTB5・PINB5)だけを抜き出して並べたものです(PB5の列に注目してください)。
| レジスタ | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
|---|---|---|---|---|---|---|---|---|
DDRB |
DDB7 | DDB6 | DDB5 | DDB4 | DDB3 | DDB2 | DDB1 | DDB0 |
PORTB |
PORTB7 | PORTB6 | PORTB5 | PORTB4 | PORTB3 | PORTB2 | PORTB1 | PORTB0 |
PINB |
PINB7 | PINB6 | PINB5 | PINB4 | PINB3 | PINB2 | PINB1 | PINB0 |
PB5を動かしたいので、どのレジスタも5番ビット(DDB5・PORTB5・PINB5)を触ればよい、と読み取れます。DDB5やPORTB5は「Port Bの5番ビット」を指す名前で、Arduino AVRの環境では番号(=5)としてそのまま使えます。
③ 各レジスタの役割を確認する
3つのレジスタは役割が違います。データシートの「13.2.1 Configuring the Pin」(p.59)に、方向を決めるDDxn(=DDRxのビット)と、出力値を決めるPORTxnの働きが次のように原文で書かれています(英文は原文引用です)。
The DDxn bit in the DDRx register selects the direction of this pin. If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.
(訳:DDRxレジスタのDDxnビットは、そのピンの方向を選ぶ。DDxnに1を書くとPxnは出力ピンに、0を書くと入力ピンになる。)If PORTxn is written logic one when the pin is configured as an output pin, the port pin is driven high (one). If PORTxn is written logic zero when the pin is configured as an output pin, the port pin is driven low (zero).
(訳:ピンが出力に設定されているとき、PORTxnに1を書くとポートピンはHIGH(1)に、0を書くとLOW(0)に駆動される。)出典:ATmega328P Datasheet(Microchip, 7810D–AVR)13.2.1 Configuring the Pin, p.59 より原文引用(© Microchip Technology)
ここで x を B、n を 5 に置き換えると、DDB5を1にすればPB5は出力、PORTB5を1でHIGH(点灯)、0でLOW(消灯)だと読めます。これが、このあと書く DDRB |= _BV(DDB5) と PORTB |= _BV(PORTB5) の根拠です。役割を整理すると次のとおりです。
| レジスタ | 役割 | このLチカでの使い方 |
|---|---|---|
DDRB |
入出力の方向を決める(Data Direction Register) | bit5を1にしてPB5を出力にする |
PORTB |
出力する値を決める | bit5を1でHIGH(点灯側)、0でLOW(消灯側) |
PINB |
端子の入力状態を読む | 出力だけの今回は使わない |
ここまで読めば、「PB5を出力にしてHIGHにする」は「DDRBのbit5を1にして、PORTBのbit5を1にする」だと分かります。ようやくコードに落とせます。
#include <Arduino.h>
void setup() {
DDRB |= _BV(DDB5); // DDRBのbit5を1に=PB5を出力にする
}
void loop() {
PORTB |= _BV(PORTB5); // PORTBのbit5を1に=PB5をHIGH(点灯)
delay(1000);
PORTB &= ~_BV(PORTB5); // PORTBのbit5を0に=PB5をLOW(消灯)
delay(1000);
}
_BV(DDB5)は、5番ビットだけが1になった値(2進数で00100000)を作るArduino AVRのマクロです。|=は「そのビットを1にする」、&= ~は「そのビットを0にする」と読めます。
| 書き方 | PB5への効果 | ほかのPort Bのビット |
|---|---|---|
DDRB |= _BV(DDB5) |
出力にする | そのまま残る |
PORTB |= _BV(PORTB5) |
1(HIGH)にする | そのまま残る |
PORTB &= ~_BV(PORTB5) |
0(LOW)にする | そのまま残る |
他の端子(PB0〜PB4など)の設定を巻き込まないため、代入の=ではなくビット演算で5番ビットだけを触ります。この「一部のビットだけを変える」書き方はC言語 ビット演算のドリルと同じ考え方です。
pinMode(LED_BUILTIN, OUTPUT)がDDRBの設定を、digitalWrite(LED_BUILTIN, HIGH)がPORTBの設定を、それぞれ肩代わりしていました。APIは「どのピンがどのレジスタの何番ビットか」を裏で対応表として持っていて、自動で正しいビットを触ってくれていた、というわけです。
別のピンを使うときも手順は同じです。①の対応表でマイコンの端子名を調べ、②でそのビット番号を見つけ、③で役割を確認する。この読み方さえ身につけば、暗記に頼らずどの端子でも操作できます。
練習問題(基礎5+発展2)
基礎5問は、API版とレジスタ版の対応を確認する問題です。発展2問は、ビット演算を使って自分で出力を操作します。まず自分で書いてから回答を開いてください。
基礎 Q1
内蔵LEDを1秒間隔で点滅させる、Arduino API版のLチカを書いてください。
解答・解説を見る
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
解説:setup()で出力設定を1回だけ行い、loop()で点灯と消灯を繰り返します。最初はピン番号の13を直接書くより、内蔵LEDを示すLED_BUILTINを使うほうが意図を読み取りやすくなります。
基礎 Q2
Q1を、点灯200ms・消灯800msに書き換えてください。
解答・解説を見る
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
delay(800);
}
解説:delay()の単位はmsです。点灯時間と消灯時間を別々に変えると、LEDの見え方も変わります。後のタイマ記事では、この待ち時間をdelay()に頼らず作る方法を扱います。
基礎 Q3
レジスタ版で、PB5を出力に設定する1行を書いてください。
解答・解説を見る
DDRB |= _BV(DDB5);
解説:DDRBの5番ビットを1にします。DDRB = _BV(DDB5);でもPB5を出力にはできますが、Port Bのほかの設定を0にしてしまうため、このシリーズでは|=で必要なビットだけを変更します。
基礎 Q4
レジスタ版で、PB5をHIGHにする1行を書いてください。
解答・解説を見る
PORTB |= _BV(PORTB5);
解説:PORTBの5番ビットを1にします。PB5がすでに出力に設定されているので、ピンからHIGHが出力されます。
基礎 Q5
レジスタ版で、PB5をLOWにする1行を書いてください。
解答・解説を見る
PORTB &= ~_BV(PORTB5);
解説:~_BV(PORTB5)は、5番ビットだけが0で、ほかのビットが1の値です。それを&=で反映すると、PB5だけを0にできます。ビットの意味があやふやなら、C言語 ビット演算のドリルを復習してください。
発展 Q6
レジスタ版で、PB5を500ms間隔で点滅させるsetup()とloop()を書いてください。
解答・解説を見る
void setup() {
DDRB |= _BV(DDB5);
}
void loop() {
PORTB |= _BV(PORTB5);
delay(500);
PORTB &= ~_BV(PORTB5);
delay(500);
}
解説:Q3〜Q5をつなげた形です。このコードはUno R3向けにコンパイル確認したレジスタ版と同じ書き方です。
発展 Q7
PB5をHIGHにする処理とLOWにする処理を、ledOn()、ledOff()という2つの関数に分けてください。
解答・解説を見る
void ledOn() {
PORTB |= _BV(PORTB5);
}
void ledOff() {
PORTB &= ~_BV(PORTB5);
}
void setup() {
DDRB |= _BV(DDB5);
}
void loop() {
ledOn();
delay(1000);
ledOff();
delay(1000);
}
解説:出力を変える処理に名前を付けると、後からコードを読んだときに何をしているか分かりやすくなります。総合課題でも、センサ読み取り、判定、赤外線送信を関数へ分けて組み立てます。
実務ではこの考え方をこう使う
実務のマイコンでも、出力ポートの設定とビット単位の操作は基本です。LEDそのものを制御するだけでなく、リレー、トランジスタ、通信ICの制御端子などをON/OFFするときに同じ考え方を使います。
/* 出力として使うビットだけを設定する */
OUTPUT_DDR |= OUTPUT_MASK;
/* 通知をONにする */
OUTPUT_PORT |= NOTIFY_MASK;
/* 通知をOFFにする */
OUTPUT_PORT &= ~NOTIFY_MASK;
このコードのOUTPUT_DDR、OUTPUT_PORT、OUTPUT_MASKは、使うマイコンとプロジェクトの命名に合わせた説明用の名前です。車載ECUでは、どのピンが何につながるかはArduinoのように固定のD13ではなく、回路図やハードウェア仕様で決まります。それでも「出力方向を設定する」「該当ビットだけを変更する」という考え方は同じです。
LEDが点かないときは、この順番で確認する
DDRBやPORTBはATmega328Pのレジスタ名です。Uno R4など、別のマイコンを搭載したボードでは使えません。レジスタ版は必ず「ボードに載っているマイコン」を確認してから書きます。
- ボードと書き込み先:Uno R3系を選び、正しいポートへ書き込めているか
- 出力設定:
setup()でpinMode(..., OUTPUT)、またはDDRBのbit5を設定しているか - 出力値:
digitalWrite()、またはPORTBのbit5をHIGH/LOWに切り替えているか - 外付けLEDだけ:抵抗が直列に入っているか、LEDの向きが逆でないか
特に混同しやすいのはDDRBとPORTBです。DDRBは入出力方向、PORTBは出力値と分けて確認してください。
よくある質問(FAQ)
LED_BUILTINと13はどちらを使えばよいですか?
内蔵LEDを使う目的なら、LED_BUILTINがおすすめです。コードを読んだ人に「内蔵LEDを使う」と伝わります。Uno R3のレジスタ版では、D13に対応するPB5を明示する必要があります。
delay()を使ってよいですか?
最初のLチカでは、点滅の確認に使ってかまいません。ただしdelay()中は他の処理を進めにくくなります。複数の処理を同時に扱う段階では、delay()に頼らず実行周期を作る方法へ進みます。
内蔵LEDが光りません
ボード選択がUno R3系になっているか、書き込み先のポートが正しいか、setup()で出力設定をしているかを確認してください。外付けLEDの場合は、向きと抵抗の有無も確認します。レジスタ版では、Uno R3以外のボードへ書き込んでいないかも見直してください。
次に読む
Lチカで出力を動かせたら、次はシリアルモニタへ値を表示して、プログラムの中で何が起きているかを見る練習へ進みます。
- C言語 基礎ドリル:ビット演算を含む、Arduinoの前提となるC言語を復習する
- C言語 ビット演算の練習問題:
|=と&= ~の意味を図で確認する - Arduino シリアルで見るドリル(#2・作成中):出力した値をPCで確認する