Timeline
Timeline
2025-12-31
init
This article introduces the basics of Linux UART serial communication, discussing in detail the fundamental concepts of serial ports, the difference and conversion relationship between baud rate and bit rate, the data frame structure and timing waveforms of serial communication protocols, and summarizes common interface level types such as TTL and RS232 along with their conversion requirements.
Linux Driver Notes
| Table of Contents | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Competition | |
| 5. Advanced Character Device Progression | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hotplug | |
| 11. pinctrl Subsystem | |
| 12. gpio Subsystem | |
| 13. Input Subsystem | |
| 14. Single Bus | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network Device | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
UART
Serial Port, also known as serial communication interface, often calledCOM interface, is a type ofasynchronous full-duplex interfacefor data communication between a computer and external devices (such as serial communication devices). It usesserial transmissionmethod, which transmits data one bit at a time.
Specifically, typical serial communication requires only three wires: ground (GND), transmit (TX), and receive (RX). As shown in the diagram below, transmission and reception each use one line,No clock line。

Baud rate
Baud rate is a key parameter in serial communication, referring to the number of bits or symbol rate transmitted per second.
In a digital channel, a pulse signal is a symbol, as shown in the figure below. The symbol rate indicates how many symbols or pulse signals can be sent in one second.

Both communicating parties must set the same baud rate to ensure correct data transmission. Common standard baud rates such as 9600, 115200, etc., usually meet most application requirements.
However, in specific cases, a non-standard baud rate may need to be set, and it must be ensured that all communication devices can support and correctly configure that baud rate.
For baud rates higher than 1.5 Mbps, it may be necessary to achieve them through fractional or integer division of the clock.
If the required baud rate cannot be achieved through division, it may be necessary to adjust the PLL (Phase-Locked Loop) settings. Adjusting the PLL carries certain risks as it may affect other modules of the device.
Bit raterefers tothe number of bits transmitted per unit time, usually expressed in bps (bits per second), with the unit bit/s. In comparison,baud ratethenthe number of symbols or pulse signals transmitted per second. The relationship between the two can be expressed by the formulabit rate = baud rate * log2(M), where M represents the amount of information carried by each symbol.
A symbol is essentially a pulse signal, which may carry 1 bit, 2 bits, or more bits of data, depending on the specific implementation of the communication system.In a binary system, the bit rate equals the baud rate because each symbol carries exactly 1 bit of information。
For example: if the baud rate of a serial port is 9600, how many bytes can be transmitted per second in a binary system?
One byte equals 8 bits, i.e., 8 high and low level changes, because in a binary system, the bit rate equals the baud rate. Therefore, the number of bytes that can be transmitted per second is 9600/8 = 1200 bytes.
Serial Communication Protocol
Data Stream Structure
In serial communication, besides paying attention to the baud rate, the structure of the data stream is also crucial.
Each frame of data includes 11 bits:
- 1 start bit
- 8 data bits
- 1 parity bit
- 1 stop bit

Start bit: Indicates the start of data transmission. The idle state on the data line is 1, and pulling it low from high level (idle state) indicates the start of data transmission.
Data bits: Refers to the number of data bits per byte, typically 7 or 8 bits.
Parity bit: Used to verify the accuracy of transmitted data. Its types include odd parity, even parity, space parity, and mark parity.
Odd parity: When the number of 1s in the actual data is even, the parity bit is 1; otherwise, the parity bit is 0.
Even parity: When the number of 1s in the actual data is even, the parity bit is 0; otherwise, the parity bit is 1.
Space parity: The parity bit is always 0; if it is 1, it indicates an error.
1 Mark Parity: The parity bit is always 1; if it is 0, it indicates an error.
Stop Bit: Refers to the number of bits sent after each data byte transmission, typically 1 or 2 bits.
Timing Waveform Analysis

Whenthe data line is at a high level when idle, and duringdata transmission, it is pulled low. The first pulse in the diagram corresponds to the start bit. Following it are 8 data bits, transmitted in order of least significant bit (LSB) first.
For example, the data bits “00110001” convert to hexadecimal 0x31, which is 49 in decimal. In ASCII, 49 corresponds to the character ‘1’, so the transmitted data is 1. After data transmission is complete, the bus is pulled high.
Types of Serial Communication Interfaces
UART only specifies the timing of transmission and reception, i.e., “send start bit first, then data bits, parity bit, and finally stop bit”. It only defines high and low levels, but does not specify what voltage the high level or low level corresponds to.
Common interface levels for serial ports includeTTL,RS232,RS485,RS422, andEach interface typically requires a corresponding level-shifting chip. DirectlyWhen directly using the serial port interface from the processor, it is usually TTL level。
However, different models or suppliers of processors may have level differences, meaning that in some cases devices cannot be directly connected. Therefore, proper level shifting must be performed to ensure normal communication. A general comparison of serial port interface levels is shown in the table below:
| Level Standard | Logic Level Definition | Typical High/Low Level Voltage | Signal Type | Maximum Transmission Distance | Main Features |
|---|---|---|---|---|---|
| TTL | 0 = Low Level 1 = High Level | Low Level: 0 V High Level: 1.8 V / 2.5 V / 3.3 V / 5 V (depending on supply) | Single-Ended Signal | Several meters (typically ≤ 2–5 m) | Short-distance communication within or between boards, directly compatible with MCUs, sensors, etc.; weak anti-interference capability, not suitable for long distances |
| RS-232 | 0 = Positive voltage(+3 V ~ +15 V) 1 = Negative voltage(-3 V ~ -15 V) | Logic 0 (space): +3 V ~ +15 V; Logic 1 (mark): -3 V ~ -15 V; commonly ±12 V | Single-ended (but uses positive and negative voltages) | Approximately 15–50 meters (lower rate allows longer distance) | Supports point-to-point communication, stronger anti-interference than TTL, requires level conversion chips (e.g., MAX232), gradually being replaced by USB, etc. |
| RS-422 | Differential signal: 1 = A > B(+2 V ~ +6 V) 0 = A < B(-2 V ~ -6 V) | Differential voltage: ±2 V ~ ±6 V | Differential signal(Full-duplex, 4-wire) | Maximum approximately1200 meters(@ 100 kbps) | Supports full-duplex communication, strong common-mode interference rejection, suitable for industrial environments, typically 1 transmitter and multiple receivers |
| RS-485 | Differential signal: 1 = A > B(+1.5 V ~ +5 V) 0 = A < B(-1.5 V ~ -5 V) | Differential voltage: ≥ ±1.5 V (typical ±2 V ~ ±5 V) | Differential signal(Half-duplex or full-duplex, 2 or 4 wires) | Maximum approximately1200 meters(@ 100 kbps) (Theoretically up to 1219 meters) | Supports multi-point communication (up to 32~256 nodes), widely used in industrial buses (e.g., Modbus), strong anti-interference, suitable for long-distance, noisy environments |
RS232 interface
RS232(Recommended Standard 232)The protocol is a serial communication standard established by the Electronic Industries Association in 1970. This standardunifies the connector and pin definitions for serial communication, as shown in the figure below, and clearly specifies the voltage level standards for each connector pin

