Armadilloフォーラム

シリアル通信(ttymxc1)にボーレートB76800をサポート

morimayu

2018年4月5日 18時51分

いつもお世話になっております。
森と申します。

シリアル通信において、ボーレートB76800をサポートさせたいと思っております。
/dev/ttymxc1をOpenして通信するドライバ(ローダブルモジュール)を作成しているのですが、
B9600、B19200、B38400は定義されているようで問題ないのですが、
B76800においては、コンパイル時にエラーとなってしまいます。

単純にB76800をdefineで定義するだけで済むのでしょうか。
それとも、標準ドライバソースに処理を追加する必要があるのでしょうか。

大変申し訳ありませんが、対策方法についてご教授願います。

あまり知識が無いため、
初心者向けに説明していただけますと、ありがたいです。。。

以上、よろしくお願いいたします。

コメント

中村です。

> シリアル通信において、ボーレートB76800をサポートさせたいと思っております。
> /dev/ttymxc1をOpenして通信するドライバ(ローダブルモジュール)を作成しているのですが、
> B9600、B19200、B38400は定義されているようで問題ないのですが、
> B76800においては、コンパイル時にエラーとなってしまいます。
>
> 単純にB76800をdefineで定義するだけで済むのでしょうか。
> それとも、標準ドライバソースに処理を追加する必要があるのでしょうか。

Armadillo-IoT G3系のCPUでハードウェア的に76800bpsが
使えるのかどうかはわかりませんが、
シリアルドライバのソースdrivers/tty/tty_ioctl.cをみると、
次のようなコードがありますので、
ドライバの改造が必要になると思います。

/*
 * Routine which returns the baud rate of the tty
 *
 * Note that the baud_table needs to be kept in sync with the
 * include/asm/termbits.h file.
 */
static const speed_t baud_table[] = {
        0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
        9600, 19200, 38400, 57600, 115200, 230400, 460800,
#ifdef __sparc__
        76800, 153600, 307200, 614400, 921600
#else
        500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
        2500000, 3000000, 3500000, 4000000
#endif
};
 
#ifndef __sparc__
static const tcflag_t baud_bits[] = {
        B0, B50, B75, B110, B134, B150, B200, B300, B600,
        B1200, B1800, B2400, B4800, B9600, B19200, B38400,
        B57600, B115200, B230400, B460800, B500000, B576000,
        B921600, B1000000, B1152000, B1500000, B2000000, B2500000,
        B3000000, B3500000, B4000000
};
#else
static const tcflag_t baud_bits[] = {
        B0, B50, B75, B110, B134, B150, B200, B300, B600,
        B1200, B1800, B2400, B4800, B9600, B19200, B38400,
        B57600, B115200, B230400, B460800, B76800, B153600,
        B307200, B614400, B921600
};
#endif

--
なかむら

中村です。

> Armadillo-IoT G3系のCPUでハードウェア的に76800bpsが
> 使えるのかどうかはわかりませんが、

Armadillo-IoT G3 のUARTのボーレートの設定については、
i.MX 7Dual のリファレンスマニュアルの次のところに説明があります。
15.3.5 Binary Rate Multiplier (BRM)
15.3.15.12 UART BRM Incremental Register (UARTx_UBIR)
15.3.15.13 UART BRM Modulator Register (UARTx_UBMR)

このあたりに書いてあることの実装は、カーネルソース
linux-3.14-x1-atXX/drivers/tty/serial/imx.c
のimx_set_termios()という関数の中にあります。

リファレンスマニュアルとこの関数をざっと見てみたのですが、
ちょっと眺めたくらいでは細かいところがよくわからないので、
UBIRとUBMRの2つのレジスタにセットする値(を+1したもの)と、
その値の元になるものをいくつかをprintk()で表示させ、
sttyコマンドで9600,19200,38400,57600,115200bpsを
セットしてみました。
(76800はsttyでセットできません)

まず、ソースimx.cの次の部分にprintk()を加えました。
(diffの差分で前後を長めに表示させてます)

--- imx.c-orig  2018-02-27 09:27:04.000000000 +0900
+++ imx.c       2018-04-07 01:32:36.874622920 +0900
@@ -1630,30 +1630,31 @@
        if (termios->c_cflag & CSTOPB)
                ucr2 |= UCR2_STPB;
        if (termios->c_cflag & PARENB) {
                ucr2 |= UCR2_PREN;
                if (termios->c_cflag & PARODD)
                        ucr2 |= UCR2_PROE;
        }
 
        del_timer_sync(&sport->timer);
 
        /*
         * Ask the core to calculate the divisor for us.
         */
        baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16);
        quot = uart_get_divisor(port, baud);
