嵌入式Linux C应用编程——串口应用

串口应用编程

一、概述

串口(UART)是嵌入式 Linux 系统中非常常见的外设,主要用途包括:

  • 调试手段:通过串口输出调试打印信息,或发送指令给主机端处理
  • 数据通信:与其他设备或传感器进行数据交互(如 sensor 通过串口与主机通信)
  • 系统终端:在 Linux 系统中,串口常作为标准输入/输出设备,用户通过串口与系统交互

二、终端(Terminal)概念

2.1 什么是终端

终端是处理主机输入/输出的一套设备,用于显示主机运算的输出并接受主机要求的输入。本质上是能接受输入、能显示输出的人机交互设备。

2.2 终端的分类

类型 说明 示例
本地终端 直接连接物理设备的终端 PC 的显示器/键盘组合、开发板的 LCD 显示器+键盘
串口远程终端 通过串口线连接的终端 开发板通过串口线连到 PC,PC 运行终端模拟程序(Putty、MobaXterm、SecureCRT 等)
网络远程终端 通过网络协议连接的终端 ssh、Telnet 远程登录到主机(伪终端)

前两类称为物理终端(直接关联物理设备),第三类称为伪终端(不直接关联物理设备)。

2.3 终端设备节点

Linux 中一切皆文件,每个终端在 /dev 目录下都有对应的设备节点:

设备节点 对应的终端类型 说明
/dev/ttyX(如 tty1~tty63) 本地终端 Linux 内核初始化时生成的 63 个本地终端
/dev/pts/X(如 pts/0、pts/1) 伪终端 ssh/telnet 远程登录时生成的设备节点
/dev/ttymxcX(如 ttymxc0、ttymxc2) 串口终端 具体名称与驱动/硬件平台有关(I.MX6U 平台)

注意:设备节点名前缀以 tty 开头,表明它是一个终端。但具体名称因平台而异,如 ZYNQ 平台串口设备节点为 /dev/ttyPSX

对于 ALPHA/Mini I.MX6U 开发板,有两个串口:

  • /dev/ttymxc0 — UART1(USB 调试串口)
  • /dev/ttymxc2 — UART3(RS232/RS485 串口)

I.MX6U SoC 支持 8 个串口外设(UART1~UART8),出厂系统只注册了 UART1 和 UART3。

使用 who 命令可以查看当前系统连接了哪些终端。

img

img

三、termios API 与 struct termios 结构体

Linux 为上层用户将 ioctl() 操作封装成了一套标准的 API——termios API,用于对所有终端设备进行编程(包括串口、本地终端、伪终端等)。

使用 termios API 需要包含头文件:

#include <termios.h>
#include <unistd.h>

struct termios 结构体

该结构体描述了终端的配置信息,串口应用编程主要就是对其各个成员进行配置:

struct termios {
    tcflag_t c_iflag;    /* 输入模式 flags */
    tcflag_t c_oflag;    /* 输出模式 flags */
    tcflag_t c_cflag;    /* 控制模式 flags */
    tcflag_t c_lflag;    /* 本地模式 flags */
    cc_t     c_line;     /* 线路规程 */
    cc_t     c_cc[NCCS]; /* 特殊控制字符 */
    speed_t  c_ispeed;   /* 输入速率 */
    speed_t  c_ospeed;   /* 输出速率 */
};

配置原则

  • 不要直接初始化赋值,应使用按位与(&=)、按位或(|=)等操作来添加或清除标志
  • 并非所有标志对所有终端设备有效(例如串口可配置波特率,但本地终端键盘/显示器没有硬件波特率概念)

四、termios 配置详解

4.1 输入模式(c_iflag)

控制接收到的字符数据在被传递给应用程序之前的处理方式。

功能
IGNBRK 忽略输入终止条件
BRKINT 检测到输入终止条件时发送 SIGINT 信号
IGNPAR 忽略帧错误和奇偶校验错误
PARMRK 对奇偶校验错误做出标记
INPCK 对接收到的数据执行奇偶校验
ISTRIP 将所有接收到的数据裁剪为 7 比特位(去除第八位)
INLCR 将接收到的 NL(换行符)转换为 CR(回车符)
IGNCR 忽略接收到的 CR(回车符)
ICRNL 将接收到的 CR(回车符)转换为 NL(换行符)
IUCLC 将接收到的大写字符映射为小写字符
IXON 启动输出软件流控
IXOFF 启动输入软件流控

