丼's tech blog

2012/11/28~2023/12/21 間で運営していた「例のブログ」の後釜です。記事を抜粋してコッチに移植します。

GPIO繋いでRaspberry Pi⇔ESP32でシリアル通信(C/C++, ArduinoCore)

Abstract

 Raspberry Pi でカメラから得た情報を ESP32(ArduinoCore)に送信したかったので,シリアル通信をしました.忘備録も兼ねてサンプルコードをここに書いときます.

やりたいこと

  • Raspberry Pi が得た情報を ESP32(Arduino Core)にシリアル通信で送りたい
  • USBシリアルではなく,GPIOピンの Tx - Rx を繋いでシリアル通信をしたい
  • ESP32 も Raspberry Pi 側も C/C++ で実装したい
f:id:teppodone:20200912113416j:plain
RPiとESPを「GPIOで通信」し「C++で実装」したかったお話です

 

環境

下記のボードを使いました.

 
詳細
Raspberry Pi Raspberry Pi 2
ESP32 HiLetgo ESP32 ESP-32S NodeMCU開発ボード2.4GHz WiFi + Bluetoothデュアルモード

 とはいえ例のごとく Raspberry Pi だったら2じゃなくて 3でも4でもZeroでも何でも良いと思いますし,ESP32側もESP32本体が載ってるなら ESP-WROOM-32ブレイクアウトSD+ - スイッチサイエンス やAliexpressで二束三文で売ってそうなボードでも出来るでしょう多分(未検証)



シリアル通信のやり方

1. コード

 RaspberryPi,ESP32 にそれぞれに下記のコードないしスケッチの書き込みを行いました.

Raspberry Pi(送信側)のコード
// serialOnGPIO.cpp 
// 「GPIOを繋いで Raspberry Pi⇔ESP32(Arduino Core) でシリアル通信をする(C/C++)」


#include <stdio.h>
#include <unistd.h>          // sleep()関数を使うのに必要
#include <wiringPi.h>  // C/C++でGPIOピンを触るのに必要
#include <wiringSerial.h> // C/C++でGPIOピンを触るのに必要


int main(){

  // シリアルポートオープン,ボーレート(19200)はESP32側と統一すること
  int fd = serialOpen("/dev/serial0",19200);    	
  wiringPiSetup();
  fflush(stdout);

  //シリアルポートを開くことが出来たか否かの確認
  if(fd<0) printf("can not open serialport\n");    
  else       printf("serialport opened\n");

  char cnt = 0;
	
  //シリアル通信で1秒おきに cnt の値を送る
  while(1){
    serialPutchar(fd,cnt);              //cntの値を送る
    printf("RPi : send %d \n",cnt);    //ついでにターミナルにcntの値を出力する

    if(255<cnt) cnt = 0;                //ざっくり言うと serialPutchar() で送れるのは 0~255 までなので,cntが255を超えたら0にするようにしてる
    else cnt ++ ;

    sleep(1);                           // 1秒待機
  }
  return 0;
}
# コンパイルと実行
pi@raspberrypi:~/ $ g++ -Wall  serialOnGPIO.cpp -lwiringPi 
pi@raspberrypi:~/ $ ./a.out

 

ESP32(受信側)のコード
// serialOnGPIO.ino
// 「GPIOを繋いで Raspberry Pi⇔ESP32(Arduino Core) でシリアル通信をする(C/C++)」

void setup() {
  Serial.begin(19200); //ボーレート(19200)はESP32側と統一すること
  Serial.println("start");  
}


void loop() {  
  if(0<Serial.available()){
    char data = Serial.read();  // 受け取った値を入れとく変数
    Serial.print("received");
    Serial.println((int)data);    // 受け取った値をシリアルモニタで表示
  }
  Serial.flush();
  delay(500);
}

 

注意点1. 書き込みの際は線を抜く

※ ArduinoIDEからESP32にスケッチを書き込む際には,Tx ,Rx の接続を解除しておく(ジャンパーケーブル抜いとく)必要があります(繋いだままスケッチを書き込もうとすると書き込みエラーとなる).書き込んだあとに元通り結線すればOKです.

 

注意点2. ボーレートを統一する

 上記コード中でボーレートを”19200”に設定していますが(Raspberry Pi のコードの13行目,ESP32のコードの5行目),このように Raspberry Pi 側(送信側),ESP32側(受信側)のボーレートは統一する必要があります.

 

2. 結線する

 Raspberry Pi と ESP32を下記のように結線しました.

f:id:teppodone:20200912113308p:plain
こんな感じ


ポイントは次のとおりです.

 なお,GPIO - Raspberry Pi Documentationによると,Tx,Rx の配置は8番,10番ピンに充てられています.

