Linux Terminals Terminal Line Speed (Baud Rate)

 

Linux Terminals – Part 7B
Terminal Line Speed (Baud Rate) β€” cfgetispeed, cfsetispeed, cfgetospeed, cfsetospeed
πŸ“˜ Chapter 62
πŸ”§ TLPI Series
βš™οΈ Systems Programming

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.

baud rate line speed cfgetispeed cfgetospeed cfsetispeed cfsetospeed speed_t termios UART serial port B9600 B115200

1. Baud Rate vs Bit Rate β€” What Is the Difference?

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

2. The Four Line Speed Functions

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 */

How to Use These Functions β€” Workflow
Step 1: tcgetattr(fd, &tios)
← Get current settings from kernel
Step 2: cfsetispeed(&tios, B115200)
← Modify input speed in struct
Step 3: cfsetospeed(&tios, B115200)
← Modify output speed in struct
Step 4: tcsetattr(fd, TCSAFLUSH, &tios)
← Apply modified struct to kernel
Step 5: cfgetospeed(&tios) to verify
← Read back and confirm

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.

3. Standard Speed Constants

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

4. Complete Working Example β€” Serial Port Configuration

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).

5. Converting speed_t Constants to Actual Baud Rate Numbers

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

6. Linux Extension: cfsetspeed() β€” Set Both Speeds at Once

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.

7. Can Input and Output Speeds Be Different?

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.

Where Speeds Live Inside struct termios
struct termios
c_iflag β€” input flags
c_oflag β€” output flags
c_cflag β€” control flags
c_lflag β€” local flags
c_ispeed β€” input baud ← cfget/setispeed
c_ospeed β€” output baud ← cfget/setospeed
cfgetispeed(&tios)
reads tios.c_ispeed β†’ returns speed_t
cfgetospeed(&tios)
reads tios.c_ospeed β†’ returns speed_t
cfsetispeed(&tios, B115200)
writes B115200 β†’ tios.c_ispeed
cfsetospeed(&tios, B115200)
writes B115200 β†’ tios.c_ospeed

8. Practical Embedded Systems Context

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

Interview Questions β€” Terminal Line Speed
Q1: What is the difference between baud and bits per second?
Baud is the number of signal changes (symbols) per second on the line. Bits per second (bps) is the number of actual data bits transmitted per second. They are equal only when each symbol encodes exactly one bit, which is the case for UART serial communication. In more complex modulation schemes, multiple bits can be encoded per symbol, making baud < bps.
Q2: Do cfsetispeed() and cfsetospeed() immediately change the terminal speed?
No. These functions only modify the 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().
Q3: What does B0 mean when passed to cfsetospeed()?
Setting the output speed to 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.
Q4: Are input and output speeds always the same?
Not required by POSIX, but in practice yes β€” modern UART hardware uses the same baud rate for transmit and receive. POSIX allows separate speeds for historical reasons (asymmetric hardware existed early in Unix). Always set both when writing portable, reliable code.
Q5: What does cfgetispeed() actually return β€” the baud rate number or something else?
It returns a 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.
Q6: Why do you need O_NOCTTY when opening a serial port?
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.
Q7: What is the difference between TCSANOW, TCSADRAIN, and TCSAFLUSH in tcsetattr()?
TCSANOW β€” changes take effect immediately, without waiting for any pending output. TCSADRAIN β€” waits for all pending output to be transmitted before applying changes. Use when changing output-related settings. TCSAFLUSH β€” like TCSADRAIN but also discards any unread input. Best choice when switching terminal modes to start with a clean state.
Q8: You open /dev/ttyUSB0 and the baud rate is B9600 but your device expects 115200. What happens?
You will receive garbled or no data. UART communication requires both ends to agree on the exact same baud rate. If the rates don’t match, the receiver’s bit-sampling clock picks up bits at the wrong times, causing framing errors and corrupted bytes. You must call cfsetispeed() + cfsetospeed() + tcsetattr() to configure the correct rate before communicating.
Q9: Is cfsetspeed() portable?
No. 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.

Chapter 62 Complete!
You have now covered terminal architecture, termios, canonical & non-canonical modes, raw mode, cbreak mode, signal handling with terminals, and line speed configuration.

embeddedpathashala.com

Leave a Reply

Your email address will not be published. Required fields are marked *