可使用 man 3 termios 查询所有标志的详细描述。

4.2 输出模式(c_oflag)

控制由应用程序发送出去的字符数据在传递到串口或屏幕之前的处理方式。

功能
OPOST 启用输出处理功能(不设置则其他标志被忽略)
OLCUC 将输出字符中的大写字符转换成小写字符
ONLCR 将输出中的 NL(\n)转换成 CR(\r
OCRNL 将输出中的 CR(\r)转换成 NL(\n
ONOCR 在第 0 列不输出回车符(CR)
ONLRET 不输出回车符
OFILL 发送填充字符以提供延时
OFDEL 填充字符为 DEL 字符,否则为 NULL 字符

4.3 控制模式(c_cflag)

控制终端设备的硬件特性,对串口来说最为重要——设置波特率、数据位、校验位、停止位等。

功能
CBAUD 波特率的位掩码
B0 ~ B3000000 波特率值(1200、2400、4800、9600、19200、38400、57600、115200、230400、460800、500000、576000、921600、1000000、1152000、1500000、2000000、2500000、3000000 等)
CSIZE 数据位的位掩码
CS5 / CS6 / CS7 / CS8 5/6/7/8 个数据位
CSTOPB 2 个停止位(不设置则默认为 1 个)
CREAD 接收使能
PARENB 使能奇偶校验
PARODD 使用奇校验(而非偶校验)
HUPCL 关闭时挂断调制解调器
CLOCAL 忽略调制解调器控制线
CRTSCTS 使能硬件流控

B0~3000000 B后的数字是多少就是多少波特率,但是注意并不可以随意取值,必须要是宏有的值才能取。

4.4 本地模式(c_lflag)

控制终端的本地数据处理和工作模式。

功能
ISIG 收到信号字符(INTR、QUIT 等)时产生相应信号
ICANON 启用规范模式
ECHO 启用输入字符的本地回显功能
ECHOE 若设置 ICANON,允许退格操作
ECHOK 若设置 ICANON,KILL 字符删除当前行
ECHONL 若设置 ICANON,允许回显换行符
ECHOCTL 控制字符显示为 ^X 格式(如退格显示为 ^H
ECHOPRT 删除字符和被删除的字符都显示
ECHOKE 允许回显 KILL 字符
NOFLSH 接收到 INTR/QUIT/SUSP 时不清空队列
TOSTOP 后台进程写控制终端时发送 SIGTTOU 信号
IEXTEN 启用输入处理功能

4.5 特殊控制字符(c_cc 数组)

c_cc 数组将各种特殊字符映射到对应的支持函数,每个位置由宏定义指定:

对应键 说明
VEOF Ctrl+D 文件结尾符 EOF,使终端驱动程序将输入行全部字符传递给应用程序
VEOL Carriage return (CR) 附加行结尾符 EOL
VEOL2 Line feed (LF) 第二行结尾符 EOL2
VERASE Backspace (BS) 删除操作符 ERASE,删除输入行中最后一个字符
VINTR Ctrl+C 中断控制字符 INTR,发送 SIGINT 信号
VKILL Ctrl+U 删除行符 KILL,删除整个输入行
VMIN 非规范模式下,指定最少读取的字符数 MIN
VQUIT Ctrl+Z 退出操作符 QUIT,发送 SIGQUIT 信号
VSTART Ctrl+Q 开始字符 START,重新启动被 STOP 暂停的输出
VSTOP Ctrl+S 停止字符 STOP,阻止进一步输出(用于 XON/XOFF 流控)
VSUSP Ctrl+Z 挂起字符 SUSP,发送 SIGSUSP 信号
VTIME 非规范模式下,指定每个字符之间的超时时间(十分之一秒为单位)TIME

五、终端的工作模式

终端有三种工作模式,通过 struct termiosc_lflag 成员中的 ICANON 标志控制:

模式 设置方式 特点
规范模式(Canonical Mode) 设置 ICANON 标志(默认) 基于行处理输入,行编辑可行,read() 读到行结束符才返回,一次最多读一行
非规范模式(Non-canonical Mode) 清除 ICANON 标志 输入即时有效,不可行编辑,read() 行为由 MIN 和 TIME 控制
原始模式(Raw Mode) 调用 cfmakeraw() 特殊的非规范模式,所有数据以字节为单位处理,不回显,禁用所有特殊处理

规范模式

当设置 ICANON 标志时,终端工作在规范模式下(默认情况)。所有的输入基于行进行处理,在用户输入一个行结束符(回车符、EOF 等)之前,read() 函数读不到任何字符。行编辑可行,一次 read() 最多只能读取一行数据。

非规范模式

当清除 ICANON 标志时,终端工作在非规范模式下。所有的输入即时有效,不需要用户输入行结束符,且不可进行行编辑。在此模式下,read() 的行为由 MINc_cc[VMIN])和 TIMEc_cc[VTIME])这两个参数控制:

MIN TIME read() 行为
0 0 立即返回,有数据则读取并返回字节数,否则返回 0
>0 0 阻塞直到读取到 MIN 个字符才返回
0 >0 有数据可读或经过 TIME 个十分之一秒后立即返回
>0 >0 有 MIN 个字节可读或两个输入字符间隔超过 TIME 个十分之一秒时返回(至少读一个字节)

原始模式

原始模式是一种特殊的非规范模式。调用 cfmakeraw() 函数可将终端设置为原始模式,在此模式下,所有输入数据以字节为单位被处理,终端不可回显,且禁用输入和输出字符的所有特殊处理。

cfmakeraw(&new_cfg);  // 等价于以下配置:
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

使用场景:当串口用于与其他设备或传感器进行数据传输通信时(而非人机交互),必须使用原始模式,确保数据不被特殊处理或解析为 ASCII 字符。

配置注意:对 c_iflagc_oflagc_cflagc_lflag 等成员赋值时,尽量不要直接对其初始化,而要将其通过"按位与"(&=)、"按位或"(|=)等操作添加标志或清除某个标志。譬如,通常不会这样对变量进行初始化

struct termios ter;
ter.c_iflag = IGNBRK | BRKINT | PARMRK;

而是要像下面这样

ter.c_iflag |= (IGNBRK | BRKINT | PARMRK | ISTRIP);

六、串口编程步骤

6.1 打开串口设备

使用 open() 函数打开串口设备节点:

int fd;
fd = open("/dev/ttymxc2", O_RDWR | O_NOCTTY);
if (0 > fd) {
    perror("open error");
    return -1;
}

O_NOCTTY 标志:告知系统该设备不会成为进程的控制终端。

为什么需要这个标志? 在 Linux 中,如果一个进程当前没有控制终端,那么它 open() 打开一个终端设备时,内核会自动将该设备设为进程的控制终端。控制终端负责处理特殊信号:Ctrl+C 发送 SIGINTCtrl+Z 发送 SIGTSTP、关闭时发送 SIGHUP 等。

如果串口是用于数据通信(而非人机交互),不加 O_NOCTTY 可能导致意外行为——例如收到的 Ctrl+C0x03)数据会让进程收到 SIGINT 而终止。因此只要串口不是用来做控制台终端,都应该加上 O_NOCTTY 标志,这是一个安全的防御性编程习惯。

