What This Tutorial Covers
Every terminal or serial line has a line speed β the rate at which bits are transmitted and received. For physical serial terminals and embedded boards connected over UART, the baud rate must match on both ends. Linux provides four functions to get and set input/output line speeds separately: cfgetispeed(), cfgetospeed(), cfsetispeed(), and cfsetospeed().
This topic is especially relevant for embedded systems engineers working with UART serial communication β configuring /dev/ttyS0, /dev/ttyUSB0, or any serial device on a Linux board.
The word baud is named after French engineer Γmile Baudot. It refers to the number of signal changes (symbols) per second on the transmission line. This is not always the same as bits per second.
For example, if each signal change encodes 2 bits, then 1200 baud = 2400 bps. But in simple UART serial communication (which is the common embedded case), each signal change represents exactly 1 bit, so baud and bps are the same value.
In Linux and POSIX APIs, the terms baud and line speed are used interchangeably with bit rate. The standard recommends saying “line speed” or “bit rate” to avoid confusion, but the symbol constants are still named B9600, B115200, etc.
| Term | Definition | Same as bits/sec? |
|---|---|---|
| Baud | Signal changes per second | Only if 1 bit per symbol |
| Bit rate (bps) | Bits transmitted per second | Yes, by definition |
| UART baud rate | Speed of UART serial link | Yes β 1 signal = 1 bit in UART |
| “Baud rate” (common usage) | Informal synonym for bps | Used as synonym in practice |
All four functions operate on a struct termios that you have already fetched with tcgetattr(). You do not call these functions directly on a file descriptor β you modify the termios structure first, then apply it with tcsetattr().
#include <termios.h>
/* GET functions β read speed from a termios structure */
speed_t cfgetispeed(const struct termios *termios_p); /* input speed */
speed_t cfgetospeed(const struct termios *termios_p); /* output speed */
/* Both return a speed_t constant (e.g. B9600, B115200) */
/* SET functions β write speed into a termios structure */
int cfsetispeed(struct termios *termios_p, speed_t speed); /* input speed */
int cfsetospeed(struct termios *termios_p, speed_t speed); /* output speed */
/* Both return 0 on success, -1 on error */
Important: Calling cfsetispeed() and cfsetospeed() alone does not change anything on the actual terminal. They only modify the in-memory struct termios. The change takes effect only after tcsetattr() is called.
The speed_t type is an integer type. The standard baud rate constants are defined in <termios.h> and are listed below. These are the values you pass to cfsetispeed() and cfsetospeed().
| Constant | Bit Rate | Common Use |
|---|---|---|
| B0 | Hang up (drop DTR) | Disconnect modem |
| B50 | 50 bps | Old teletype |
| B300 | 300 bps | Old modems |
| B1200 | 1200 bps | Old modems |
| B2400 | 2400 bps | Old modems |
| B4800 | 4800 bps | Older UART |
| B9600 | 9600 bps | Most common default |
| B19200 | 19200 bps | GPS modules, sensors |
| B38400 | 38400 bps | Some Bluetooth UART |
| B57600 | 57600 bps | Embedded boards |
| B115200 | 115200 bps | Most common for MCU/SBC |
| B230400 | 230400 bps | High-speed UART |
| B460800 | 460800 bps | High-speed UART |
| B921600 | 921600 bps | Very high-speed UART |
Here is a practical example of opening a UART serial port and setting it to 115200 baud, 8 data bits, no parity, 1 stop bit (the common “8N1” configuration used in embedded systems):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
int open_serial_port(const char *device)
{
int fd;
struct termios tios;
speed_t current_ispeed, current_ospeed;
/* Open the serial device */
fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open");
return -1;
}
/* Get current terminal settings */
if (tcgetattr(fd, &tios) == -1) {
perror("tcgetattr");
close(fd);
return -1;
}
/* Check current speeds */
current_ispeed = cfgetispeed(&tios);
current_ospeed = cfgetospeed(&tios);
printf("Current input speed : %u\n", (unsigned int)current_ispeed);
printf("Current output speed : %u\n", (unsigned int)current_ospeed);
/* Set input and output speed to 115200 */
if (cfsetispeed(&tios, B115200) == -1) {
perror("cfsetispeed");
close(fd);
return -1;
}
if (cfsetospeed(&tios, B115200) == -1) {
perror("cfsetospeed");
close(fd);
return -1;
}
/* Configure 8N1: 8 data bits, no parity, 1 stop bit */
tios.c_cflag &= ~PARENB; /* No parity */
tios.c_cflag &= ~CSTOPB; /* 1 stop bit */
tios.c_cflag &= ~CSIZE; /* Clear data size bits */
tios.c_cflag |= CS8; /* 8 data bits */
tios.c_cflag |= CREAD; /* Enable receiver */
tios.c_cflag |= CLOCAL; /* Ignore modem control lines */
/* Raw input: no echo, no signals, no special characters */
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tios.c_iflag &= ~(IXON | IXOFF | IXANY); /* No software flow control */
tios.c_oflag &= ~OPOST; /* Raw output */
/* Read returns when at least 1 byte available */
tios.c_cc[VMIN] = 1;
tios.c_cc[VTIME] = 0;
/* Apply all settings to the terminal */
if (tcsetattr(fd, TCSAFLUSH, &tios) == -1) {
perror("tcsetattr");
close(fd);
return -1;
}
/* Verify the speed was actually set */
if (tcgetattr(fd, &tios) == -1) {
perror("tcgetattr verify");
close(fd);
return -1;
}
printf("New input speed : %u (B115200 = %u)\n",
(unsigned int)cfgetispeed(&tios), (unsigned int)B115200);
printf("New output speed : %u (B115200 = %u)\n",
(unsigned int)cfgetospeed(&tios), (unsigned int)B115200);
return fd;
}
int main(void)
{
int fd;
char buf[256];
ssize_t n;
fd = open_serial_port("/dev/ttyUSB0");
if (fd == -1)
return EXIT_FAILURE;
printf("Serial port opened and configured at 115200 8N1\n");
/* Read loop */
while ((n = read(fd, buf, sizeof(buf) - 1)) > 0) {
buf[n] = '\0';
printf("Received: %s\n", buf);
}
close(fd);
return EXIT_SUCCESS;
}
Note: the values printed by cfgetispeed() are not the actual baud rate numbers. They are opaque speed_t constants. On Linux, B115200 happens to have the value 0010002 (octal). To convert them back to a human-readable number, you need a lookup table (shown below).
Since speed_t constants are not the actual numeric baud rate, here is a utility function to convert them to human-readable integers:
#include <termios.h>
/* Convert a speed_t constant to an actual integer baud rate.
Returns -1 if the speed is not recognized. */
int speed_to_baud(speed_t speed)
{
struct { speed_t constant; int baud; } table[] = {
{ B0, 0 },
{ B50, 50 },
{ B75, 75 },
{ B110, 110 },
{ B134, 134 },
{ B150, 150 },
{ B200, 200 },
{ B300, 300 },
{ B600, 600 },
{ B1200, 1200 },
{ B1800, 1800 },
{ B2400, 2400 },
{ B4800, 4800 },
{ B9600, 9600 },
{ B19200, 19200 },
{ B38400, 38400 },
{ B57600, 57600 },
{ B115200, 115200 },
{ B230400, 230400 },
{ B460800, 460800 },
{ B921600, 921600 },
};
int n = sizeof(table) / sizeof(table[0]);
for (int i = 0; i < n; i++)
if (table[i].constant == speed)
return table[i].baud;
return -1; /* Unknown */
}
/* Usage: */
struct termios tios;
tcgetattr(fd, &tios);
int baud = speed_to_baud(cfgetospeed(&tios));
printf("Output baud rate: %d bps\n", baud);
Linux provides a non-POSIX convenience function cfsetspeed() that sets both input and output speed in a single call. This is simpler when you always want both speeds the same (which is the usual case for UART):
#include <termios.h>
/* Linux-specific: sets both input and output speed at once */
int cfsetspeed(struct termios *termios_p, speed_t speed);
/* Returns 0 on success, -1 on error */
/* Example: */
struct termios tios;
tcgetattr(fd, &tios);
cfsetspeed(&tios, B115200); /* input AND output both set to 115200 */
tcsetattr(fd, TCSAFLUSH, &tios);
cfsetspeed() is available on Linux and macOS but is not in the POSIX standard. For portable code, call cfsetispeed() and cfsetospeed() separately.
The POSIX API allows separate input and output speeds to be set. In the early Unix days, some hardware allowed asymmetric speeds (for example, a printer receives data slowly but acknowledges status quickly). However, most modern UART hardware requires both speeds to be identical.
If you set them to different values, the kernel may silently use only one of them, or may return an error on tcsetattr(). In practice, always set both input and output speeds to the same value when working with real serial hardware.
In embedded Linux work (Raspberry Pi, BeagleBone, i.MX6, STM32 + Linux, etc.), you regularly need to configure UART serial ports to talk to microcontrollers, GPS modules, BLE modules, or other peripherals. Here is a quick-reference function for common use:
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
/* Configure serial port: baud rate + 8N1, raw mode */
int configure_serial(int fd, speed_t baud)
{
struct termios tios;
if (tcgetattr(fd, &tios) == -1)
return -1;
/* Set speed */
cfsetispeed(&tios, baud);
cfsetospeed(&tios, baud);
/* 8N1 */
tios.c_cflag = (tios.c_cflag & ~CSIZE) | CS8;
tios.c_cflag &= ~PARENB;
tios.c_cflag &= ~CSTOPB;
tios.c_cflag |= (CREAD | CLOCAL);
/* Raw input, no processing */
tios.c_lflag = 0;
tios.c_iflag = 0;
tios.c_oflag = 0;
/* Block until at least 1 byte */
tios.c_cc[VMIN] = 1;
tios.c_cc[VTIME] = 0;
return tcsetattr(fd, TCSAFLUSH, &tios);
}
/* Usage examples: */
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
configure_serial(fd, B9600); /* GPS module */
configure_serial(fd, B115200); /* MCU / debug console */
configure_serial(fd, B921600); /* High-speed sensor */
Common baud rates in embedded projects:
- B9600 β GPS NMEA output, old sensor modules
- B115200 β most Arduino/STM32 debug UARTs, BLE UART modules (HM-10, etc.)
- B230400 / B460800 β high-throughput UART to embedded Linux targets
- B921600 β very fast UART, e.g. nRF5340 coprocessor transport
struct termios in memory. The change is applied to the actual terminal or serial port only when tcsetattr() is called with the modified structure. Always call tcgetattr() first to get the current settings, modify with cfsetispeed() / cfsetospeed(), then apply with tcsetattr().B0 causes the modem control lines to be dropped β specifically, the DTR (Data Terminal Ready) signal is dropped. This is used to hang up a modem connection. It does not set the speed to zero; it is a special signal to disconnect.speed_t constant such as B115200, not the integer 115200. These constants are implementation-defined opaque values. On Linux, B115200 is actually 0010002 in octal. To get the human-readable baud rate number, you need a lookup table that maps the speed_t constant to the integer baud rate.O_NOCTTY prevents the opened serial device from becoming the controlling terminal of the process. If your process has no controlling terminal and you open a terminal device without O_NOCTTY, it automatically becomes the controlling terminal. This is almost never desired when opening serial ports for communication β you just want to read/write data, not interact with job control.cfsetispeed() + cfsetospeed() + tcsetattr() to configure the correct rate before communicating.cfsetspeed() is a convenience function available on Linux and macOS/BSD but is not part of the POSIX standard. For fully portable code targeting embedded Linux and other Unix systems, use cfsetispeed() and cfsetospeed() separately. In practice on Linux embedded projects, cfsetspeed() is fine.