UART
In this chapter, we will learn how to use serial ports and terminal devices.
Sample program : Code.zip
1. Terminal Devices
Terminal devices, abbreviated as TTY (short for Teletypewriter), originally originated from teletypewriters and were used for interactive communication with computers. TTY initially referred to physical or virtual terminals connected to Unix systems. Over time, the concept of TTY has also extended to serial devices such as ttyn, ttySACn, and so on. In the Linux system, terminal devices are very well-supported, and they are typically accessed and controlled through special device files, enabling serial communication. These device files are located in the /dev
directory, and they can be used to read and write data as well as perform various terminal control operations.
2. Serial Port Testing (Shell)
2.1 Pinout
LuckFox Pico and LuckFox Pico Mini A/B has three serial ports: UART2, UART3, and UART4, with UART2 being the debugging serial port.
LuckFox Pico Plus has four serial ports: UART2, UART3, UART4, and UART5, with UART2 being the debugging serial port.
For instructions on how to enable other serial ports, please refer to the PWM section.
LuckFox Pico Diagram:
LuckFox Pico Mini A/B Diagram:
LuckFox Pico Plus Diagram:
2.2 Device Directories
In the /dev
directory, each UART device has its own directory. The directory names are ttyS
followed by the serial port number, for example, /dev/ttyS3
represents UART3. You can check them using the following command:
# ls /dev/ttyS*
/dev/ttyS3 /dev/ttyS4
2.3 Configuring the Serial Port
Use the
stty
tool to query its communication parameters:# stty -F /dev/ttyS3
speed 9600 baud; line = 0;
-brkint -imaxbelModify the baud rate, where
ispeed
is the input speed, andospeed
is the output speed:# stty -F /dev/ttyS3 ispeed 115200 ospeed 115200
Disable echo:
stty -F /dev/ttyS3 -echo
2.4 Communicating with a Windows Host
Connect one end of the serial module to your computer and the other end to pins 18 (GND), 19 (UART7_TX), and 20 (UART7_RX) on LuckFox Pico.
Download and open MobaXterm, select the serial port, and set the baud rate (default is 9600; please adjust it according to your actual configuration).
Execute the following commands on the terminal on your development board to write the strings "Hello" and "world!" to the terminal device file using the
echo
command:echo Hello > /dev/ttyS3
echo "world !" > /dev/ttyS3The serial debugging assistant on Windows will receive the content:
3. Serial Communication (Python Program)
In the previous section, we demonstrated how to configure the serial port using commands and achieve serial communication by writing strings to the terminal device file. Next, we will implement serial communication using a Python program.
3.1 Using pyserial
Complete Code
This code uses the Python
serial
library to implement serial communication on serial port 3.import serial
import time
with serial.Serial(
"/dev/ttyS3",
baudrate=115200,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_ONE,
parity=serial.PARITY_NONE,
timeout=1,
) as uart3:
uart3.write(b"Hello World!\n")
buf = uart3.read(128)
print("Raw data:\n", buf)
data_strings = buf.decode("utf-8")
print("Read {:d} bytes, printed as string:\n {:s}".format(len(buf), data_strings))Open Serial Port
In this code, the
serial.Serial
object opens a serial connection on the device file/dev/ttyS3
. Different parameters, such as baud rate (baudrate
), data bits (bytesize
), stop bits (stopbits
), parity (parity
), and timeout (timeout
), can be configured to set the communication properties of the serial port. This code uses thewith
statement to ensure that the connection is closed correctly after use.with serial.Serial(
"/dev/ttyS3",
baudrate=115200,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_ONE,
parity=serial.PARITY_NONE,
timeout=1,
) as uart3:Send Data
By calling the
write
method of the serial port object, this code sends a byte string (b"Hello World!\n"
) to the opened serial port.uart3.write(b"Hello World!\n")
Receive Data
In this code, the
read
method is used to read up to 128 bytes of data from the serial port, with a timeout of 1 second. The raw data read is printed in byte form, and then thedecode("utf-8")
method is used to decode the byte data into a UTF-8 encoded string. Finally, the number of bytes read and the decoded string are printed.buf = uart3.read(128)
print("Raw data:\n", buf)
data_strings = buf.decode("utf-8")
print("Read {:d} bytes, printed as string:\n {:s}".format(len(buf), data_strings))
3.2 Using python-periphery
Complete Code
This code uses the
Serial
class from theperiphery
library to implement serial communication on serial port 3.from periphery import Serial
try:
serial = Serial(
"/dev/ttyS3",
baudrate=115200,
databits=8,
parity="none",
stopbits=1,
xonxoff=False,
rtscts=False,
)
serial.write(b"Hello World!\n")
buf = serial.read(128, 1)
print("Raw data:\n", buf)
data_strings = buf.decode("utf-8")
print("Read {:d} bytes, printed as string:\n {:s}".format(len(buf), data_strings))
finally:
serial.close()Open Serial Port
This code uses the
Serial
class from theperiphery
library to open a serial connection. The configuration of the serial port includes parameters such as the device file path (/dev/ttyS3
), baud rate, data bits, parity bits, stop bits, etc.serial = Serial(
"/dev/ttyS3",
baudrate=115200,
databits=8,
parity="none",
stopbits=1,
xonxoff=False,
rtscts=False,
)Send Data
This code, through the opened serial connection
serial
, sends a data packet containing "Hello World!\n" bytes to the serial port.serial.write(b"Hello World!\n")
Receive Data
In this code, the
serial.read(128, 1)
method is called to read up to 128 bytes of data from the serial port, with a timeout of 1 second. The raw data read is printed in byte form, and then thedecode("utf-8")
method is used to decode the byte data into a UTF-8 encoded string. Finally, the number of bytes read and the decoded string are printed.buf = serial.read(128, 1)
print("Raw data:\n", buf)
data_strings = buf.decode("utf-8")
print("Read {:d} bytes, printed as string:\n {:s}".format(len(buf), data_strings))
3.3 Running the Program
Use the vi tool in the terminal to create a Python file, paste the Python program, and save it.
# vi uart.py
Run the program.
# python3 uart.py
Experimental Observations.
Connect the TX and RX of UART3, run the program:
4. Serial Communication (C Program)
In the previous section, we demonstrated how to achieve serial communication using Shell commands and Python programs. Additionally, we can use C library functions or system calls to read and write device files for the purpose of serial communication. Please note that to run programs on specific embedded systems, it is typically necessary to use cross-compilation tools to compile the code and generate executable files that can run on the target development board. Now, let's explore the specific implementation steps.
4.1 Complete Code
Serial communication can be achieved through the following program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
int main() {
int serial_port_num;
char serial_port[15];
printf("Select a serial port (3/4): ");
scanf("%d", &serial_port_num);
sprintf(serial_port,"/dev/ttyS%d",serial_port_num);
int serial_fd;
serial_fd = open(serial_port, O_RDWR | O_NOCTTY);
if (serial_fd == -1) {
perror("Failed to open serial port");
return 1;
}
struct termios tty;
memset(&tty, 0, sizeof(tty));
if (tcgetattr(serial_fd, &tty) != 0) {
perror("Error from tcgetattr");
return 1;
}
cfsetospeed(&tty, B9600);
cfsetispeed(&tty, B9600);
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) {
perror("Error from tcsetattr");
return 1;
}
char tx_buffer[] = "hello world!\n";
ssize_t bytes_written = write(serial_fd, tx_buffer, sizeof(tx_buffer));
if (bytes_written < 0) {
perror("Error writing to serial port");
close(serial_fd);
return 1;
}
printf("\rtx_buffer: \n %s ", tx_buffer);
char rx_buffer[256];
int bytes_read = read(serial_fd, rx_buffer, sizeof(rx_buffer));
if (bytes_read > 0) {
rx_buffer[bytes_read] = '\0';
printf("\rrx_buffer: \n %s ", rx_buffer);
} else {
printf("No data received.\n");
}
close(serial_fd);
return 0;
}
4.2 Opening the Serial Port
This code first allows the user to choose between communicating via UART3 or UART4, and then opens the corresponding serial port device file, saving its file descriptor in the serial_fd
variable.
printf("Select a serial port (3/4): ");
scanf("%d", &serial_port_num);
sprintf(serial_port,"/dev/ttyS%d",serial_port_num);
int serial_fd;
serial_fd = open(serial_port, O_RDWR | O_NOCTTY);
if (serial_fd == -1) {
perror("Failed to open serial port");
return 1;
}
4.3 Configuring the Serial Port
In this part of the code, we define a termios
structure named tty
for configuring the parameters of serial communication. First, we initialize it to zero using memset
. Then, we use the tcgetattr
function to get the current attributes of the serial port and store them in the tty
structure.
struct termios tty;
memset(&tty, 0, sizeof(tty));
if (tcgetattr(serial_fd, &tty) != 0) {
perror("Error from tcgetattr");
return 1;
}
In this part of the code, we set some parameters for serial communication. We use the cfsetospeed
and cfsetispeed
functions to set the baud rate to 9600, for output and input, respectively. We clear the PARENB
flag to disable parity; clear the CSTOPB
flag using the c_cflag
attribute to use one stop bit; clear the data bits by clearing the CSIZE
flag and set the data bits to 8 bits using the CS8
flag. Finally, we use the tcsetattr
function to set the modified attributes as the current attributes of the serial port, using the TCSANOW
flag to apply these settings immediately.
cfsetospeed(&tty, B9600);
cfsetispeed(&tty, B9600);
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) {
perror("Error from tcsetattr");
return 1;
}
4.4 Sending Data
This code achieves serial data transmission by writing the string "hello world!\n" to serial_fd
. Successful transmission will print the data on the terminal.
char tx_buffer[] = "hello world!\n";
ssize_t bytes_written = write(serial_fd, tx_buffer, sizeof(tx_buffer));
if (bytes_written < 0) {
perror("Error writing to serial port");
close(serial_fd);
return 1;
}
printf("\rtx_buffer: \n %s ", tx_buffer);
4.5 Receiving Data
This code achieves serial data reception by reading from serial_fd
. Successful reception will print the data on the terminal.
char rx_buffer[256];
int bytes_read = read(serial_fd, rx_buffer, sizeof(rx_buffer));
if (bytes_read > 0) {
rx_buffer[bytes_read] = '\0';
printf("\rrx_buffer: \n %s ", rx_buffer);
} else {
printf("No data received.\n");
}
4.6 Cross-Compilation
Specify the Cross-Compilation Tool
First, you need to add the path to the cross-compilation tool to the system's
PATH
environment variable so that you can use the cross-compilation tool from anywhere. You can add the following line to your shell configuration file (usually~/.bashrc
or~/.bash_profile
or~/.zshrc
, depending on your shell). Note that the path afterPATH=
should point to the directory where the cross-compilation tool is located.gcc path
<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
Open the shell configuration file.
nano ~/.bashrc
Add the path of the cross-compilation tool to the system's PATH environment variable. Replace
<SDK Directory>
with your own SDK path, such as/home/luckfox/luckfox-pico/
.export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
Reload the shell configuration file to apply the changes:
source ~/.bashrc
Compile the Program Using the Cross-Compilation Tool
arm-rockchip830-linux-uclibcgnueabihf-gcc uart.c -o uart
After successful cross-compilation, an executable file that can run on the development board will be generated in the current directory.
# ls
uart uart.c
4.7 Running the Program
File Transfer
First, transfer the
uart
program from the virtual machine to Windows, and then transfer it to the development board via TFTP or ADB. Here are the steps to transfer the file from Windows to the development board using ADB:adb push path_to_file destination_on_development_board
eg: (Transferring the `uart` file from the current directory to the root directory of the development board)
adb push uart /Running the Program
Modify the permissions of the
uart
file and then run the program:# chmod 777 uart
# ./uartExperimental Observations
Connect the TX and RX of UART3, run the program, and select UART3 for communication: