2学期開始!体験授業 受付中 →
ROBO SCIENCE

Pico 2個で会話させる ―
シリアル通信

2026.10.11
R3 PE(Pre-expert) ES(Expert-soccer) ER(Expert-rescue)

シリアル通信は、マイコンでよく使われる通信です。パソコンとのやりとりも、センサーとのやりとりも、この通信で動いています。今回はそれを Pico 2個のあいだで使い、センサーを読む担当とモーターを回す担当に分けます。1台のロボットの中のマイコン同士の話です。まずは 1 から 100 までを送って受け取り、つながったことを目で確かめます。

仕事を分担する ― サブが計算して、メインが動かす

ロボットがやっていることは、大きく2つに分かれます。センサーを読んで、どう動くかを計算すること。そしてその結果を判断し、モーターを回すことです。

この2つを1個のマイコンにやらせると、どちらかが待たされます。サッカーロボットなら、loop() の中でボールの向きを計算しているあいだ、モーターへの指示は止まっています。

そこで、Pico を2個使って分担します。分けるときは、どちらがメインでどちらがサブかをはっきり決めておきます。

担当
サブ(Pico B)センサーを読んで計算し、結果をメインに送る
メイン(Pico A)受け取った結果をもとに、モーターを回す

モーターを持っているほうをメインとします。最後に動きを決める係で、重い計算はサブが引き受けます。

サブが渡すのは結論だけです。「ボールは右30度」なら 30 だけ。センサーの生の値は送りません。この受け渡しがシリアル通信です。

つなぐ線は4本 ― TX・RX・GND・3.3V

使うのは UART というしくみです。ロボットで「シリアル通信」と言うとき、たいていこの UART を指します。

ピンの名前に出てくる TX は送る口、RX は受ける口です。Transmitter(送信)と Receiver(受信)の頭文字で、GP0 (TX) なら「GP0 から送り出す」という意味になります。

信号そのものは、送る線1本と GND があれば届きます。ただし今回はメインからサブへ信号と電源も通して、4本にします。

つなぐときの決まりは3つです。TX は相手の RX へつなぎます。TX どうしでは、データは伝わりません。GND は共通にします。電源はメインの 3V3(OUT) からサブの VSYS へ入れ、サブに電気を送ります。

メインもサブも Pico なので、信号は3.3V同士。そのままつないでかまいません。ただし5Vで動く相手の TX を直接つなぐと、Pico が壊れます。

サブに書き込むときは、3.3V の線を抜きます。つないだまま USB をさすと、USB の電気がメインの 3.3V 側へ逆に流れこみ、メインが壊れることがあります。

メイン(Pico A) モーターを回す サブ(Pico B) センサーを読んで計算する GP1 (RX) GP0 (TX) 計算した結果を送る GP0 (TX) GP1 (RX) 問い合わせ・指示を送る GND GND 0V の基準をそろえる 3V3 (OUT) VSYS 電源を配る ✕ TX と TX をつないでも、RX と RX をつないでも、何も届かない
行きと帰りの信号2本に、GND と電源が付いて4本。I2C のときも本数は同じです。
線役割
サブ TX → メイン RX計算した結果を送る(これが主役)
メイン TX → サブ RX問い合わせや指示を送る
GND0V の基準をそろえる
3.3Vメインからサブへ、電源を配る

実際にブレッドボードで組むと、こうなります。

配線図
ブレッドボードに Raspberry Pi Pico を2個並べ、4本のジャンパー線でつないだ配線図。上側に黒(GND)と赤(電源)、下側に黄と青(GP0 と GP1)が渡してある。
2個を向かい合わせに差して4本を渡す。上の黒が GND、赤が電源。下の黄と青が GP0 と GP1 の行き帰りです。
回路図
同じつなぎ方の回路図。2個の Pico の GP0 と GP1 がたすきがけになり、下の GND 同士が1本でつながり、3V3 から相手の39番ピンへ線が引かれている。
同じつなぎ方を回路図で。39番ピンが VSYS です(Fritzing の記号では VSS と出ます)。
名前つながる先相手
SerialUSBケーブルパソコンのシリアルモニタ
Serial1UART0(GP0=TX, GP1=RX)もう1個の Pico
Serial2UART1(GP4=TX, GP5=RX)さらに別の相手