+printk(KERN_DEBUG "baud=%d quot=%d\n", baud, quot);
 
        spin_lock_irqsave(&sport->port.lock, flags);
 
        sport->port.read_status_mask = 0;
        if (termios->c_iflag & INPCK)
                sport->port.read_status_mask |= (URXD_FRMERR | URXD_PRERR);
        if (termios->c_iflag & (BRKINT | PARMRK))
                sport->port.read_status_mask |= URXD_BRK;
 
        /*
         * Characters to ignore
         */
        sport->port.ignore_status_mask = 0;
        if (termios->c_iflag & IGNPAR)
                sport->port.ignore_status_mask |= URXD_PRERR | URXD_FRMERR;
@@ -1695,39 +1696,41 @@
                       sport->port.membase + UCR2);
        }
        old_txrxen &= (UCR2_TXEN | UCR2_RXEN);
 
        /* custom-baudrate handling */
        div = sport->port.uartclk / (baud * 16);
        if (baud == 38400 && quot != div)
                baud = sport->port.uartclk / (quot * 16);
 
        div = sport->port.uartclk / (baud * 16);
        if (div > 7)
                div = 7;
        if (!div)
                div = 1;
 
+printk(KERN_DEBUG "baud=%d div=%d\n", baud, div);
        rational_best_approximation(16 * div * baud, sport->port.uartclk,
                1 << 16, 1 << 16, &num, &denom);
 
        tdiv64 = sport->port.uartclk;
        tdiv64 *= num;
        do_div(tdiv64, denom * 16 * div);
        tty_termios_encode_baud_rate(termios,
                                (speed_t)tdiv64, (speed_t)tdiv64);
 
+printk(KERN_DEBUG "num=%lu denom=%lu\n", num, denom);
        num -= 1;
        denom -= 1;
 
        ufcr = readl(sport->port.membase + UFCR);
        ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
        if (sport->dte_mode)
                ufcr |= UFCR_DCEDTE;
        writel(ufcr, sport->port.membase + UFCR);
 
        writel(num, sport->port.membase + UBIR);
        writel(denom, sport->port.membase + UBMR);
 
        if (!is_imx1_uart(sport))
                writel(sport->port.uartclk / div / 1000,
                                sport->port.membase + IMX21_ONEMS);

このカーネルに入れ替えて再起動し、
/var/log/debugを"tail -f"しながらsttyをやると次のようになりました。

root@armadillo:~# stty -F /dev/ttymxc1 9600
root@armadillo:~# stty -F /dev/ttymxc1 19200
root@armadillo:~# stty -F /dev/ttymxc1 38400
root@armadillo:~# stty -F /dev/ttymxc1 76800
stty: invalid argument '76800'
Try 'stty --help' for more information.
root@armadillo:~# stty -F /dev/ttymxc1 57600
root@armadillo:~# stty -F /dev/ttymxc1 115200
# tail -f /var/log/debug
...
Apr  7 01:44:06 armadillo kernel: baud=9600 quot=521
Apr  7 01:44:06 armadillo kernel: baud=9600 div=7
Apr  7 01:44:06 armadillo kernel: num=42 denom=3125
...
Apr  7 01:44:27 armadillo kernel: baud=19200 quot=260
Apr  7 01:44:27 armadillo kernel: baud=19200 div=7
Apr  7 01:44:27 armadillo kernel: num=84 denom=3125
...
Apr  7 01:45:06 armadillo kernel: baud=38400 quot=130
Apr  7 01:45:06 armadillo kernel: baud=38400 div=7
Apr  7 01:45:06 armadillo kernel: num=168 denom=3125
...
Apr  7 01:47:15 armadillo kernel: baud=57600 quot=87
Apr  7 01:47:15 armadillo kernel: baud=57600 div=7
Apr  7 01:47:15 armadillo kernel: num=252 denom=3125
...
Apr  7 01:48:29 armadillo kernel: baud=115200 quot=43
Apr  7 01:48:29 armadillo kernel: baud=115200 div=7
Apr  7 01:48:29 armadillo kernel: num=504 denom=3125

