common-lisp
電子工作
RaspberryPi

CommonLispでRaspberryPi電子工作 ~シリアル通信2~

はじめに

前回はRaspberryPiからパソコンへの通信を行いましたが、今回はパソコンからRaspberryPIへの通信(RaspberryPiでのデータ受信)をやってみたいと思います。
前回同様、超小型USBシリアル・モジュール【MPL2303SA】を使います。

注) シリアルコンソールの無効化、回路図、パッケージは前回同様なので今回は省略します。

APIラッパー作成

今回追加する関数は1つだけです。

・serialGetchar
シリアルデバイス上で利用可能な次の文字を返します。
この呼び出しは、データが利用出来無い場合(-1を返す場合)、最大10秒間ブロックします。

libwiringPi.lisp
(define-foreign-library libwiringPi
  (:unix "libwiringPi.so"))

(use-foreign-library libwiringPi)

;;;; Core function

;; Initialization of the wiringPi
(defcfun "wiringPiSetupGpio" :int)

;; Set the mode of the GPIO pin
(defcfun "pinMode" :void (pin :int) (mode :int))

;; GPIO pin output control
(defcfun "digitalWrite" :void (pin :int) (value :int))

;; Waiting process
(defcfun "delay" :void (howlong :uint))

;; Set the state when nothing is connected to the terminal
(defcfun "pullUpDnControl" :void (pin :int) (pud :int))

;; Read the status of the GPIO pin
(defcfun "digitalRead" :int (pin :int))

;;;; I2C Library

;; Initialization of the I2C systems.
(defcfun "wiringPiI2CSetup" :int (fd :int))

;; Writes 8-bit data to the instructed device register.
(defcfun "wiringPiI2CWriteReg8" :int (fd :int) (reg :int) (data :int))

;; It reads the 16-bit value from the indicated device register.
(defcfun "wiringPiI2CReadReg16" :int (fd :int) (reg :int))

;;;; SPI library

;; Initialization of the SPI systems.
(defcfun "wiringPiSPISetup" :int (channel :int) (speed :int))

;; Execute concurrent write / read transactions on the selected SPI bus
(defcfun "wiringPiSPIDataRW" :int (channel :int) (data :pointer) (len :int))

;;;; Serial Library

;; Initialize the serial device and set the baud rate.
(defcfun "serialOpen" :int (device :string) (baud :int))

;; Close the device identified by the specified file descriptor.
(defcfun "serialClose" :void (fd :int))

;; Sends one byte to the device identified by the specified file descriptor.
(defcfun "serialPutchar" :void (fd :int) (c :unsigned-char))

;; シリアルデバイス上で利用可能な次の文字を返します。
;; この呼び出しは、データが利用出来無い場合(-1を返す場合)、最大10秒間ブロックします。
(defcfun "serialGetchar" :int (fd :int))

シリアル通信(受信)プログラム本体

パソコン側から送られてきたデータを受信し、標準出力するプログラムです。

serial-mpl2303sa-receive.lisp
;; Load packages
(load "packages.lisp" :external-format :utf-8)

(in-package :cl-cffi)

;; Load wrapper API
(load "libwiringPi.lisp" :external-format :utf-8)

(defun main ()
  (let (fd input-data)
    ;; Serial Open. For RaspberryPi2, use "/dev/ttyAMA0".
    (setf fd (serialOpen "/dev/ttyS0" 115200))
    (if (< fd 0)
        (return-from main nil))

    ;; データ受信処理ループ
    (loop
       (setf input-data (serialGetchar fd))
       ;; 「serialGetchar」が「-1」を返すまでの10秒間ループする。
       (if (< input-data 0) (return))
       (format t "~a" (code-char input-data))
       (delay 100))

    ;; Serial Close
    (serialClose fd)))

;; Executable!!!
(main)

Tera Termの設定
パソコン側ではTera Termを使ってデータを送信します。

Tera Termを起動し、シリアルポートを選択して、「OK」をクリック。

serial-connect-01.png

メニューバーから「設定(S)」⇒「シリアルポート(E)...」を選択すると以下のような画面が現れます。
赤枠で囲った箇所を「115200」に設定し、「OK」をクリック。

serial-connect-02.png

次に、メニューバーから「設定(S)」⇒「端末(T)...」を選択すると以下のような画面が現れます。
赤枠で囲った箇所を「CR」に設定し、「OK」をクリック。

serial-connect-03.png

また、送信するためのデータを作成します。
send-data.ttlというファイルを作成し、以下の内容を記載してください。
これは、Hello, world!という文字列をTera Termで送信するためのマクロです。

send-data.ttl
send $48$65$6C$6C$6F$2C$20$77$6F$72$6C$64$21$0D

実行

以下のコマンドで実行します。

sbcl --load serial-mpl2303sa-receive.lisp

serial-mpl2303sa-receive.lispを実行した後、10秒以内に以下手順でTera Termからデータを送信してください。

  1. メニューバーから「コントロール(O)」⇒「マクロ(M)」
  2. エクスプローラーが起動するので、先ほど作成した「send-data.ttl」を選択し、「開く」を押す。

なぜ10秒以内に行わなければいけないかというと、serialGetchar関数が10秒後に「-1」を返してきてしまいループを抜けてしまうからです。
また、なぜかいったんループを抜けないと受信したデータが標準出力されてくれません。
そんな理由もあってこんな変な作りになってしまっています。
これのもっといい使い方ないのでしょうか・・・。

実行中の様子

はい、送信のときと違いがわかりませんが、これは受信した結果です。(笑)
Tera Termからデータを送信し、およそ10秒後くらいにHello, world!と表示され処理が終わります。

最後に

送信と違い受信の方はなかなか厄介ですね~。
一応できることにはできましたが、もっと良いやり方があると思います。

とりあえず、Tera Termでデータ送信のやり方がわかってよかった。(笑)

今回のソースコードはGithubで公開しています。
よろしければ以下からどうぞ~。
Github