« ◇Pythonプログラミングにおける最重要事項 | トップページ | ◇raspi3お手軽試験キットで、LEDランプ点滅、センサー値取得 »

◇USB-シリアル-Python/Java:Raspi/Ubntu/WIn

 RasPiでUSBシリアルアクセスする

RaspberryPi3にはデフォルトでUSBシリアル(FT232)のドライバが組まれているため、 変換ケーブルをUSBに挿してシリアルアクセスすることができます。

Raspi2台をUSBシリアルで対向させてみました。

「USB-DSUB9pinケーブル」2本をDSUBクロスアダプタ(NULLMODEM)でつないで、両USBを2台のRaspiに繋ぎます。

 端末ソフトcuを用いてアクセス

まず、USB-シリアルがデバイスとしてどう見えているかをdmesgコマンドで調べます。

$ dmesg | grep FTDI
usb 1-1.2: Manufacturer: FTDI
usbserial: USB Serial support registered for FTDI USB Serial Device
[ftdi_sio 1-1.2:1.0: FTDI USB Serial Device converter detected
usb 1-1.2: FTDI USB Serial Device converter now attached to
           ttyUSB0
ftdi_sio ttyUSB0: FTDI USB Serial Device converter now disconnected from ttyUSB0
通常"ttyUSB0"になるようです。/dev/ttyUSB0がデバイスとなります。

cuコマンドがインストールされていない場合apt-getでインストールします。

$ sudo apt-get install cu

アクセス前にケーブルが挿さっていることを確認の上デバイスのモードを666に変えておきます。

$ ls -l /dev/ttyUSB0              # ケーブルが刺さっていれば次のような行がでる
crw-rw---- 1 root dialout 188, 0 11月 26 22:01 /dev/ttyUSB0
$ sudo chmod 666 /dev/ttyUSB0     # 立ち上げ直したりケーブルを挿しかえる度に必要
双方のRasPiでcuコマンドを起動します。
cuコマンドでは-sでボーレート、-lでデバイスを指定します。
$ cu -s 115200 -l /dev/ttyUSB0
Connected. (シリアルアクセスの開始)
hello      (相手が打った文字が出る:こちらで打ったものは相手側に出る)
キーを打つと、相手側に表示されます。

~.(チルダドット)を打つとcuは切断し終了となります。
(切断といってもこちら側での話で相手には切断したことは伝わりません)

 Pythonで装置側とコントロール側を模してアクセス

RasPi2台を簡単な装置役とコントローラー役に分けてシリアル通信を行うプログラムを作成してみました。

言語としてPythonを用いています。 Pythonは変数の宣言や型定義がなく極めて貧弱で危険な言語ですので、Javaの導入を別途検討しています。

装置役のプログラムはカウンターを持ち3秒に1回カウントアップしてはシリアル端子に値を出します。
シリアル端子から"reset"コマンドを受け取るとカウンタをリセットします。
"stop"コマンドを受け取るとプログラムを停止終了します。

コントローラー役のプログラムはキーボードから人間が入力するコマンドを解釈し、シリアル端子に制御コマンドを出します。
装置から送られてくる最新値を記憶し、人間からの要求コマンドで画面に記憶してある値を表示します。

装置役プログラム

# -*- coding: utf-8 -*-
# 内部カウンタの値をUSBシリアルに出力し続ける疑似装置
# USBシリアルから次のコマンドを受け付ける
#   reset : カウンタを0に戻す
#   stop  : 終了する (quit/end/exit)も可
import threading
import time
import serial

class WriteThread(threading.Thread):
   def __init__(self,com_):               # 決まり事
      super(WriteThread, self).__init__() # 必須
      self.com   = com_ # 注意!selfを忘れるとグローバル変数になる
      self.count = 0
      self.stop_flag = False
   def run(self):# 決まり事
      while( not self.stop_flag ):
         self.count = self.count + 1
         self.com.write("count="+str(self.count)+"\r\n")
         time.sleep(3)

class ReadThread(threading.Thread):
   def __init__(self,com_,writer_):      # 決まり事
      super(ReadThread, self).__init__() # 必須
      self.com    = com_
      self.writer = writer_
   def run(self):                        # 決まり事
      while(True):
         line = readline(self.com)
         if( line=="reset" ):
            self.writer.count=0
         if( line=="quit" or line=="end"  or # 行分割は行の最後に \  
             line=="exit" or line=="stop" ):
            self.com.write(line+"\r\n")
            self.com.close()
            self.writer.stop_flag = True
            break

def readline(com_): # Srialのreadlineは2.6以降は行が読めないので自作
   line_raw = ""
   while(True):
      char = com_.read()
      if( char=="" ): return ""
      if( char=="\n" or char=="\r"):break
      line_raw = line_raw+char
   line = line_raw.decode('utf-8')
   return line

def main():
   com    = serial.Serial('/dev/ttyUSB0',115200)
   writer = WriteThread(com)
   writer.start()
   reader = ReadThread(com,writer)
   reader.start()
if __name__ == '__main__':
   main()

コントローラー役プログラム

# -*- coding: utf-8 -*-
# USBシリアルに繋がった疑似装置を制御する。
# 次のキーボード入力コマンドがある
#   reset: 疑似装置にresetコマンドを送る
#   get  : 疑似装置から受っとった最新の値を表示する
#   end  : 終了する
#   stop : 疑似装置を停止する
import threading
import time
import serial
import os

class WriteThread(threading.Thread):
   def __init__(self,com_,reader_):
      super(WriteThread, self).__init__() # 必須
      self.com    = com_
      self.reader = reader_
   def run(self):
      while( True ):
         command_raw = raw_input("> ")
         command = command_raw.decode('utf-8')
         if( command   == "reset" ):
            self.com.write("reset\r\n");
         elif( command == "get" ):
            print self.reader.value
         elif( command == "end" ):
            os._exit(0)
            break
         elif( command == "stop" ):
            self.com.write("stop\r\n")
         elif( command == "help" or command =="?" ):
            print  "get: print current value"
            print  "reset: reset counter on equipment"
            print  "end: end this program"
            print  "stop: stop equipment"
         else:
            print "unknown command"

class ReadThread(threading.Thread):
   def __init__(self,com_):
      super(ReadThread, self).__init__() # 必須
      self.com    = com_
      self.value  = ""
      self.stop_flag   = False
   def run(self):
      while(True and not self.stop_flag):
         line = readline(self.com)
         if( line != "" ):self.value = line

def readline(com_): # PySrialのreadlineは2.6以降は行が読めないので自作
   line_raw = ""
   while(True):
      char = com_.read()
      if( char=="" ): return ""
      if( char=="\n" or char=="\r"):break
      line_raw = line_raw+char
   line = line_raw.decode('utf-8')
   return line

def main():
   com    = serial.Serial('/dev/ttyUSB0',115200)
   reader = ReadThread(com)
   writer = WriteThread(com,reader)
   reader.start()
   writer.start()
if __name__ == '__main__':
   main()

 Pythonプログラミング注意点

前の記事◇Pythonプログラミングにおける最重要事項 に幾つかの注意事項を上げました。

それ以外の点として

・serial.Serialのgetlineは使い物にならない

serial.SerialのgetLineは"\r\n"を行の終わりと解釈しますので、 cuのように"\n"のみを送る相手からの行を受け取ることはできません。
ここでは行制御関数readline(com_)を自作しました。

・serial.Serialにcancel_read()はない

cancel_read()でread()を止めることはできません。

・スレッドでは先頭にsuper(xxx,self).__init__()

これがないと実行時エラーとなります。

 Ubunto(intel 64bit)/Java

ubntuでもUSBシリアルドライバは標準で入っており、cuコマンドも、インストールすれば同じように動きました。

$ sudo apt install cu
後は/dev/ttyUSB0になることなども含め同じです。

JavaでUSBシリアルを使うにはlibrxtx-javaをインストールする必要があります。

$ sudo apt-get install librxtx-java
次のファイルが作られます
/usr/share/java/RXTXcomm.jar 
/usr/lib/jni/librxtxSerial.so

javaコンパイル時にこのjarを指定します
javac -classpath .:/usr/share/java/RXTXcomm.jar sTest.java

実行時はjarとsoを指定します
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/jni/
export CLASSPATH=.:$CLASSPATH:/usr/share/java/RXTXcomm.jar
java sTest

次のような形でアクセスできます。

 ライブラリを標準化する

RXTXcomm.jarとlibrxtxSerial.soを「標準」扱いとし、java/javacで明示する必要をなくすには 次の手続きをとります。
# RXTXcomm.jarをJavaのデフォルト検索フォルダに入れる
# まずはjreフォルダ検索
$ sudo find / -name jre -print
/usr/lib/jvm/java-7-oracle/jre
# jreの下のlib/extフォルダに入れる(lib/ではなくlib/ext/)
$ sudo cp /usr/share/java/RXTXcomm.jar \
          /usr/lib/jvm/java-7-oracle/jre/lib/ext/
#--------------
# librxtxSerial.soを標準にする
$ cd /etc/ld.so.conf.d/
$ sudo vi jni.conf
/usr/lib/jni/     <- この1行だけ入れて保存
$ sudo ldconfig -v

 Windows(64bit)JavaでUSB-シリアルアクセスする

http://jlog.org/rxtx-win.html などから、

  • RXTXcomm.jar
  • rxtxSerial.dll
をダウンロードします。

RXTXcomm.jarをJavaの\lib\extフォルダ、例えば

C:\Program Files\Java\jdk1.7.0_71\jre\lib\ext
C:\Program Files\Java\jre7\lib\ext
にコピーします。
rxtxSerial.dllはbinフォルダ、例えば
C:\Program Files\Java\jdk1.7.0_71\jre\bin
C:\Program Files\Java\jre7\bin
にコピーします。

|

« ◇Pythonプログラミングにおける最重要事項 | トップページ | ◇raspi3お手軽試験キットで、LEDランプ点滅、センサー値取得 »