公開日:2019/09/28
title.png

7th Season


#7-7 Pi ZeroWの非同期シリアル通信

 「#7-5 Raspberry PiにRTC(DS1302)を接続する」では、クロック同期シリアル通信方式でRTC(DS1302)をPi ZeroWから制御することができました。一方で、「#7-3 Raspberry Piで気温・湿度センサを読み出す」の様な、μsオーダの非同期通信には対応できないこともわかっています。
 つまり、「Raspberry Piでも、一部の電子制御は可能だけど、Arduinoの代わり(置き換え)にはならない」と言うこと。

 「餅は餅屋」、Raspberry Pi、Arduino共、お互いに得意なことを分担して処理させるのが良さそうです。そのためには、Raspberry PiとArduinoの通信手段を確保しておく必要があります。

 今回はそんなお話。

FTDIを使って、Pi ZeroWと通信する

 まず、最初の段階は、「#7-2 デザートにラズベリーパイはいかが?」で実行した初期設定の状態になっていることを前提とします。「ラズベリーアイコン ⇒ 設定 ⇒ Raspberry Piの設定 ⇒ インターフェイスタブ」で、「Serial Port」が「有効」になっていることを確認して下さい。

 USB⇔UART変換モジュール(FTDI、以下図)を使って、PCとPi ZeroWをシリアル接続してみます。

ar41_ftdi.jpg

 Pi ZeroWの物理ピン#8と#10が、UARTのTxD,RxDに割り当たっていますので、以下の様に接続します。