この結果を見ると、imx_set_termios()に76800bpsに相当する
termiosの設定を渡すことができれば、CPUのレジスタ設定は
うまくいくように思います。

ちなみに、上の結果からbaud=76800のときには
quot=65 div=7 num=336 denom=3125
になるのではないかと思われます。

--
なかむら

中村です。

1ヶ月たっても質問者さんから何も反応がないので:-<
もうどうでもいいのかもしれませんが・・・

少し長い投稿になってしまいます。

今日の別スレッドでのやりとりでsetserialコマンドの話が出て、
https://armadillo.atmark-techno.com/forum/armadillo/3181#comment-5449

setserialのmanページなどを見ていたところ、
以前の投稿でlinux-3.14-x1-atXX/drivers/tty/serial/imx.cの
imx_set_termios()の中のコードのボーレート設定処理で
気になっていた部分が何のためにあるのかわかりました。
38400bpsだけ特別扱いしている次の部分です。

>         /* custom-baudrate handling */
>         div = sport->port.uartclk / (baud * 16);
>         if (baud == 38400 && quot != div)
>                 baud = sport->port.uartclk / (quot * 16);

参考:
setserialのmanページ
https://linuxjm.osdn.jp/html/setserial/man8/setserial.8.html
Raspberry PiのUARTで、non-standardなbaud rateを設定する
https://qiita.com/air-gh/items/fb29b754013c27e62c9b

これを使えば、非標準のボーレートを設定できそうです。

で、早速試してみました。

まず、setserialコマンドが入っていないので、インストールします。
root@armadillo:~# apt-get install setserial

printk()によるデバッグ表示を前回と少し変えました。
差分を貼っておきます。
(デバッグ表示を追加していますが、処理は何も変更していません)

$ diff -u imx.c-orig imx.c
--- imx.c-orig  2018-02-27 09:27:04.000000000 +0900
+++ imx.c       2018-05-07 22:07:18.964737800 +0900
@@ -1696,19 +1696,27 @@
        }
        old_txrxen &= (UCR2_TXEN | UCR2_RXEN);
 
+// どのような理由で使い分けをしているのか不明だが、
+// sport->port.xxxxとport->xxxxの実体は同じ。
+printk(KERN_DEBUG "line=%d custom_divisor=%d uartclk=%d\n",
+  port->line, port->custom_divisor, port->uartclk);
        /* custom-baudrate handling */
        div = sport->port.uartclk / (baud * 16);
+printk(KERN_DEBUG "baud=%d quot=%d div=%d\n", baud, quot, div);
        if (baud == 38400 && quot != div)
                baud = sport->port.uartclk / (quot * 16);
 
        div = sport->port.uartclk / (baud * 16);
+printk(KERN_DEBUG "div=%d\n", div);
        if (div > 7)
                div = 7;
        if (!div)
                div = 1;
 
+printk(KERN_DEBUG "baud=%d div=%d\n", baud, div);
        rational_best_approximation(16 * div * baud, sport->port.uartclk,
                1 << 16, 1 << 16, &num, &denom);
+printk(KERN_DEBUG "num=%lu denom=%lu\n", num, denom);
 
        tdiv64 = sport->port.uartclk;
        tdiv64 *= num;

前回と同様にsttyでいくつかのボーレート設定をして、
/var/log/debugのログを確認します。

root@armadillo:~# stty -F /dev/ttymxc1 19200
root@armadillo:~# stty -F /dev/ttymxc1 38400
root@armadillo:~# stty -F /dev/ttymxc1 57600
root@armadillo:~# stty -F /dev/ttymxc1 115200
May  7 22:37:44 armadillo kernel: line=1 custom_divisor=0 uartclk=80000000
May  7 22:37:44 armadillo kernel: baud=19200 quot=260 div=260
May  7 22:37:44 armadillo kernel: div=260
May  7 22:37:44 armadillo kernel: baud=19200 div=7
May  7 22:37:44 armadillo kernel: num=84 denom=3125
...
May  7 22:38:12 armadillo kernel: line=1 custom_divisor=0 uartclk=80000000
May  7 22:38:12 armadillo kernel: baud=38400 quot=130 div=130
May  7 22:38:12 armadillo kernel: div=130
May  7 22:38:12 armadillo kernel: baud=38400 div=7
May  7 22:38:12 armadillo kernel: num=168 denom=3125
...
May  7 22:38:17 armadillo kernel: line=1 custom_divisor=0 uartclk=80000000
May  7 22:38:17 armadillo kernel: baud=57600 quot=87 div=86
May  7 22:38:17 armadillo kernel: div=86
May  7 22:38:17 armadillo kernel: baud=57600 div=7
May  7 22:38:17 armadillo kernel: num=252 denom=3125
...
May  7 22:38:21 armadillo kernel: line=1 custom_divisor=0 uartclk=80000000
May  7 22:38:21 armadillo kernel: baud=115200 quot=43 div=43
May  7 22:38:21 armadillo kernel: div=43
May  7 22:38:21 armadillo kernel: baud=115200 div=7
May  7 22:38:21 armadillo kernel: num=504 denom=3125

