Question

What are the ways of setting custom baudrates on Linux?

An answer to this question must be at a level of userland low-level APIs (ioctl, etc.) above the level of a syscall. It should be useful in these circumstances at least:

  1. Writing low-level C-based userland code that uses serial ports,

  2. Writing libraries that abstract the serial port functionality,

  3. Writing kernel serial port drivers.

Was it helpful?

Solution

Things are, unfortunately, driver-dependent. Good drivers will implement all of the methods below. Bad drivers will implement only some of the methods. Thus you need to try them all. All of the methods below are implemented in the helper functions in linux/drivers/tty/serial/serial_core.c.

The following 4 choices are available.

  1. Standard baud rates are set in tty->termios->c_cflag. You can choose from:

        B0
        B50
        B75
        B110
        B134
        B150
        B200
        B300
        B600
        B1200
        B1800
        B2400
        B4800
        B9600
        B19200
        B38400
        B57600
        B115200
        B230400
    
  2. If you need rates not listed above, e.g. 460800 (this is a deprecated hack that the kernel developers wish to die, per the source code comments):

    • set tty->termios->c_cflag speed to B38400

    • call TIOCSSERIAL ioctl with (struct serial_struct) set as follows:

      serial->flags & ASYNC_SPD_MASK == ASYNC_SPD_[HI, VHI, SHI, WARP]
      // this is an assertion, i.e. what your code must achieve, not how
      

      This sets alternate speed to HI: 57600, VHI: 115200, SHI: 230400, WARP: 460800

  3. You can set an arbitrary speed using alt_speed as follows:

    • Set tty->termios->c_cflag speed to B38400. This is unrelated to the speed you chose!

    • Set the intended speed in tty->alt_speed. It gets ignored when alt_speed==0.

  4. You can also an arbitrary speed rate by setting custom divisor as follows:

    • Set tty->termios->c_cflag speed to B38400. This is unrelated to the speed you chose!

      bool set_baudrate(int fd, long baudrate) {
        struct termios term;
        if (tcgetattr(fd, &term)) return false;
        term.c_cflag &= ~(CBAUD | CBAUDEX);
        term.c_cflag |= B38400;
        if (tcsetattr(fd, TCSANOW, &term)) return false;
        // cont'd below
      
    • Call TIOCSSERIAL ioctl with struct serial_struct set as follows:

      serial->flags & ASYNC_SPD_MASK == ASYNC_SPD_CUST
      serial->custom_divisor == serial->baud_base / your_new_baudrate
      // these are assertions, i.e. what your code must achieve, not how
      

    How to do it? First get the structure filled (including baud_base you need) by calling TIOCGSERIAL ioctl. Then modify it to indicate the new baudrate and set it with TIOCSSERIAL:

          // cont'd
          struct serial_struct serial;
          if (ioctl(fd, TIOCGSERIAL, &serial)) return false;
          serial->flags &= ~ASYNC_SPD_MASK;
          serial->flags |= ASYNC_SPD_CUST;
          serial->custom_divisor = serial->baud_base / baudrate.
          if (ioctl(fd, TIOCSSERIAL, &serial)) return false;
          return true;
       }
    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top