How to Set a Non-Standard Baudrate on a Serial Port Device on Linux

How to set a non-standard baudrate on a serial port device on Linux?

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;
    }

Specifying non-standard baud rate for FTDI virtual serial port under Linux

You can't change baud base, I suppose it is hardware related. So messing with the module won't do you any good. In your third point you only talk about the first method proposed for setting a custom baudrate, where you need to access the tty->alt_speed. It seems there is no interface to directly set tty struct from userspace, at least not with the ftdi_sio driver.

However, there is another method explained in the comments :

     * 3. You can also set baud rate by setting custom divisor as follows
* - set tty->termios->c_cflag speed to B38400
* - call TIOCSSERIAL ioctl with (struct serial_struct) set as
* follows:
* o flags & ASYNC_SPD_MASK == ASYNC_SPD_CUST
* o custom_divisor set to baud_base / your_new_baudrate

Did you try it ?

C code for non-standard baud rate on Debian/Raspberry Pi

So after a bit more searching I stumbled upon the following code at:

https://jim.sh/ftx/files/linux-custom-baudrate.c

Below, is a copy of the above code which I've dumbed down quite a bit for my purposes however, it should be quite simple to implement now.

#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
#include<termio.h>
#include <linux/serial.h>

static int rate_to_constant(int baudrate) {
#define B(x) case x: return B##x
switch(baudrate) {
B(50); B(75); B(110); B(134); B(150);
B(200); B(300); B(600); B(1200); B(1800);
B(2400); B(4800); B(9600); B(19200); B(38400);
B(57600); B(115200); B(230400); B(460800); B(500000);
B(576000); B(921600); B(1000000);B(1152000);B(1500000);
default: return 0;
}
#undef B
}

int main() {

struct termios options;
struct serial_struct serinfo;
int fd;
int speed = 0;
int rate = 625000;

/* Open and configure serial port */
if ((fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY)) == -1)
{
return -1;
}

// if you've entered a standard baud the function below will return it
speed = rate_to_constant(rate);

if (speed == 0) {
/* Custom divisor */
serinfo.reserved_char[0] = 0;
if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
return -1;
serinfo.flags &= ~ASYNC_SPD_MASK;
serinfo.flags |= ASYNC_SPD_CUST;
serinfo.custom_divisor = (serinfo.baud_base + (rate / 2)) / rate;
if (serinfo.custom_divisor < 1)
serinfo.custom_divisor = 1;
if (ioctl(fd, TIOCSSERIAL, &serinfo) < 0)
return -1;
if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
return -1;
if (serinfo.custom_divisor * rate != serinfo.baud_base) {
warnx("actual baudrate is %d / %d = %f",
serinfo.baud_base, serinfo.custom_divisor,
(float)serinfo.baud_base / serinfo.custom_divisor);
}
}

fcntl(fd, F_SETFL, 0);
tcgetattr(fd, &options);
cfsetispeed(&options, speed ?: B38400);
cfsetospeed(&options, speed ?: B38400);
cfmakeraw(&options);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~CRTSCTS;
if (tcsetattr(fd, TCSANOW, &options) != 0)
{
//return -1;
}

//return fd;

char ping_cmd[] = {2,1};
char ping_rec[7];

write(fd,&ping_cmd,sizeof(ping_cmd));
read(fd,&ping_rec,sizeof(ping_rec));

int i;
for (i = 0; i < sizeof(ping_rec); i++)
{
printf("%d ",ping_rec[i]);
}

close(fd);
return 0;
}

As the more astute coders out there will notice, since I pulled this code into my main, the presence of all those "return -1" is almost certainly bad programming practice, however, I'm not sure how I should clean it up and hence I'd love hear your suggestions - I will make edits as suggested.

In the meantime though, should you face a similar problem to me, the above should do nicely.

Uncommon baud rate on serial port - Linux

Finally i've found another topic on stackoverflow wich is complete and solve my problem : How to set baud rate to 307200 on Linux?

Here is my code with the modification :

static int rate_to_constant(int baudrate) {
#define B(x) case x: return B##x
switch(baudrate) {
B(50); B(75); B(110); B(134); B(150);
B(200); B(300); B(600); B(1200); B(1800);
B(2400); B(4800); B(9600); B(19200); B(38400);
B(57600); B(115200); B(230400); B(460800); B(500000);
B(576000); B(921600); B(1000000);B(1152000);B(1500000);
B(2000000);B(2500000);B(3000000);B(3500000);B(4000000);
default: return 0;
}
#undef B
}

int Custom_Baudrate(const char* Device, int rate)
{

/*Declaration of all the variables needed*/
struct termios2 options;
struct serial_struct serinfo;
int file=-1;
int speed = 0;
int r=rate;

/* Open and configure serial port */
file = open(Device,O_RDWR|O_NOCTTY);

if(file==-1){printf("\nERROR : Unable to open the serial port\n\n");return 1;}

speed = rate_to_constant(r);

/*Find best Baudrate*/
if (speed == 0) {

/* Custom divisor */
serinfo.reserved_char[0] = 0;
if (ioctl(file, TIOCGSERIAL, &serinfo) < 0) file=-1;
serinfo.flags &= ~ASYNC_SPD_MASK;
serinfo.flags |= ASYNC_SPD_CUST;

serinfo.custom_divisor = ((serinfo.baud_base + (r / 2)) / r);

if (serinfo.custom_divisor < 1)
serinfo.custom_divisor = 1;
if (ioctl(file, TIOCSSERIAL, &serinfo) < 0) file=-1;
if (ioctl(file, TIOCGSERIAL, &serinfo) < 0) file=-1;
if (serinfo.custom_divisor * r != serinfo.baud_base) {
warnx("actual baudrate is %d / %d = %f",
serinfo.baud_base, serinfo.custom_divisor,
(float)
serinfo.baud_base / serinfo.custom_divisor);
}
}

/*Set the best Baudrate (if desired baudrate is unvailable, it's set automatically at 38400)*/
fcntl(file, F_SETFL, 0);
tcgetattr(file, &options);
cfsetispeed(&options, speed ?: B38400);
cfsetospeed(&options, speed ?: B38400);
cfmakeraw(&options);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~CRTSCTS;
if (tcsetattr(file, TCSANOW, &options) != 0) file=-1;

/*Read the serial port*/
communicate_Serial_Port(file);

close(file);
return 1;
}

In this code you just have to precise wich baudrate you want and it find the nearest value that you can use. This value is based on the "base baudrate" of your device and it search a divisor to set the best baudrate. However, some baudrate should always being unavailable so this program will put the 38400 as a base (it's a choice).
I've test it with several baudrate and it always work.

I'm new on stackoverflow, i hope this post will complete correctly the question.

Tcl refuses to set 76800 baud rate for serial channel on Linux

Tcl refuses to set the speed to those values because the underlying C functions don't support those baud rates on Linux. In fact, it's not Tcl or even your libc that's the problem here, but Linux: it supports a fixed set of baud rates, and 76800 is not one of them.

On my system (Debian sid), the baud rates beyond the ones specified by POSIX are visible in /usr/include/x86_64-linux-gnu/bits/termios-baud.h. This location may differ based on the OS and version.

If you want to use this serial device, you'll need to configure it for a different rate. The closest ones are 57600 and 115200. The maximum supported POSIX-specified version is 38400.



Related Topics



Leave a reply



Submit