setserialコマンドで現在の状態を確認します。

root@armadillo:~# setserial -a /dev/ttymxc1
/dev/ttymxc1, Line 1, UART: undefined, Port: 0x0000, IRQ: 59
        Baud_base: 5000000, close_delay: 50, divisor: 0
        closing_wait: 3000
        Flags: spd_normal

ここで表示されるBaud_baseの5000000はuartclk=80000000の1/16で、
上にあげたラズパイの参考ページにも記載がありますが、
これは、serial_core.cの次の部分です。
retinfo->baud_base = uport->uartclk / 16;

76800bpsにするには、
5000000/76800=65.10...
なので、divisor=65にすればよさそうです。

root@armadillo:~# setserial /dev/ttymxc1 spd_cust divisor 65
setserial sets custom speed on ttymxc1. This is deprecated.
 
root@armadillo:~# setserial -a /dev/ttymxc1
/dev/ttymxc1, Line 1, UART: undefined, Port: 0x0000, IRQ: 59
        Baud_base: 5000000, close_delay: 50, divisor: 65
        closing_wait: 3000
        Flags: spd_cust

"This is deprecated."と言われてしまいますが、
設定はできているようです。

この状態で特別扱いの38400を設定すると、
実際には76800bpsになるはずです。

root@armadillo:~# stty -F /dev/ttymxc1 38400
stty: /dev/ttymxc1: unable to perform all requested operations

これも怒られてしまいましたが、syslogを見ると、
設定はされていそうです。

May  7 22:42:07 armadillo kernel: line=1 custom_divisor=65 uartclk=80000000
May  7 22:42:07 armadillo kernel: baud=38400 quot=65 div=130
May  7 22:42:07 armadillo kernel: div=65
May  7 22:42:07 armadillo kernel: baud=76923 div=7
May  7 22:42:07 armadillo kernel: num=7 denom=65

リファレンスマニュアルの説明で問題なさそうなことは確認しました。

上のログの num=7 denom=65 は、imx_set_termios()の
終わりのあたりでUARTの2つのレジスタに設定される値です。

        num -= 1;
        denom -= 1;
        ...
        writel(num, sport->port.membase + UBIR);
        writel(denom, sport->port.membase + UBMR);

この2つのレジスタについては、i.MX 7Dual の
リファレンスマニュアルの次のところに説明があります。
15.3.5 Binary Rate Multiplier (BRM)

BaudRate = RefFreq / (16 * (UBMR + 1) / (UBIR + 1))

RefFreqはuartclk=80000000をdivで割った値らしいので、
BaudRate = 80000000/7/(16*65/7) = 76923.0769231
となり、大丈夫そうです。

実際の通信は試していません。
通信する相手がいなくても、0xAAや0x55を送信して
TXDをオシロやロジアナでみてみればいいのですが、
それもやっていません。

--
なかむら

中村様

> 1ヶ月たっても質問者さんから何も反応がないので:-<
> もうどうでもいいのかもしれませんが・・・

何のコメントもできておらず、申し訳ありません。
質問者の森です。

解決策の詳細までまとめていただきまして、ありがとうございますm(__)m
本対応の優先度が下がり、作業中断しておりました。
少し先になるかもしれませんが、また確認させていただきます。

中村です。

> 解決策の詳細までまとめていただきまして、ありがとうございますm(__)m

昨晩のものは、ドライバソースに手を入れないでどこまでできるか?
を試したもので、きっちり76800bpsに合わせることができませんでしたが、
先ほど、76800bpsちょうどになるようにする方法を投稿しました。
(imx.cを改造する必要があります)

