在工业控制领域,RS485总线因其出色的抗干扰能力和多节点组网特性,成为远距离通信的首选方案。不同于常见的RS232全双工通信,RS485采用半双工工作模式,这意味着同一时刻只能进行发送或接收操作。这种特性带来一个关键问题:如何精确控制收发状态的切换?
以瑞芯微RK3588平台为例,其UART控制器原生并不支持RS485自动收发控制功能。这导致开发者通常需要采用以下两种方案:
这两种方案都存在明显缺陷:前者增加硬件成本,后者则存在时序控制不精确、占用CPU资源等问题。本文将详细介绍一种更优的解决方案——通过修改Linux内核串口驱动,实现硬件级的自动收发控制。
典型RS485收发器(如SP3485)具有以下关键引脚:
| 引脚 | 功能描述 | 连接方式 |
|---|---|---|
| RO | 接收输出 | 连接UART RX线 |
| DI | 发送输入 | 连接UART TX线 |
| RE | 接收使能(低有效) | 与DE短接后接GPIO |
| DE | 发送使能(高有效) | 与RE短接后接GPIO |
| A/B | 差分信号线 | 连接总线终端电阻 |
硬件连接示意图:
code复制RK3588 UART_TX ---> DI
RK3588 UART_RX <--- RO
GPIOx ---> DE+RE (短接)
通过单个GPIO控制收发状态切换:
关键时序要求:
以RK3588的UART9为例,标准设备树配置如下:
dts复制&uart9 {
pinctrl-names = "default";
pinctrl-0 = <&uart9m2_xfer>;
status = "okay";
};
改造后的设备树配置:
dts复制&uart9 {
pinctrl-names = "default";
pinctrl-0 = <&uart9m2_xfer>;
rts_gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
关键注意事项:
rts_gpio(单数形式),不能使用rts-gpiosGPIO_ACTIVE_HIGH:适用于DE高电平有效的收发器GPIO_ACTIVE_LOW:适用于RE低电平有效的收发器同时配置UART0和UART9:
dts复制&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0m2_xfer>;
rts_gpio = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
&uart9 {
pinctrl-names = "default";
pinctrl-0 = <&uart9m2_xfer>;
rts_gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
首先在include/linux/serial_8250.h中扩展结构体:
c复制struct uart_8250_port {
/* 原有字段... */
int rts_gpios; // 新增RS485控制GPIO
};
修改drivers/tty/serial/8250/8250_dw.c的probe函数:
c复制static int dw8250_probe(struct platform_device *pdev)
{
/* 原有初始化代码... */
// 新增GPIO处理
int rts_gpios = of_get_named_gpio(dev->of_node, "rts_gpio", 0);
if (rts_gpios > 0) {
up->rts_gpios = rts_gpios;
ret = gpio_request(rts_gpios, "rs485_rts");
if (ret) {
dev_err(dev, "Failed to request GPIO%d\n", rts_gpios);
return ret;
}
gpio_direction_output(rts_gpios, 0); // 默认接收模式
}
/* 后续初始化代码... */
}
关键修改位于drivers/tty/serial/8250/8250_port.c:
c复制void serial8250_tx_chars(struct uart_8250_port *up)
{
/* 原有发送准备代码... */
// 发送前使能发送器
if (up->rts_gpios > 0)
gpio_set_value(up->rts_gpios, 1);
/* 数据发送过程... */
// 发送完成后检测空状态
if (uart_circ_empty(xmit)) {
// 等待FIFO完全清空
int timeout = 200; // 最大600ms
while (timeout--) {
mdelay(3);
if (serial_in(up, UART_LSR) & UART_LSR_TEMT)
break;
}
// 切换回接收模式
if (up->rts_gpios > 0)
gpio_set_value(up->rts_gpios, 0);
}
}
状态检测机制:
超时保护设计:
超时时间 > (最长帧字节数×10×1000)/波特率GPIO状态管理:
GPIO无反应:
rts_gpio数据截断:
总线冲突:
c复制// 根据波特率计算合理超时
int baud = uart_get_baud_rate(port);
int byte_time_us = 1000000 / (baud / 10); // 含起始/停止位
int timeout_ms = (max_frame_size * byte_time_us) / 1000 + 10; // 10ms余量
中断优化:
电源管理:
| 方案类型 | 时序精度 | CPU占用 | 硬件成本 | 兼容性 |
|---|---|---|---|---|
| 外置协议芯片 | 高 | 低 | 高 | 好 |
| 应用层GPIO控制 | 低 | 高 | 低 | 一般 |
| 本驱动方案 | 极高 | 极低 | 低 | 优秀 |
实际测试数据(RK3588 @1.8GHz):
对于需要多个RS485端口的应用,可在设备树中配置多组GPIO:
dts复制&uart0 {
rts_gpio = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
/* 其他配置... */
};
&uart4 {
rts_gpio = <&gpio4 RK_PB3 GPIO_ACTIVE_HIGH>;
/* 其他配置... */
};
可通过扩展设备树支持延时配置:
dts复制rs485_settings {
pre_send_delay = <10>; // 发送前延时(ms)
post_send_delay = <5>; // 发送后延时(ms)
};
驱动中读取并使用这些参数:
c复制int pre_delay;
of_property_read_u32(np, "pre_send_delay", &pre_delay);
if (pre_delay)
mdelay(pre_delay);
平台兼容性:
内核版本适配:
调试接口:
c复制static ssize_t show_rs485_mode(struct device *dev, ...)
{
return sprintf(buf, "%d\n", gpio_get_value(up->rts_gpios));
}
稳定性增强:
这个驱动方案已经在眺望电子RK3588核心板上经过长期稳定性测试,支持7×24小时连续运行。在实际工业网关项目中,实现了超过200个节点的稳定组网通信。