6.2 获取当前配置参数

使用 tcgetattr() 获取串口当前的配置参数并保存,以便后续恢复:

int tcgetattr(int fd, struct termios *termios_p);
// 成功返回 0,失败返回 -1 并设置 errno

struct termios old_cfg;
if (0 > tcgetattr(fd, &old_cfg)) {
    /* 出错处理 */
}

6.3 配置串口参数

(1) 设置为原始模式

struct termios new_cfg = {0};
cfmakeraw(&new_cfg);  // 无返回值

(2) 使能接收

new_cfg.c_cflag |= CREAD;

(3) 设置波特率

使用专门的函数设置波特率,不要直接通过位掩码操作:

int cfsetispeed(struct termios *termios_p, speed_t speed);  // 设置输入波特率
int cfsetospeed(struct termios *termios_p, speed_t speed);  // 设置输出波特率
int cfsetspeed(struct termios *termios_p, speed_t speed);    // 同时设置输入输出

// 示例:设置为 115200
cfsetspeed(&new_cfg, B115200);  // 成功返回 0,失败返回 -1

常用波特率宏:B1200B2400B4800B9600B19200B38400B57600B115200B230400B460800B500000B576000B921600B1000000B1152000B1500000B2000000B2500000B3000000

(4) 设置数据位大小

