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 Lyra, UART0 is designated as the debug serial port, and the baud rate of the debug serial port is 1500000 by default.
  • Luckfox Lyra Ultra/Ultra W Pin Diagram:

3. Serial Port Testing (Shell)

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

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

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

    iomux 0 12 16 
    iomux 0 13 17
    stty -F /dev/ttyS1 ispeed 115200 ospeed 115200
  • Disable echo:

    stty -F /dev/ttyS1 -echo

3.1 Communication with a Windows Host

Connect one end of a serial-to-TTL module to the PC and the other to pins 3(GND)、4(RMIO12)and 5(RMIO13)on the Lyra development board.

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

  2. 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/ttyS1
    echo "world !" > /dev/ttyS1
  3. 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# 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/ttyS1"
    #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;
    }
  1. Cross-compile and run the program. To build a cross-compilation environment, please refer to the "Program Compilation" or "GPIO" section.

    arm-none-linux-gnueabihf-gcc uart.c -o uart

6. Device Tree Overview

  1. The device file path is located inkernel-6.1/arch/arm/boot/dts/rk3506g-luckfox-lyra.dts, The following snippet enablesuart:

    &uart1 {
    pinctrl-0 = <&rm_io12_uart1_tx &rm_io13_uart1_rx>;
    status = "okay";
    pinctrl-names = "default";
    };