UART
本章介绍 Luckfox Lume 的串口设备、40Pin 接线、设备树配置,以及 UART3 PD14/PD15 引脚组与 Windows 主机通信和回环测试方法。
1. UART 简介
UART(Universal Asynchronous Receiver‑Transmitter,通用异步收发器)是嵌入式领域广泛使用的串行通信协议。UART 采用异步传输机制,收发双方不需要共用时钟信号,数据以逐位串行的方式进行传输,非常适用于短距离、低速率外设之间的数据交互。
Luckfox Lume 使用 /dev/ttyASx 命名片内串口。本章使用 UART3:/dev/ttyAS3,115200波特率、8数据位、无校验、1停止位(8N1),关闭软、硬件流控。
/dev/ttyAS0是系统控制台,不用于本章数据回环测试。
2. 串口测试(Shell)
2.1 串口引脚图
| 串口设备 | 功能 | Lume 信号 | 40Pin 物理引脚 |
|---|---|---|---|
/dev/ttyAS0 | 系统控制台 TX | PB9 / UART0_TX | 8 |
/dev/ttyAS0 | 系统控制台 RX | PB10 / UART0_RX | 10 |
/dev/ttyAS3 | 通用串口 TX | PD14 / UART3_TX | 15 |
/dev/ttyAS3 | 通用串口 RX | PD15 / UART3_RX | 13 |
| 公共地 | GND | GND | 6(或其他 GND 引脚) |

2.2 设备树配置
-
板级设备树路径:
device/config/chips/t153/configs/luckfox_lume/linux-5.10-origin/board.dts -
SDK 中已定义
&pioUART3 引脚组,需要启用&uart3:&pio {uart3_pins_active: uart3_pins@0 {pins = "PD14", "PD15";function = "uart3";};uart3_pins_sleep: uart3_pins@1 {pins = "PD14", "PD15";function = "io_disabled";};};&uart3 {pinctrl-names = "default", "sleep";pinctrl-0 = <&uart3_pins_active>;pinctrl-1 = <&uart3_pins_sleep>;status = "okay";}; -
编译设备树及打包镜像:
./build.sh dts./build.sh./build.sh pack
2.3 查看串口设备
在 /dev 目录下,每一路 UART 串口对应一个独立的字符设备文件。设备命名一般以 ttyAS 搭配串口号生成,例如 /dev/ttyAS3 代表第 3 路 UART 串口。
可执行以下命令查看系统内所有串口设备:
root@luckfox:~# ls /dev/ttyAS*
/dev/ttyAS0 /dev/ttyAS3
2.4 设置串口
- 用stty工具查询其通信参数:
root@luckfox:~# stty -F /dev/ttyAS3speed 9600 baud; line = 0;
- 修改波特率,其中ispeed为输入速率,ospeed为输出速率:
stty -F /dev/ttyAS3 ispeed 115200 ospeed 115200
- 关闭回显:
stty -F /dev/ttyAS3 -echo
3.与Windows主机通讯
-
将串口转接模块一端连接至电脑 USB 接口,另一端接线接入 LuckFox Lume 开发板,接线如下:
Luckfox Lume UAB to UART 第15脚 PD14 RX 第13脚 PD15 TX 第6脚 GND GND -
打开 MobaXterm 软件,新建串口 (Serial)会话;配置串口波特率,波特率为 115200,可根据实际修改后的串口参数进行调整。

