title: "Linux RS232与485通信驱动" tags:
串口是嵌入式系统中最常用的通信外设之一。Linux下通过串口驱动框架实现对UART的统一管理,无论底层电平是TTL、RS232还是RS485,驱动程序的本质都是UART驱动。
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第六十三章内容,为扩展知识(原书本章重点为 UART 驱动框架、设备树配置与 minicom 测试)。
| 特性 | TTL电平 | RS232 | RS485 |
|---|---|---|---|
| 电平范围 | 0V / 3.3V或5V | -3V~-15V / +3V~+15V | 差分信号 ±2V~±6V |
| 传输距离 | <1m | <15m | <1200m |
| 通信方式 | 单端 | 单端 | 差分 |
| 最大速率 | 取决于MCU | 115200bps | 10Mbps |
| 抗干扰能力 | 弱 | 一般 | 强 |
| 网络拓扑 | 点对点 | 点对点 | 多点总线 |
| 典型芯片 | MCU内置UART | MAX232/SP3232 | MAX485/SP3485 |
┌──────────┐ TTL电平 ┌─────────────┐ RS232电平 ┌──────────┐
│ MCU │ ──────────▶ │ SP3232 │ ──────────▶ │ PC/DB9 │
│ UART │ ◀────────── │ 电平转换 │ ◀────────── │ 串口 │
└──────────┘ 0/3.3V └─────────────┘ ±12V └──────────┘
┌──────────┐ TTL电平 ┌─────────────┐ RS485差分 ┌──────────┐
│ MCU │ ──────────▶ │ SP3485 │ ──────────▶ │ 总线 │
│ UART │ ◀────────── │ 差分转换 │ ◀────────── │ A/B线 │
└──────────┘ └─────────────┘ └──────────┘
串口通信需要双方约定相同的参数:
串口帧格式示例(8N1 = 8数据位 + 无校验 + 1停止位):
起始位(0) | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 停止位(1)
1bit ─────── 8bit数据 ─────── 1bit
graph TD
A[用户空间] -->|"open()/read()/write()"| B[/dev/ttymxcX]
B --> C[tty_core]
C --> D[tty_operations]
C --> E[uart_core]
E --> F[uart_driver]
E --> G[uart_port]
E --> H[uart_ops]
F -->|"注册/注销"| I[串口驱动层]
G -->|"绑定到driver"| I
H -->|"底层操作回调"| I
I --> J[硬件UART寄存器]
Linux串口驱动采用分层架构:
/dev/ttymxcX访问uart_driver、uart_port、uart_ops三要素imx.c,直接操作UART寄存器用户空间调用链:
open("/dev/ttymxc2") → tty_open() → uart_open() → startup() → imx_startup()
write(fd, data) → tty_write() → uart_write() → start_tx() → imx_start_tx()
read(fd, buf) → tty_read() → uart_read() → 等待中断
close(fd) → tty_release() → uart_close() → shutdown() → imx_shutdown()
// include/linux/serial_core.h
struct uart_driver {
struct module *owner; // 模块所属者
const char *driver_name; // 驱动名字(内核内部名)
const char *dev_name; // 设备名字前缀(如"ttymxc")
int major; // 主设备号
int minor; // 次设备号起始值
int nr; // 支持的最大端口数
struct console *cons; // 控制台(可选)
struct uart_state *state; // 内部状态数组
struct tty_driver *tty_driver; // 关联的tty驱动
};
// include/linux/serial_core.h (简化)
struct uart_port {
spinlock_t lock; // 端口自旋锁
unsigned long iobase; // I/O端口基地址(x86)
unsigned char __iomem *membase; // 内存映射基地址(ARM常用)
unsigned int type; // 端口类型(PORT_IMX等)
unsigned int iotype; // 访问方式(UPIO_MEM等)
unsigned int irq; // 中断号
unsigned int fifosize; // FIFO大小
unsigned int line; // 端口索引(0,1,2...)
const struct uart_ops *ops; // 操作函数集
unsigned int uartclk; // 时钟频率
resource_size_t mapbase; // 物理基地址(ioremap用)
struct device *dev; // 父设备
struct serial_rs485 rs485; // RS485配置参数
int (*rs485_config)(struct uart_port *, struct serial_rs485 *);
};
// include/linux/serial_core.h
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); // 发送FIFO是否为空
void (*set_mctrl)(struct uart_port *, unsigned int); // 设置Modem控制
unsigned int (*get_mctrl)(struct uart_port *); // 获取Modem状态
void (*stop_tx)(struct uart_port *); // 停止发送
void (*start_tx)(struct uart_port *); // 启动发送
void (*stop_rx)(struct uart_port *); // 停止接收
void (*enable_ms)(struct uart_port *); // 使能Modem状态中断
void (*break_ctl)(struct uart_port *, int); // Break信号控制
int (*startup)(struct uart_port *); // 启动串口(申请中断、使能时钟等)
void (*shutdown)(struct uart_port *); // 关闭串口(释放资源)
void (*flush_buffer)(struct uart_port *); // 清空发送缓冲区
void (*set_termios)(struct uart_port *, struct ktermios *, struct ktermios *); // 设置波特率等参数
const char *(*type)(struct uart_port *); // 返回端口类型字符串
void (*release_port)(struct uart_port *); // 释放I/O资源
int (*request_port)(struct uart_port *); // 请求I/O资源
void (*config_port)(struct uart_port *, int); // 配置端口
int (*verify_port)(struct uart_port *, struct serial_struct *); // 验证端口参数
};
I.MX6U的UART3在imx6ull.dtsi中的定义:
uart3: serial@021ec000 {
compatible = "fsl,imx6ul-uart", "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x021ec000 0x4000>;
interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART3_IPG>,
<&clks IMX6UL_CLK_UART3_SERIAL>;
clock-names = "ipg", "per";
dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
dma-names = "rx", "tx";
status = "disabled";
};
属性说明:
| 属性 | 说明 |
|---|---|
compatible |
匹配字符串,驱动通过此属性查找匹配的platform驱动 |
reg |
寄存器基地址和大小(0x021EC000为UART3基地址) |
interrupts |
中断号和触发方式 |
clocks |
时钟源(IPG总线时钟和PER外设时钟) |
dma |
DMA通道配置(UART3使用SDMA通道29/30) |
status |
状态,"disabled"表示默认关闭,需要在板级设备树中使能 |
UART3使用UART3_TX_DATA和UART3_RX_DATA两个引脚:
/* 在iomuxc节点下添加 */
pinctrl_uart3: uart3grp {
fsl,pins = <
MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
>;
};
pinctrl引脚电气属性值说明:
| 位段 | 含义 |
|---|---|
| bit[16] | HYS滞回使能(1=使能) |
| bit[15:14] | Pull/Keeper选择 |
| bit[13] | Pull保留使能(1=上拉) |
| bit[12:11] | Pull/Keeper强度 |
| bit[10] | Open Drain使能 |
| bit[7:6] | 驱动强度 |
| bit[3:0] | 压摆率 |
0x1b0b1对应的配置:上拉使能、驱动强度适中、压摆率适中。
在板级设备树(如imx6ull-alientek-emmc.dts)中:
/* 删除uart2节点(与UART3引脚冲突) */
/delete-node/uart2;
/* 使能UART3 */
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
系统启动后会生成设备节点:/dev/ttymxc2(UART3对应第3个串口,索引从0开始)。
设备号对应关系:
| UART | 设备文件 | 索引 |
|---|---|---|
| UART1 | /dev/ttymxc0 | 0 |
| UART2 | /dev/ttymxc1 | 1 |
| UART3 | /dev/ttymxc2 | 2 |
| UART4 | /dev/ttymxc3 | 3 |
| UART5 | /dev/ttymxc4 | 4 |
int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
驱动入口函数中注册uart_driver,出口函数中注销:
static struct uart_driver imx_reg = {
.owner = THIS_MODULE,
.driver_name = "IMX", // 内核内部驱动名
.dev_name = "ttymxc", // /dev/ttymxcX的前缀
.major = SERIAL_IMX_MAJOR, // 主设备号(通常253)
.minor = MINOR_START, // 次设备号起始(通常64)
.nr = ARRAY_SIZE(imx_ports), // 支持的最大端口数
.cons = IMX_CONSOLE, // 控制台(可选)
};
static int __init imx_serial_init(void)
{
int ret = uart_register_driver(&imx_reg); // 注册uart_driver
if (ret)
return ret;
ret = platform_driver_register(&serial_imx_driver); // 注册platform驱动
if (ret != 0)
uart_unregister_driver(&imx_reg);
return ret;
}
static void __exit imx_serial_exit(void)
{
platform_driver_unregister(&serial_imx_driver);
uart_unregister_driver(&imx_reg);
}
module_init(imx_serial_init);
module_exit(imx_serial_exit);
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport);
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport);
| 参数 | 说明 |
|---|---|
drv |
对应的uart_driver(如imx_reg) |
uport |
要添加/移除的uart_port |
| 返回值 | 0成功,负值失败 |
在platform驱动的probe()中调用uart_add_one_port()将端口绑定到驱动:
static int serial_imx_probe(struct platform_device *pdev)
{
struct imx_port *sport;
// ... 初始化sport ...
sport->port.ops = &imx_pops; // 设置操作函数集
return uart_add_one_port(&imx_reg, &sport->port);
}
/* 设备树匹配表 */
static const struct of_device_id imx_uart_dt_ids[] = {
{ .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
{ .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
{ .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
{ /* sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe,
.remove = serial_imx_remove,
.suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.id_table = imx_uart_devtype,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
},
};
| 函数 | 用途 | 调用时机 |
|---|---|---|
uart_register_driver() |
注册uart_driver到内核 | 模块init |
uart_unregister_driver() |
从内核注销uart_driver | 模块exit |
uart_add_one_port() |
将uart_port绑定到uart_driver | platform probe |
uart_remove_one_port() |
将uart_port从uart_driver解绑 | platform remove |
I.MX6ULL的UART驱动位于drivers/tty/serial/imx.c,本质上是一个platform驱动:
classDiagram
class uart_driver {
+owner: module*
+driver_name: char*
+dev_name: char*
+major: int
+minor: int
+nr: int
}
class uart_port {
+lock: spinlock_t
+membase: void*
+type: unsigned int
+irq: unsigned int
+fifosize: unsigned int
+ops: uart_ops*
+rs485: serial_rs485
}
class uart_ops {
+startup()
+shutdown()
+start_tx()
+stop_tx()
+set_termios()
+set_mctrl()
}
class imx_port {
+port: uart_port
+timer: timer_list
+clk_ipg: clk*
+clk_per: clk*
}
uart_driver "1" --> "*" uart_port : 绑定多个端口
uart_port --> uart_ops : ops指针
imx_port --|> uart_port : 继承
static struct uart_driver imx_reg = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME, // "IMX"
.dev_name = DEV_NAME, // "ttymxc"
.major = SERIAL_IMX_MAJOR, // 主设备号
.minor = MINOR_START, // 次设备号起始值
.nr = ARRAY_SIZE(imx_ports), // 最大端口数
.cons = IMX_CONSOLE, // 控制台
};
static int serial_imx_probe(struct platform_device *pdev)
{
struct imx_port *sport;
void __iomem *base;
int ret = 0;
struct resource *res;
int txirq, rxirq, rtsirq;
// 1. 分配imx_port内存
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return -ENOMEM;
// 2. 从设备树获取DT配置(compatible等)
ret = serial_imx_probe_dt(sport, pdev);
if (ret > 0)
serial_imx_probe_pdata(sport, pdev); // 传统方式
else if (ret < 0)
return ret;
// 3. 获取寄存器基地址并进行内存映射
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 4. 获取中断号(RX/TX/RTS)
rxirq = platform_get_irq(pdev, 0);
txirq = platform_get_irq(pdev, 1);
rtsirq = platform_get_irq(pdev, 2);
// 5. 初始化uart_port成员
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start; // 物理基地址
sport->port.membase = base; // 虚拟基地址
sport->port.type = PORT_IMX; // 端口类型
sport->port.iotype = UPIO_MEM; // 内存映射IO
sport->port.irq = rxirq; // 中断号
sport->port.fifosize = 32; // FIFO大小32字节
sport->port.ops = &imx_pops; // 操作函数集
sport->port.flags = UPF_BOOT_AUTOCONF; // 自动波特率检测
// 6. 配置RS485参数
sport->port.rs485_config = imx_rs485_config;
sport->port.rs485.flags =
SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
// 7. 初始化定时器(超时检测)
init_timer(&sport->timer);
sport->timer.function = imx_timeout;
sport->timer.data = (unsigned long)sport;
// 8. 获取时钟
sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
sport->clk_per = devm_clk_get(&pdev->dev, "per");
sport->port.uartclk = clk_get_rate(sport->clk_per);
// 9. 申请中断
if (txirq > 0) {
devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,
dev_name(&pdev->dev), sport);
devm_request_irq(&pdev->dev, txirq, imx_txint, 0,
dev_name(&pdev->dev), sport);
} else {
devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
dev_name(&pdev->dev), sport);
}
imx_ports[sport->port.line] = sport;
platform_set_drvdata(pdev, sport);
// 10. 将端口添加到uart_driver
return uart_add_one_port(&imx_reg, &sport->port);
}
static struct uart_ops imx_pops = {
.tx_empty = imx_tx_empty, // 检查发送FIFO是否为空
.set_mctrl = imx_set_mctrl, // 设置Modem控制(RTS/CTS/DTR等)
.get_mctrl = imx_get_mctrl, // 获取Modem状态
.stop_tx = imx_stop_tx, // 停止发送
.start_tx = imx_start_tx, // 启动发送
.stop_rx = imx_stop_rx, // 停止接收
.enable_ms = imx_enable_ms, // 使能Modem状态中断
.break_ctl = imx_break_ctl, // Break信号控制
.startup = imx_startup, // 启动串口(使能时钟、申请中断)
.shutdown = imx_shutdown, // 关闭串口(释放资源)
.flush_buffer = imx_flush_buffer, // 清空缓冲区
.set_termios = imx_set_termios, // 设置波特率/数据位/停止位/校验位
.type = imx_type, // 返回端口类型字符串
.config_port = imx_config_port, // 配置端口类型
.verify_port = imx_verify_port, // 验证端口参数合法性
};
RS232在I.MX6U-ALPHA开发板上的硬件连接:
MCU UART3_TX ──▶ SP3232 TXDin ──▶ RS232 DB9 TXD
MCU UART3_RX ◀── SP3232 RXDout ◀── RS232 DB9 RXD
▲
│ JP1跳线帽 1-3, 2-4 连接
│
MCU UART3_TX/RX ───┘
设备树配置(RS232无需额外驱动,只需使能UART3):
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
JP1跳线帽设置:将1-3和2-4连接,使UART3通过SP3232芯片输出RS232电平。
RS485是半双工通信,需要控制收发方向。I.MX6U-ALPHA开发板通过硬件巧妙地将RE和DE连接到同一控制线,省掉了额外的GPIO。原书 63.3 节说明:SP3485 的 RO 为数据输出端、DI 为数据输入端,RE 是接收使能(低有效)、DE 是发送使能(高有效);原理图中 RE 和 DE 经过一系列电路最终由 RS485_RX 信号控制,从而把 RS485 完全当作串口使用:
MCU UART3_TX ──▶ SP3485 DI(数据输入)
MCU UART3_RX ◀── SP3485 RO(数据输出)
UART3_RX 经外围电路 ─┬─▶ SP3485 DE(发送使能,高有效)
└─▶ SP3485 /RE(接收使能,低有效)
▲
│ 硬件电路自动切换方向
│
RS485总线 A/B
关键:RE和DE通过电路设计连接到同一信号,发送时DE高电平使能发送,接收时RE低电平使能接收。这种设计将RS485完全当作串口使用,无需软件控制方向。
RS485在驱动中的配置:
// serial_imx_probe中的RS485配置
sport->port.rs485_config = imx_rs485_config;
sport->port.rs485.flags =
SER_RS485_RTS_ON_SEND | // 发送时RTS置高
SER_RS485_RX_DURING_TX; // 发送期间仍使能接收
RS485标志位说明:
| 标志位 | 说明 |
|---|---|
SER_RS485_ENABLED |
使能RS485模式 |
SER_RS485_RTS_ON_SEND |
发送时RTS为高(控制方向) |
SER_RS485_RTS_AFTER_SEND |
发送完成后RTS为高 |
SER_RS485_RX_DURING_TX |
发送期间允许接收 |
SER_RS485_TERMINATE_BUS |
发送完成后发送总线终止信号 |
struct serial_rs485 定义(include/uapi/linux/serial.h,Linux 4.1.15):
struct serial_rs485 {
__u32 flags; /* RS485 功能标志 */
#define SER_RS485_ENABLED (1 << 0)
#define SER_RS485_RTS_ON_SEND (1 << 1)
#define SER_RS485_RTS_AFTER_SEND (1 << 2)
#define SER_RS485_RX_DURING_TX (1 << 4)
__u32 delay_rts_before_send; /* 发送前 RTS 延迟(ms) */
__u32 delay_rts_after_send; /* 发送后 RTS 延迟(ms) */
__u32 padding[5]; /* 预留,动态分配内存用 */
};
原书示例代码 63.2.5 中,驱动侧设置
sport->port.rs485.flags = SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;。SER_RS485_TERMINATE_BUS等标志在较新内核中才定义。
用户空间可通过ioctl()配置RS485参数:
#include <linux/serial.h>
struct serial_rs485 rs485conf;
int fd = open("/dev/ttymxc2", O_RDWR);
// 使能RS485,发送时RTS高电平
rs485conf.flags |= SER_RS485_ENABLED;
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
ioctl(fd, TIOCSRS485, &rs485conf);
// 读取RS485配置
ioctl(fd, TIOCGRS485, &rs485conf);
原书使用 minicom 2.7.1 测试串口,它依赖 ncurses 6.0。
1) 移植 ncurses(ncurses-6.0.tar.gz):
tar -vxzf ncurses-6.0.tar.gz
./configure --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/ncurses \
--host=arm-linux-gnueabihf --target=arm-linux-gnueabihf \
--with-shared --without-profile --disable-stripping \
--without-progs --with-manpages --without-tests
make
make install
# 将 include/lib/share 分别拷到根文件系统 /usr/include、/usr/lib、/usr/share
sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rfa
sudo cp share/* /home/zuozhongkai/linux/nfs/rootfs/usr/share/ -rfa
sudo cp include/* /home/zuozhongkai/linux/nfs/rootfs/usr/include/ -rfa
在根文件系统 /etc/profile(没有则新建)中加入:
#!/bin/sh
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
export TERM=vt100
export TERMINFO=/usr/share/terminfo
2) 移植 minicom(minicom-2.7.1.tar.gz):
tar -vxzf minicom-2.7.1.tar.gz
cd minicom-2.7.1/
./configure CC=arm-linux-gnueabihf-gcc \
--prefix=/home/zuozhongkai/linux/IMX6ULL/tool/minicom \
--host=arm-linux-gnueabihf \
CPPFLAGS=-I/home/zuozhongkai/linux/IMX6ULL/tool/ncurses/include \
LDFLAGS=-L/home/zuozhongkai/linux/IMX6ULL/tool/ncurses/lib \
--enable-cfg-dir=/etc/minicom
make
make install
sudo cp bin/* /home/zuozhongkai/linux/nfs/rootfs/usr/bin/
3) 新建 /etc/passwd:如果执行 minicom -s 提示 “Go away”,在根文件系统新建 /etc/passwd 并写入下面内容后重启:
root:x:0:0:root:/root:/bin/sh
完成后 minicom -v 应显示版本 2.7.1,minicom -s 可正常打开配置界面。
⚠️ 来源说明:6.1~6.2 节的 stty/picocom/echo 用法不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(原书使用 minicom 测试)。
# 查看当前串口参数
stty -F /dev/ttymxc2 -a
# 设置波特率115200,8数据位,1停止位,无校验
stty -F /dev/ttymxc2 115200 cs8 -cstopb -parenb
# 设置波特率9600
stty -F /dev/ttymxc2 9600
# 关闭硬件流控
stty -F /dev/ttymxc2 crtscts -crtscts
# 关闭软件流控
stty -F /dev/ttymxc2 ixon -ixon ixoff -ixoff
常用stty选项:
| 选项 | 说明 |
|---|---|
cs8 / cs7 |
8/7数据位 |
cstopb / -cstopb |
2/1停止位 |
parenb / -parenb |
使能/禁用校验 |
parodd / -parodd |
奇/偶校验 |
crtscts / -crtscts |
使能/禁用硬件流控 |
ixon / -ixon |
使能/禁用XON/XOFF软件流控 |
# 配置minicom
minicom -s
# 直接打开串口(波特率115200)
minicom -D /dev/ttymxc2 -b 115200
# 打开回显(Ctrl+A → E)
# 退出minicom(Ctrl+A → X)
minicom配置要点:
/dev/ttymxc2# 打开串口
picocom -b 115200 /dev/ttymxc2
# 退出(Ctrl+A → Ctrl+X)
# 发送数据
echo "Hello RS232" > /dev/ttymxc2
# 接收数据(阻塞)
cat /dev/ttymxc2
# 后台接收并保存
cat /dev/ttymxc2 > /tmp/uart_data.log &
RS485测试与RS232完全一致,区别仅在于:
GPS模块通过UART3输出NMEA数据:
# GPS模块默认波特率38400
minicom -D /dev/ttymxc2 -b 38400
# 或使用picocom
picocom -b 38400 /dev/ttymxc2
NMEA数据格式示例:
$GPGGA,062219.00,3150.7892,N,11711.9538,E,1,08,1.03,45.6,M,-3.3,M,,*67
$GPRMC,062219.00,A,3150.7892,N,11711.9538,E,0.05,31.5,170926,,,A*64
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | IMX6ULL | STM32F4xx | RK3568 |
|---|---|---|---|
| UART数量 | 8个 | 6个 | 6个 |
| FIFO深度 | 32字节 | 128字节 | 32字节 |
| DMA支持 | SDMA | DMA1/DMA2 | DMA |
| 设备树驱动 | 是 | 否(HAL库) | 是 |
| RS485硬件支持 | RTS/DE自动切换 | 需要额外GPIO | RTS/DE自动切换 |
| 时钟配置 | ipg+per双时钟 | APB1/APB2 | bus_clk+fun_clk |
| 驱动文件 | drivers/tty/serial/imx.c | stm32-usart.c | drivers/tty/serial/rk_tty.c |
IMX6ULL:
STM32:
RK3568:
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
答:三者是"驱动-端口-操作"的分层关系:
uart_driver描述串口驱动整体信息(设备号、设备名、最大端口数),一个驱动管理多个端口uart_port描述具体端口的硬件资源(寄存器地址、中断号、FIFO大小),一个端口绑定到一个驱动uart_ops是操作函数集,定义了串口的底层操作(启动发送、设置波特率等),每个端口通过ops指针指向它关系链:uart_driver管理N个uart_port,每个uart_port通过ops指针调用uart_ops中的函数。在platform驱动的probe()中,通过uart_add_one_port()将端口绑定到驱动。
答:
硬控制(本方案):将DE和RE连接到同一信号线(如RTS),驱动在发送时自动拉高该信号切换为发送模式,发送完成自动拉低切换为接收模式。优点:无需软件干预,延迟小。
软控制:通过额外GPIO控制DE/RE引脚,在start_tx()中拉高GPIO,在发送完成中断中拉低GPIO。缺点:软件延迟可能导致总线冲突或数据丢失。
Linux内核通过serial_rs485结构体配置RS485方向控制。在serial_imx_probe()中设置:
sport->port.rs485_config = imx_rs485_config;
sport->port.rs485.flags = SER_RS485_RTS_ON_SEND;
用户空间通过ioctl(TIOCSRS485)配置RS485模式。
答:startup()是串口打开时调用的初始化函数,主要工作:
clk_enable()使能UART模块时钟(ipg和per)request_irq()注册RX和TX中断处理函数对应的shutdown()需要做相反操作:释放中断、关闭时钟、禁用引脚。
答:设备文件生成流程:
compatible = "fsl,imx6ul-uart"与驱动匹配platform_driver的probe()函数被调用probe()中调用uart_add_one_port()将端口添加到imx_reg驱动uart_add_one_port()内部调用tty_register_device()注册tty设备imx_reg.dev_name("ttymxc")和端口索引(line=2)生成设备名udev/mdev根据内核事件创建/dev/ttymxc2设备文件所以/dev/ttymxc2对应UART3(索引从0开始:UART1→ttymxc0,UART2→ttymxc1,UART3→ttymxc2)。
答:通过ioctl()系统调用配置RS485参数:
#include <linux/serial.h>
int fd = open("/dev/ttymxc2", O_RDWR);
struct serial_rs485 rs485conf;
// 使能RS485模式
ioctl(fd, TIOCGRS485, &rs485conf); // 先读取当前配置
rs485conf.flags |= SER_RS485_ENABLED;
rs485conf.flags |= SER_RS485_RTS_ON_SEND; // 发送时RTS高
rs485conf.delay_rts_before_send = 1; // 发送前延迟1ms
rs485conf.delay_rts_after_send = 1; // 发送后延迟1ms
ioctl(fd, TIOCSRS485, &rs485conf); // 写入配置
// 之后正常write()即可,驱动自动控制方向
write(fd, data, len);
驱动层面,rs485_config回调函数(如imx_rs485_config())根据用户配置设置UART控制寄存器中的RTS极性、发送/接收延迟等参数。start_tx()时驱动自动拉高RTS切换为发送模式,发送完成中断中自动拉低RTS切换为接收模式。
内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第六十三章 Linux RS232/485/GPS驱动实验 最后更新: 2026-09-17