DB9 pin description
DB9 is a common serial connector, typically used for RS-232 serial communication and other serial communication applications. It contains 9 pins, each with a specific function. Below is the pin description of the DB9 connector:
- Pin 1 - DCD (Data Carrier Detect): Data Carrier Detect. Indicates whether the remote device is ready for communication.
- Pin 2 - RXD (Receive Data): Receive Data. Receives data stream from the remote device.
- Pin 3 - TXD (Transmit Data): Transmit Data. Sends data stream to the remote device.
- Pin 4 - DTR (Data Terminal Ready): Data Terminal Ready. Indicates that the data terminal equipment (e.g., computer) is ready for communication.
- Pin 5 - GND (Ground): Ground. Electrical ground, used for circuit reference and shielding.
- Pin 6 - DSR (Data Set Ready): Data Set Ready. Indicates that the remote device is ready to receive and send data.
- Pin 7 - RTS (Request to Send): Request to Send. The sender uses this signal to request starting data transmission.
- Pin 8 - CTS (Clear to Send): Clear to Send. The receiver uses this signal to indicate readiness to receive data.
- Pin 9 - RI (Ring Indicator): Ring Indicator. Indicates that the remote device has sent a ring signal.
RS232 is essentially also a serial port protocol, identical to the serial port protocol. However, it specifies the physical interface and level characteristics of the serial port, so it differs at the hardware level, but there is no difference in software programming and serial communication implementation.
Level Characteristics
The electrical signal at the RS232 receive-transmit end is
relative to the common ground(GND)a voltage signal 。- In the RS232 standard,A voltage difference between +3V and +15V is defined as logic “0”, while a voltage difference between**-3V and -15V represents logic “1”**. The voltage difference between -3V and +3V is undefined.
- Typically, in practical applications, the absolute value of the voltage difference is expected to be between 5V and 15V to ensure reliable signal transmission.
- When transmitting data, the driver at the sending end outputs a positive voltage signal of +5V to +15V to represent logic “0”, and a negative voltage signal of -5V to -15V to represent logic “1”.
- When receiving data, any electrical signal greater than 3V is considered a valid signal.
The RS232 interface has relatively high current capability during transmission and reception, allowing it to handle relatively large current loads, which makes it suitable for long-distance communication and connecting external devices.
RS232 signals have high anti-interference capability, enabling stable operation in industrial environments and being less susceptible to electromagnetic interference.
RS232 includes the following signal lines:
- Transmit line (Tx)
- Receive line (Rx)
- Ground line
- Data Terminal Ready (DTR)
- Data Set Ready (DSR)
- Request to Send (RTS)
- Clear to Send (CTS)
- Data Set Ready (DSR)
RS232 supports various baud rates, typically from low to high speeds, up to hundreds of kilobits per second (kbps).
On the iTOP-RK3568 development board, the debug serial port uses the MAX3232 chip to convert TTL levels to RS-232 levels. The schematic diagram of the debug serial port is shown below.

RS485 Interface
The RS485 standard was jointly developed by the Telecommunications Industry Association (TIA) and the Electronic Industries Alliance (EIA). The main purpose of this standard is to
Features and Advantages
- Long-distance communication capability: RS485 can achieve a communication distance of up to 1200 meters under ideal conditions, making it suitable for scenarios requiring long-distance data transmission.
- Strong anti-interference capability: Due to the use of differential signal transmission, RS485 can effectively resist electromagnetic interference (EMI) and radio frequency interference (RFI), ensuring the stability and reliability of data transmission.
- Multi-station capability: RS485 supports communication between multiple devices (up to 32) on the same bus, with each device able to independently send and receive data, enabling flexible network configuration.
- Wide application: RS485 is widely used in industrial automation control systems, building automation, smart home systems, power system monitoring, and other fields, meeting the requirements for long-distance, high-speed, and reliable communication.
Level characteristics
The level characteristics of RS485 are as follows:
- Differential signal transmission: RS485 uses differential signals for data transmission, meaning the data signal is represented by the voltage difference between two signal lines (typically labeled A and B). This differential signal transmission method gives RS485 good anti-interference capability and long-distance transmission capability.
- Voltage range:
- Logic ‘1’ is represented by a voltage difference of +(2~6)V between the two lines;
- Logic ‘0’ is represented by a voltage difference of -(2~6)V between the two lines。
- Interface signal level ratio RS232 is reduced, making it less likely to damage the interface circuit chip, andthis level is compatible with TTL Level compatibility, which can be conveniently connected to TTL circuits.
- Current capability: The RS485 transmitter has strong driving capability, enabling it to drive long communication lines and multiple receivers. The receiver can handle larger input currents to ensure reliable signal reception.
- Electrical characteristics: RS485 supportsmultiple devices(up to 32 a/an (general measure word))communicating on the same bus. It supports communication distances up to 1200 meters, and can even be longer under certain conditions.
The iTOP-RK3568 development board uses theSIT3485E chipto convert TTL levels to 485 levels. The schematic diagram is shown below:

It can be seen that the RS485 interface of the RK3568 development board is actually serial port 7 converted through the SIT3485E chip.
SIT3485E chip