Serial に出してもパソコンのシリアルモニタに出るだけで、相手の Pico には届きません。相手に送るのは Serial1 です。

UART0 と UART1 がどのピンに出ているかは、公式のピン配置図 ↗で確かめます。GP0・GP1、GP4・GP5 のほかにも割り当てられるピンがあります。

ボーレートを合わせる ― 競技では 115200

ボーレートは、1秒間に何回信号を切り替えるかを表す数字です。受ける側は、この速さだけを頼りに「どこまでが1ビットか」を数えています。

ボーレート1バイトにかかる時間使いどき
9600約1ms最初につながるか試すだけ
115200約0.09ms競技ではこちら

9600 は遅すぎます。1バイト送るだけで 1ms かかり、その間 loop() が進みません。115200 を標準にします。

送る側と受ける側で必ず同じ数字にします。

Serial1.write で1バイト送る

送るときに使うのは Serial1.write() です。かっこの中の数値が、そのまま1バイト線に流れます。

書き方線に流れるもの
Serial1.write(25)0x19 を1バイト
Serial1.write(0xFF)0xFF を1バイト
Serial1.write('A')0x41 を1バイト

write('A') と write(65) は同じです。文字も数も、線の上では同じ1バイトです。

送れる数値は 0〜255 だけ。そのかわり速くて単純です。変換もなく、ただ1バイト流すだけ。サッカーのように1周期を短くしたい競技では、これが効きます。

送るプログラム ― 1から100までくり返し送る

サブからメインにデータが送れることを確認するために、サブから 1、2、3 … 100 と順に送り、100 まで行ったら 1 に戻ります。

// サブ(Pico B)― 送る側
byte value = 1;

void setup() {
  Serial1.setTX(0);                   // GP0 から送る
  Serial1.setRX(1);                   // GP1 で受ける
  Serial1.begin(115200);              // 速さは送受で同じに
}

void loop() {
  Serial1.write(value);               // 1バイト送る

  value++;                            // つぎの番号へ
  if (value > 100) { value = 1; }     // 100 の次は 1 に戻す

  delay(100);                         // 目で追えるようにゆっくり
}

setTX と setRX は、UART0 をどのピンで使うか設定します(GP0・GP1 であればデフォルトなので省略できます)。どのピンを使っているかがコードを見て分かるように、書いておきます。別のピンに変えるときも、ここの数字を直すだけです。

どちらも begin() より前に置きます。あとから呼んでも効きません。

delay(100) は、目で追えるようにわざと遅くしてあります。本番ではもっと短くします。

受けるプログラム ― 受け取ってシリアルモニタに出す

メインは、届いたバイトをそのままパソコンに流すだけです。

// メイン(Pico A)― 受ける側
void setup() {
  Serial.begin(115200);               // パソコン確認用(USB)

  Serial1.setTX(0);                   // GP0 から送る
  Serial1.setRX(1);                   // GP1 で受ける
  Serial1.begin(115200);              // サブとの通信用
}

void loop() {
  while (Serial1.available()) {       // 届いている分を読み切る
    byte value = Serial1.read();      // 1バイト取り出す
    Serial.println(value);            // シリアルモニタに出す
  }
}

メインを USB でパソコンにつなぎ、Arduino IDE のシリアルモニタを 115200 で開きます。

シリアルモニタの出かた読みとれること
1, 2, 3 … 100, 1, 2 … と並ぶ配線もボーレートも合っている
何も出ないTX と RX が逆、または GND がつながっていない
でたらめな値が出るボーレートがちがう
数が飛ぶ(5 のつぎが 8 など)どこかでバイトが落ちている

連番にしておくのがコツです。同じ値だと1つ落ちても気づけませんが、1ずつ増やしておけば飛んだ瞬間に分かります。