それから、実験ではsttyコマンドを使っていますが、
シリアルデバイスをopen()してtcsetattr()で
ボーレートを設定する方法でも大丈夫だと思います。

printk()や自分のメモなどを追加してあるので差分が読みにくいかもしれませんが、
参考にしてください。

--
なかむら

中村です。

2つ前の投稿で、
>> ちなみに、上の結果からbaud=76800のときには
>> quot=65 div=7 num=336 denom=3125
>> になるのではないかと思われます。
と書きましたが、
昨晩書いたsetserialを使ったカスタムボーレートでは、
quot=65 div=7 num=7 denom=65
となっていました。
また、76800としたいところが
baud=76923
となってしまっています。

以下は、これを76800にする方法です。

昨晩のものではprintk()を追加しただけで処理には手を入れていませんが、
今回のものはimx.cに少し手を加えます。
76800bpsだけに対応です。

--- imx.c-orig  2018-02-27 09:27:04.000000000 +0900
+++ imx.c       2018-05-08 13:59:39.422819933 +0900
@@ -1696,19 +1696,36 @@
        }
        old_txrxen &= (UCR2_TXEN | UCR2_RXEN);
 
+// どのような理由で使い分けをしているのか不明だが、
+// sport->port.xxxxとport->xxxxの実体は同じ。
+printk(KERN_DEBUG "line=%d custom_divisor=%d uartclk=%d\n",
+  port->line, port->custom_divisor, port->uartclk);
        /* custom-baudrate handling */
+// serial_core.cのuart_get_divisor()のように、quot != div ではなく、
+// (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) をチェックした方がいいと思う。
        div = sport->port.uartclk / (baud * 16);
-       if (baud == 38400 && quot != div)
+printk(KERN_DEBUG "baud=%d quot=%d div=%d\n", baud, quot, div);
+       if (baud == 38400 && quot != div) {
+#if 1
+           // 80000000/16/65=76923.0769231を76800にする
+           if (sport->port.uartclk == 80000000 && quot == 65)
+               baud = 76800;
+           else
+#endif
                baud = sport->port.uartclk / (quot * 16);
+       }
 
        div = sport->port.uartclk / (baud * 16);
+printk(KERN_DEBUG "div=%d\n", div);
        if (div > 7)
                div = 7;
        if (!div)
                div = 1;
 
+printk(KERN_DEBUG "baud=%d div=%d\n", baud, div);
        rational_best_approximation(16 * div * baud, sport->port.uartclk,
                1 << 16, 1 << 16, &num, &denom);
+printk(KERN_DEBUG "num=%lu denom=%lu\n", num, denom);
 
        tdiv64 = sport->port.uartclk;
        tdiv64 *= num;

この修正により、setserialでカスタムボーレートを設定したときの
デバッグログ(syslog)は次のようになります。

root@armadillo:~# setserial /dev/ttymxc1 spd_cust divisor 65
setserial sets custom speed on ttymxc1. This is deprecated.
May  8 14:02:24 armadillo kernel: line=1 custom_divisor=65 uartclk=80000000
May  8 14:02:24 armadillo kernel: baud=38400 quot=65 div=130
May  8 14:02:24 armadillo kernel: div=65
May  8 14:02:24 armadillo kernel: baud=76800 div=7
May  8 14:02:24 armadillo kernel: num=336 denom=3125

このときの
BaudRate = RefFreq / (16 * (UBMR + 1) / (UBIR + 1))
を計算すると
BaudRate = 80000000/7/(16*3125/336) = 76800
となります。

--
なかむら

中村です。

訂正です。

> この修正により、setserialでカスタムボーレートを設定したときの
> デバッグログ(syslog)は次のようになります。
>

> root@armadillo:~# setserial /dev/ttymxc1 spd_cust divisor 65
> setserial sets custom speed on ttymxc1. This is deprecated.
> 

の部分にsttyで38400bpsを設定するのを貼り付け忘れてました。
次のようになります。

root@armadillo:~# setserial /dev/ttymxc1 spd_cust divisor 65
setserial sets custom speed on ttymxc1. This is deprecated.
 
root@armadillo:~# stty -F /dev/ttymxc1 38400
stty: /dev/ttymxc1: unable to perform all requested operations

--
なかむら