13

Basically I'm using the following code to set the baud rate of a serial port:

struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
tcsetattr(fd, TCSANOW, &options);

This works very well. But know I have to communicate with a device that uses a baud rate of 307,200. How can I set that? cfsetispeed(&options, B307200); doesn't work, there is no B307200 defined.

I tried it using a MOXA Uport 1150 (that's actually a USB-to-serial converter) and the standard serial port of an Intel motherboard. I don't know the exact kind of the latter, setserial just reports it as 16550A.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cairol
  • 8,573
  • 9
  • 27
  • 25
  • 1
    Can you set this rate with 'stty'? If not, I doubt you can (seems like an obscure speed to me); if you can, then have a look at the code for it. The 'speed_t' options are defined by an octal value in termios.h so you could hypothetically derive the correct value by analyzing those values. – gamen Feb 11 '11 at 12:02
  • No `stty` doesn't work because it checks the baud rate with some hard-coded values. Interestingly `serserial` allows to set 307200 and doesn't report an error. But when I try to read from the serial port it doesn't work. – cairol Feb 14 '11 at 08:52
  • Are you _really_ sure the baud rate of your device is 307200? Isn't this something specific to radio communication? – groovingandi Feb 14 '11 at 14:34
  • 1
    Yep I'm really sure, 307200 is correct. – cairol Feb 15 '11 at 08:28
  • The correct answer is to use `BOTHER` approach and IOCTLs from termios2.h. – 0andriy Jul 15 '21 at 20:58
  • From the user side, it is possible to use a non-standard baud rate with [picocom](https://linux.die.net/man/8/picocom) (but not PuTTY). Tried on [Ubuntu MATE 20.04](https://en.wikipedia.org/wiki/Ubuntu_MATE#Releases) (Focal Fossa) - for [Mecrisp Stellaris](https://mecrisp-stellaris-folkdoc.sourceforge.io/) [Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) on the 25 MHz [1Bitsy](https://1bitsy.org/) at 360,000 baud through [Black Magic Probe's](https://pmortensen.eu/world2/2019/12/08/arm-toolchain-ubuntu-19-04-black-magic-probe/) auxiliary serial port. – Peter Mortensen Oct 24 '21 at 16:35
  • Or in other words, perhaps the source code for picocom provides some clues? Because, otherwise, searching on Stack Overflow can give the impression that only the very limited standard Baud rates are possible on Linux. That is not the case. – Peter Mortensen Oct 24 '21 at 18:34

6 Answers6

26

Linux uses a dirty method for non-standard baud rates, called "baud rate aliasing". Basically, you tell the serial driver to interpret the value B38400 differently. This is controlled with the ASYNC_SPD_CUST flag in serial_struct member flags.

You need to manually calculate the divisor for the custom speed as follows:

// Configure port to use custom speed instead of 38400
ioctl(port, TIOCGSERIAL, &ss);
ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
ss.custom_divisor = (ss.baud_base + (speed / 2)) / speed;
closestSpeed = ss.baud_base / ss.custom_divisor;

if (closestSpeed < speed * 98 / 100 || closestSpeed > speed * 102 / 100) {
    fprintf(stderr, "Cannot set serial port speed to %d. Closest possible is %d\n", speed, closestSpeed));
}

ioctl(port, TIOCSSERIAL, &ss);

cfsetispeed(&tios, B38400);
cfsetospeed(&tios, B38400);

Of course, you need a serial driver with suitable baud_base and divisor settings. The preceding snippet allows for 2% deviation, which should be ok for most purposes.

And to tell the driver to interpret B38400 as 38400 baud again:

ioctl(mHandle, TIOCGSERIAL, &ss);
ss.flags &= ~ASYNC_SPD_MASK;
ioctl(mHandle, TIOCSSERIAL, &ss);

As a word of caution: I'm not sure if this method is portable between other *nix flavors.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cyco130
  • 4,654
  • 25
  • 34
  • 1
    This worked for me. There is a [full code example](http://jim.sh/ftx/files/linux-custom-baudrate.c) as well. There is also another [question](http://stackoverflow.com/questions/12646324/how-to-set-a-custom-baud-rate-on-linux) related to this answer. – Jakob Jul 10 '13 at 07:32
  • 3
    OMG. That's just ... horrible, really. :( Someone completely forgot to have their abstraction sprinkles on their breakfast cereal before designing that API. – unwind Sep 04 '14 at 08:39
  • 2
    @unwind Even more horrible than you think since it's badly documented. I suppose that's why this answer from 3 years ago still receives up votes :) – cyco130 Sep 04 '14 at 08:41
  • 1
    This is not supported for all chips. For the Prolific PL2303, the driver does not support the `TIOCSSERIAL` `ioctl` command. I was able to set the baud rate directly (using `B921600` in my case). – Patrick Jun 10 '15 at 19:51
  • 1
    You have to use `struct termios2` in Linux for that and corresponding IOCTLs. – 0andriy Nov 30 '15 at 17:24
  • 1
    This seems like a pretty horrible hack. For one thing, the code doesn't check for `custom_divisor` ending up zero. – kralyk Feb 08 '18 at 10:51
  • @kralyk I didn't add any real error checking, I didn't even wrap the snippet in a function to focus on the actual hack. And yes, it is a horrible hack but there was no other way of achieving it at the time. I haven't done any serial port programming since, so I don't know if things have improved. – cyco130 Feb 27 '18 at 17:11
  • when I try to set 14400 baud rate, my baud rate becomes 14397 because of the formula. Do you have any idea how can I set baudrate to 14400? – Mustafa Chelik Mar 19 '19 at 10:48
  • 1
    @MustafaChelik It's only 0.02% off. It seems like that's the best you can achieve with your chip. Honestly, I can't think of any real application where it wouldn't be close enough. – cyco130 Mar 19 '19 at 11:07
  • yeah seems that 0.02% is not a problem but I can't communicate. I receive garbage bytes at both sides. Any idea? – Mustafa Chelik Mar 19 '19 at 13:01
  • 1
    @MustafaChelik You probably need to configure start bits, stop bits, parity bits etc. Less likely but character size may also be misconfigured. – cyco130 Mar 20 '19 at 10:07
4

I accomplished this using termios2 and ioctl() commands.

struct termios2 options;
ioctl(fd, TCGETS2, &options);
options.c_cflag &= ~CBAUD;    //Remove current baud rate
options.c_cflag |= BOTHER;    //Allow custom baud rate using int input
options.c_ispeed = 307200;    //Set the input baud rate
options.c_ospeed = 307200;    //Set the output baud rate
ioctl(fd, TCSETS2, &options);

After that, you should be able to query the port settings and see your custom baud rate, as well as the other settings (possible with stty commands).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chef Pharaoh
  • 2,387
  • 3
  • 27
  • 38
  • Does the Baud rate actually become 307,200? Have you measured it? The actual nearest Baud rates may or may not be 230,400 and 460,800. – Peter Mortensen Feb 03 '18 at 00:31
  • @PeterMortensen I did not physically measure the actual BAUD rate but I am using a custom micro controller that is running at a custom BAUD rate and have not seem any communication issues. Make sure you are following all the steps above to allow a custom BAUD rate. – Chef Pharaoh Feb 06 '18 at 16:00
1

On many OSes, the enumerated values are numerically equal to the baud rate. So just skip the macro/enumeration and pass the baud rate you want, e.g.

cfsetispeed(&options, 307200);

Of course you should check the return code to make sure this trick actually worked, furthermore not all baud rates are supported by all UARTs.

You can also try setting the options in struct serial_struct using the TIOCGSERIAL and TIOCSSERIAL ioctl codes.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    -1 This is not true on Linux, see the definitions in /usr/include/bits/termios.h – payne Feb 11 '11 at 15:28
  • As payne said this doesn't work on Linux. `cfsetispeed` expects an octal value. I've tried with `0000022` which should be 307200, but it doesn't work. – cairol Feb 14 '11 at 09:09
  • 1
    `0000022` = `18`, I don't see how that "should be 307200". – Ben Voigt Feb 14 '11 at 14:22
  • You're right it should be 0000024. But it's just an assumption because 9600 is defined as 0000015, 19200 as 0000016 and 38400 as 0000017. – cairol Feb 14 '11 at 14:33
  • @cairol: I posted some additional information. Details on `TIOCSSERIAL` are hard to find, you probably will have to read the source, but you can get non-standard baud rates that way. – Ben Voigt Feb 14 '11 at 14:50
1

USB Negotiation has a similar issue. I found this answer for you which might be used as well:

struct serial_struct ser_info; 
ioctl(ser_dev, TIOCGSERIAL, &ser_info); 
ser_info.flags = ASYNC_SPD_CUST | ASYNC_LOW_LATENCY; 
ser_info.custom_divisor = ser_info.baud_base / CUST_BAUD_RATE; 
ioctl(ser_dev, TIOCSSERIAL, &ser_info);
Bruce Chidester
  • 621
  • 1
  • 5
  • 16
0

Support for that speed is system dependent. If B307200 isn't defined, then your system likely doesn't support it.

Here's the source code for stty.c: http://www.koders.com/c/fid35874B30FDEAFEE83FAD9EA9A59F983C08B714D7.aspx

You can see that all of the high-speed variables are #ifdef'd, because support for those speeds varies by system.

payne
  • 13,833
  • 5
  • 42
  • 49
  • Thanks for the link. In the code are many high-speed baud rates defined, but not 307200. It seems this baud rate is unsupported on Linux, because it's neither defined in `/usr/include/bits/termios.h` where the other `Bxy` are. – cairol Feb 14 '11 at 08:39
  • Oldfashioned UNIX had 4 bits for the speed. Maybe the VAX hardware had a similar 4-bit baudrate in the hardware. Anyway, modern hardware has a "base baud" and a divisor that can be something like 16 bits, allowing for 65536 different baud rates. The Linux kernel has been keeping the old interface for way too long. (I've stopped arguing to change it about two decades ago). The proper interface to the kernel would simply have an integer for the requested baud rate. The kernel can then do whatever is necessary to try and implement that, userspace libc can emulate the old behavio... too long – rew Mar 05 '19 at 16:20
  • The link is broken: *"Not Found 404.0"* – Peter Mortensen Mar 29 '22 at 18:11
-2

Try the ioctl call - you can specify an arbitrary baud rate. That is,

ioctl(serialFileDescriptor, IOSSIOSPEED, &baudRate);

To open the serial port:

// Open the serial like POSIX C
serialFileDescriptor = open(
    "/dev/tty.usbserial-A6008cD3",
    O_RDWR |
    O_NOCTTY |
    O_NONBLOCK );

// Block non-root users from using this port
ioctl(serialFileDescriptor, TIOCEXCL);

// Clear the O_NONBLOCK flag, so that read() will
//   block and wait for data.
fcntl(serialFileDescriptor, F_SETFL, 0);

// Grab the options for the serial port
tcgetattr(serialFileDescriptor, &options);

// Setting raw-mode allows the use of tcsetattr() and ioctl()
cfmakeraw(&options);

// Specify any arbitrary baud rate
ioctl(serialFileDescriptor, IOSSIOSPEED, &baudRate);

To read from the serial port:

// This selector will be called as another thread
- (void)incomingTextUpdateThread: (NSThread *) parentThread {
    char byte_buffer[100]; // Buffer for holding incoming data
    int numBytes=1; // Number of bytes read during read

    // Create a pool so we can use regular Cocoa stuff.
    //   Child threads can't re-use the parent's autorelease pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // This will loop until the serial port closes
    while(numBytes>0) {
        // read() blocks until data is read or the port is closed
        numBytes = read(serialFileDescriptor, byte_buffer, 100);

        // You would want to do something useful here
        NSLog([NSString stringWithCString:byte_buffer length:numBytes]);
    }
}

To write to the serial port:

uint8_t val = 'A';
write(serialFileDescriptor, val, 1);

To list availble serial ports:

io_object_t serialPort;
io_iterator_t serialPortIterator;

// Ask for all the serial ports
IOServiceGetMatchingServices(
    kIOMasterPortDefault,
    IOServiceMatching(kIOSerialBSDServiceValue),
    &serialPortIterator);

// Loop through all the serial ports
while (serialPort = IOIteratorNext(serialPortIterator)) {
    // You want to do something useful here
    NSLog(
        (NSString*)IORegistryEntryCreateCFProperty(
            serialPort, CFSTR(kIOCalloutDeviceKey),
            kCFAllocatorDefault, 0));
    IOObjectRelease(serialPort);
}

IOObjectRelease(serialPortIterator);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bruce Chidester
  • 621
  • 1
  • 5
  • 16
  • 3
    This question is about Linux, not BSD. Linux supports different IOCTLs, I think I've listed the appropriate ones in my answer. If you have some example code for `TIOCSSERIAL` that would be helpful and worthy of an upvote. – Ben Voigt Feb 25 '11 at 02:44