ここで使った命令は3つだけです。

命令役割
Serial1.write(x)1バイト送る
Serial1.available()いま何バイト届いているか
Serial1.read()1バイト取り出す(取り出した分は消える)

郵便ポストで考える ― バッファはあふれる

受け側には郵便ポストがあります。届いたバイトはいったんそこにたまり、read() で取り出すと消えます。これをバッファといいます。

25 27 30 受け側のバッファ(=郵便ポスト) サブから届く 順にうまっていく read() は前から1バイトずつ取り出す(取り出すと消える) 右端まで埋まると、 入らない分は捨てられる
届いたバイトは空いているうしろに入り、read() は前から取り出す。右端まで埋まったあとに届いた分は、捨てられる。

大事なのは順番です。read() が取り出すのは、いちばん古いバイト。新しく届いたものはうしろに並ぶので、追い越しは起きません。

つまり、ポストに3つたまっているとき、1回目の read() で出てくるのはいちばん最初に届いたものです。いまの値ではありません。

ポストの大きさには限りがあります。取りに行く前に次が来続けると、入らなかった分は捨てられます。

あふれる原因何が起きているか
受け側の loop() に長い delay() がある取りに行く回数が減って、ポストが満杯になる
送る側が速すぎる受け側が1周する前に、次が届いてしまう

まず、送る側と受ける側の両方で手を打ちます。受け側の loop() に delay() は置きません。サブ側も送りっぱなしにせず、メインの周期に合わせて送ります。

そのうえで、取り出し方そのものにコツがあります。

最新の値を取る ― while で読み切る

ロボットが欲しいのはいまの値です。0.3秒前の値は要りません。

ところが if で1周に1つずつ取り出すと、たまったぶんだけ遅れていきます。通信は生きているのに、ロボットは少し前の世界を見て動くことになります。

そこで while で届いている分を読み切ります。ポストの手紙を古いものから順に全部取り出し、最後に手に取った1通が、いちばん新しい手紙。それだけを使います。

if で1バイトずつwhile で読み切る
1周で取り出す数1バイトだけ届いている分すべて
たまったとき遅れがたまっていくその場で追いつく
手に入る値古い順に1バイトずついちばん新しい1バイト
// メイン ― 最新の値だけ残す
byte latest = 0;

void loop() {
  while (Serial1.available()) {
    latest = Serial1.read();     // 上書きしていく。最後の1つが残る
  }

  // ここから先は latest を使う
}

while を抜けた時点で、latest にはいちばん新しい値が入っています。古い値は上書きされて消えています。

ひとつだけ弱点があります。この書き方では、届かなくなったことに気づけません。サブが止まっても latest は最後の値のまま残るので、メインはそれを「いまの値」として使い続けます。気になるときは、受け取った時刻を millis() で覚えておき、しばらく来なければ止める、という手が使えます。

1バイトは10ビット ― スタート・データ8・ストップ

ここまで「1バイト送る」と書いてきましたが、線の上を流れているのは電圧の高い・低いだけです。0 と 1 が並んでいるだけで、「ここからが1バイト」とは書いてありません。

では、受け側はどうやって区切っているのか。ここを見ておくと、ボーレートをそろえる理由まで一度に分かります。

まず、送っていないときの線は 1(HIGH)です。ずっと 1 のまま待っています。

送るときは、いきなり 0 に落とします。これがスタートビットです。受け側が見張っているのは、この落ちた瞬間だけです。

1 0 0 スタート 1 D0 0 D1 0 D2 1 D3 1 D4 0 D5 0 D6 0 D7 1 ストップ アイドル 1 → 0 に落ちた。ここから数えはじめる 1ビット(115200 なら 8.68µs) Serial1.write(25) のとき、線の上を流れるもの
アイドルの 1 から落ちた瞬間がスタートビット。そこから1ビットずつ数えて8ビット読み、最後に 1 へ戻る。●は受け側がのぞく場所。
順番値中身
アイドル1何も送っていないとき
スタートビット0 固定ここから始まる、の合図
データ 8ビット0 か 1下位のビットから順に
ストップビット1 固定線を 1 に戻す

