嵌入式 —— 串口与 UART 深入详解 —— i.MX6ULL 实战

嵌入式 ------ 串口与 UART 深入详解 ------ i.MX6ULL 实战

一句话总结:串口(UART)是一种"发一条线、收一条线、不要时钟"的异步串行通信方式------发送方和接收方各自按约定好的速率(波特率)收发数据,通过起始位、数据位、校验位、停止位组成一帧数据。


第 1 章 · 串口通信基本概念

1.1 核心概念

一句话总结:串口通信 = 数据按位(bit)逐根线发送,一条线发、一条线收、一条共地,双方约定好速度(波特率)和格式(数据位/停止位/校验位)。

生活类比 :串口通信就像两个人隔着一道墙用对讲机通话------你按一下按钮(起始位)开始说话,说完松开(停止位)。双方需要提前说好用多快的语速讲(波特率),讲中文还是英文(数据格式),否则对方听不懂。而且不存在"同步时钟线",全靠各自掌握节奏(异步)。


1.2 串口 vs 并口

对比维度 串口(UART) 并口
传输方式 逐位发送(1 根数据线) 多位同时发送(8/16 根数据线)
传输距离 远(可达几十米) 近(通常 < 1 米)
抗干扰能力 强(线少) 弱(线多,易串扰)
硬件成本 低(2 根数据线 + GND) 高(多根线)
典型应用 嵌入式调试、传感器、GPS 打印机、老式硬盘

第 2 章 · 串口通信协议

2.1 核心概念

一句话总结:串口通信协议定义了一帧数据的结构------先拉低电平说"我要开始发了"(起始位),然后从低位到高位逐位发出数据(数据位),可选一个校验位检查错误,最后拉高电平说"发完了"(停止位)。

生活类比:就像打电话------先拨号(起始位),然后说话内容(数据位),最后说"再见"挂断(停止位)。双方还要约定好语速(波特率)和语言(数据格式)。


2.2 数据帧格式

