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

シリアル通信で 255 を超える値を送る ―
3バイトに分けて、ヘッダーを付ける

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

シリアル通信の回では、Serial1.write で1バイト(0〜255)を送りました。今回も同じシリアル通信で、255 を超える値を送ります。サッカーロボットでは、サブがジャイロの角度 0〜359 をメインへ送るときに、この方法を使っています。値を3バイトに分け、先頭にヘッダーという目印を付けます。配線はシリアル通信の回と同じ4本です。

1バイトでは足りない ― 角度は 0〜359

Serial1.write() が1回で送るのは1バイト、つまり 0〜255 です。ジャイロの角度 0〜359 は、1回では送れません。

そこで、値を何バイトかに分けて送ります。

ところが分けたとたん、別の問題が出ます。線の上を流れるのは、ただのバイトの列です。メインが途中から読みはじめると、どのバイトが先頭なのか分かりません。

だから先頭のバイトに目印を付けます。これがヘッダーです。

7ビットずつに分ける ― ヘッダーは 128 以上

値を8ビットずつに分けると、データのバイトは 0〜255 のどれにもなります。目印に使える数が残りません。

そこで7ビットずつに分けます。データのバイトは 0〜127 に収まるので、128 以上はヘッダー専用にできます。

359 を16ビットで書き、3バイトに入れる 0 0 0 0 0 0 0 1 0 1 1 0 0 1 1 1 359 = 上の2ビット まん中の7ビット 下の7ビット 1 0 0 0 0 0 0 0 head = 128 1 0 0 0 0 0 0 1 0 high = 2 0 0 1 1 0 0 1 1 1 low = 103 0 先頭を 1 にする(目印) 先頭は 0 先頭は 0 データは 0〜127。128 以上になるのは、先頭のバイトだけ
16ビットを上から 2・7・7 に切って、3バイトに入れる。359 は 128・2・103 になる。
送る順中身とりうる値
head残りの上のビット + 128128〜255
highまん中の7ビット0〜127
low下の7ビット0〜127

head には7ビットまで入ります。7ビット × 3 で21ビット。送れるのは 0〜2,097,151 です。3バイト送るのにかかる時間は、115200 で約0.26ms です。

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

サブから 0、1、2 … 359 と順に送り、359 まで行ったら 0 に戻ります。

// サブ(Pico B)― 送る側
int angle = 0;

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

void loop() {
  sendIntData(angle);                 // 3バイトに分けて送る

  angle++;                            // つぎの角度へ
  if (angle > 359) { angle = 0; }     // 359 の次は 0 に戻す

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

// 値を3バイトに分けて送る
void sendIntData(int value) {
  byte low  = (byte)(value & 127);                  // 下の7ビット
  byte high = (byte)((value >> 7) & 127);           // まん中の7ビット
  byte head = (byte)(((value >> 14) & 127) + 128);  // 残りの上のビット+128

  Serial1.write(head);                // 先頭から順に送る
  Serial1.write(high);
  Serial1.write(low);
}

値を分けているのは sendIntData() の3行です。

書き方はたらき359 のとき
value & 127下の7ビットだけを残す103
value >> 77ビット右へずらす(128 で割る)2
value >> 1414ビット右へずらす0
+ 128先頭のビットを 1 にする0 + 128 = 128

送る順は head → high → low です。メインはこの順で届くと決めて読みます。

受けるプログラム ― 128 以上を待って、3バイトそろえる

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

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

void loop() {
  while (Serial1.available() >= 3) {  // 3バイトたまっているあいだ
    int head = Serial1.read();        // 1バイト取り出す

    if (head >= 128) {                // ヘッダーを見つけた
      int high = Serial1.read();
      int low  = Serial1.read();

      if (high < 128 && low < 128) {  // データは必ず 127 以下
        int value = ((head - 128) << 14) | (high << 7) | low;
        Serial.println(value);        // シリアルモニタに出す
      }
    }
    // 128 未満はヘッダーではない。捨てて次へ
  }
}

メインがやっていることは3つです。

書き方やっていること
available() >= 33バイトたまるまで読まない。1組がそろう前に読みはじめないためです
head >= 128ヘッダーかどうかを見る。128 未満なら、その1バイトを捨てる
<< と |左へずらして元の位置に戻し、3つをつなぐ

359 なら (0 << 14) | (2 << 7) | 103。2 を7ビット左へずらすと 256、そこに 103 をつないで 359 に戻ります。

メインを USB でパソコンにつなぎ、シリアルモニタを 115200 で開きます。0, 1, 2 … 359, 0, 1 … と並べば成功です。

実際のシリアルモニタ。255 を超えても数がとぎれず、359 のつぎは 0 にもどる。

ずれても自分で戻る ― 128 未満は捨てる

メインが読みはじめたとき、バッファの先頭が1組の先頭とは限りません。途中の1バイトが落ちることもあります。

途中から読みはじめたとき 101 128 2 102 128 2 103 128 未満 捨てる 128 以上。ここが先頭 3バイトで1組 → 358 3バイトで1組 → 359 古い 新しい 128 以上が出るまで1バイトずつ捨てる。つぎの組からは正しく読める
先頭にあるのが前の組の残りでも、128 以上が出たところから数えなおせる。

high < 128 && low < 128 の1行は、途中の1バイトが落ちたときのためにあります。そのときメインは、次の組のヘッダーを low として読みます。データは必ず 127 以下なので、128 以上がまざった組は捨てます。

最新の値を取る ― 関数にまとめる

ロボットが使うのはいちばん新しい角度だけです。受ける部分を関数にして、最後に読めた1組を返します。

// メイン ― 最新の角度を返す
int readAngle() {
  static int result = 0;              // 前回の値を覚えておく

  while (Serial1.available() >= 3) {
    int head = Serial1.read();

    if (head >= 128) {
      int high = Serial1.read();
      int low  = Serial1.read();

      if (high < 128 && low < 128) {
        result = ((head - 128) << 14) | (high << 7) | low;   // 上書きしていく
      }
    }
  }

  return result;                      // 最後の1組が残る
}

void loop() {
  int angle = readAngle();

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

static を付けた変数は、関数を抜けても値が消えません。新しい組が届いていない周は、前回の値をそのまま返します。

while で読み切るのは、シリアル通信の回の「最新の値を取る」と同じ考え方です。たまっている組を古い順に読み、最後の1組だけが result に残ります。

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

症状原因対処
何も出ない配線かボーレートがちがうシリアル通信の回の 1〜100 のプログラムに戻して、先に確かめる
まるでちがう値が出る送る順と読む順がちがう送る側も読む側も head → high → low にそろえる
数が飛ぶバッファがあふれているメインの loop() から delay() を外す
値が変わらないサブが止まっているサブの loop() が回っているか、LED を点滅させて見る
ロボサイエンスについて

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

前の回:Pico 2個で会話させる ― シリアル通信 →
ロボサイエンスの記録一覧 →
体験授業・お問い合わせ →

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