f:id:teppodone:20200912123805p:plain
画像はGPIO - Raspberry Pi Documentation(2020-09-12閲覧)より引用

 

3. プログラムを実行し,シリアル通信を行う
1. 結線がちゃんと出来ているか確認する

 スケッチを書き込む度に「結線を外す→ESP32にスケッチをアップロードする→アップロード後に再度結線する」…という手順を踏む必要がありますが,僕は94割の確率でRxとTxを逆刺ししてしまいます.

2. RaspberryPi側の送信プログラムを実行する

 スケッチを書き込んだ後に,RaspberryPi 側の送信プログラムを実行します.

pi@raspberrypi:~/ $ ./a.out

 そうると,ターミナルに「RPi : send (0~255の数字)」が1秒おきに表示されると思います.

3. ESP32のシリアルモニタで,RaspberryPi側が送った数字を確認出来れば成功

 最後に,そのまま(RaspberryPi側のプログラムを実行したまま)PCとESP32をUSBケーブル等で繋ぎArduinoIDE等からシリアルモニタを開きます(ここでもボーレートは19200).ここで,RaspberryPi側が送った数字と同じ数字がシリアルモニタに写っていればシリアル通信成功です!

www.youtube.com



ポイント

 ポイントと言うほどのこともありませんが,メモ書き程度に記しておきます.

Wiring Pi を使った

 今回,C/C++でシリアル通信を行うため Raspberry Pi 側(送信側)のプログラムに「Wiring Pi」を使いました.
  wiringpi.com
 Wiring Pi は,Raspberry Pi の GPIOピンをC言語で操るためのライブラリで,その中にシリアル通信を行うための便利機能も含まれています.上記コード中では6, 7行目でインクルードし,13, 14, 25行目でWiring Pi のライブラリからシリアル通信を行っています.

 Wiring Pi のインストール方法等はググれば出てくるので適当に調べて下さい.

 

Raspberry Pi ⇔ ESP32 間の通信と,ESP32 ⇔ PC(シリアルモニタ)間の通信で同じチャンネル使うのあんま良くなさそう

 ここの項はかなり推測まみれのお話なんですが(リファレンス読め)…

 上記のコードでは「Raspberry Pi ⇔ ESP32 間の通信も,ESP32 ⇔ PC(シリアルモニタ)間の通信 も,たった1つの同じ”Serial”オブジェクトで行っている(同じチャンネルを使っている)」といった振る舞いをしているような気がします.
 要は「ESP32から見たときに,1つのチャンネルを使ってRaspberry Pi と通信をし,シリアルモニタとも通信しているんじゃないか」ということです.(要出典).

 本来,シリアル通信(UART)とは1対1通信のための規格なので,あまりこういうお行儀の悪いことはしないほうが良いんじゃねぇかなァ~って気がします.無用なトラブルを未然に防ぐ的な意味で.
 

じゃぁどうすればええねん

 ESP32には3チャンネルの Hardwareserial が備わっている*1ので

  • Raspberry Pi ⇔ ESP32 間の通信に UART0 を
  • ESP32 ⇔ PC(シリアルモニタ)間の通信にUART1を

 といった感じでRaspberry Pi ⇔ ESP32 間,ESP32 ⇔ PC(シリアルモニタ)間で別々のハードウエアシリアルを割り当ててあげればいいんじゃないでしょうか.しらんけど*2
qiita.com

 

int 型の数字とか送ろうと思ったらもうひと工夫必要

 本記事のコードでは char 型すなわち 0~255 の範囲でしか数字を送れませんが,それより広い範囲の数字を送ろうと思ったらもうひと工夫必要です.一応,出来るには出来たので気が向いたら記事書きます(2バイトint (最大 32767)の情報をやりとりしてる様子).




参考文献

日記
f:id:teppodone:20200912132541j:plain
てぽどさん「聞いてよアカネチャン」
 ふと久々にウンコジャマーの動画見たら,知らんうちにニコニコ兵器開発局のタグランキングで1位取ってました.しかも今年の3月.嬉し半分,なんで今さら??という疑問も大きいです(投稿したのは2018年)
 とはいえ,動画を色んな人に見てもられることは工作動画マンにとってはこの上なく嬉しことです.ありがとうございます.もっと見て下さい(視聴推奨).
 
ウンコ妨害装置を作ってみた【ウンコジャマー】 - ニコニコ動画
ウンコ妨害装置を作ってみた【ウンコジャマー】, YouTube
 

*1:Arduino-ESP32 Serial通信 - Qiita,2020-09-10閲覧

*2:というか,最初これ(1つのチャンネルで2方向に通信するのは)出来んでしょと思い込んでたけど,適当にやってみたら出来てしまったのでビビった