-
在开发板上的终端执行以下指令,使用 echo 命令向终端设备文件写入字符串"Hello"和"world !":
root@luckfox:~# echo Hello > /dev/ttyAS3root@luckfox:~# echo "world !" > /dev/ttyAS3 -
Windows 上的串口调试助手会接收到内容:
4. 串口通信(Python程序)
-
完整代码:
#!/usr/bin/env python3import osimport selectimport sysimport termiosimport timeDEVICE = "/dev/ttyAS3"MESSAGE = b"Luckfox Lume UART test\r\n"READ_SIZE = 128TIMEOUT = 3.0def configure_uart(fd):config = termios.tcgetattr(fd)config[0] = 0config[1] = 0config[2] &= ~(termios.CSIZE | termios.PARENB |termios.PARODD | termios.CSTOPB |getattr(termios, "CRTSCTS", 0))config[2] |= termios.CS8 | termios.CLOCAL | termios.CREADconfig[3] = 0config[4] = config[5] = termios.B115200config[6][termios.VMIN] = 0config[6][termios.VTIME] = 0termios.tcsetattr(fd, termios.TCSANOW, config)def write_all(fd, data, deadline):sent = 0while sent < len(data):remaining = deadline - time.monotonic()if remaining <= 0 or not select.select([], [fd], [], remaining)[1]:raise TimeoutError("UART transmit timeout")try:count = os.write(fd, data[sent:])except BlockingIOError:continueif count == 0:raise OSError("UART write returned zero")sent += countdef read_reply(fd, deadline):received = bytearray()while len(received) < READ_SIZE:remaining = deadline - time.monotonic()if remaining <= 0:breakif not select.select([fd], [], [], remaining)[0]:breaktry:chunk = os.read(fd, READ_SIZE - len(received))except BlockingIOError:continueif not chunk:breakreceived.extend(chunk)return bytes(received)def communicate(device=DEVICE):fd = os.open(device, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)saved = Nonetry:saved = termios.tcgetattr(fd)configure_uart(fd)termios.tcflush(fd, termios.TCIFLUSH)deadline = time.monotonic() + TIMEOUTwrite_all(fd, MESSAGE, deadline)termios.tcdrain(fd)print("TX:", MESSAGE.decode("utf-8").rstrip("\r\n"))reply = read_reply(fd, deadline)if not reply:print("RX: (no data)")print("Read 0 bytes")return 0text = reply.decode("utf-8", errors="replace")print(f"RX: {text}")print(f"Read {len(reply)} bytes")return 0finally:try:if saved is not None:termios.tcsetattr(fd, termios.TCSANOW, saved)finally:os.close(fd)def main():try:return communicate()except KeyboardInterrupt:print("UART communication interrupted.", file=sys.stderr)return 130except (OSError, termios.error) as error:print(f"UART error: {error}", file=sys.stderr)return 1if __name__ == "__main__":sys.exit(main()) -
打开并配置串口:
fd = os.open(device, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)saved = termios.tcgetattr(fd)configure_uart(fd)以读写、非阻塞方式打开
/dev/ttyAS3,配置115200、8N1、无软硬件流控,并关闭回显和字符转换。 -
发送数据:
write_all(fd, MESSAGE, deadline)termios.tcdrain(fd)write_all()循环处理部分写入,tcdrain()等待发送队列中的数据传输完成。 -
接收数据:
reply = read_reply(fd, deadline)发送完成后等待 Windows 回复,达到128字节或总计3秒超时后返回。
-
执行程序:
python3 UART.py执行结果:
5. 串口通信(C程序)
-
完整代码:
#define _DEFAULT_SOURCE#define _POSIX_C_SOURCE 200809L#include <errno.h>#include <fcntl.h>#include <stdio.h>#include <sys/select.h>#include <termios.h>#include <time.h>#include <unistd.h>#define DEVICE "/dev/ttyAS3"#define READ_SIZE 128#define TIMEOUT 3.0static const unsigned char message[] = "Luckfox Lume UART test\r\n";static double monotonic_seconds(void){struct timespec now;if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)return -1.0;return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;}static int wait_ready(int fd, int writing, double deadline){for (;;) {double now = monotonic_seconds();double remaining;struct timeval timeout;fd_set set;int result;if (now < 0)return -1;remaining = deadline - now;if (remaining <= 0)return 0;timeout.tv_sec = (time_t)remaining;timeout.tv_usec =(suseconds_t)((remaining - timeout.tv_sec) * 1000000.0);FD_ZERO(&set);FD_SET(fd, &set);result = select(fd + 1, writing ? NULL : &set,writing ? &set : NULL, NULL, &timeout);if (result < 0 && errno == EINTR)continue;return result;}}static int configure_uart(int fd, struct termios *saved){struct termios config;if (tcgetattr(fd, saved) < 0)return -1;config = *saved;config.c_iflag = 0;config.c_oflag = 0;config.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);#ifdef CRTSCTSconfig.c_cflag &= ~CRTSCTS;#endifconfig.c_cflag |= CS8 | CLOCAL | CREAD;config.c_lflag = 0;config.c_cc[VMIN] = 0;config.c_cc[VTIME] = 0;if (cfsetispeed(&config, B115200) < 0 ||cfsetospeed(&config, B115200) < 0 ||tcsetattr(fd, TCSANOW, &config) < 0)return -1;return 0;}static int write_all(int fd, const unsigned char *data, size_t size,double deadline){size_t sent = 0;while (sent < size) {int ready = wait_ready(fd, 1, deadline);ssize_t count;if (ready <= 0) {if (ready == 0)errno = ETIMEDOUT;return -1;}count = write(fd, data + sent, size - sent);if (count < 0 && (errno == EAGAIN || errno == EINTR))continue;if (count <= 0) {if (count == 0)errno = EIO;return -1;}sent += (size_t)count;}return 0;}static ssize_t read_reply(int fd, unsigned char *data, size_t size,double deadline){size_t used = 0;while (used < size) {int ready = wait_ready(fd, 0, deadline);ssize_t count;if (ready < 0)return -1;if (ready == 0)break;count = read(fd, data + used, size - used);if (count < 0 && (errno == EAGAIN || errno == EINTR))continue;if (count < 0)return -1;if (count == 0)break;used += (size_t)count;}return (ssize_t)used;}static int communicate(const char *device){int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);int result = 1;int have_saved = 0;struct termios saved;unsigned char received[READ_SIZE];const size_t message_size = sizeof(message) - 1;double now;double deadline;ssize_t received_size;if (fd < 0) {perror(device);return 1;}if (fd >= FD_SETSIZE) {fprintf(stderr, "UART file descriptor exceeds select limit\n");goto cleanup;}if (configure_uart(fd, &saved) < 0)goto error;have_saved = 1;if (tcflush(fd, TCIFLUSH) < 0)goto error;now = monotonic_seconds();if (now < 0)goto error;deadline = now + TIMEOUT;if (write_all(fd, message, message_size, deadline) < 0)goto error;if (tcdrain(fd) < 0)goto error;printf("TX: %.*s\n", (int)(message_size - 2), message);received_size = read_reply(fd, received, sizeof(received), deadline);if (received_size < 0)goto error;if (received_size == 0) {puts("RX: (no data)");puts("Read 0 bytes");result = 0;goto cleanup;}printf("RX: ");fwrite(received, 1, (size_t)received_size, stdout);putchar('\n');printf("Read %zd bytes\n", received_size);result = 0;goto cleanup;error:perror("UART");cleanup:if (have_saved && tcsetattr(fd, TCSANOW, &saved) < 0) {perror("UART restore");result = 1;}if (close(fd) < 0) {perror("UART close");result = 1;}return result;}int main(void){return communicate(DEVICE);} -
打开并配置串口:
int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);config.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);config.c_cflag |= CS8 | CLOCAL | CREAD;cfsetispeed(&config, B115200);cfsetospeed(&config, B115200);以非阻塞方式打开
/dev/ttyAS3,设置为115200、8位数据位、无校验、1位停止位,并关闭软硬件流控。发送前清空接收缓冲区,结束时恢复串口原配置。 -
发送与接收数据:
if (write_all(fd, message, message_size, deadline) < 0)goto error;if (tcdrain(fd) < 0)goto error;received_size = read_reply(fd, received, sizeof(received), deadline);串口发送
Luckfox Lume UART test\r\n,然后在同一个3秒截止时间内接收最多128字节,并直接显示收到的内容和字节数。 -
交叉编译:使用 Lume SDK 的 ARM 工具链。
export PATH=<Luckfox_Lume_SDK>/out/toolchain/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin:$PATHarm-linux-gnueabihf-gcc -std=c11 -O2 -Wall -Wextra UART.c -o UART -
运行程序:
chmod +x UART./UART执行结果:
超时内没有收到数据时: