Armadilloフォーラム

シリアル通信について

koji_wakita

2014年7月31日 9時42分

ArmadilloというよりはLinuxに関する質問となるかもしれませんが
宜しくお願いします。

シリアル通信にてブレーク信号を送信したいのですが
端末から送信する方法を御教示頂けないでしょうか。

*ブレーク信号:1データ長(例えば11ビット)を超えて0を続けて送信する。

目的は、Armadilloと通信する相手側に
ブレーク信号を渡す事によって、起動トリガを掛けるためです。

相手側との通信はRS485、SDI-12といった半二重通信になります。

送信データとしては
ブレーク信号+アドレス+制御コードという形です。

アドレスが「12」、制御コードが「CC」の場合で、
通信設定が、ボーレート9600、データ長8bit 、パリティ無し、ストップビット1の時
 10ビット相当の0送信後、「12CC」といったイメージになります。

通信設定が、ボーレート9600、データ長8bit 、パリティ有り、ストップビット1の時
 11ビット相当の0送信後、「12CC」といったイメージになります。

このようなブレーク信号の出力をする方法がありますでしょうか

以上宜しくお願いします。

コメント

koji_wakita

2014年8月1日 11時43分

平野様 ご提案有難うございます。

C言語での方法について検討してみます。

また、説明が下手ですみません。
「端末」というのは、コマンドラインから操作をしたく
ATDEではメニューの「アプリケーション」→「アクセサリ」→「端末」になります。

ここから操作できるものなのでしょうか?

nakayama.junichi

2014年8月1日 12時57分

中山と申します。

> 「端末」というのは、コマンドラインから操作をしたく
> ATDEではメニューの「アプリケーション」→「アクセサリ」→「端末」になります。
>
> ここから操作できるものなのでしょうか?
>

私の場合、Windows環境ではTeraTerm、
Linux環境ではminicomを使ってシリアル接続しています。

TeraTermの場合は、Alt+B、
minicomの場合は、Ctrl+Aを押した後、Fを押すと
ブレーク信号が送れるようです。
minicomは、Ctrl+Aを押しても見た目上は変化がないので注意してください。

koji_wakita

2014年8月1日 14時16分

中山様、御教示有難うございます。

大変申し訳ございません、まだ私の説明が足りませんでした。
「10ビット相当の0送信後、「12CC」といったイメージになります。」
と書かせて頂いた点について説明を追加させてください。

ブレーク信号を送りたいのはttyUSB0といった先のRS485等に対して。という事です。
通常こういったシリアルに対しては
echo 〇〇 > /dev/ttyUSB0
とすれば〇〇がttyUSB0より出力されます。

このttyUSB0に対してブレーク信号をATDEの端末から出力する事が可能でしょうか?

nakayama.junichi

2014年8月1日 14時35分

中山です。

> このttyUSB0に対してブレーク信号をATDEの端末から出力する事が可能でしょうか?

試したわけではないのですが、
echo -n \022\314 > /dev/ttyUSB0
とするとできるかもしれません。
バックスラッシュと8進数でASCIIコードを指定できるようです。

nakayama.junichi

2014年8月1日 14時37分

echo -e \022\314 > /dev/ttyUSB0
かもしれません。すいません。

tic-hirano

2014年8月1日 17時15分

> echo -e \022\314 > /dev/ttyUSB0
> かもしれません。すいません。

Armadillo-460のターミナルから下記を試したところ接続先に12h,CCh,が届きました.
# echo -ne \\022\\314 > /dev/ttymxc3

nオプションが無いと 0Ah も送信されました.

tic-hirano

2014年8月1日 17時17分

2ページ開いていたためか空で投稿されてしまいました.

Armadillo-460で下記を試したところ接続先に 12h,CCh, が届きました.
# echo -ne \\022\\314 > /dev/ttymxc3

\\でないとバックスラッシュ扱いにならないのとnオプションが無いと 0Ah が送信されました.

kasahara.adincs

2014年8月1日 18時47分

koji_wakita さま

 割り込みで申し訳ありません アディンクスの笠原と申します。

 たぶん echo からでは無理ではないでしょうか (推測ですみません)
Break信号は基本的にキャラクタに割り当てられるものではありませんから
汎用的なインターフェース(echo 〇〇 > 出力先)では送信できないと思います。
minicomなどのターミナルソフトでは「Ctrl+a」「 f」等のキャラクタを受け取った時に
シリアルデバイスに対して特別な制御を行ってBreak信号を送信しているはずです。
(Ctrl+a fはminicomでソフトによって異なります)
一般的にこの手の設定はstty で設定しますが、見た感じではbreakの送信設定は
なさそうです。

PS:
 「端末から」というよりデバイスにバイナリ(00~FF)列を送ってBreakを送信させる
方法といったイメージで良いでしょうか?
実際はshell等で実行できなければダメなんですよね?
(端末操作でよければ中山さんが書かれた通りminicom等で送出可能です)

> 中山様、御教示有難うございます。
>
> 大変申し訳ございません、まだ私の説明が足りませんでした。
> 「10ビット相当の0送信後、「12CC」といったイメージになります。」
> と書かせて頂いた点について説明を追加させてください。
>
> ブレーク信号を送りたいのはttyUSB0といった先のRS485等に対して。という事です。
> 通常こういったシリアルに対しては
> echo 〇〇 > /dev/ttyUSB0
> とすれば〇〇がttyUSB0より出力されます。
>
> このttyUSB0に対してブレーク信号をATDEの端末から出力する事が可能でしょうか?
>

saitoh

2014年8月3日 15時34分

齊藤と申します。
不確かな情報で済みません。

