Skip to main content

UART

1. Introduction to UART

UART (Universal Asynchronous Receiver-Transmitter) is a widely used serial communication protocol for embedded systems and computers. UART transmits data bit by bit using asynchronous communication, eliminating the need to share a clock signal between the transmitter and receiver. This makes UART a common choice for short-distance, low-speed data transfers between devices. In Linux systems, UART is typically registered as a serial terminal device, appearing in the system under /dev/tty* or /dev/serial/*, with the specific device name depending on hardware and driver configurations.

2. Serial Port Pin Diagram

  • In the Omni3576, UART0_M0 is designated as the debug serial port.For details on how to use the debug serial port, please refer to the “Serial Login” section in the Login chapter.The location of the debug serial port interface is shown below:

  • Omni3576 Pin Diagram:

3. Serial Port Testing (Shell)

  • Taking UART2_M1 as an example, after successfully opening it, use the following command to inspect UART2_M1

    root@luckfox:/home/luckfox# ls /dev/ttyS*
    /dev/ttyS2 /dev/ttyS4
  • Query its communication parameters with the stty tool:

    root@luckfox:/home/luckfox# stty -F /dev/ttyS2
    speed 9600 baud; line = 0;
    -brkint -imaxbel
  • Change the baud rate (where ispeed is the input rate, and ospeed is the output rate):

    stty -F /dev/ttyS2 ispeed 115200 ospeed 115200
  • Disable echo:

    stty -F /dev/ttyS2 -echo

3.1 Communication with a Windows Host

  1. Connect one end of a serial-to-TTL module to the PC and the other to pins 6 (GND), 8 (UART2_TX), and 10 (UART2_RX) on the Omni3576 development board.

  2. Download and open MobaXterm, select the serial port, and set the baud rate (default: 9600; adjust according to your actual configuration).

  3. Execute the following commands on the development board terminal to write the strings "Hello" and "world!" to the terminal device file using the echo command:

    echo Hello > /dev/ttyS2
    echo "world !" > /dev/ttyS2
  4. The serial debugging tool on Windows will receive the content.

4. Serial Communication (Python Program)

  1. Use Python's serial library to implement a complete program for sending and receiving data:

    #!/usr/bin/python3

    from periphery import Serial

    # Use context manager to automatically close the serial port
    with Serial("/dev/ttyS3", baudrate=115200, databits=8, parity="none", stopbits=1, xonxoff=False, rtscts=False) as serial:
    try:
    # Send data
    serial.write(b"Hello World!\n")

    # Read data
    buf = serial.read(128, 1)
    if buf:
    data_string = buf.decode("utf-8")
    print(f"Read {len(buf)} bytes:\n{data_string}")
    else:
    print("No data received.")

    except Exception as e:
    print(f"An error occurred: {e}")
  2. Run the program.

    root@luckfox:/home/luckfox# sudo python3 uart.py
    Read 13 bytes:
    Hello World!

5. Serial Communication (C Program)

  1. Opening the Serial Port.

    int open_serial(const char *device) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
    perror("Error opening serial port");
    return -1;
    }
    return fd;
    }
  2. Configuring Serial Port Parameters.

    int configure_serial(int fd, speed_t baudrate) {
    struct termios tty;

    if (tcgetattr(fd, &tty) != 0) {
    perror("Error getting tty attributes");
    return -1;
    }

    cfsetospeed(&tty, baudrate); // Set output baud rate
    cfsetispeed(&tty, baudrate); // Set input baud rate

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8 data bits
    tty.c_cflag |= (CLOCAL | CREAD); // Enable receiver, ignore modem lines
    tty.c_cflag &= ~(PARENB | PARODD); // No parity
    tty.c_cflag &= ~CSTOPB; // 1 stop bit
    tty.c_cflag &= ~CRTSCTS; // No RTS/CTS hardware flow control

    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Disable software flow control
    tty.c_iflag &= ~(ICRNL | INLCR); // Disable CR-to-LF translation

    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Disable canonical mode and echo
    tty.c_oflag &= ~OPOST; // Disable output processing

    tty.c_cc[VMIN] = 0; // Non-blocking mode
    tty.c_cc[VTIME] = 10; // 1-second timeout

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
    perror("Error setting tty attributes");
    return -1;
    }

    return 0;
    }
  3. Sending Data.

    int send_data(int fd, const char *data) {
    int len = strlen(data);
    int bytes_written = write(fd, data, len);

    if (bytes_written < 0) {
    perror("Error writing to serial port");
    return -1;
    }
    return bytes_written;
    }
  4. Receiving Data.

    int receive_data(int fd, char *buffer, int buffer_size) {
    int bytes_read = read(fd, buffer, buffer_size);

    if (bytes_read < 0) {
    perror("Error reading from serial port");
    return -1;
    }
    return bytes_read;
    }
  5. Closing the Serial Port.

    void close_serial(int fd) {
    if (close(fd) != 0) {
    perror("Error closing serial port");
    }
    }
  6. Complete Example Code.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h> // open()
    #include <termios.h> // tcgetattr(), tcsetattr()
    #include <unistd.h> // read(), write(), close()
    #include <errno.h> // errno

    #define SERIAL_PORT "/dev/ttyS3"
    #define BAUDRATE B115200
    #define BUFFER_SIZE 128


    int open_serial(const char *device) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
    perror("Error opening serial port");
    return -1;
    }
    return fd;
    }


    int configure_serial(int fd, speed_t baudrate) {
    struct termios tty;

    if (tcgetattr(fd, &tty) != 0) {
    perror("Error getting tty attributes");
    return -1;
    }

    cfsetospeed(&tty, baudrate);
    cfsetispeed(&tty, baudrate);

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /
    tty.c_cflag |= (CLOCAL | CREAD);
    tty.c_cflag &= ~(PARENB | PARODD);
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;

    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(ICRNL | INLCR);

    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_oflag &= ~OPOST;

    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 10;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
    perror("Error setting tty attributes");
    return -1;
    }

    return 0;
    }


    int send_data(int fd, const char *data) {
    int len = strlen(data);
    int bytes_written = write(fd, data, len);

    if (bytes_written < 0) {
    perror("Error writing to serial port");
    return -1;
    }
    return bytes_written;
    }


    int receive_data(int fd, char *buffer, int buffer_size) {
    int bytes_read = read(fd, buffer, buffer_size);

    if (bytes_read < 0) {
    perror("Error reading from serial port");
    return -1;
    }
    return bytes_read;
    }


    void close_serial(int fd) {
    if (close(fd) != 0) {
    perror("Error closing serial port");
    }
    }

    int main() {
    int serial_fd;
    char recv_buffer[BUFFER_SIZE];


    serial_fd = open_serial(SERIAL_PORT);
    if (serial_fd < 0) return 1;


    if (configure_serial(serial_fd, BAUDRATE) != 0) {
    close_serial(serial_fd);
    return 1;
    }


    const char *message = "Hello World!\n";
    if (send_data(serial_fd, message) < 0) {
    close_serial(serial_fd);
    return 1;
    }
    printf("Sent: %s", message);


    int bytes_received = receive_data(serial_fd, recv_buffer, BUFFER_SIZE - 1);
    if (bytes_received > 0) {
    recv_buffer[bytes_received] = '\0';
    printf("Received: %s\n", recv_buffer);
    } else if (bytes_received == 0) {
    printf("No data received.\n");
    }


    close_serial(serial_fd);

    return 0;
    }
  7. Compile and Run the Program

    root@luckfox:/home/luckfox# gcc uart.c -o uart
    root@luckfox:/home/luckfox# ./uart
    Sent: Hello World!
    Received: Hello World!

6. Device Tree Overview

  1. The uart2 node is defined in the kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi file and can be directly referenced:

    uart2: serial@2ad50000 {
    compatible = "rockchip,rk3576-uart", "snps,dw-apb-uart";
    reg = <0x0 0x2ad50000 0x0 0x100>;
    interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru SCLK_UART2>, <&cru PCLK_UART2>;
    clock-names = "baudclk", "apb_pclk";
    reg-shift = <2>;
    reg-io-width = <4>;
    dmas = <&dmac0 10>, <&dmac0 11>;
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m0_xfer>;
    status = "disabled";
    };
  2. The device file path is located in kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts. The following snippet enables pwm2m0_ch0:

    &uart2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m1_xfer>;
    };