Pi ZeroW 接続 FTDI
GND(phys#6等) GND
TxD(phys#8) RXD
RxD(phys#10) TXD
  × VCC(ジャンパを3.3V側に設定)

 Pi ZeroWのI/Oは3.3V定格なので、FTDIのジャンパは3.3V側に設定してください。後はいつもの通り、GNDを取って、RXDとTXDをクロス接続です。

 これで、何かしら通信は成立しているはずなのですが、通信速度やフォーマットが不明ですね。まずは、WindowsでTera Termを起動して、FTDIのCOMポートと接続してみます。
 Tera Termのウインドウで「ENTERキー」を押したところ、何やら文字化けしましたが、何らかのメッセージがPi ZeroWから返ってきているみたいです。当初、Tera Termの通信速度設定を9,600baudにしていたのですが、これを115,200baudに変更して、もう一度「ENTERキー」を押したところ、以下の様なメッセージを確認できました。

  raspberrypi login:

 Raspbianへのログインメッセージみたいですね。ユーザpiと、そのパスワードを入力したところ、何と、ログインできてしまいました。普通に、【ls -l】とか、コマンドを受け付けるし、Pi ZeroWで作ったファイルやディレクトリも、そのまま見えています。

 この時のTera Termの設定は以下の通りです:

 すごぉ〜い🌟。昔を思い出すよ(SLIPとかPLIPとか) 。でも、これぢゃ、無いんだなぁ〜。Arduinoからログインできても、嬉しくないし……。
 それどころか、逆に邪魔(1x_x)。シリアルからログインできるようにするための、何らかのプログラムが、貴重なシリアルポートを占有してしまっているということ。何とかせねば。

Pi ZeroWのシリアルユニット

 調査を進めると、Pi ZeroWのシリアルユニットは、2本あるみたいです。mini UARTとPL011 UARTと、呼ばれており、デフォルトではmini UARTが#8と#10に割り当たっていて、PL011 UARTはBluetooth通信に使われているとのこと。
 それぞれ、Raspbianでは、/dev/ttyS0と、/dev/ttyAMA0と言う名前のデバイス名で参照されています(以下、まとめ表)。

シリアルユニット デバイス名 別名 備考
mini UART /dev/ttyS0 /dev/serial0 I/Oピン#8と#10に割り当たっている。
PL011 UART /dev/ttyAMA0 /dev/serial1 Bluetooth通信で使用されている。

※この設定と、デバイス名は、Pi ZeroWのデフォルト設定の場合です。何らかの設定変更を行った場合や、Pi ZeroW以外の機種では、異なる場合があります。

 別名は、I/Oポート(#8と#10)に割り当てられているシリアルユニットが、/dev/serial0(プライマリシリアル)となるように構成されます。よって、デバイス名でアクセスする場合は、別名(/dev/serial0)の方を使っておくと、他の機種に乗り換えた場合にも、そのまま使用することが可能です。
 逆に、別名の方は、設定が変わると、"0"と"1"が入れ替わったりしますので、「どんな時でも確実にPL011 UARTを使いたい」と言う場合には、/dev/ttyAMA0でアクセスする必要があります。

 今回使いたいのはmini UARTの方で、デバイス名はttyS0ですね。

シリアルの設定変更

 良さげな設定項目を見つけました。

  実験サンプル:【 】内はshellウインドウでのコマンド入力
  【sudo raspi-config】 …… 設定画面が開く。

 「login shell=No」としたことで、シリアルからログインを受け付けるプログラムが無効化できたのではないか?と推測しました。
 再起動が終了したところで、さっき開いて、そのままにしておいたTera Termのウインドウで、ENTERキーを押してみましたが、反応がありません。無効化できたのか?Raspbianのデスクトップでshellウインドウを開いて、状態を確かめてみます。

  実験サンプル:【 】内はshellウインドウでのコマンド入力
  【ls -l /dev/serial0 /dev/ttyS0】
  lrwxrwxrwx 1 root root 5 Sep1 10:45 /dev/serial0 -> ttyS0
  crw--w---- 1 root tty 4, 64 Sep1 10:45 /dev/ttyS0

  【stty -F /dev/ttyS0 speed】
  9600

 前節で、通信に使いたいUARTのデバイス名がttyS0だとわかりましたので、これを確認してみました。何で?通信速度が9,600baudに変わってますね。Tera Termの「Setupメニュー ⇒ Serial port」で通信速度を9,600baudに合わせてみましょう。
 9,600baudに設定して、Tera TermのウインドウでENTERキーを押したところ……。

  raspberrypi login:

 (1^_^;)ダメじゃん。通信速度が合ってなかったので、反応が無いように見えただけ。通信速度が115,200baudから9,600baudに変わったけど、相変わらず、シリアルからログインを受け付けるプログラムが起動している模様。

 どうやら、シリアルからログインを受け付けるプログラムが2系統あって、その内の1つを無効化できただけみたいです。

更に調査を続けて、ようやくこれが正解?

 まず、前節までの設定は、「必要かつ正解(の一部)」です。前節の設定を実行した上で、以下に進んでください。

 更に調査を進めたところ、前節の設定に加えて、以下の設定が必要でした。

  実験サンプル:【 】内はshellウインドウでのコマンド入力
  【sudo systemctl stop serial-getty@ttyS0.service】 …… ttyS0のloginを停止

  【sudo systemctl disable serial-getty@ttyS0.service】 …… 将来に渡って無効化
  Removed /etc/systemd/system/getty.target.wants/serial-getty@ttyS0.service.

  【sudo reboot】 …… ここで、一旦再起動が必要

  …… 以下、再起動後に実行
  【ls -l /dev/serial0 /dev/ttyS0】
  lrwxrwxrwx 1 root root 5 Sep1 11:00 /dev/serial0 -> ttyS0
  crw-rw---- 1 root dialout 4, 64 Sep1 11:00 /dev/ttyS0

  【stty -F /dev/ttyS0 speed】
  9600

 lsコマンドの表示が上の様になっていたら、成功です。着目点は、ttyS0のグループdialoutにリード/ライトアクセスが許可されていることです。piはdialoutグループに属しているので、これで、ttyS0にアクセス可能となりました。
 完全に設定を変更するので、一度実行しておくと、再起動しても同じ状態で起動します。本HP(Arduino DIY)では、以後、上の設定を実行してあることを前提とします。

 ふと、「ラズベリーアイコン ⇒ 設定 ⇒ Raspberry Piの設定 ⇒ インターフェイス」を見たところ、「カメラ」と「リモートGPIO」以外すべて「有効」にしてあったはずなのに、「Serial Console」が「無効」になっていました(下図)。

ar77_rpiset.png

 2系統の内のどっちの設定かは、わかわからないけど、シリアルからログインを受け付けるプログラムを無効化したことが反映されている模様です。イメージ的には、【sudo raspi-config】の方ですね。
 冷静になってみると、確かに……。いつか使うだろうからと、「全部有効だ〜」ってしていたけど、愚かだった……。

元に戻すには?

 本HP(Arduino DIY)では、基本的にArduinoなどとの通信用にシリアルを使うので、元に戻す必要は無いのですが、一応、元に戻す方法を記載しておきます。

  【sudo systemctl enable serial-getty@ttyS0.service】
  Created symlink /etc/systemd/system/getty.target.wants/serial-getty@ttyS0.service
  → /lib/systemd/system/serial-getty@.service.

  【sudo raspi-config】
  「5 Interfacing Options ⇒ P6 Serial」を選択。
  「login shell=Yes」に変更。
  「Finish」すると、再起動するか聞いてくるので、再起動。

 試してみたことはありませんが、単に、シリアルからコンソールに繋ぎたいだけなら、上のどちらか一方だけでも良さそうな気はします。

Re:FTDIとの非同期シリアル通信(リトライ)

 まずは、Pi ZeroW側のシリアル設定を確認しておきましょう。

  【stty -F /dev/serial0 -a】
  speed 9600 baud; rows 0; columns 0; line = 0;
  intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
  eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
  werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
  -parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal -crtscts
  -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
  -iuclc -ixany -imaxbel -iutf8
  opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
  isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
  echoctl echoke -flusho -extproc

※パラメータの詳細については、【man stty】でご確認ください。

 各パラメータは、頭にハイフン(-)が付いていると無効、付いていなければ有効、を表しています。
 8ビット(cs8)、パリティなし(-parenb)、1ストップビット(-cstopb)と、非同期シリアル通信フォーマットは、ArduinoのSERIAL_8N1(デフォルト値)と一致しています。ただし、シリアルに何らかの「端末」が繋がることを想定しているためか、echoが有効になっています。これを無効にしておく必要があります。

  【stty -F /dev/serial0 -echo】

 もし、Pi ZeroW側の通信速度を変更したい場合には、以下の様にします。

  【stty -F /dev/serial0 115200】

 これからArduinoと通信しようとしているので、Arduino側の通信速度と合わせておく必要があります。Tera Termの場合も同じです。
 これらの設定は、再起動するとデフォルトに戻ってしまいますので、必要なら、毎回実行する必要があります。デフォルト値をどこで変更できるか?は、知りませんm(1__)m。

 これで準備OK。改めて、Tera TermでFTDIのCOMポートを開きます。そうしたら、Pi ZeroWのshellウインドウで、以下の様に実行してみます。

  【echo "hello, world" > /dev/serial0】

 Tera Termのウインドウに、「hello, world」と、表示されましたか?

 逆方向のテストとして、Tera Termからデータを送信してみましょう。まずは、Pi ZeroWのshellウインドウで以下のコマンドを実行しておきます。

  【cat /dev/serial0】 …… /dev/serial0を読み込んで、画面に表示

 Tera Termから、何か任意の文字列を打ち込んでみます。例えば、「hello, raspbian<ENTERキー>」。今度は、Pi ZeroWの画面に表示されましたか?これで動作確認は終了です。catコマンドは、ずっと実行し続けるので、不要になったら、CTRL+Cで停止してください。

 ようやく、Pi ZeroWのシリアルユニットを使って、通信する準備が整いました。たったこれだけのことだけど、調査時間と試行錯誤を含めて、何時間かかっただろう?わかってしまえば、簡単なのですが、たどり着くのが大変です。
 Arduinoだと、「Serial.begin (9600);」で終了なのにね……。Raspbianは、やっぱり窮屈だ……。

Arduinoとの非同期シリアル通信

 「#7-3 Raspberry Piで気温・湿度センサを読み出す」では、うまく読み出せなかった、気温・湿度センサDHT11をArduinoに読ませて、その結果をシリアル経由でArduino→Pi ZeroWへ送信させてみようと思います。
 この連携が可能になると、DHT11もそうですが、赤外線リモコンの送・受信など、リアルタイム処理が必要で、Raspbianの苦手とする処理はArduinoにやらせて、結果を使った上位の制御はPi ZeroWで実行する、という分業が可能になります(Arduinoを電子制御コプロセッサ化する)。

 これって、実は、今までの連載の中で、Arduino⇔Windows間でやっていたこと、その物ですよね?Arduinoの通信相手がRaspbianになっただけのことです。もっとストレートに言うと、今までArduinoで作ってきたプログラムが、(ほぼ)そのまま、使用することができる、ということです。以下、ご参考です。

 「(ほぼ)」の理由は、ArduinoのSerialクラス(のインスタンス)です。Arduino UNOでWindowsと通信するのに使っていた"Serial"インスタンスは、USB経由で非同期シリアル通信するクラスでした。今回は、USB経由ではなく、直接シリアルユニットとつながる必要があるので、Arduino側のプログラムで、「Serial.read()」等とアクセスしていたところを、例えば「SerialHssw.read()」等に置き換えて、高速ソフトウェアシリアルライブラリ(HsswSerial.h)を#includeする必要があります。機種によって対応が異なるので、以下、まとめておきます。

 手元にArduino Pro Miniがあるので、これを使って、Arduinoのプログラムを修正せずに使用します。Arduino Pro Miniには、予め、プログラム(meas_am2302.ino)をコンパイルして転送しておいて下さい。

 プログラムの転送が終わったら、Pi ZeroW、L/S、Pro Mini、DHT11を、以下の様に接続します。I/Oレベルが合わないので、レベルシフタ(L/S)が必要です。

Pi ZeroW 接続 L/S 接続 Arduino Pro Mini 接続 DHT11モジュール
3.3V(phys#1等) LV    
5V(phys#2等) HV VCC VDD
GND(phys#6等) GND GND GND
    D2 SDA
TxD(phys#8) LV1    
RxD(phys#10) LV2
  HV1 RXD(D0)
HV2 TXD(D1)

 Pro Miniなら、そんなに電流を流さないはずなので、Pi ZeroWから5V電源を取ってしまいます。……消費電流については、「#7-4 USB電圧・電流計で電流を測定する」を参照のこと。
 他の機体を使用する場合は、各自判断してください。

通信スクリプトを作成する

 awkで、Arduinoと通信するスクリプトを組みます。以下、スクリプトの下にその部分の説明を追加します。

  #! /usr/bin/awk -f
  BEGIN {


 「awkのスクリプトが始まりますよ」のおまじないです。

    # init
    Serial="/dev/serial0";
    system ("stty -F " Serial " 9600 -echo igncr");
    PROCINFO[Serial, "READ_TIMEOUT"]=4000;


 初期化処理。シリアルのデバイス名をSerialに設定、通信速度(9,600)とエコーの禁止(-echo)、CRコード無視(igncr)、読み出しタイムアウトを4,000msに設定します。
 やってみてわかったのですが、ArduinoのSerial.println()が、改行コードにCR+LFを送ってくる様です。受け取るPi ZeroWは?と言うと、sttyの設定でicrnl(CRをLFとして扱う)が活きているので、CR+LFの改行コードに対して、1行余計に空行を受信してしまいます。
 これを回避するために、CRコードを無視する設定(igncr)を追加しました。何か他に、もっと良い設定があるのかも?だけど……。今後、バイナリデータを受信する時にigncrを設定してしまうとアウトなので、留意しておいてください。

    # "rrt" command
    print "rrt" > Serial;


 Arduino(meas_am2302.ino)へ、シリアルを通して【rrt】コマンドを送る。

    getline < Serial;

 Arduino(meas_am2302.ino)からの応答を、シリアル経由で受け取る。

    if (ERRNO=="")
      print;
    else
      print ERRNO;
  }


 タイムアウトするまでデータを受信できなかった場合など、エラーが起きた時の処理。エラーが起きなかった場合は、ERRNO==""なので、受信したデータを表示して終了。

  完成スクリプト:meas_am2302.awk

 以下、実験中の様子です。Arduino Pro Miniの電源はPi ZeroWのピンヘッダから供給できるので、余計な電源ケーブル(USBケーブル等)無しでコンパクトに仕上がりました(以下図)。

ar77_expr.jpg

  実験サンプル:【 】内はshellウインドウでのコマンド入力
  【meas_am2302.awk】
  DHT11,1.02
  OK,31.0,49.0

 meas_am2302.inoのバージョンと、室温31℃/湿度49%RHが読み出せました。引き続き、連続して読み出すテストを実施します。

  【meas_am2302.awk 10】
  DHT11,1.02
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0
  OK,31.0,49.0

※DHT11のサイクルタイム(2s)を満たすため、各測定には2s以上のインターバルが取られます。よって、上例の様に(10+1)回読み出すと、(10+1)回×2s=22sほどかかります。

 「#7-3 Raspberry Piで気温・湿度センサを読み出す」では、Pi ZeroWからDHT11を制御しようとして、通信エラーが多発したのですが、Arduinoとの非同期シリアル通信でデータを受け取る方法では、エラーも発生せず、安定感があります。

 もし、うまく通信できない様なら、以下を実行してシリアルの設定値を確認してみてください。

  【stty -F /dev/serial0】 …… meas_am2302.awk実行後のstty設定を表示
  speed 9600 baud; line = 0;
  -brkint igncr -imaxbel
  -echo

 デフォルト値の設定が違っていると、動かない場合があるかもしれませんが、その場合は、awkスクリプト中のsttyコマンドにパラメータを追加して、上の表示結果に合わせるようにすれば動くはずです。

機体差、OS(バージョン等)構成差、等

 Pi ZeroWで非同期シリアル通信をするために、色々調べました。インターネット上で複数の方法が見つかったのですが、いくつかの方法では、うまく行きませんでした。Raspbianのバージョン、セットアップ状態、Raspberry Piの型番、等々によって、必要な対応が異なることが原因の様です。
 特にPi ZeroWは、外部のチップ(ただしオンボード)と、Bluetooth通信するためにシリアルI/Fを使っているため、BluetoothとのI/Fが無い、他のRaspberry Piとは事情が異なっているようですので、ご注意ください。

 同様に、本ページに辿り着いた皆様の中にも、本ページの方法では、うまく行かないケースがあるだろうと推測します。「OSに遊ばれている」様で、良い気分ではありませんが、皆様の環境に合った対処方法を探すしか手はありませんので、他のページも参考に、試行してみてください。


 やうやうと、Raspberry Piの使い所(使い方)が解ってきました。「細かな電子制御には全く使えない」と言う事でもないし、不得意とするリアルタイム処理の部分だけをArduino等のコプロセッサに肩代わりさせれば、後は、高速CPUと大容量メモリが、大抵の処理を可能にしてくれるでしょう。ようやく、「これから」です。


btn_arduino.png

btn_prev.png btn_next.png

home