通过位掩码操作,先清除 CSIZE 位再设置:

new_cfg.c_cflag &= ~CSIZE;       // 清除数据位相关比特位
new_cfg.c_cflag |= CS8;          // 设置为 8 位数据位

(5) 设置奇偶校验

涉及 c_cflagc_iflag 两个成员:

// 奇校验
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;

// 偶校验
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD;  // 清除 PARODD,配置为偶校验
new_cfg.c_iflag |= INPCK;

// 无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;

(6) 设置停止位

// 1 个停止位
new_cfg.c_cflag &= ~CSTOPB;

// 2 个停止位
new_cfg.c_cflag |= CSTOPB;

(7) 设置 MIN 和 TIME

new_cfg.c_cc[VTIME] = 0;  // 超时时间
new_cfg.c_cc[VMIN] = 0;   // 最少字符数

原始模式是一种特殊的非规范模式,所以 MIN 和 TIME 在原始模式下也是有效的。

这样则在任何情况下 read() 调用都会立即返回,此时对串口的 read 操作会设置为非阻塞方式。

6.4 缓冲区处理

在使用串口前,需要对缓冲区进行处理,因为其中可能已有数据:

#include <termios.h>
#include <unistd.h>

int tcdrain(int fd);                          // 阻塞直到输出缓冲区数据全部发送完毕
int tcflush(int fd, int queue_selector);       // 清空缓冲区
int tcflow(int fd, int action);               // 暂停/恢复数据传输

tcflush()queue_selector 参数:

取值 功能
TCIFLUSH 清空已接收但未读取的数据
TCOFLUSH 清空未发送成功的输出数据
TCIOFLUSH 同时清空输入和输出缓冲区

tcflow()action 参数:

取值 功能
TCOOFF 暂停数据输出
TCOON 重新启动暂停的输出
TCIOFF 发送 STOP 字符,停止终端向系统发送数据
TCION 发送 START 字符,启动终端向系统发送数据

常用方式:

tcdrain(fd);                    // 等待发送完成
// 或
tcflush(fd, TCIOFLUSH);         // 清空缓冲区

6.5 写入配置使其生效

使用 tcsetattr() 将配置参数写入终端设备:

int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
// 成功返回 0,失败返回 -1 并设置 errno

optional_actions 参数:

取值 生效时机
TCSANOW 配置立即生效
TCSADRAIN 所有写入 fd 的输出传输完毕后才生效
TCSAFLUSH 丢弃所有已接收但未读取的输入后生效

6.6 读写数据

配置完成后,直接使用 read()write() 进行数据读写:

write(fd, buf, len);   // 向串口写入数据
read(fd, buf, len);    // 从串口读取数据

七、完整示例程序(原始模式数据传输)

7.1 程序流程与完整示例

串口应用编程的具体流程如下:

  • 定义硬件参数结构体 — 封装波特率、数据位、校验位、停止位等配置项
  • 打开串口设备 — 调用 open() 打开设备节点(如 /dev/ttymxc2),必须加上 O_NOCTTY 标志
  • 获取当前配置 — 调用 tcgetattr() 保存串口当前的配置参数,以便退出时恢复
  • 配置串口参数 — 依次完成:设为原始模式 → 使能接收 → 设置波特率 → 设置数据位 → 设置校验位 → 设置停止位 → 设置 MIN/TIME → 清空缓冲区 → 调用 tcsetattr() 写入配置
  • 读写数据 — 根据需求选择读取或写入:读操作使用异步 I/O + 信号方式;写操作直接调用 write() 循环发送
  • 退出清理 — 调用 tcsetattr() 恢复原始配置,然后 close() 关闭串口

以下是完整的示例程序 uart_test.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>

typedef struct uart_hardware_cfg {
    unsigned int baudrate;      /* 波特率 */
    unsigned char dbit;         /* 数据位 */
    char parity;                /* 奇偶校验 */
    unsigned char sbit;         /* 停止位 */
} uart_cfg_t;

static struct termios old_cfg;  //用于保存终端的配置参数
static int fd;                  //串口终端对应的文件描述符

