11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Raspberry PiのUARTで、non-standardなbaud rateを設定する

Last updated at Posted at 2016-03-06

きっかけ

前回、FTDIのUSB-UART変換で、100Kbps対応ができたが、やっぱり当初の目的だったRaspberry Pi(以下RPi)でも、non-standardなbaud rateを実現したい。

下調べ

ググる

前回も参照したこちらによると、init_uart_clockでクロックの設定ができるらしい。が、sttyは

$ sudo stty -F /dev/ttyAMA0 100000
stty: invalid argument '100000'

と、失敗。
さらにこのinit_uart_clockでググると、他にもinit_uart_baudや、/boot/cmdline.txt(?)にbcm2708.uart_clock追加、などの情報もある。

いろいろ試してオシロで測ったが、どれでやっても実際のbaud rateには変化が無い。
Raspbianは2016-02-26で、kernelは4.1だ。

sttyのbaud rate指定

sttyのbaud rateにはどんな値が設定できるのか。coreutilsのソースを読む。
mainのコマンドラインパーサーには、

coreutils-8.25/src/stty.c
          else if (string_to_baud (arg) != (speed_t) -1)

とある。さらに、このstring_to_baudは、

coreutils-8.25/src/stty.c
string_to_baud (const char *arg)
{
  int i;

  for (i = 0; speeds[i].string != NULL; ++i)
    if (STREQ (arg, speeds[i].string))
      return speeds[i].speed;
  return (speed_t) -1;
}

となっており、speeds[i]がポイントか。

coreutils-8.25/src/stty.c
static struct speed_map const speeds[] =
{
  {"0", B0, 0},
  {"50", B50, 50},
  {"75", B75, 75},

(以下略)となっており、standardなbaud rateがテーブルになっているように見える。

kernelのbaud rate指定

linux/drivers/tty/tty_ioctl.c
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,

(以下略)となっており、sttyと同じようなテーブルが定義されているように見える。

これでは、non-standardなbaud rateをstty経由でkernelに設定することは難しそうだ。

RPiのUARTドライバー

そもそも、clockを変えれば、実際のbaud rateに変化があってもいいじゃないか。君は何故変化が無いんだ。

linux/drivers/tty/serial/amba-pl011.c
        /* Set baud rate */
        writew(quot & 0x3f, port->membase + UART011_FBRD);
        writew(quot >> 6, port->membase + UART011_IBRD);

で、レジスターにbaud rateを設定している。quotは、

linux/drivers/tty/serial/amba-pl011.c
        if (baud > port->uartclk/16)
                quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
        else
                quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);

で、baudに応じた適切な値が設定される。baudは、

linux/drivers/tty/serial/amba-pl011.c
	/*
	 * Ask the core to calculate the divisor for us.
	 */
	baud = uart_get_baud_rate(port, termios, old, 0,
                   		  port->uartclk / clkdiv);

で、簡単に言うと、termiosからtty_termios_baud_rate()で持ってきている。

つまり、kernelのcmdlineが何であれ、clockがどうであれ、誰かがsttyなどでbaud rateを指定した初期化を行ってしまえば、standardなbaud rateを元に、適切なレジスタ値が計算されてしまいそうな気がする。
昔は、/etc/inittabの修正などによって、ttyを初期化する人を抹殺して、kernelのcmdlineがそのまま/dev/ttyAMA0に適用され続け、うまく行っていたのかもしれない(妄想)。ちなみにRaspbianは既にsystemdだった。

setserial

setserialによる設定

FTDIのドライバへは、setserialでnon-standardなbaud rateを設定できた。これは、どんな仕組みなのか。

setserial-2.17/setserial.c
        if (ioctl(fd, TIOCSSERIAL, &new_serinfo) < 0) {
	        perror("Cannot set serial info");
                exit(1);
        }

となっており、TIOCSSERIALで設定される。

setserialによる設定取得

試しに、RPi上で、/dev/ttyAMA0にsetserialで設定してみる。

$ setserial /dev/ttyAMA0
/dev/ttyAMA0, UART: undefined, Port: 0x0000, IRQ: 83
$ setserial /dev/ttyAMA0 spd_cust
$ setserial /dev/ttyAMA0
/dev/ttyAMA0, UART: undefined, Port: 0x0000, IRQ: 83, Flags: spd_cust

君は、spd_custを覚える能力があるではないか。FTDIのドライバーではなく、ARMのUARTドライバーなのに。
設定取得はどうなっているのか。kernelのTIOCGSERIALの実装を追う。

linux/drivers/tty/serial/serial_core.c
static void do_uart_get_info(struct tty_port *port,
                        struct serial_struct *retinfo)
{
        struct uart_state *state = container_of(port, struct uart_state, port);
        struct uart_port *uport = state->uart_port;

        memset(retinfo, 0, sizeof(*retinfo));
:
        retinfo->flags      = uport->flags;
:
        retinfo->baud_base          = uport->uartclk / 16;
:
        retinfo->custom_divisor  = uport->custom_divisor;

(:=中略)flagscustom_divisorも、struct uart_portの情報を元にしていそうじゃないか。
つまり、struct uart_portflagscustom_divisorなら、setserialから設定できそうじゃないか。しかも、uartclkなんていうclockそのままっぽいメンバーも、struct uart_portに居るじゃないか。

設計

  • ユーザーは、前回と同じように、setserialからspd_custとdivisorを設定する
  • UARTドライバーは、setserialから設定された値をstruct uart_portで参照して、non-standardなbaud rateを計算する
  • non-standardなbaud rateを元に、既存のドライバーの処理で、レジスター値の計算等を行う

実装

linux/drivers/tty/serial/amba-pl011.c
        /*
         * Ask the core to calculate the divisor for us.
         */
        baud = uart_get_baud_rate(port, termios, old, 0,
                                  port->uartclk / clkdiv);
#if 1
        if (baud == 38400 &&
            ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) &&
             (port->custom_divisor)) {
                baud = port->uartclk / port->custom_divisor;
        }
#endif

#if 1#endifが差分で、ftdi_sio.cのコードを元にしている。ただ、このブロックで参照できるのはstruct uart_portなので、これにあわせてメンバーを調整している。
serial_core.cによると、baud_base = uport->uartclk / 16なので、port->uartclk * 16のほうが良いのかもしれないが、その時はdivisorも16倍することになる。まぁこのままでいいや。
port->uartclkには、一番最初に出てきたinit_uart_clockの値が入っていることを期待している(ぶっつけ本番未確認)。

確認

kernelのbuild

本家にはこんなに丁寧なドキュメントがある。何も困らなかった。

init_uart_clockの変更

/boot/config.txt
init_uart_clock=1600000

編集後のrebootで反映される模様。編集後にkernelを置き換えると、設定がリセットされてしまうようなので注意。

dmesg
[    0.132315] bcm2708.uart_clock = 1600000

設定

# stty -F /dev/ttyAMA0 38400
# setserial /dev/ttyAMA0 spd_cust divisor 16
# echo -n U > /dev/ttyAMA0

1600000 / 16 = 100000 = 100Kbps、になるはず…。

測定

100KHz-RPi.png

蛇足

当初、devmem2で/dev/memをmmapし、ユーザー空間からレジスター設定しようと思ったが、設定手順や計算が面倒そうだったので、ドライバーに押し込んでしまった。

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?