僕の知る限りsttyで「本物の」BREAK信号を送ることはできません。
しかしながら、多くの装置はフレーミングエラーが起きることでBREAK受信とみなしていますので、「BREAK(と解釈される確率が高い信号)」を送信することは可能です。

たとえば、もともとの速度が38400だとしたら
(stty 50; echo -n '\377';stty 38400) >/dev/ USB00 2>&1 0>&1
見たいな感じで行けるはず(うろ覚え)。 ミソはそのハードウェアが設定可能な最も低いビットレートに設定することです。

377じゃなくて200 かもしれません。

問題はsttyが stdin、stdout,stderr, /dev/tty のどれに向かって動作するのか?ってとこ。

昔この手のことをやってた時の資料がいま手元にないので、うろ覚えの記憶ベースの話になってしまってすみません。

y.nakamura

2014年8月3日 18時02分

中村です。

なんか、迷走している感じですね。

齊藤さんが書かれているようにsttyやechoを使って疑似的に
やる方法もあるとは思いますが・・・
基本的には、平野さんが書いてくれたtcsendbreak()を使うことになります。
シェルスクリプトからブレークを送信したいならば、tcsendbreak()を
するだけの簡単なプログラムを作るのが早いと思います。

tcsendbreak()は、内部でioctlでTIOCSBRKとTIOCCBRKを使って
ブレーク信号を送信していたと思います。(たぶんlibcの中)
man tty_ioctl によると、ioctlのTCSBRKだけでもtcsendbreak()と
同じことができるとの記述がありますし、POSIX版のTCSBRKPという
ioctlもあるようです。

tcsendbreak()や各ioctlでブレーク信号を送信できるかどうかは、
ドライバ(とチップ)次第です。
ドライバがioctlのTIOCSBRKとTIOCCBRK、TCSBRK、TCSBRKPを
サポートしているかどうかですね。

Armadillo-840でttyUSB0を使ってブレーク信号を送信したい、
とのことですので、ソースを調べてみました。

linux-3.4-at5/drivers/tty/tty_io.c

        /*
         * Break handling
         */
        case TIOCSBRK:  /* Turn break on, unconditionally */
                if (tty->ops->break_ctl)
                        return tty->ops->break_ctl(tty, -1);
                return 0;
        case TIOCCBRK:  /* Turn break off, unconditionally */
                if (tty->ops->break_ctl)
                        return tty->ops->break_ctl(tty, 0);
                return 0;
        case TCSBRK:   /* SVID version: non-zero arg --> no break */
                /* non-zero arg means wait for all output data
                 * to be sent (performed above) but don't send break.
                 * This is used by the tcdrain() termios function.
                 */
                if (!arg)
                        return send_break(tty, 250);
                return 0;
        case TCSBRKP:   /* support for POSIX tcsendbreak() */
                return send_break(tty, arg ? arg*100 : 250);

TCSBRKとTCSBRKPが使っているsend_break()は、
その中でtty->ops->break_ctl()を呼び出しています。
(tty_io.cの中にありますので興味がある人はソースを見てください)

USBシリアル変換に何を使うのかわかりませんが、
FTDIのドライバは次のようになってます。

linux-3.4-at5/drivers/usb/serial/ftdi_sio.c

static struct usb_serial_driver ftdi_sio_device = {
        .driver = {
                .owner =        THIS_MODULE,
                .name =         "ftdi_sio",
        },
        .description =          "FTDI USB Serial Device",
   (途中省略)
        .break_ctl =            ftdi_break_ctl,
};
static void ftdi_break_ctl(struct tty_struct *tty, int break_state)
{
        struct usb_serial_port *port = tty->driver_data;
        struct ftdi_private *priv = usb_get_serial_port_data(port);
        __u16 urb_value;
 
        /* break_state = -1 to turn on break, and 0 to turn off break */
        /* see drivers/char/tty_io.c to see it used */
        /* last_set_data_urb_value NEVER has the break bit set in it */
 
        if (break_state)
                urb_value = priv->last_set_data_urb_value | FTDI_SIO_SET_BREAK;
        else
                urb_value = priv->last_set_data_urb_value;
 
        if (usb_control_msg(port->serial->dev,
                        usb_sndctrlpipe(port->serial->dev, 0),
                        FTDI_SIO_SET_DATA_REQUEST,
                        FTDI_SIO_SET_DATA_REQUEST_TYPE,
                        urb_value , priv->interface,
                        NULL, 0, WDR_TIMEOUT) < 0) {
                dev_err(&port->dev, "%s FAILED to enable/disable break state "
                        "(state was %d)\n", __func__, break_state);
        }
 
        dbg("%s break state is %d - urb is %d", __func__,
                                                break_state, urb_value);
 
}

cp210x.cはこんなふうになってました。

static void cp210x_break_ctl (struct tty_struct *tty, int break_state)
{
        struct usb_serial_port *port = tty->driver_data;
        unsigned int state;
 
        dbg("%s - port %d", __func__, port->number);
        if (break_state == 0)
                state = BREAK_OFF;
        else
                state = BREAK_ON;
        dbg("%s - turning break %s", __func__,
                        state == BREAK_OFF ? "off" : "on");
        cp210x_set_config(port, CP210X_SET_BREAK, &state, 2);
}
 
static int cp210x_set_config(struct usb_serial_port *port, u8 request,
                unsigned int *data, int size)
{
   (省略)
}

--
なかむら

koji_wakita

2014年8月4日 9時16分

ご回答いただいた皆様、有難うございました。

半二重の通信では、ブレーク信号の受信をきっかけに
起動するものがありまして、今回の質問となりました。

シェルスクリプトにこだわったのは開発言語がCではないので…

tcsendbreak()を使う方法では
中村様の仰るとおり、USBのチップが対応していないと意味が無いですね。

齊藤様の擬似的にやる方法
を検討してみます。