/* 串口初始化操作 */
static int uart_init(const char *device)
{
    fd = open(device, O_RDWR | O_NOCTTY);
    if (0 > fd) {
        fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
        return -1;
    }
    if (0 > tcgetattr(fd, &old_cfg)) {
        fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
        close(fd);
        return -1;
    }
    return 0;
}

/* 串口配置 */
static int uart_cfg(const uart_cfg_t *cfg)
{
    struct termios new_cfg = {0};
    speed_t speed;

    cfmakeraw(&new_cfg);                     // 设置为原始模式
    new_cfg.c_cflag |= CREAD;                // 使能接收

    /* 设置波特率 */
    switch (cfg->baudrate) {
    case 1200:   speed = B1200;   break;
    case 2400:   speed = B2400;   break;
    case 4800:   speed = B4800;   break;
    case 9600:   speed = B9600;   break;
    case 19200:  speed = B19200;  break;
    case 38400:  speed = B38400;  break;
    case 57600:  speed = B57600;  break;
    case 115200: speed = B115200; break;
    case 230400: speed = B230400; break;
    case 460800: speed = B460800; break;
    case 500000: speed = B500000; break;
    default:     speed = B115200;
        printf("default baud rate: 115200\n"); break;
    }
    if (0 > cfsetspeed(&new_cfg, speed)) {
        fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
        return -1;
    }

    /* 设置数据位 */
    new_cfg.c_cflag &= ~CSIZE;
    switch (cfg->dbit) {
    case 5: new_cfg.c_cflag |= CS5; break;
    case 6: new_cfg.c_cflag |= CS6; break;
    case 7: new_cfg.c_cflag |= CS7; break;
    case 8: new_cfg.c_cflag |= CS8; break;
    default: new_cfg.c_cflag |= CS8;
        printf("default data bit size: 8\n"); break;
    }

    /* 设置奇偶校验 */
    switch (cfg->parity) {
    case 'N':
        new_cfg.c_cflag &= ~PARENB;
        new_cfg.c_iflag &= ~INPCK;
        break;
    case 'O':
        new_cfg.c_cflag |= (PARODD | PARENB);
        new_cfg.c_iflag |= INPCK;
        break;
    case 'E':
        new_cfg.c_cflag |= PARENB;
        new_cfg.c_cflag &= ~PARODD;
        new_cfg.c_iflag |= INPCK;
        break;
    default:
        new_cfg.c_cflag &= ~PARENB;
        new_cfg.c_iflag &= ~INPCK;
        printf("default parity: N\n"); break;
    }

    /* 设置停止位 */
    switch (cfg->sbit) {
    case 1: new_cfg.c_cflag &= ~CSTOPB; break;
    case 2: new_cfg.c_cflag |= CSTOPB;  break;
    default: new_cfg.c_cflag &= ~CSTOPB;
        printf("default stop bit size: 1\n"); break;
    }

    /* 设置 MIN 和 TIME 为 0,read 立即返回 */
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN] = 0;

    /* 清空缓冲区 */
    if (0 > tcflush(fd, TCIOFLUSH)) {
        fprintf(stderr, "tcflush error: %s\n", strerror(errno));
        return -1;
    }

    /* 写入配置、使配置生效 */
    if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
        fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

/* 打印帮助信息 */
static void show_help(const char *app)
{
    printf("Usage: %s [选项]\n"
        "\n 必选选项:\n"
        "  --dev=DEVICE     指定串口终端设备名称, 如 --dev=/dev/ttymxc2\n"
        "  --type=TYPE      指定操作类型, read 表示读、write 表示写\n"
        "\n 可选选项:\n"
        "  --brate=SPEED    指定波特率, 如 --brate=115200\n"
        "  --dbit=SIZE      指定数据位个数, 5/6/7/8\n"
        "  --parity=PARITY  指定校验方式, N(无)/O(奇)/E(偶)\n"
        "  --sbit=SIZE      指定停止位个数, 1/2\n"
        "  --help           查看帮助信息\n\n", app);
}

/* 信号处理函数,串口有数据可读时跳转到此函数执行 */
static void io_handler(int sig, siginfo_t *info, void *context)
{
    unsigned char buf[10] = {0};
    int ret, n;
    if (SIGRTMIN != sig) return;
    if (POLL_IN == info->si_code) {
        ret = read(fd, buf, 8);     //一次最多读 8 个字节
        printf("[ ");
        for (n = 0; n < ret; n++)
            printf("0x%hhx ", buf[n]);
        printf("]\n");
    }
}