The SIT3485E is a wide power supply range of 3.0V~5.5V, with bus port ESD level exceeding 15KV HBM and bus withstand voltage range reaching ±15V,half-duplex、low power consumption,an RS-485 transceiver whose functions fully meet the requirements of the TIA/EIA-485 standard。
The SIT3485E includesa driveranda receiver, both of which can beindependently enabled and disabled. When both are disabled, the driver and receiver both output a high-impedance state.
The SIT3485E has a 1/8 load, allowing up to 256 SIT3485E transceivers to be connected in parallel on the same communication bus. It can achieve error-free data transmission up to 12Mbps.
The SIT3485E operates over a supply voltage range of 3.0~5.5 V and features fail-safe, current limiting protection, overvoltage protection, and other functions.

| Pin Number | Pin Name | Pin Function |
|---|---|---|
| 1 | RO | Receiver Output. When /RE is low, if A-B ≥ -10mV, RO outputs high; if A-B ≤ -200mV, RO outputs low. |
| 2 | /RE | Receiver Output Enable Control. When /RE is low, receiver output is enabled and RO is active; when /RE is high, receiver output is disabled and RO is high impedance; when /RE is high and DE is low, the device enters low-power shutdown mode. |
| 3 | DE | Driver Output Enable Control. When DE is high, driver output is active; when DE is low, output is high impedance; when /RE is high and DE is low, the device enters low-power shutdown mode. |
| 4 | DI | Driver Input. When DE is high, a low level on DI makes driver non-inverting output A low and inverting output B high; a high level on DI makes non-inverting output high and inverting output low. |
| 5 | GND | Ground |
| 6 | A | Receiver Non-inverting Input and Driver Non-inverting Output |
| 7 | B | Receiver Inverting Input and Driver Inverting Output |
| 8 | VCC | Power Supply |
From the pin definition diagram, it can be seen that:
- The RO pin is the receiver output, connected to UART7 in the schematic diagram_RX_M1 pin.
- The DI pin is the driver input pin, connected to UART7 in the schematic diagram_TX_M1 pin.
- The A pin is the receiver non-inverting input and driver non-inverting output.
- The B pin is the receiver inverting input and driver inverting output. A and B form the RS485 differential pair.
- REThe /RE pin is the receiver output enable control pin.
- When /RE is low, the receiver output is enabled, and RO output is active.
- When /RE is high, the receiver output is disabled, and RO is in high-impedance state.
- The DE pin is the driver output enable control pin. When DE is high, the driver output is active; when DE is low, the output is in high-impedance state.
- REWhen /RE is high and DE is low, the device enters low-power mode.
In simple terms:
- REWhen /RE is low, RO output is active. Otherwise, RO is in high-impedance state, meaning inactive. RO is connected to UART7_RX_M1,UART7_RX_M1 is the serial port's receive pin. Therefore, when /RE is low, the serial port can receive data; otherwise, the serial port cannot receive data.
- When DE is high, DI is active, and DI is connected to UART7._TX_M1,UART7_RX_M1 is the serial port transmit pin. Therefore, when DE is high, the low level on DI causes the driver’s non-inverting output A to be low and the inverting output B to be high, allowing the serial port to send data; otherwise, the serial port cannot send data.
REis exactly opposite to the active level of DE., and because RS-485 ishalf-duplex, it cannot transmit and receive simultaneously. Therefore,REand DE definitely cannot be enabled at the same time,that is,REthe levels of and DE must be the same.. In this way,REwhen is enabled, it is equivalent to DE being disabled, so these two pins are connected together.
This makes it very clear: if GPIO0_C6 outputs a low level,REand DE are low,REwhen is low, the serial port can receive data; when DE is low, the serial port cannot send data.
If GPIO0_C6 outputs a high level,REWhen both and DE are high,REWhen is high, the serial port cannot receive data; when DE is high, the serial port can send data.
That is, we need to use GPIO0_The high or low level of pin C6 controls whether RS485 is transmitting or receiving. Therefore, we need to write a driver to achieve this. The source code provided by Xunwei has a default driver configured to control GPIO0_C6。
Self-transceiver 485 circuit
In addition to software control, automatic switching can also be achieved through hardware to realize the automatic transceiver function of RS485. There is a certain time delay in software-controlled transmission and reception. To reduce this delay, the baseboard schematic of the Xunwei development board has been optimized to be compatible with hardware-implemented automatic transceiver. The specific modification method is to remove R295 and solder all components marked as DNP in the figure below.

- When UART_TX_When M1 is high, the base of Q17 is also high, causing Q17 to conduct,REand DE are low, so the serial port is in receive data mode
- When UART_TX_When M1 is low, the base of Q17 is low, causing Q17 to cut off,REand DE are high, the serial port is in transmit data mode. Since UART_TX_M1 is high when idle, indicating the serial port is in receive data state; when UART_TX_M1 is pulled low, the serial port is in transmit data state.
Since UART_TX_M1 high level indicates receive mode, so when sending a 1, it is also high level. This would keep the chip in receive mode, preventing the 1 from being sent out, right?
When the transceiver chip is in receive mode, both pins A and B are in a high-impedance state.。
High-impedance state means a very large resistance, almost equivalent to an open circuit.At this point, A is pulled high by a pull-up resistor, and B is pulled low by a pull-down resistor.。
Therefore,**A high and B low represent a logic 1 in communication.**Through this ‘receive mode’, we cleverly send out a ‘1’.
This optimization eliminates the need for an RS485 transceiver control IO, allowing RS485 to be used entirely as a serial port, making driver development easier.
Serial Subsystem Framework
The serial subsystem framework is a modular framework in the Linux kernel specifically designed for handling serial port devices. The framework diagram is shown below.

