I2C も、マイコンでよく使われる通信です。データを1ビットずつ送る点では、前回のシリアル通信(UART)の仲間です。ちがうのは、メインが聞いたときだけサブが答えること。今回も Pico 2個をつなぎ、1 から 100 までを受け取って確かめます。前回と同じ順番で進めるので、くらべながら読めます。
仕事を分担する ― メインが聞いて、サブが答える
役割はシリアル通信の回と同じです。
| 担当 | |
|---|---|
| サブ(Pico B) | センサーを読んで計算し、結果をメインに渡す |
| メイン(Pico A) | 受け取った結果をもとに、モーターを回す |
ちがうのは話し方です。シリアル通信では、サブが好きなときに送りました。I2C では、メインが聞いたときだけ、サブが答えます。サブのほうから話しかけることはできません。
資料によっては、メインを「マスター」「コントローラ」、サブを「スレーブ」「ターゲット」と書いてあります。同じ意味です。
つなぐ線は4本 ― SDA・SCL・GND・3.3V
信号の線は2本です。SDA はデータの線、SCL は時計(クロック)の線です。データは行きも帰りも SDA の1本を通ります。SCL には、メインが「いま読んで」という合図を出します。
ここに GND と電源を足して、4本にします。本数はシリアル通信のときと同じです。
つなぐときの決まりは3つです。SDA は SDA へ、SCL は SCL へつなぎます。シリアル通信のような、たすきがけにはしません。GND は共通にします。電源はメインの 3V3(OUT) からサブの VSYS へ入れ、サブに電気を送ります。
サブに書き込むときは、3.3V の線を抜きます。つないだまま USB をさすと、USB の電気がメインの 3.3V 側へ逆に流れこみ、メインが壊れることがあります。
SDA と SCL は、3.3V へ引き上げておく必要があります(プルアップ)。Pico 同士を短い線でつなぐなら、Pico の中の抵抗だけで動きます。線が長いとき、速くするとき、つなぐ数が増えたときは、SDA と SCL のそれぞれと 3.3V のあいだに 2.2kΩ〜4.7kΩ の抵抗を付けます。
サブを増やすときは、同じ2本の線に並べてつなぎます。プルアップ抵抗は、SDA と SCL に1個ずつあれば足ります。
どのピンが SDA・SCL に使えるかは、公式の図で確かめられます。
Raspberry Pi Pico のピン配置(Raspberry Pi 公式)無線つきの Pico W は別の図です。同じページの少し下にあります。
実際にブレッドボードで組むと、こうなります。
VSS と出ます)。| 名前 | つながる先 | はじめのピン |
|---|---|---|
| Wire | I2C0 | GP4=SDA, GP5=SCL |
| Wire1 | I2C1 | GP26=SDA, GP27=SCL |
プログラムでは Wire を使います。シリアル通信の Serial1 にあたるものです。
速さは合わせなくていい ― メインが時計を出す
シリアル通信では、送る側と受ける側のボーレートを同じ数字にしました。受ける側が、速さだけを頼りに数えていたからです。
I2C には時計の線(SCL)があります。メインが SCL を上げ下げし、サブはそれに合わせるだけです。速さを決めるのはメインだけで、サブ側に設定はありません。
| 速さ | 1バイト受け取る時間 | 使いどき |
|---|---|---|
| 100kHz | 約0.2ms | はじめの設定。まずこれで動かす |
| 400kHz | 約0.05ms | 競技で速くしたいとき。プルアップ抵抗を付ける |
速くするときは、メインの Wire.begin() のあとに Wire.setClock(400000); と書きます。
番地で相手を選ぶ ― 0x08 のサブに聞く
I2C では、同じ2本の線にサブを何個もつなげます。だれに聞いているのかを区別するために、サブには1個ずつ番地(アドレス)を付けます。使えるのは 0x08〜0x77 です。
| 書き方 | 意味 |
|---|---|
| Wire.begin() | メインとして始める |
| Wire.begin(0x08) | 番地 0x08 のサブとして始める |
| Wire.requestFrom(0x08, 1) | メインが、0x08 に1バイト求める |
| Wire.onRequest(answer) | サブが、聞かれたときに呼ぶ関数を決める |
| Wire.write(25) | 1バイト送る |
| Wire.read() | 1バイト取り出す |
送れる数値は 0〜255 だけ。数値をそのまま1バイトで流すところは、Serial1.write() と同じです。
サブのプログラム ― 聞かれたら 1 から 100 を順に答える
メインがサブのデータを受け取れることを確認するために、サブは聞かれるたびに 1、2、3 … 100 と順に答え、100 まで行ったら 1 に戻ります。
// サブ(Pico B)― 答える側
#include <Wire.h>
byte value = 1;
void answer() { // メインに聞かれたら呼ばれる
Wire.write(value); // 1バイト答える
value++; // つぎの番号へ
if (value > 100) { value = 1; } // 100 の次は 1 に戻す
}
void setup() {
Wire.setSDA(4); // GP4 をデータの線に
Wire.setSCL(5); // GP5 を時計の線に
Wire.begin(0x08); // 番地 0x08 のサブとして始める
Wire.onRequest(answer); // 聞かれたら answer を呼ぶ
}
void loop() {
}
setSDA と setSCL は、I2C0 をどのピンで使うか設定します(GP4・GP5 であればデフォルトなので省略できます)。どのピンを使っているかがコードを見て分かるように、書いておきます。どちらも begin() より前に置きます。
loop() は空です。サブは自分からは送らず、聞かれたときに answer() が呼ばれるだけだからです。
メインのプログラム ― 聞いて、シリアルモニタに出す
メインは 0x08 に1バイト求め、返ってきた値をパソコンに流します。
// メイン(Pico A)― 聞く側
#include <Wire.h>
void setup() {
Serial.begin(115200); // パソコン確認用(USB)
Wire.setSDA(4); // GP4 をデータの線に
Wire.setSCL(5); // GP5 を時計の線に
Wire.begin(); // 番地なし=メインとして始める
}
void loop() {
Wire.requestFrom(0x08, 1); // 0x08 に「1バイトください」
if (Wire.available()) { // 答えが届いていれば
byte value = Wire.read(); // 1バイト取り出す
Serial.println(value); // シリアルモニタに出す
}
delay(100); // 目で追えるようにゆっくり
}
メインを USB でパソコンにつなぎ、Arduino IDE のシリアルモニタを 115200 で開きます。
| シリアルモニタの出かた | 読みとれること |
|---|---|
| 1, 2, 3 … 100, 1, 2 … と並ぶ | 配線も番地も合っている |
| 何も出ない | SDA と SCL が逆、GND がつながっていない、または番地がちがう |
| ときどき止まる・値がおかしい | 線が長い、またはプルアップ抵抗が足りない |
番地は、サブの Wire.begin(0x08) とメインの Wire.requestFrom(0x08, 1) で同じ数字にします。
郵便ポストにたまらない ― 聞いたぶんだけ届く
シリアル通信では、届いたバイトが受け側のポスト(バッファ)にたまりました。取りに行くのが遅いと、あふれました。
I2C では、メインが聞いたぶんだけ届きます。聞いていないときには何も来ないので、たまることも、あふれることもありません。
そのかわり、サブに決まりが1つあります。聞かれたら、すぐ答えることです。answer() の中で重い計算をすると、メインを待たせます。answer() の中は、用意してある値を出すだけにします。
最新の値を取る ― 聞いた瞬間の値が返る
シリアル通信では、while で読み切って最後の1バイトを使いました。古い値がポストに並んでいたからです。
I2C では、その必要がありません。サブは最新の値を変数に置いておき、聞かれたらそれを出すだけです。メインが受け取るのは、いつも聞いた瞬間の値です。
// サブ ― いつ聞かれても、最新の値を答える
#include <Wire.h>
volatile byte latest = 0; // answer と loop の両方で使う
void answer() {
Wire.write(latest); // 置いてある値を出すだけ
}
void setup() {
Wire.setSDA(4);
Wire.setSCL(5);
Wire.begin(0x08);
Wire.onRequest(answer);
}
void loop() {
latest = calcValue(); // 重い計算はここでやる
}
calcValue() のところは、自分のセンサー処理に置き換えてください。出てくるのが 0〜255 の数値であれば、なんでもかまいません。
もう1つ、シリアル通信ではできなかったことができます。届かなかったことに気づけます。Wire.requestFrom() は受け取ったバイト数を返すので、0 ならサブが答えていません。
// メイン ― 答えがないときに気づく
if (Wire.requestFrom(0x08, 1) == 0) {
// 0 バイト=サブが答えていない
}
1バイトは9ビット ― 8ビットと返事
シリアル通信では、受け側はスタートビットが落ちた瞬間から時間を数えてビットを区切りました。
I2C は時間を数えません。受け取る側は、SCL が上がるたびに SDA を1ビット読みます。時計の線があるので、区切りは時計が教えてくれます。
| 順番 | 出す側 | 中身 |
|---|---|---|
| スタート | メイン | これから始める、の合図 |
| 番地 7ビット | メイン | だれに話すか |
| 読む/書く 1ビット | メイン | 聞くのか、送るのか |
| 返事 1ビット | サブ | 0 なら「います」 |
| データ 8ビット | サブ | 上位のビットから順に |
| 返事 1ビット | メイン | 0 なら「続けて」、1 なら「ここまで」 |
| ストップ | メイン | 終わり、の合図 |
8ビットごとに、受け取った側が返事を1ビット返します。これを ACK(アック)といいます。データ8ビットと返事1ビットで、1バイトは9ビットです。
番地を呼んでも返事がなければ、その番地にサブはいません。届かなかったことに気づけるのは、この返事のビットがあるからです。
ビットを送る順番は、シリアル通信と逆です。シリアル通信は下位のビットから、I2C は上位のビットから送ります。図の 25 は、0・0・0・1・1・0・0・1 の順に流れています。
シリアル通信とくらべる ― 同じところ、ちがうところ
| シリアル通信(UART) | I2C | |
|---|---|---|
| 線の本数 | 4本 | 4本 |
| 信号の線 | TX・RX(たすきがけ) | SDA・SCL(同じ名前どうし) |
| つなげる数 | 1対1 | 同じ2本に何個も |
| 相手の選び方 | 線で決まる | 番地で選ぶ |
| 話しはじめる側 | どちらからでも | メインだけ |
| 速さ | 両方で同じ数字にする | メインが時計を出す |
| 1バイト | 10ビット(スタート・8・ストップ) | 9ビット(8・返事) |
| 1バイトの時間 | 約0.09ms(115200) | 約0.2ms(100kHz)/約0.05ms(400kHz) |
| 受け側にたまるか | たまる。あふれる | たまらない |
| 届かないとき | 気づけない | 返事がないので分かる |
サブが1個で、値を流しつづけるなら、シリアル通信が向いています。サブを何個もつなぐとき、メインが聞きたいときだけ値がほしいときは、I2C が向いています。
動かないときに見るところ ― 症状から原因を引く
| 症状 | 原因 | 対処 |
|---|---|---|
| 何も返ってこない | SDA と SCL が逆 | GP4 どうし、GP5 どうしになっているか見る |
| 何も返ってこない | 番地がちがう | サブの Wire.begin() とメインの Wire.requestFrom() の番地をそろえる |
| 動いたり動かなかったりする | GND がつながっていない | GND同士を必ず結ぶ。線の半抜けも疑う |
| ときどき値がおかしい | プルアップ抵抗が足りない | SDA・SCL と 3.3V のあいだに抵抗を付ける。線を短くする |
| 値が古いまま変わらない | サブの loop() が止まっている | サブ側で LED を点滅させて、回っているか見る |
| サブが起動しない | 3.3V か GND が届いていない | サブの VSYS と GND に電圧が来ているか、テスターで見る |
Wire でエラーが出る | Pico用のボードが入っていない | Arduino IDE のボードマネージャで Raspberry Pi Pico/RP2040 を入れる |
ロボカップジュニア2027 に向けて、レスキュー(災害救助)とサッカーの2種目に取り組みます。プログラミングと電子工作の経験は問いません。