/* 异步 I/O 初始化 */
static void async_io_init(void)
{
    struct sigaction sigatn;
    int flag;
    flag = fcntl(fd, F_GETFL);
    flag |= O_ASYNC;
    fcntl(fd, F_SETFL, flag);        //使能异步 I/O
    fcntl(fd, F_SETOWN, getpid());   //设置异步 I/O 所有者
    fcntl(fd, F_SETSIG, SIGRTMIN);   //指定 SIGRTMIN 作为通知信号
    sigatn.sa_sigaction = io_handler;
    sigatn.sa_flags = SA_SIGINFO;
    sigemptyset(&sigatn.sa_mask);
    sigaction(SIGRTMIN, &sigatn, NULL);
}

int main(int argc, char *argv[])
{
    uart_cfg_t cfg = {0};
    char *device = NULL;
    int rw_flag = -1;
    unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44,
                               0x55, 0x66, 0x77, 0x88};
    int n;

    /* 解析命令行参数 */
    for (n = 1; n < argc; n++) {
        if (!strncmp("--dev=", argv[n], 6))
            device = &argv[n][6];
        else if (!strncmp("--brate=", argv[n], 8))
            cfg.baudrate = atoi(&argv[n][8]);
        else if (!strncmp("--dbit=", argv[n], 7))
            cfg.dbit = atoi(&argv[n][7]);
        else if (!strncmp("--parity=", argv[n], 9))
            cfg.parity = argv[n][9];
        else if (!strncmp("--sbit=", argv[n], 7))
            cfg.sbit = atoi(&argv[n][7]);
        else if (!strncmp("--type=", argv[n], 7)) {
            if (!strcmp("read", &argv[n][7]))
                rw_flag = 0;        //读
            else if (!strcmp("write", &argv[n][7]))
                rw_flag = 1;        //写
        }
        else if (!strcmp("--help", argv[n])) {
            show_help(argv[0]);
            exit(EXIT_SUCCESS);
        }
    }

    if (NULL == device || -1 == rw_flag) {
        fprintf(stderr, "Error: the device and read|write type must be set!\n");
        show_help(argv[0]);
        exit(EXIT_FAILURE);
    }

    /* 串口初始化 */
    if (uart_init(device))
        exit(EXIT_FAILURE);

    /* 串口配置 */
    if (uart_cfg(&cfg)) {
        tcsetattr(fd, TCSANOW, &old_cfg);
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 读或写串口 */
    switch (rw_flag) {
    case 0:  //读串口
        async_io_init();
        for ( ; ; )
            sleep(1);   //等待有数据可读,自动跳转到 io_handler()
        break;
    case 1:  //写串口
        for ( ; ; ) {
            write(fd, w_buf, 8);   //每隔 1 秒写入 8 个字节
            sleep(1);
        }
        break;
    }

    /* 退出清理 */
    tcsetattr(fd, TCSANOW, &old_cfg);
    close(fd);
    exit(EXIT_SUCCESS);
}

7.2 编译

arm-linux-gnueabihf-gcc -o testApp uart_test.c

7.3 测试方法

  1. 将编译好的可执行文件拷贝到开发板 /home/root 目录
  2. ALPHA 开发板有两个串口:
    • USB 串口(UART1,/dev/ttymxc0)— 系统控制台终端,不能用于测试
    • RS232/RS485 串口(UART3,/dev/ttymxc2)— 可用于测试
    • 通过跳线帽 JP1 切换 RS232 或 RS485
  3. Mini 开发板只有一个 USB 串口,但串口 I/O 已通过扩展口引出,可使用 USB 转 TTL 模块测试

读测试

# 开发板端
./testApp --dev=/dev/ttymxc2 --type=read

# PC 端:打开串口调试助手(如 XCOM),配置相同参数(115200,8,N,1)
# 发送 8 字节数据,开发板应用程序会读取并打印

写测试

# 开发板端
./testApp --dev=/dev/ttymxc2 --type=write

# 程序每隔 1 秒向串口写入 [0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88]
# PC 端串口调试助手会接收到这些数据
posted @ 2026-07-23 15:04  Javenwww  阅读(15)  评论(0)    收藏  举报