flowchart LR IDLE[&#34;空闲(高电平)&#34;] --> START[&#34;起始位<br/>1 位低电平&#34;] START --> DATA[&#34;数据位<br/>5~8 位<br/>LSB 先发&#34;] DATA --> PARITY[&#34;校验位<br/>可选<br/>奇/偶/无&#34;] PARITY --> STOP[&#34;停止位<br/>1~2 位<br/>高电平&#34;] STOP --> NEXT[&#34;下一帧<br/>起始位...&#34;] style IDLE fill:#95A5A6,color:#fff style START fill:#E74C3C,color:#fff style DATA fill:#3498DB,color:#fff style PARITY fill:#E67E22,color:#fff style STOP fill:#27AE60,color:#fff

一帧数据的组成

字段 长度 电平 说明
空闲位 任意 高电平 没有数据传输时,线路保持高电平
起始位 1 位 低电平 通知接收方"准备接收数据"
数据位 5~8 位 高/低 实际数据,低位(LSB)先发
校验位 0/1 位 高/低 可选奇校验、偶校验、0 校验、1 校验
停止位 1~2 位 高电平 通知接收方"数据发送完毕"

常见配置

css 复制代码
波特率:115200
数据位:8 位
校验位:无(NONE)
停止位:1 位
简写:115200-8-N-1

2.3 波特率

一句话总结:波特率 = 每秒传输的位数(bit/s),双方必须一致,否则数据乱码。

生活类比:两个人用对讲机通话------一个人说得飞快(高波特率),另一个人听得很慢(低波特率),听到的就是"叽里呱啦"(乱码)。

常见波特率

波特率 每秒传输位数 传输 1 字节所需时间
9600 9600 bit/s ≈ 1.04 ms
38400 38400 bit/s ≈ 0.26 ms
115200 115200 bit/s ≈ 0.087 ms
921600 921600 bit/s ≈ 0.011 ms

注意:1 字节 = 8 位数据 + 1 位起始 + 1 位停止 = 10 位(无校验时)


2.4 校验位

校验方式 规则 能否检测错误
无校验(NONE) 不发送校验位 ❌ 不能
奇校验(ODD) 数据位 + 校验位中 1 的总数为奇数 ✅ 单比特错误
偶校验(EVEN) 数据位 + 校验位中 1 的总数为偶数 ✅ 单比特错误
0 校验(SPACE) 校验位固定为 0 ❌ 不能
1 校验(MARK) 校验位固定为 1 ❌ 不能

示例 :数据位 8 位 = 0101 1010(4 个 1)

  • 奇校验:1 的总数应为奇数 → 4 已是偶数 → 校验位 = 1
  • 偶校验:1 的总数应为偶数 → 4 已是偶数 → 校验位 = 0

实际开发中 :奇偶校验只能检测单比特错误,通常不开启校验位,数据完整性由更高层协议(如 CRC)保证。


第 3 章 · 串口电平标准

3.1 核心概念

一句话总结:TTL、RS232、RS485、RS422 是四种不同的串口电平标准------TTL 是芯片内部用的,RS232 是早期电脑串口,RS485/RS422 是工业总线。


3.2 先看对比:四大电平标准

flowchart TD NEED[&#34;需要串口通信&#34;] NEED --> SPLIT{&#34;通信距离和<br/>环境要求?&#34;} SPLIT -->|&#34;板内通信<br/>短距离 < 1m&#34;| TTL[&#34;TTL<br/>0~3.3V/5V<br/>最简单&#34;] SPLIT -->|&#34;电脑串口<br/>短距离 < 15m&#34;| RS232[&#34;RS232<br/>-15V~+15V<br/>负逻辑&#34;] SPLIT -->|&#34;工业总线<br/>远距离 > 100m&#34;| RS485[&#34;RS485<br/>差分信号<br/>半双工&#34;] SPLIT -->|&#34;远距离全双工<br/>多设备&#34;| RS422[&#34;RS422<br/>差分信号<br/>全双工&#34;] TTL --> T1[&#34;+ 无需转换芯片<br/>- 抗干扰差&#34;] RS232 --> R1[&#34;+ 电压摆幅大<br/>- 速率低&#34;] RS485 --> R2[&#34;+ 抗干扰强<br/>- 半双工需切换&#34;] RS422 --> R3[&#34;+ 全双工<br/>- 多一对线&#34;] style NEED fill:#2C3E50,color:#fff style TTL fill:#3498DB,color:#fff style RS232 fill:#E67E22,color:#fff style RS485 fill:#27AE60,color:#fff style RS422 fill:#9B59B6,color:#fff

3.3 知识对比表:四大电平标准

对比维度 TTL RS232 RS485 RS422
信号类型 单端 单端 差分 差分
电压范围 0~3.3V/5V -15V~+15V -6V~+6V(差) -6V~+6V(差)
逻辑 1 2V~5V -15V~-3V A-B > 200mV A-B > 200mV
逻辑 0 0~0.8V +3V~+15V A-B < -200mV A-B < -200mV
传输距离 < 1m < 15m > 1200m > 1200m
传输速率 最高(可达 10Mbps) 低(通常 115200) 高(可达 10Mbps) 高(可达 10Mbps)
通信方式 全双工 全双工 半双工 全双工
导线数量 2 线 + GND 3 线 + GND 2 线(A/B) 4 线(两对)
抗干扰能力
多设备通信 ❌ 点对点 ❌ 点对点 ✅ 一对多 ✅ 一对多
典型场景 芯片间通信 老式电脑串口 工业总线、PLC 工业设备、仪表

关键类比

标准 一句话类比
TTL 两个人面对面说话(声音小,距离近)
RS232 用扩音器喊话(声音大,但需要转换器)
RS485 一群人用对讲机轮流说话(半双工,距离远,抗干扰)
RS422 一群人用电话会议(全双工,两对线各说各的)

全双工 vs 半双工 vs 单工

一句话总结:全双工 = 两个人同时说同时听(电话),半双工 = 一个人说另一个人听,说完再换(对讲机),单工 = 一个人说另一个人只能听(广播)。

flowchart TD MODE[&#34;通信方向&#34;] MODE -->|&#34;单工(Simplex)&#34;| SIMPLEX[&#34;单向传输<br/>A 只能发,B 只能收<br/>例:广播、电视&#34;] MODE -->|&#34;半双工(Half-Duplex)&#34;| HALF[&#34;双向交替传输<br/>同一时刻只能一个方向<br/>例:对讲机、RS485&#34;] MODE -->|&#34;全双工(Full-Duplex)&#34;| FULL[&#34;双向同时传输<br/>收发互不干扰<br/>例:电话、RS232、RS422&#34;] HALF --> H1[&#34;需要切换方向 ✅&#34;] HALF --> H2[&#34;一对线就够了 ✅&#34;] HALF --> H3[&#34;效率较低 ❌&#34;] FULL --> F1[&#34;随时收发 ✅&#34;] FULL --> F2[&#34;需要两对线 ⚠️&#34;] FULL --> F3[&#34;效率高 ✅&#34;] style MODE fill:#2C3E50,color:#fff style SIMPLEX fill:#95A5A6,color:#fff style HALF fill:#E67E22,color:#fff style FULL fill:#27AE60,color:#fff

三种模式对比

对比维度 单工 半双工 全双工
数据流向 单向固定 双向交替 双向同时
导线数量 1 根 1 对(2 根) 2 对(4 根)
通信效率
硬件复杂度
典型应用 广播、电视 对讲机、RS485 电话、RS232、RS422
串口举例 RS485 RS232、RS422、TTL

第 4 章 · UART 硬件原理

4.1 核心概念

一句话总结:UART(Universal Asynchronous Receiver/Transmitter)是 SOC 内部集成的串口控制器------负责把 CPU 的并行数据转成串行波形发送,以及把接收到的串行波形转成并行数据交给 CPU。

生活类比 :UART 就像翻译官------CPU 说"并行语"(8 位同时说),UART 翻译成"串行语"(一位一位说),对方设备的 UART 再翻译回"并行语"交给对方的 CPU。


4.2 串口 vs UART:到底有什么区别?

一句话总结 :**串口(Serial Port)**是通信协议和接口标准(第 1~3 章讲的内容),UART 是实现串口协议的硬件控制器------串口是"规矩",UART 是"执行规矩的人"。

生活类比 :串口协议就像交通规则 (红灯停绿灯行、靠右行驶),UART 就像交警(负责执行交通规则、指挥车辆通行)。没有交通规则,交警不知道该怎么指挥;没有交警,交通规则没人执行。

两者的关系

flowchart LR subgraph &#34;串口(Serial Port)------概念层&#34; S1[&#34;通信协议<br/>起始位/数据位/停止位&#34;] S2[&#34;电平标准<br/>TTL/RS232/RS485&#34;] S3[&#34;波特率<br/>9600/115200&#34;] end subgraph &#34;UART------硬件实现层&#34; U1[&#34;发送器(TX)<br/>并→串转换&#34;] U2[&#34;接收器(RX)<br/>串→并转换&#34;] U3[&#34;波特率发生器<br/>控制收发速度&#34;] end S1 & S2 & S3 --> U1 & U2 & U3 style S1 fill:#E67E22,color:#fff style S2 fill:#E67E22,color:#fff style S3 fill:#E67E22,color:#fff style U1 fill:#3498DB,color:#fff style U2 fill:#3498DB,color:#fff style U3 fill:#3498DB,color:#fff

区别对比

对比维度 串口(Serial Port) UART
本质 通信协议和接口标准 实现协议的具体硬件
包含内容 波特率、数据位、校验位、停止位、电平标准 发送器、接收器、波特率发生器、寄存器
是否可见 逻辑概念(软件层面) 物理硬件(芯片内部)
举例 "115200-8-N-1" 是串口协议配置 i.MX6ULL 芯片内部的 UART1~UART8 是硬件
能否独立存在 可以(用 GPIO 模拟"软件串口") 可以(但需要外部引脚连接)

为什么分开说?

原因 说明
串口是协议,UART 是硬件 第 1~3 章讲的是"串口通信的规则是什么",第 4 章开始讲"UART 硬件怎么实现这些规则"
串口可以用其他方式实现 不一定要 UART------可以用 GPIO 配合定时器模拟串口协议(俗称"软件串口"或"bit-banging"),但 UART 是专用的硬件实现,效率更高、更准确
UART 也可以做其他事 UART 硬件除了标准串口协议,还支持红外模式、9 位数据模式(RS485 多机通信)等扩展功能

总结

flowchart TD QUESTION[&#34;串口和 UART 什么关系?&#34;] QUESTION --> A[&#34;串口 = 通信的规矩<br/>(波特率、数据位、停止位等)&#34;] QUESTION --> B[&#34;UART = 执行规矩的硬件<br/>(把规矩变成实际波形)&#34;] A --> C[&#34;没有 UART 也能串口通信<br/>用 GPIO 模拟(软件串口)&#34;] B --> D[&#34;UART 天生为串口而生<br/>但也支持扩展功能(红外/9位)&#34;] style QUESTION fill:#2C3E50,color:#fff style A fill:#E67E22,color:#fff style B fill:#3498DB,color:#fff style C fill:#E74C3C,color:#fff style D fill:#27AE60,color:#fff

4.3 UART vs USART

对比维度 UART USART
全称 通用异步收发器 通用同步/异步收发器
时钟线 ❌ 不需要 ✅ 有单独的时钟线
通信方式 仅异步 支持同步和异步
硬件复杂度
传输速度 受限于波特率精度 同步模式可更快
典型芯片 i.MX6ULL UART STM32 USART

4.3 UART 数据流

sequenceDiagram participant CPU as CPU participant UART_TX as UART 发送器 participant UART_RX as UART 接收器 participant DEV as 外部设备 Note over CPU: 准备发送数据 'A' (0x41) CPU->>UART_TX: 写入发送寄存器 (0x41) UART_TX->>UART_TX: 并行数据转串行 UART_TX->>DEV: TXD 逐位发送:起始位→8位数据→停止位 Note over DEV: 接收电平变化 DEV->>UART_RX: RXD 接收串行波形 UART_RX->>UART_RX: 串行转并行数据 UART_RX->>CPU: 读取接收寄存器 (0x41) Note over CPU: 收到数据 'A'

第 5 章 · i.MX6ULL UART

5.1 核心概念

一句话总结:i.MX6ULL 内部集成了 8 个 UART 模块,每个都支持多种电平标准(通过外部转换芯片),UART1 通常用于串口调试(通过 CH340 转 USB)。


5.2 8 个 UART 资源

UART 编号 用途 备注
UART1 调试串口(默认) 通过 CH340 转 USB 连接电脑
UART2~8 通用串口 连接外部设备(GPS、传感器等)

5.3 核心寄存器

寄存器 作用 关键位
UART1_URXD 数据接收寄存器 bit0:7 = 接收到的 8 位数据
UART1_UTXD 数据发送寄存器 写入 bit0:7 即可发送
UART1_UCR1 控制寄存器 1 bit0 = UART 使能
UART1_UCR2 控制寄存器 2 配置数据位、停止位、校验位、收发使能
UART1_UCR3 控制寄存器 3 其他控制
UART1_USR1 状态寄存器 1 bit3 = 发送完成标志(TRDY)
UART1_USR2 状态寄存器 2 bit0 = 数据接收就绪(RDR)
UART1_UFCR 分频控制寄存器 bit7:9 = 分频系数
UART1_UBIR 波特率整数寄存器 波特率计算
UART1_UBMR 波特率小数寄存器 波特率计算

5.4 波特率计算公式

scss 复制代码
波特率 = 时钟频率 / (16 × (UBMR + 1) / (UBIR + 1))

示例:时钟频率 80 MHz,目标波特率 115200

scss 复制代码
80 × 10^6 / (16 × 115200) = 43.4
选择 UBMR = 433,UBIR = 9
验证:80 × 10^6 / (16 × (433+1) / (9+1)) = 115,207 ≈ 115200 ✅

第 6 章 · 串口编程实现

6.1 核心概念

一句话总结:串口编程 = 开时钟 → 设引脚 → 配参数(波特率/数据位/停止位/校验位)→ 打开发送接收 → 读写数据寄存器。


6.2 初始化流程

flowchart TD A[&#34;① 开启 UART 时钟<br/>CCGR5 寄存器&#34;] --> B[&#34;② 配置 GPIO 引脚<br/>复用为 UART 功能&#34;] B --> C[&#34;③ 复位 UART 模块&#34;] C --> D[&#34;④ 配置通信参数<br/>波特率、数据位、停止位、校验位&#34;] D --> E[&#34;⑤ 使能接收和发送&#34;] E --> F[&#34;⑥ 使能 UART 模块&#34;] F --> G[&#34;初始化完成 ✅&#34;] style A fill:#E67E22,color:#fff style G fill:#27AE60,color:#fff,stroke-width:3px

6.3 完整代码实现

c 复制代码
#include "imx6ul.h"

// ① UART1 初始化:115200-8-N-1
void uart1_init(void) {
    // 1. 开启 UART1 时钟
    CCM_CCGR5 |= (3 << 0);          // CG15: UART1 时钟使能

    // 2. 配置 GPIO 引脚复用为 UART1
    // TXD: GPIOE_16, RXD: GPIOE_17
    IOMUXC_SW_MUX_CTL_PAD_GPIOE16 = 3;  // ALT3 = UART1_TXD
    IOMUXC_SW_MUX_CTL_PAD_GPIOE17 = 3;  // ALT3 = UART1_RXD

    // 3. 复位 UART1
    UART1_UCR2 = 0;                 // 先禁用 UART
    while (UART1_UCR2 & (1 << 0));  // 等待复位完成

    // 4. 配置通信参数
    // UCR2: 8 位数据位, 1 位停止位, 无校验, 使能收发
    UART1_UCR2 = (1 << 1)   // RXEN: 使能接收
               | (1 << 2)   // TXEN: 使能发送
               | (1 << 5)   // WS: 8 位数据位(与 bit6 配合)
               | (1 << 6)   // WS: 8 位数据位
               | (0 << 7);  // STPB: 1 位停止位

    // 5. 配置波特率:115200(时钟 80 MHz)
    UART1_UFCR = (5 << 7);          // 分频系数 = 5(不分频)
    // 公式:80MHz / (16 × (UBMR+1) / (UBIR+1)) = 115200
    UART1_UBIR = 9;                 // UBIR = 9
    UART1_UBMR = 433;               // UBMR = 433

    // 6. 使能 UART1
    UART1_UCR1 |= (1 << 0);         // UARTEN = 1
}

// ② 发送一个字节
void uart1_putchar(char c) {
    // 等待发送缓冲区为空(TRDY 标志位置位)
    while (!(UART1_USR1 & (1 << 3)));
    // 写入发送寄存器
    UART1_UTXD = c;
}

// ③ 发送字符串
void uart1_puts(const char *str) {
    while (*str) {
        uart1_putchar(*str++);
    }
}

// ④ 接收一个字节
char uart1_getchar(void) {
    // 等待数据接收就绪(RDR 标志位置位)
    while (!(UART1_USR2 & (1 << 0)));
    // 读取接收寄存器
    return UART1_URXD & 0xFF;
}

// ⑤ 主函数:串口回显测试
int main(void) {
    uart1_init();                   // 初始化 UART1
    uart1_puts("Hello i.MX6ULL!\r\n");

    while (1) {
        char c = uart1_getchar();   // 接收一个字符
        uart1_putchar(c);           // 回发(回显)
    }

    return 0;
}

执行结果(串口终端)

css 复制代码
Hello i.MX6ULL!
a           ← 在键盘上输入 'a'
a           ← 回显显示 'a'
b           ← 输入 'b'
b           ← 回显 'b'
(每按一个键,立即回显)

6.4 支持 printf 格式化输出

为了让串口像 printf 一样支持格式化输出(%d%x%s 等),需要实现 fputc 重定向或使用 vsprintf

c 复制代码
#include <stdarg.h>
#include <string.h>

// 缓冲区
static char printf_buf[256];

// 格式化打印(支持 %d, %x, %s, %c)
void uart1_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    // vsprintf 将格式化字符串写入缓冲区
    vsprintf(printf_buf, fmt, args);

    va_end(args);

    // 通过串口发送缓冲区内容
    uart1_puts(printf_buf);
}

// 使用示例
int main(void) {
    uart1_init();

    int val = 100;
    uart1_printf("整数: %d\r\n", val);
    uart1_printf("十六进制: 0x%X\r\n", val);
    uart1_printf("字符串: %s\r\n", "Hello UART");

    while (1) {
        char c = uart1_getchar();
        uart1_printf("收到: %c (0x%02X)\r\n", c, c);
    }

    return 0;
}

执行结果

less 复制代码
整数: 100
十六进制: 0x64
字符串: Hello UART
a           ← 输入 'a'
收到: a (0x61)
b           ← 输入 'b'
收到: b (0x62)

第 7 章 · Linux 下的串口编程

7.1 核心概念

一句话总结 :Linux 把串口设备当作文件来操作------open("/dev/ttyXXX") 打开串口,用 tcsetattr 配置参数,用 read/write 收发数据。


7.2 串口设备文件

设备 路径 说明
USB 转串口 /dev/ttyUSB0 CH340/CP2102/PL2303
原生串口 /dev/ttyS0 主板上的串口
虚拟串口 /dev/pts/0 伪终端

7.3 完整 C 代码示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>

// 打开串口
int open_serial(const char *dev) {
    int fd = open(dev, O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    return fd;
}

// 配置串口:115200-8-N-1
int config_serial(int fd) {
    struct termios opt;

    // 获取当前配置
    tcgetattr(fd, &opt);

    // 设置波特率:115200
    cfsetispeed(&opt, B115200);
    cfsetospeed(&opt, B115200);

    // 控制模式标志
    opt.c_cflag &= ~CSIZE;          // 清除数据位设置
    opt.c_cflag |= CS8;             // 8 位数据位
    opt.c_cflag &= ~CSTOPB;         // 1 位停止位
    opt.c_cflag &= ~PARENB;         // 无校验
    opt.c_cflag |= CREAD | CLOCAL;  // 使能接收,忽略调制解调器控制

    // 本地模式标志
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // 原始模式

    // 输出模式标志
    opt.c_oflag &= ~OPOST;          // 原始输出

    // 输入模式标志
    opt.c_iflag &= ~(IXON | IXOFF | IXANY);  // 关闭软件流控
    opt.c_iflag &= ~(INLCR | ICRNL);         // 不转换换行符

    // 超时设置
    opt.c_cc[VMIN] = 1;             // 至少读取 1 个字节
    opt.c_cc[VTIME] = 0;            // 不等待

    // 应用配置
    tcsetattr(fd, TCSANOW, &opt);
    return 0;
}

// 发送数据
int send_data(int fd, const char *data, int len) {
    return write(fd, data, len);
}

// 接收数据
int recv_data(int fd, char *buf, int max_len) {
    return read(fd, buf, max_len);
}

int main(int argc, char *argv[]) {
    const char *dev = "/dev/ttyUSB0";  // 根据实际情况修改

    int fd = open_serial(dev);
    if (fd < 0) return 1;

    config_serial(fd);

    printf("串口 %s 已打开,115200-8-N-1\n", dev);

    // 发送数据
    send_data(fd, "Hello from Linux!\r\n", 19);

    // 接收并回显
    char buf[256];
    while (1) {
        int n = recv_data(fd, buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            printf("收到: %s", buf);
            send_data(fd, buf, n);  // 回显
        }
    }

    close(fd);
    return 0;
}

编译与运行

bash 复制代码
# 编译
gcc serial_linux.c -o serial_linux

# 运行(需要 root 权限或加入 dialout 组)
sudo ./serial_linux
# 输出:
# 串口 /dev/ttyUSB0 已打开,115200-8-N-1
# 收到: hello
# 收到: world

第 8 章 · Linux 常用串口库

除了直接使用 termios 系统调用,Linux 生态中有多个封装好的串口库,提供更简洁的 API。

8.1 常用库一览

库名 语言 特点 适用场景 典型项目
termios C 系统标准 API,零依赖 底层串口编程 嵌入式 Linux 系统
libserialport C 跨平台,sigrok 项目维护 需要跨平台支持 PulseView、sigrok-cli
Boost.Asio C++ 现代 C++ 异步框架 C++ 项目串口通信 大量 C++ 后端
libserial C++ 轻量级 C++ 串口封装 简单的 C++ 串口需求 机器人、工控
Qt SerialPort C++ Qt 框架模块 Qt 项目串口通信 QSerialPort 终端
pyserial Python 简单易用,生态丰富 快速原型、测试脚本 串口调试工具

8.2 libserialport(C 语言,跨平台首选)

libserialport 是 sigrok 项目开发的跨平台串口库,支持 Linux、Windows、macOS,API 简洁稳定。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <libserialport.h>

int main(void) {
    struct sp_port *port;
    char buf[256];

    // ① 获取串口列表并选择第一个
    struct sp_port **port_list;
    sp_list_ports(&port_list);
    if (port_list[0] == NULL) {
        fprintf(stderr, "没有找到串口\n");
        return 1;
    }
    printf("找到串口: %s\n", sp_get_port_name(port_list[0]));

    // ② 打开串口
    sp_open(port_list[0], SP_MODE_READ_WRITE);

    // ③ 配置参数:115200-8-N-1
    sp_set_baudrate(port_list[0], 115200);
    sp_set_bits(port_list[0], 8);
    sp_set_parity(port_list[0], SP_PARITY_NONE);
    sp_set_stopbits(port_list[0], 1);
    sp_set_flowcontrol(port_list[0], SP_FLOWCONTROL_NONE);

    printf("串口已配置:115200-8-N-1\n");

    // ④ 发送数据
    sp_nonblocking_write(port_list[0], "Hello from libserialport!\r\n", 27);

    // ⑤ 接收数据
    int n = sp_nonblocking_read(port_list[0], buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        printf("收到: %s\n", buf);
    }

    // ⑥ 关闭
    sp_close(port_list[0]);
    sp_free_port_list(port_list);

    return 0;
}

编译与运行

bash 复制代码
# 安装
sudo apt install libserialport-dev

# 编译
gcc serial_libserialport.c -o serial_libserialport -lserialport

# 运行
./serial_libserialport
# 输出:
# 找到串口: /dev/ttyUSB0
# 串口已配置:115200-8-N-1

8.3 Boost.Asio(C++ 项目首选)

cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>

int main(void) {
    boost::asio::io_context io;
    boost::asio::serial_port serial(io);

    try {
        // ① 打开串口
        serial.open("/dev/ttyUSB0");

        // ② 配置参数:115200-8-N-1
        serial.set_option(boost::asio::serial_port::baud_rate(115200));
        serial.set_option(boost::asio::serial_port::character_size(8));
        serial.set_option(boost::asio::serial_port::parity(
            boost::asio::serial_port::parity::none));
        serial.set_option(boost::asio::serial_port::stop_bits(
            boost::asio::serial_port::stop_bits::one));
        serial.set_option(boost::asio::serial_port::flow_control(
            boost::asio::serial_port::flow_control::none));

        std::cout << "串口已打开:115200-8-N-1" << std::endl;

        // ③ 发送数据
        std::string msg = "Hello from Boost.Asio!\r\n";
        boost::asio::write(serial, boost::asio::buffer(msg));

        // ④ 接收数据
        char buf[256];
        size_t n = boost::asio::read(serial,
            boost::asio::buffer(buf, sizeof(buf)));
        buf[n] = '\0';
        std::cout << "收到: " << buf << std::endl;

    } catch (std::exception &e) {
        std::cerr << "错误: " << e.what() << std::endl;
        return 1;
    }

    serial.close();
    return 0;
}

编译与运行

bash 复制代码
# 安装
sudo apt install libboost-dev

# 编译
g++ serial_boost.cpp -o serial_boost -lboost_system -lpthread

# 运行
./serial_boost

8.4 pyserial(Python 快速原型)

python 复制代码
import serial
import time

# 打开串口
ser = serial.Serial(
    port='/dev/ttyUSB0',
    baudrate=115200,
    bytesize=8,
    parity='N',
    stopbits=1,
    timeout=1
)

print(f"串口已打开: {ser.port} 115200-8-N-1")

# 发送数据
ser.write(b"Hello from Python!\r\n")

# 接收数据
data = ser.readline()
print(f"收到: {data.decode().strip()}")

# 循环收发
while True:
    if ser.in_waiting > 0:
        data = ser.read(ser.in_waiting)
        print(f"收到: {data.decode().strip()}")
        # 回显
        ser.write(data)

    time.sleep(0.1)

运行

bash 复制代码
# 安装
pip install pyserial

# 运行
python3 serial_python.py

8.5 知识对比表

对比维度 termios(系统 API) libserialport Boost.Asio pyserial
语言 C C C++ Python
安装 无需安装 apt install libserialport-dev apt install libboost-dev pip install pyserial
代码量 多(需手动配置 termios) 少(几行搞定)
跨平台 仅 Linux ✅ Linux/Win/Mac ✅ Linux/Win/Mac ✅ Linux/Win/Mac
学习曲线 中高 极低
性能 最高
推荐场景 嵌入式 Linux 底层 跨平台 C 项目 C++ 项目 测试脚本、快速原型

附录 · 核心概念速查

概念 一句话总结 生活类比
串口 一根线发、一根线收、不要时钟 对讲机通话
UART 把并行数据转串行发送的硬件 翻译官
波特率 每秒传输的位数 说话语速
起始位 拉低电平说"准备开始" 按下对讲机按钮
停止位 拉高电平说"说完了" 松开对讲机按钮
TTL 0~3.3V,芯片内部用 面对面说话
RS232 -15V~+15V,负逻辑,电脑串口 用扩音器喊话
RS485 差分信号,半双工,工业总线 一群人用对讲机轮流说
RS422 差分信号,全双工,两对线 电话会议

一句话总结:串口通信 = 约定好波特率(速度)和帧格式(起始位 + 数据位 + 校验位 + 停止位),通过 TXD 和 RXD 两根线异步收发数据。不同电平标准(TTL/RS232/RS485/RS422)决定了电压范围和传输距离,通过外部转换芯片实现互联。

相关推荐
浅止菌7 小时前
嵌入式Linux内存优化:从OOM到稳定运行的6个实战技巧
嵌入式
大辉狼_音频架构7 小时前
Vol.05 VendorHAL-NXP
嵌入式·音视频开发
声讯电子8 小时前
AU-60 VS A-59F 语音模组完整对比(研发选型专用)
嵌入式·ai降噪·回声消除·语音模组
Highcharts9 小时前
用 Highcharts 构建交互式 ICU 实时生命体征监护仪表盘
嵌入式·数据可视化
左手厨刀右手茼蒿3 天前
PCIe设备驱动开发:从枚举到DMA传输的完整链路
linux·嵌入式·系统内核
济6173 天前
I.MX6U Linux 驱动开发篇---零基础必看!中断实验(按键中断 + 定时器消抖 + 设备树配置实战教程)--- Ubuntu20.04
linux·驱动开发·嵌入式·嵌入式linux驱动开发
凉、介3 天前
Linux 设备驱动匹配机制
linux·笔记·单片机·学习·操作系统·嵌入式
凉、介4 天前
Virtio 系列(一):框架概览
笔记·学习·嵌入式·虚拟化·virtio
charlie1145141914 天前
RK3506B: buildroot:出一份正规的最小 rootfs
开发语言·嵌入式·开源项目·rk3506b