- Application Layer: Located at the topmost layer, it is the interface between user-space applications and kernel space in the serial subsystem. The application layer includes user-space serial port applications, such as the serial communication tool minicom.
- Character Device Layer: Located below the application layer, it is responsible for passing serial port read/write requests from user space to the tty_core layer in kernel space. The character device layer treats the serial port device as a special character device and operates through the character device interface.
- tty_core layer: Located below the character device layer, it is the core module in the Linux kernel for managing serial port devices. It handles basic functions of serial port devices, such as data transmission, control, and buffer management. The tty_core layer is independent of specific serial hardware and serves as a generic processing layer for serial devices.
- uart_core layer: Located below the tty_core layer, it provides the low-level driver interface for serial port devices and is responsible for communicating with specific serial hardware. The uart_core layer is responsible for controlling low-level operations such as serial data transmission and reception, interrupt handling, and clock management.
- Hardware layer: Located at the bottom layer, it is the part of the serial subsystem that is specific to the hardware. The hardware layer includes the driver for the serial hardware, communicates with the specific serial controller, and implements low-level control and operation of the hardware.
Configuring the serial driver
In the SDK kernel source code provided by Rockchip, the serial driver uses the 8250 generic serial driver. The following are the main driver files:
drivers/tty/serial/8250/8250_core.c8250 serial driver coredrivers/tty/serial/8250/8250_dw.cSynopsis DesignWare 8250 serial driverdrivers/tty/serial/8250/8250_dma.c8250 serial DMA driverdrivers/tty/serial/8250/8250_port.c8250 serial port operationsdrivers/tty/serial/8250/8250_early.c8250 serial early console driver
Select the driver in make menuconfig
1 | Device Driver / Character devices / Serial drivers |
In the device tree of the SDK source code provided by Xunwei, UART 9 is enabled by default. Openarch/arm64/boot/dts/rockchip/rk3568.dtsithe device tree file. The device tree node for the UART 9 controller is as follows:
1 | uart9: serial@fe6d0000 { |
compatible: Specifies the compatibility string of the device, indicating that this UART device is compatible withrockchip,rk3568-uartandsnps,dw-apb-uarttwo types of UART controllers. This helps the device tree bind the corresponding driver.reg: Specifies the address and size of the UART device. 0xfe6d0000 is the base address of the UART device, and 0x100 indicates the size of the address space.interrupts: Specifies the interrupt information of the UART device, including the interrupt type and interrupt number.clocks: Specifies the clock sources used by the UART device, including the baud rate clock and the APB clock.clock-names: Specifies the name of the clock source, used to match the specific configuration of the clock source.reg-shift: Indicates the number of bits for the address offset, i.e., whether the offset of each register is in bytes or words.reg-io-width: Indicates the access width for device addresses and data. Here, 4 means a width of 4 bytes.dmas: Specifies the DMA controller and DMA channel number used by the UART device for DMA data transfer operations.dma-names: Optional values aretxEnable TX DMArxEnable RX DMA!txDisable TX DMA!rxDisable RX DMA
pinctrl-namesandpinctrl-0: Used for pin control of serial port devices to configure and manage pin settings. Optional parameters are as follows:&uart9m0_xferConfigure TX and RX pins as iomux group 0&uart9m1_xferConfigure TX and RX pins as iomux group 1&uart9m0_ctsnand&uart1m0_rtsnConfigure hardware auto flow control CTS and RTS pins as iomux group 0&uart9m1_ctsnand&uart1m1_rtsnConfigure hardware auto flow control CTS and RTS pins as iomux group 1
status: Indicates the status of the serial port device. Here, “disabled” means the device is currently disabled. If set to “okay”, it means the device is enabled.
Analysis of uart_driver Registration Process
UART Related Low-Level Structures
The relationship between UART related low-level structures is as follows

struct uart_driver
uart_driverThe structure represents the UART driver,uart_driverdefined in theinclude/linux/serial_core.hfile, with the following content:
1 | struct uart_driver { |
struct uart_driverencapsulatestty_driver, so that the underlying UART driver does not need to care abouttty_driver。
struct uart_port
uart_port is an abstraction for a serial port, defined inkernel/include/linux/serial_core.h, which internally contains auart_statemember variable of type, as follows:
1 | struct uart_port { |
struct uart_state
struct uart_stateis a structure, defined ininclude/linux/serial_core.h, which internally contains atty_portmember variable of type, typically used to represent the status information of the UART driver. Through theuart_driverstate member pointer in the structure, data related to the UART device status can be accessed and manipulated.
1 | /* |
struct uart_ops
The structureuart_opscontains a series of function pointers, which define the interface for operating the UART port. Each function pointer corresponds to a specific operation, such as sending data, setting control signals, starting or stopping transmission, etc.
Through theuart_opsstructure, upper-layer applications or drivers can call these function pointers to operate the UART port, achieving data transmission and control operations.
1 | /* |
Analysis of uart_driver registration
drivers/tty/serial/8250/8250_core.cThe functions in this file are mainly related to the core functionality of the 8250 series UART driver. This file implements the core operations of the 8250 serial communication device, including initialization, configuration, interrupt handling, data transmission, and other functions.
serial8250_init()
1 | static int __init serial8250_init(void) |
Initialize the 8250 serial port
serial8250_isa_init_ports()
serial8250_init(void)The function is mainly responsible for initializing and registering the 8250/16550 serial device driver at system startup, where the function on line 10serial8250_isa_init_ports()initializes the 8250 serial port, and the function is as follows:
1 | static void __init serial8250_isa_init_ports(void) |
serial8250_init_port()
In the code above, line 21 executedserial8250_init_portfunction:
1 | /* |
serial8250_popsAs follows:
1 | static const struct uart_ops serial8250_pops = { |
Register UART driver
serial8250_init(void)The function is mainly responsible for initializing and registering the 8250/16550 serial port device driver during system startup, where theuart_register_driver(&serial8250_reg), through theuart_register_driverfunction registers this uart_driver with the system, the function prototype is as follows
1 | int uart_register_driver(struct uart_driver *uart) |
The meanings of the function parameters and return value are as follows:
- uart: The uart_driver to be registered.
- Return value: 0 for success; negative value for failure.
When unregistering the driver, it is also necessary to unregister the previously registereduart_driver, which requires the use ofuart_unregister_driverThe function prototype is as follows:
1 | void uart_unregister_driver(struct uart_driver *uart) |
The meanings of the function parameters and return value are as follows:
- uart: The uart_driver to be unregistered.
- Return value: None
serial8250_init(void)In the function,uart_register_driverthe parameter passed to the function isserial8250_reg
1 | static struct uart_driver serial8250_reg = { |
uart_register_driver()
uart_register_driverThe function content is as follows, defined in the source codedrivers/tty/serial/serial_core.c.
1 | /** |
uart_register_driverLine 48 of the function sets the tty driver’s operation functions, which define operations related to specific devices, such as read, write, control, etc.
1 | static const struct tty_operations uart_ops = { |
uart_register_driverCode call at line 61 in the functiontty_register_driverThe function is as shown below, defined indrivers/tty/tty_io.c
tty_register_dirver()
1 | /* |
Allocate a platform device structure and register it
serial8250_init(void)The function is mainly responsible for initializing and registering the 8250/16550 serial port device driver during system startup:
1 | { |
platform_device_allocThe function is used forAllocate a platform device structure without registering it on the platform bus. The returned structure can later be registered on the platform bus using the functionplatform_device_add()to register it on the platform bus.
Register serial port
serial8250_init(void)The function is mainly responsible for initializing and registering the 8250/16550 serial device driver during system startup. Line 42 of the code is as follows:
1 | // Register serial port |
serial8250_register_ports()
serial8250_register_portsThe function content is as follows:
1 | static void __init |
Register the platform_driver
serial8250_init(void)The function is mainly responsible for initializing and registering the 8250/16550 serial device driver during system startup. Line 44 of the code is as follows
1 | // Register platform driver |
Port registration process analysis
In the SDK source device tree provided by Xunwei, UART 9 is enabled by default. Openarch/arm64/boot/dts/rockchip/rk3568.dtsithe device tree file. The device tree node for the UART 9 controller is as follows:
1 | uart9: serial@fe6d0000 { |
The compatible attribute value on line 2 issnps,dw-apb-uart. Searching for this value in the Linux source code will locate the corresponding UART driver file, which isdrivers/tty/serial/8250/8250_dw.c
1 | static struct platform_driver dw8250_platform_driver = { |
It can be seen that Rockchip’s UART is essentially a platform driver. After the node matches successfully, thedw_probefunction is executed, and its content is as follows:
dw_probe()
1 | static int dw8250_probe(struct platform_device *pdev) |
Among them, line 159 of code usesserial8250_register_8250_portfunction to register the 8250 port,serial8250_register_8250_portThe function content is as follows:
serial8250_register_8250_port()
1 | /** |
Among them,serial8250_find_match_or_unused The function content is as follows
serial8250_find_match_or_unused()
1 | static struct uart_8250_port *serial8250_find_match_or_unused(struct uart_port *port) |
serial8250_register_8250_port()Line 33uart_add_one_port()The function registers a UART port with the tty core layer,uart_add_one_port()The function content is as follows:
1 | /** |
tty_port_register_device_attr_serdev The function content is as follows:
tty_port_register_device_attr_serdev()
1 | /** |
This function is used to register a tty device to the tty core layer. If the registered device is a serdev device, cdev is not created.
First, it links the tty port to the device, then attempts to register the serdev device. If a serdev device is registered, it directly returns the registration result; otherwise, by calling thetty_register_device_attr()function, it creates cdev and registers the device to the tty core layer.tty_register_device_attrThe function content is as follows:
tty_register_device_attr()
1 | /** |
This function is used to register a tty device to the tty core layer, including creating a cdev and adding attribute groups.
First, it generates the device name based on the driver type, allocates memory space for the device structure, and sets various attributes of the device.
Then, it registers the device to the kernel and decides whether to add the cdev to the tty core layer based on whether the driver is dynamically allocated.
Finally, it un-suppresses the uevent and sends a uevent to notify that the device has been added, and returns the device structure pointer.
tty_cdev_add()
Adding cdev to the tty core layer uses thetty_cdev_addfunction, as shown below:
1 | /* |
This function is used to add a cdev to the tty core layer. First, it allocates a cdev structure and sets its operations and owner. Then, it calls thecdev_add()function to add the cdev to the kernel. If the addition fails, it releases the allocated resources.
In line 13 of the above code,tty_fopsis the file operations structure in the tty driver, which defines the operation functions for tty device files.
These functions include implementations for operations such as opening, closing, reading, writing, and controlling tty device files. Generally, these functions call the corresponding tty core layer functions to complete operations on the underlying tty device.
tty_fopsThe structure is as follows:
1 | static const struct file_operations tty_fops = { |
When user space operates on a tty device file, it is actually calling the corresponding operation functions in tty_fops. Below is a simple example showing how to operate on a tty device file in user space
1 |
|
Serial port programming
In Linux systems, file I/O operations and ioctl operations can be used for serial port programming.
File I/O operations can be used to read and write serial port data
ioctl operations can be used to set serial port parameters, control flow control, and obtain serial port status, among other operations
Serial port device node
In Linux systems, each device is represented by a device node, which is a file associated with the device
and exists as a file in the /dev directory.
Serial port device nodes usually start with tty, and the specific naming convention varies depending on the type and number of serial ports. After the development board system starts, use the following command to print the terminal device nodes, as shown in the figure below:

dev/ttyX (X is a number, such as 0, 1, 2, 3, etc.) device node: tty is short for teletype. In Linux,
/dev/ttyXthey represent local terminals. The Linux kernel generates 63 local terminals during initialization, including/dev/tty1~/dev/tty63a total of 63 local terminals, which can be connected to the development board’s LCD display, keyboard, mouse, etc.Serial port terminal device node: From the development board schematic, it can be seen that the iTOP-3568 development board has four serial ports: UART2, UART4, UART7, and UART9. Among them, UART2 is the serial debug terminal, and its corresponding device node is
/dev/ttyFIQ0, and the other three serial ports UART4, UART7, and UART9 correspond to/dev/ttyS4、/dev/ttyS7、/dev/ttyS9。USB-based virtual serial ports:
ttyGS0andttyUSBX(X is a number, such as 0, 1, 2, 3, etc.) are all USB virtual serial ports. Among them, ttyGS0 is the serial port virtualized by the USB for flashing. After the system starts, you can enter the development board console via the “adb shell” command in a Windows terminal. ttyUSBX here is the virtual serial port for the 4G module.
struct termios structure
struct termiosis a structure in the Linux kernel used to describe parameters of terminal devices (including serial port devices).
It is defined in the<linux/termios.h>header file, containing multiple fields for configuring and managing the attributes and behavior of terminal devices, including input/output baud rate, data bits, parity bits, stop bits, etc. Its definition is as follows:
1 | struct termios { |
Below are some important fields of the struct termios structure:
tcflag_t c_iflag: This field contains input mode flags, used to configure the input behavior of the terminal device, such as input control characters, input data processing, etc.tcflag_t c_oflag: This field contains output mode flags, used to configure the output behavior of the terminal device, such as output data processing, output control characters, etc.tcflag_t c_cflag: This field contains control mode flags, used to configure the control parameters of the terminal device, such as baud rate, data bits, stop bits, parity bits, etc.tcflag_t c_lflag: This field contains local mode flags, used to configure the local operations and input/output behavior of the terminal device.cc_t c_cc[NCCS]: This field contains an array of special control characters, used to configure the control characters of the terminal device, such as erase character, end character, stop character, etc.
Input mode
Input mode settings
| Members | Meaning of corresponding members |
|---|---|
| IGNBRK | Ignore input termination conditions |
| BRKINT | Send SIGINT signal when input termination condition is detected |
| IGNPAR | Ignore framing errors and parity errors |
| PARMRK | Mark parity errors |
| INPCK | Perform parity check on received data |
| ISTRIP | Strip all received data to 7 bits, i.e., remove the eighth bit |
| INLCR | Map received NL (newline) to CR (carriage return) |
| IGNCR | Ignore received CR (carriage return) |
| ICRNL | Map received CR (carriage return) to NL (newline) |
| IUCLC | Map received uppercase characters to lowercase characters |
| IXON | Enable output software flow control |
Output mode
Output mode controls how output characters are processed, i.e., how character data sent by the application is handled before being transmitted to the serial port or screen. It can be used forc_oflagThe macros for the member are as follows:
| Member | Meaning of the corresponding member |
|---|---|
| OPOST | Enable output processing; if this flag is not set, other flags are ignored |
| OLCUC | Convert uppercase characters in output to lowercase characters |
| ONLCR | Convert newline (NL ‘\n’) in output to carriage return (CR ‘\r’) |
| OCRNL | Convert carriage return (CR ‘\r’) in output to newline (NL ‘\n’) |
| ONOCR | Do not output carriage return (CR) at column 0 |
| ONLRET | Do not output carriage return |
| OFILL | Send fill characters to provide delay |
| OFDEL | If this flag is set, the fill character is DEL; otherwise, it is NULL character |
Control mode
In this structure, the most importantc_cflag, can control the hardware characteristics of the terminal device. For example, for a serial port, this field is important and can set hardware features such as baud rate, data bits, parity bits, and stop bits. By settingstruct termiosin the structurec_cflagThe member’s flags configure the control mode. Can be used forc_cflagThe member’s flags are as follows:
Baud Rate bitmask
| Constant Name | Meaning |
|---|---|
| B0 | 0 baud (DTR dropped) |
| B1800 | 1800 baud |
| B2400 | 2400 baud |
| B4800 | 4800 baud |
| B9600 | 9600 baud |
| B19200 | 19200 baud |
| B38400 | 38400 baud |
| B57600 | 57600 baud |
| B115200 | 115200 baud |
Data Bits Bitmask
| Constant Name | Meaning |
|---|---|
| CS5 | 5 Data Bits |
| CS6 | 6 Data Bits |
| CS7 | 7 Data Bits |
| CS8 | 8 Data Bits |
Stop Bits Bitmask
| Constant Name | Meaning |
|---|---|
| CSTOPB | 2 Stop Bits (default is 1 Stop Bit) |
Other Control Flags
| Constant Name | Meaning |
|---|---|
| CREAD | Receive Enable |
| PARENB | Parity enable |
| PARODD | Use odd parity instead of even parity |
| HUPCL | Hang up on last close (drop DTR) |
| CLOCAL | Local connection (do not change port owner) |
| LOBLK | Block job control output |
| CNET_CTSRTS | Hardware flow control enable |
Local mode
Local mode is used to control the terminal’s local data processing and working mode. By setting thestruct termiosstructure member’sc_lflagflags, the local mode is configured. The availablec_lflagmember flags are as follows:
| Constant name | Meaning description |
|---|---|
| INPCK | Enable parity check function. When enabled, the system checks received data for parity errors. |
| IGNPAR | Ignore parity errors. Even if a parity error is detected, data is not discarded or an exception is raised. |
| PARMRK | When a parity error occurs, mark the character (usually by inserting a\033and\000) for debugging. |
| ISTRIP | Clear the 8th bit (most significant bit) of all received data bytes, i.e., keep only the lower 7 bits (for 7-bit character sets). |
| IXON | Enableoutput software flow control(XON/XOFF). When the buffer is full, send XOFF (^S) to pause transmission; when idle, send XON (^Q) to resume. |
| IXOFF | Enableinput software flow control. Allows the device to actively send XON/XOFF control characters at the receiving end to control the sender. |
| IXANY | Allow any character (not just XON) to trigger flow control restart. By default, only XON can restart transmission. |
| IGNBRK | Ignore the break condition. That is, ignore line break signals (such as prolonged low level). |
| BRKINT | When a break is detected, send aSIGINTsignal to the process (usually used to interrupt program execution). |
| INLCR | Convert newline characters (NL,\n) converted to carriage return (CR,\r). Common in some older terminals. |
| IGNCR | Ignore carriage return (CR,\r) in input, do nothing. |
| ICRNL | Convert carriage return (CR,\r) to newline (NL,\n). This is a common end-of-line handling method. |
| ICANON | Enable canonical mode. In this mode, input is processed line by line (ending with newline), supporting editing (e.g., backspace, delete), echo, etc. Disabling enters raw mode. |
Special control characters
Special control characters are character combinations such as Ctrl+C, Ctrl+Z, etc. When a user types such key combinations, the terminal takes special action.struct termiosIn the structure,c_ccthe array maps various special characters to corresponding support functions. Each character position (array index) is defined by corresponding macros, as shown below
| Constant name | Function | Default key | Usage Scenarios |
|---|---|---|---|
| VKILL | Delete Entire Line | Ctrl+U | Edit Long Commands |
| VEOF | End of File | Ctrl+D | Submit Input or Close |
| VEOL | End-of-Line Marker | CR | Compatibility with Old Terminals |
| VEOL2 | Second End-of-Line | LF | Multiple Line Break Formats |
| VMIN | Minimum Character Count | - | Non-Canonical Mode Read Control |
| VTIME | Timeout Duration | - | Non-Canonical Mode Read Control |
| VINTR | Interrupt | Ctrl+C | Terminate Program |
| VQUIT | Pause | Ctrl+Z | Suspend Process |
| VERASE | Delete Character | Backspace | Single Character Editing |
Common Serial Port Control Functions
tcgetattr()
Function: Get the attribute configuration of the current terminal (or serial port device).
Prototype:
1 |
|
- Parameters:
fd: File descriptor (e.g., opened serial port/dev/ttyS0)termios_p: Pointer tostruct termiospointer, used to store the current configuration
- Return Value: Returns 0 on success, -1 on failure
- Purpose: Reads the current serial port settings such as baud rate, data bits, parity, flow control, input/output mode, etc.
Usually, this function is called before modifying the serial port to save the original configuration, so that it can be restored when the program exits.
tcsetattr()
Function: Sets the attribute configuration of the terminal (or serial port).
Prototype:
1 | int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); |
- Parameters:
fd: File descriptoroptional_actions: Specifies when to apply the new settings. Common values:TCSANOW: Takes effect immediatelyTCSADRAIN: Takes effect after output is complete (commonly used for output-related settings)TCSAFLUSH: Takes effect immediately after clearing the input/output buffer
termios_p: Contains the new configurationstruct termiospointer
- Return value: Returns 0 on success, -1 on failure
- Purpose: Apply new serial port parameters (e.g., baud rate, 8N1 configuration, raw mode, etc.)
⚠️ After modification, it is recommended to check the return value to ensure the setting is successful.
cfgetispeed() and cfgetospeed()
Function: Get the input (receive) and output (transmit) baud rates respectively.
Prototype:
1 | speed_t cfgetispeed(const struct termios *termios_p); |
- Return Value: Baud rate constant (e.g.,
B9600,B115200), not the actual value (e.g., 9600) - Note: Returns a
speed_ttype ofmask value, cannot be used directly as an integer
The actual baud rate needs to be converted via lookup table or system-specific methods (some systems provide
cfmakeraw()orioctlto obtain the actual rate).
cfsetispeed() and cfsetospeed()
Function: Set input and output baud rates respectively.
Prototype:
1 | int cfsetispeed(struct termios *termios_p, speed_t speed); |
Parameters:
termios_p: Pointer to thetermiosstructure to be modifiedspeed: Baud rate constant (e.g.,B9600,B115200)
Return value: Returns 0 on success, -1 on failure
Typical usage:
1
2
3
4
5struct termios tty;
tcgetattr(fd, &tty);
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tcsetattr(fd, TCSANOW, &tty);
Typically, the transmit and receive baud rates are set to the same value (full-duplex communication), unless special requirements exist.
tcflush() and tcflow()
tcflush()
Function: Flush the terminal’s input or output queue.
Prototype:
1 | int tcflush(int fd, int queue_selector); |
queue_selectorValues:TCIFLUSH: Flushinput queue(data received but not read)TCOFLUSH: Flushoutput queue(data to be sent but not yet transmitted)TCIOFLUSH: Flush both input and output queues
- Purpose: Clear dirty data before reconfiguring the serial port or restarting communication.
tcflow()
Function: Controls the data flow of the serial port (pause/resume transmission).
Prototype:
1 | int tcflow(int fd, int action); |
actionValues:TCOOFF: PauseOutput(Sender stops sending)TCOON: ResumeOutputTCIOFF: PauseInput(Send XOFF to the peer, requesting it to pause)TCION: ResumeInput(Send XON to the peer, allowing it to continue sending)
- Usage: Manually implement software flow control (XON/XOFF), or handle buffer overflow.
💡 Note:
tcflow()depends onIXON/IXOFFwhether the flag is enabled.
Serial Port Operation Flow
Set the Baud Rate of the Serial Port
When writing serial port applications, setting the baud rate is one of the necessary steps, as it determines the data transmission speed.
In Linux, setting the baud rate typically uses the cfsetspeed() function. The required header file and function prototype for cfsetspeed() are as follows:
1 |
|
It accepts astruct termiosstructure as an input parameter and returns an integer value indicating whether the operation was successful.
This function actually sets the baud rate in thec_cflagfield. For example, to set the baud rate to 115200, you can call the following code:
1 | struct termios options; |
The following points need to be noted:
- First, you need to open the serial port device file and obtain the serial port’s attributes, including the baud rate and other properties. You can use the
tcgetattr()function to get the attribute values and store them in atermiosstruct variable. - When setting the baud rate, you need to call the
cfsetspeed()function and pass a baud rate constant as a parameter. Common baud rate constants include B9600, B115200, etc. These constants can be found in the header filetermios.h. - After setting, you need to use the
tcsetattr()function to write the attribute values back to the serial port device.
Setting the Data Bit Size
In serial communication, the data bit refers to the number of actual data bits contained in each character (byte). Typically, a character contains 8 bits (i.e., 8 zeros or ones), but it can sometimes be 7 bits or other values.
When writing serial port applications, you need to use thestruct termiosmember variable in thec_cflagstruct to set the data bit size. Specifically, you need to clear the bits related to the data bit in thec_cflagmember variable, and then set the corresponding value as needed.
The clear operation typically uses the bitwise AND (&) operator and the bitwise NOT (~) operator. The specific steps are as follows:
1 | new_cfg.c_cflag &= ~CSIZE; // Clear the bits related to the data bits. |
Here, CSIZE is a macro definition representing the bit mask for the data bits. The macro is usually defined in thetermios.hheader file, with its value as follows:
1 |
Through the clear operation, all bits related to the data bits are set to 0. Next, the bitwise OR (|) operator and macro definitions can be used to set the specific number of data bits, for example:
1 | new_cfg.c_cflag |= CS8; // Set the number of data bits to 8 bits. |
At this point, the CS8 macro definition will be interpreted as a bit mask containing 8 bits, and it is set into thec_cflagmember variable via the bitwise OR operation, thus completing the setting of the data bits.
Set the parity bit.
The configuration of the serial port’s parity bit involves a total ofstruct termiostwo member variables in the structure:c_cflagandc_iflag。
First, for thec_cflagmember, it is necessary to addPARENBFlag to enable the parity check function of the serial port. Only after enabling the parity check function will a parity bit be generated for output data, thereby performing parity check on input data;
At the same time, forc_iflagmembers, it is also necessary to addINPCKflag, so that parity check can be performed on the received data. The code is as follows:
Odd parity enable
1 | new_cfg.c_cflag |= (PARODD | PARENB); // Set to odd parity |
Even parity enable
1 | new_cfg.c_cflag &= ~PARODD; // Set to even parity |
No parity
1 | new_cfg.c_cflag &= ~PARENB; // Disable parity check |
Set stop bits
In serial communication, the stop bit is used to specify the end position of each data frame.
After transmitting a complete data byte, one or more stop bits are usually required so that the receiving end can determine the end of a data frame.
The number of stop bits is typically 1 or 2 bits, with 1 stop bit being widely used and 2 stop bits being less common.
In Linux, by setting thestruct termiosmember variable in thec_cflagstructure to control theCSTOPBflag bit to control the number of stop bits. WhenCSTOPBis 0, only 1 stop bit is used; whenCSTOPBis 1, 2 stop bits are used.
For example, the following code sets the stop bit of the serial port to 1 bit:
1 | new_cfg.c_cflag &= ~CSTOPB; // Set stop bit to 1 bit |
Example
1 |
|
GPS module programming
The Global Navigation Satellite System is a system that uses satellite technology to provide precise time and location information to users worldwide.
The positioning module is often connected to the CPU via a serial port, and then the CPU uploads the coordinates through other methods such as Wi-Fi or Bluetooth.
The GPS module schematic is as follows

Among them,BUF_GPS_RSTthe interface is left floating and not connected. The following is the connection table for the 20-pin base connecting to the RK3568 development board GPIO interface:
| GPS module pin number | GPS module pin name | Connected development board pin number | Connected development board pin name |
|---|---|---|---|
| 2 | BUF_GPS_TXD | 8 | UART9_RX_M1 |
| 3 | BUF_GPS_RXD | 6 | UART9_TX_M1 |
| 11 | GND | 19/20 | GND |
| 19 | VDD33_A31 | 2/4 | VCC3V3_SYS |
After the GPS module is connected, the antenna should be placed outdoors
Introduction to GPS data frames

Listed here are the common data types in the GPS NMEA format, including:
- GPRMC (Recommended Minimum Specific GPS/Transit data): The GPRMC data type provides the most basic GPS positioning information such as position, speed, and heading. This data type is commonly used in navigation systems and automatic ship navigation systems.
- GPVTG (Track Made Good and Ground Speed): The GPVTG data type provides course over ground and ground speed information, used to display navigation information for moving objects such as ships and vehicles. It provides the course over ground and speed relative to the ground.
- GPGGA (Global Positioning System Fix Data): The GPGGA data type includes position fix, time, position accuracy, altitude, and other location information, typically used by receivers to display the current position in real time.
- GPGSA (GPS DOP and Active Satellites): The GPGSA data type includes DOP values and the current satellite positioning status. It is used to provide information such as the number of available satellites in GPS satellite measurement data and the calculated position dilution of precision (DOP).
- GPGSV (GPS Satellites in View): The GPGSV data type provides information on currently visible satellites, including the satellite’s PRN, elevation, azimuth, signal strength, and more. This information helps the receiver find more satellites and improve positioning accuracy.
- GPGLL (Geographic Position - Latitude/Longitude): The GPGLL data type provides longitude and latitude information, used to describe the receiver’s current latitude and longitude position.
We only need to focus on the GPRMC message, as shown in the figure below:

GPRMC (Recommended Minimum Specific GNSS Data) is a common GPS data frame format used to transmit GPS position information between GPS devices or between GPS devices and other devices. The following is the information contained in the GPRMC data frame:
| Field Number | Field Value (Example) | Meaning | Detailed Explanation |
|---|---|---|---|
| 0 | $GPRMC | Message ID | Indicates this is the “Recommended Minimum Positioning Information” frame.GPIndicates provided by GPS system; other systems like GLONASS useGL, BeiDou usesBDorGB。 |
| 1 | 083634.00 | UTC time | Format:hhmmss.sssIndicates08:36:34.000 UTC→ Beijing time = UTC + 8 hours →16:36:34 (4:36 PM) |
| 2 | A | Status indicator | -A:Valid positioning(Active / Valid) -V:Invalid positioning(Void / Invalid), possibly due to weak signal or no satellite lock |
| 3 | 3854.62194 | Latitude (value) | Format:ddmm.mmmmi.e.**38°54.62194′**Convert to decimal degrees:38 + 54.62194/60 ≈ 38.910366° N |
| 4 | N | Latitude hemisphere | -N: North -S: South |
| 5 | 11526.10876 | Longitude (value) | Format:dddmm.mmmmi.e.**115°26.10876′**Convert to decimal degrees:115 + 26.10876/60 ≈ 115.435146° E |
| 6 | E | Longitude hemisphere | -E: East -W: West |
| 7 | 0.932 | Ground speed (knots) | Unit:knots1 knot = 1 nautical mile/hour ≈ 1.852 km/h →0.932 × 1.852 ≈ 1.73 km/h(walking speed) |
| 8 | 55.00 | Course Over Ground | Unit:degrees (°), withtrue north as 0°Range: 0.0 ~ 359.9° 55° indicates northeast direction |
| 9 | 200624 | UTC Date | Format:ddmmyy→ June 20, 2024 |
| 10 | (empty) | Magnetic Declination | Optional field, indicating the angle between magnetic north and true north (unit: degrees) |
| 11 | (empty) | Magnetic declination direction | EorW, used with field 10 |
| 12 | (Empty) | Positioning mode | -A: AutonomousD: DGPSE: Dead ReckoningN: No positioning (Note: Some modules output in this field, some put it in$GPGSA) |
| 13 | D*50 | Checksum | From$the first character after*to all characters beforeXOR checksumUsed to verify data integrity |
Example
gps.h
1 |
|
gps.c
1 |
|
uart.c
1 |
|

