Edited at

Goでのシリアル通信でハマった事

More than 3 years have passed since last update.

こんばんは。

普段は組み込み系(主にWindowsCEやOSレス)のC, C++やってます。

Goはほぼ趣味でたまに触っている程度なので、お手柔らかに…。


対象の方


  • Goでシリアル通信してみたい方

  • Goで組み込みっぽい事をやってみたい方


はじめに

Goでお手軽にサブGHz帯通信をしたかったので、go-im920 をつくりました。

インタープラン株式会社様の920MHz無線モジュール、IM920用の制御ライブラリです。

シリアル通信のライブラリは tarm/serial を使わせて貰ったのですが、データ受信でハマったのでご紹介します。

開発はWindows上でおこなっています。


最初はリードタイムアウト


main.go

sc := &serial.Config{Name: "COM4", Baud: 19200, ReadTimeout: 1 * time.Second}

buf := make([]byte, 128)
n, _ = sc.Read(buf)

上記のコードだと受信データサイズが128byteとなるかReadTimeoutするまで、Read()から復帰しません。

IM920は送信したコマンドに対し毎回応答を返してくるのですが、その度にReadTimeoutを待つのは時間がかかりすぎです。

かといって、ReadTimeoutを短くし過ぎると応答を受信できない可能性があります。

いくつかのコマンドを除き応答は固定長(4byte)だったので、とりあえず buf := make([]byte, 4) で逃げました。


次は応答受信しきる前に復帰

tarm/serial の このコミット から、受信データがあれば即座に復帰するように挙動が変わりました。


IM920はほとんどのコマンドに対して "OK\r\n" か "NG\r\n" を返してきますが、この変更で応答を1byte受信しただけ(例:"O")でRead()が復帰するようになってしまいました。困った。


受信インターバルタイマを有効にしようと思ったけど方針転換

Windowsの場合、例えば「受信処理トータルでのタイムアウト」と「データ受信間隔に対してのタイムアウト」が 設定 できます。

Linuxの場合、TERMIOS でタイムアウト周りの設定が出来るようですが、Windowsのように「受信処理トータルでのタイムアウト」と「データ受信間隔に対してのタイムアウト」の両立はこれ単体ではできないようです。1

当初はtarm/serialを変更して受信インターバルタイマを有効にしようと考えてましたが、Linuxで同等の挙動を実現するのは難しそう&tarm/serialのREADME.mdに


Please note that this is the total timeout the read operation will wait and not the interval timeout between two bytes.


と記載があったので、受信インターバルタイマ無しがtarmさんの期待値だと判断し、go-im920 内部でどうにかすることにしました。

1byteずつRead()することでtarm/serialのReadTimeoutは受信インターバルタイマとしてのみ使用し、time.NewTimer()で受信処理トータルでのタイムアウトを検知します。


go-im920.go

func (im *IM920) receive(p []byte) (readed int, err error) {

timer := time.NewTimer(im.readTimeout)
defer timer.Stop()

readedInitialbyte := false

for {
select {
case <-timer.C:
if readed == 0 {
err = fmt.Errorf("error: Read failed: no data")
}
return
default:
n, rerr := im.s.Read(p[readed : readed+1])
if rerr != nil {
err = fmt.Errorf("error: Read failed: %s", rerr)
return
}
if n > 0 && !readedInitialbyte {
readedInitialbyte = true
}
if n == 0 && readedInitialbyte {
return
}

readed += n
if readed >= len(p) {
return
}
}
}
}


もっとうまいやり方があるのかもしれませんが、正常に動作しているようなので良しとします。


最後に

このネタ全然Goっぽくない…。





  1. 実はselect()併用すればできそう?