Windowsのシリアル通信でBREAK信号を送受信する

https://msdn.microsoft.com/ja-jp/library/cc429842.aspx

https://github.com/bamchoh/go-serial/commit/f74298f65a6834a06357510f1d208ff8df764c11

SetCommMask で EV_BREAK を設定しておくこと。そうしないと、エラーになる。

あと、立ち上がりは検出できるけど、立下りは検出できなさそう。

https://ja.stackoverflow.com/questions/41123/rs232c%E3%81%AEbreak%E4%BF%A1%E5%8F%B7%E3%81%AEon-off%E3%81%AE%E6%A4%9C%E5%87%BA%E3%82%92windows%E3%81%A7%E8%A1%8C%E3%81%86%E3%81%AB%E3%81%AF

一応、BREAK信号を送信するのと、受信(?)するののサンプルは以下のような感じで

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/bamchoh/go-serial"
)

type SIO struct {
    port serial.Port
    txq  chan []byte
    rxq  chan []byte
}

func (sio *SIO) Start() {
    go sio.Write()
    go sio.Read()
}

func (sio *SIO) Write() {
    for {
        q := <-sio.txq
        log.Printf("send: %q", q)
        sio.port.Write(q)
    }
}

func (sio *SIO) Read() {
    err := sio.port.SetCommMask(0x0041)
    if err != nil {
        fmt.Println(err)
    }
    for {
        buf := make([]byte, 128)
        n, err := sio.port.Read(buf)
        if err != nil {
            fmt.Println(err)
            time.Sleep(1 * time.Second)
            continue
        }
        sio.rxq <- buf[:n]
    }
}

func (s *SIO) SetBreak() {
    s.port.SetBreak()
}

func (s *SIO) ClearBreak() {
    s.port.ClearBreak()
}

var port = flag.String("p", "COM3", "port name")
var baud = flag.Int("b", 9600, "baud rate")

func main() {
    flag.Parse()
    c := &serial.Mode{BaudRate: *baud}
    s, err := serial.Open(*port, c)
    if err != nil {
        log.Fatal(err)
    }

    txq := make(chan []byte, 5)
    rxq := make(chan []byte, 5)

    sio := SIO{
        s,
        txq,
        rxq,
    }

    go sio.Start()

    go func() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            text := scanner.Text()
            s.SetBreak()
            time.Sleep(10 * time.Millisecond)
            s.ClearBreak()
            txq <- []byte(text)
        }
    }()

    for {
        q := <-rxq
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("recv: %v", q)
    }
}

余談だけど、LinuxだとBREAK信号の受信が\FF\00\00という3バイトに変換されてしまうみたい。 バイナリデータの送受信だと、このバイト列が来るようなプロトコルを使うようなものだと BREAK信号を送信できないという制限がかかってしまう。回避策ってないのかな?

https://stackoverflow.com/questions/14803434/receive-read-break-condition-on-linux-serial-port

追記(2018/7/17) Linux の BREAK信号の受信方法がなんとなくわかった。

Armadillo460シリアルとTFTP | アットマークテクノ ユーザーズサイト

ここによると、ioctlのTIOCGICOUNTを使うとできるらしい。 ラズパイで、確かにBREAK信号を受信するとカウントアップしていることが確認できた。 カウントが上がってることと、\xFF\x00\x00 のバイト列が来てることをダブルチェックして BREAK信号を認識できるかもしれない。O_NONBLOCKなので、ループがすごい回ってしまうのが どうにかしたいところ。

(追記2018/7/18) 非カノニカルモードにすることでブロッキングしていても、指定のバイト数を受信すれば 処理が戻るようにできた。後、残ってる課題は カウンタのクリアの仕方かなぁ

The Linux Serial Programming HOWTO: $B%W%m%0%i%`Nc(B

以下にサンプルを例示しておく。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <linux/serial.h>

#define SERIAL_PORT "/dev/ttyAMA0"

int main(int argc, char *argv[])
{
    unsigned char msg[] = "serial port open...\n";
    unsigned char buf[255];
    int fd;
    struct termios tio;
    int baudRate = B9600;
    int i;
    int len;

    fd = open(SERIAL_PORT, O_RDWR|O_NOCTTY);
    if (fd < 0) {
        printf("open error\n");
        return -1;
    }

    tio.c_cflag += CREAD;
    tio.c_cflag += CLOCAL;
    tio.c_cflag += CS8;
    tio.c_cflag += 0;
    tio.c_cflag += 0;

    cfsetispeed(&tio, baudRate);
    cfsetospeed(&tio, baudRate);

    // non canonical mode setting
    tio.c_lflag = 0;
    tio.c_cc[VTIME] = 0;
    tio.c_cc[VMIN] = 1;

    ioctl(fd, TCSETS, &tio);

    // send break signal for 100 ms(?)
    tcsendbreak(fd, 100);
    tcdrain(fd);

    // write 2 bytes
    buf[0] = 'A';
    buf[1] = 'B';
    len = 2;
    write(fd, buf, len);
    tcdrain(fd);

    // waiting for receive any data
    while (1) {
        len = read(fd, buf, sizeof(buf));
        if (0 < len) {
            // check break detection count
            struct serial_icounter_struct icount;
            ioctl(fd, TIOCGICOUNT, &icount);
            printf("brk: %d", icount.brk);

            // print received data
            for(i = 0; i < len; i++) {
                if(i != 0) {
                    if((i % 8) == 0) {
                        printf("\n");
                    } else {
                        printf(":");
                    }
                }
                printf("%02X", buf[i]);
            }
            printf("\n");
        }
    }
    close(fd);
    return 0;
}