データビットには「自分はデータです」という印がありません。落ちたところから何ビットめか、それだけで決まります。つまり値ではなく、時間の位置で見分けています。

受け側は、まず0.5ビット分だけ待ちます。スタートビットの真ん中に来るためです。そこでまだ 0 かを確かめ(1 に戻っていればノイズとして捨てます)、あとは1ビットずつ、ビットの真ん中をのぞいていきます。図の●がその場所です。

最後にもう1ビット進んで、1 に戻っているかを見ます。ここが 0 なら、どこかがおかしいと分かります。

ストップビットが 1 固定なのには理由があります。ここで線を 1 に戻しておかないと、次のバイトのスタートビットが「落ちる」ことができません。続けて送っても、1バイトごとに必ず「落ちる瞬間」が作れるようにしてあります。

ストップビットの長さは1ビット分ですが、これは最短です。次を送るまでは 1 のまま続きます。

ここでボーレートの話に戻ります。受け側は時間を物差しにして数えているだけなので、1ビットの長さの見積もりがちがうと、のぞく場所がどんどんずれていきます。

ボーレート1ビットの長さ1バイト(10ビット)
9600104µs約1.04ms
1152008.68µs約0.087ms

第3節で出した「1バイトにかかる時間」は、この10ビット分です。8ビットではありません。

ずれは後ろのビットほど積み重なります。最初の1〜2ビットは合っていても、最後のほうは完全に別の場所を読みます。結果、まるでちがう数が届きます。これが「ボーレートがちがうとでたらめな値になる」の正体です。

GND をつなぐ理由も、ここにあります。「高い・低い」は電圧の判定で、その基準の 0V が GND です。GND がつながっていないと、受け側はそもそも「落ちた」ことを正しく見られません。

最後に呼び名です。この形を 8N1 といいます。

意味ほかに選べるもの
8データ長 8ビット5〜8。ふつうは 8
Nパリティなし偶数/奇数。1ビット足して誤りを見つける
1ストップビット 12。相手が遅いとき用

Serial1.begin(115200) と書くと、この 8N1 になります。Pico 同士の短い配線なら、既定のままで困りません。

動かないときに見るところ ― 症状から原因を引く

症状原因対処
何も受信しないTX と RX が逆送る側の GP0 → 受ける側の GP1 になっているか見る
でたらめな値が届くボーレートがちがう両方の Serial1.begin() の数字をそろえる
動いたり動かなかったりするGND がつながっていないGND同士を必ず結ぶ。線の半抜けも疑う
数が飛ぶバッファがあふれている受け側の loop() から delay() を外す。送る間隔を広げる
値が古いまま変わらないサブが止まっているサブ側の loop() が回っているか、LEDを点滅させて見る
反応が遅れていくif で1バイトずつ処理しているwhile で読み切る形にする
サブが起動しない3.3V か GND が届いていないサブの VSYS と GND に電圧が来ているか、テスターで見る
Serial1 でエラーが出るPico用のボードが入っていないArduino IDE のボードマネージャで Raspberry Pi Pico/RP2040 を入れる

ここまでで、数値を1つ渡せるようになりました。ところが実際には、値1つでは足りません。ボールの向きも、距離も送りたくなります。

値が2つ以上になると、どこからが1組なのか分からなくなります。そこで先頭にヘッダーという目印を付けます。255 を超える値を2バイトに分けて送るのも、同じ回でやります。次はそこへ進みます。

ロボサイエンスについて

ロボカップジュニア2027 に向けて、レスキュー(災害救助)とサッカーの2種目に取り組みます。プログラミングと電子工作の経験は問いません。

前回:入力と出力 ― CdSで暗くなったら、LEDが光る →
ロボサイエンスの記録一覧 →
体験授業・お問い合わせ →

← ロボサイエンスの記録一覧へ戻る