• 正文
  • 相关推荐
申请入驻 产业图谱

RK3588串口RS485自动收发控制:内核驱动层改造实战

03/21 08:56
2735
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

【前言】

在工业通信场景中,RS485因其远距离传输、抗干扰能力强、支持多节点组网等特性,成为工控领域的首选通信方式。然而,与RS232的全双工通信不同,RS485采用半双工模式——同一时刻只能发送或接收。这就要求我们必须精确控制收发状态切换:

发送数据前:将控制脚置为高电平,使能发送器

数据发送完成后:将控制脚置为低电平,切换为接收模式

瑞芯微RK系列芯片(以眺望电子RK3588核心板为例)的UART控制器并未内置RS485自动控制功能,本文介绍一种内核级驱动改造方案,通过GPIO实现可靠的收发控制。

一、硬件连接原理

RS485收发器通常有以下引脚

引脚 功能 控制方式
RO 接收输出 连接UART RX
DI 发送输入 连接UART TX
RE 接收使能 低电平有效
DE 发送使能 高电平有效
A/B 差分信号线 总线接口

如下图所示,将RE和DE引脚短接,通过单个GPIO统一控制,实现收发模式切换:


GPIO输出高电平 → DE=1, RE=1 → 发送模式

GPIO输出低电平 → DE=0, RE=0 → 接收模式

瑞芯微的内核源码没有⾃带的配置接⼝,需要我们修改如下驱动代码。

二、设备树配置

在设备树中为UART节点添加 rts_gpio 属性:

--- a/arch/arm64/boot/dts/rockchip/talowe-rk3588-common.dtsi
+++ b/arch/arm64/boot/dts/rockchip/talowe-rk3588-common.dtsi
@@ -1057,13 +1057,14 @@ &uart6 {
&uart9 {
pinctrl-names = "default";
pinctrl-0 = <&uart9m2_xfer>;
- // rts-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
+ rts_gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_LOW>;
status = "okay";
};
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0m2_xfer>;
+ rts_gpio = <&gpio3 RK_PC5 GPIO_ACTIVE_LOW>;
status = "okay";
};

⚠️ 重要提示:属性名必须是 rts_gpio(单数形式),不能写成 rts-gpios(复数形式)。若使用复数形式,串口驱动会触发 mctrl_gpio 机制,导致本文方案失效。

三、驱动层改造详解

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/825
0/8250_core.c
index 2f7553cea..3cb98ae0d 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -1024,6 +1024,8 @@ int serial8250_register_8250_port(const struct uart
_8250_port *up)
#ifdef
 CONFIG_ARCH_ROCKCHIP
uart->port.line = up->port.line;
#endif
+ if (up->rts_gpios > 0)
+ uart->rts_gpios = up->rts_gpios;
/* Take tx_loadsz from fifosize if it wasn't set separately */
if (uart->port.fifosize && !uart->tx_loadsz)
uart->tx_loadsz = uart->port.fifosize;
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/
8250_dw.c
index 816c0122c..d37909cd3 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -35,7 +35,8 @@
#else
#include
 "8250_dwlib.h"
#endif
-
+
#include
 <linux/of_gpio.h>
+
#include
 <linux/gpio.h>
/* Offsets for the DesignWare specific registers */
#define
 DW_UART_USR 0x1f /* UART Status Register */
#define
 DW_UART_DMASA 0xa8 /* DMA Software Ack */
@@ -573,7 +574,7 @@ static int dw8250_probe(struct platform_device *pdev)
int irq;
int err;
u32 val;
+ int rts_gpios;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs)
return dev_err_probe(dev, -EINVAL, "no registers defined\n");
@@ -610,7 +611,16 @@ static int dw8250_probe(struct platform_device *pde
v)
#ifdef
 CONFIG_ARCH_ROCKCHIP
data->irq = irq;
#endif
-
+ rts_gpios = of_get_named_gpio(dev->of_node,"rts_gpio", 0);
+ up->rts_gpios = rts_gpios;
+ if (up->rts_gpios > 0)
+ {
+ printk("rts_gpios=%d\n", up->rts_gpios);
+ gpio_direction_output(up->rts_gpios, 0);
+ gpio_set_value(up->rts_gpios, 0);
+ }
+
+
data->uart_16550_compatible = device_property_read_bool(dev,
"snps,uart-16550-compatible");
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/825
0/8250_port.c
index b7a3634c6..e7c49272a 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -38,6 +38,8 @@
#include
 "8250.h"
+
#include
 <linux/gpio.h>
/* Nuvoton NPCM timeout register */
#define
 UART_NPCM_TOR 7
#define
 UART_NPCM_TOIE BIT(7) /* Timeout Interrupt Enable */
@@ -1825,7 +1827,7 @@ void serial8250_tx_chars(struct uart_8250_port *up)
struct uart_port *port = &up->port;
struct circ_buf *xmit = &port->state->xmit;
int count;
-
+ int lsr,cnt;
if (port->x_char) {
uart_xchar_out(port, UART_TX);
return;
@@ -1838,7 +1840,11 @@ void serial8250_tx_chars(struct uart_8250_port *u
p)
__stop_tx(up);
return;
}
+ if (up->rts_gpios > 0 )
+ {
+ gpio_set_value(up->rts_gpios, 1);
+ }
count = up->tx_loadsz;
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
@@ -1878,7 +1884,19 @@ void serial8250_tx_chars(struct uart_8250_port *u
p)
if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM))
+ {
__stop_tx(up);
+ if (up->rts_gpios > 0 )
+ {
+ for (cnt = 0; cnt < 200; cnt++)
+ {
+ mdelay(3);
+ lsr = serial_in(up, UART_LSR);
+ if(UART_LSR_TEMT == (lsr & UART_LSR_TEMT))
+ {
+ break;
+ }
+ }
+ gpio_set_value(up->rts_gpios, 0);
+ }
+ }
}
EXPORT_SYMBOL_GPL(serial8250_tx_chars);
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h
index 46d76b035..e088fe6ba 100644
--- a/include/linux/serial_8250.h
+++ b/include/linux/serial_8250.h
@@ -141,6 +141,7 @@ struct uart_8250_port {
/* Serial port overrun backoff */
struct delayed_work overrun_backoff;
u32 overrun_backoff_time_ms;
+ int rts_gpios;
};
static inline struct uart_8250_port *up_to_u8250p(struct uart_port *up)
diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h
index cea06924b..9f3435e98 100644
--- a/include/uapi/linux/serial.h
+++ b/include/uapi/linux/serial.h
@@ -134,7 +134,7 @@ struct serial_rs485 {
__u32 delay_rts_before_send; /* Delay before send (milliseconds) */
__u32 delay_rts_after_send; /* Delay after send (milliseconds) */
-
+
/* The fields below are defined by flags */
union {
__u32 padding[5]; /* Memory is cheap, new structs are a pain */

3.1  核心数据结构扩展

修改 include/linux/serial_8250.h,在 uart_8250_port 结构体中新增GPIO字段:

struct uart_8250_port {
// ... 原有字段
/* Serial port overrun backoff */
struct delayed_work overrun_backoff;
u32 overrun_backoff_time_ms;
int rts_gpios;  /* 新增:RS485方向控制GPIO */
};

3.2  平台驱动初始化

修改 drivers/tty/serial/8250/8250_dw.c,在probe函数中解析设备树GPIO:

#include
 <linux/of_gpio.h>
#include
 <linux/gpio.h>
static int dw8250_probe(struct platform_device *pdev)
{
// ... 原有代码
int rts_gpios;
// 从设备树获取GPIO
rts_gpios = of_get_named_gpio(dev->of_node, "rts_gpio", 0);
up->rts_gpios = rts_gpios;
if (up->rts_gpios > 0) {
printk("rts_gpios=%d\n", up->rts_gpios);
gpio_direction_output(up->rts_gpios, 0);
gpio_set_value(up->rts_gpios, 0);  // 默认接收模式
}
// ... 后续代码
}

3.3  注册时传递GPIO信息

修改 drivers/tty/serial/8250/8250_core.c:

int serial8250_register_8250_port(const struct uart_8250_port *up)
{
// ... 原有代码
#ifdef
 CONFIG_ARCH_ROCKCHIP
uart->port.line = up->port.line;
#endif
// 传递GPIO信息到新实例
if (up->rts_gpios > 0)
uart->rts_gpios = up->rts_gpios;
// ... 后续代码
}

3.4  发送时序控制(核心逻辑)

修改 drivers/tty/serial/8250/8250_port.c 中的 serial8250_tx_chars 函数:

#include
 <linux/gpio.h>
void serial8250_tx_chars(struct uart_8250_port *up)
{
struct uart_port *port = &up->port;
struct circ_buf *xmit = &port->state->xmit;
int count;
int lsr, cnt;
// ... 原有检查代码
/* ===== 发送前:置高GPIO,进入发送模式 ===== */
if (up->rts_gpios > 0) {
gpio_set_value(up->rts_gpios, 1);
}
count = up->tx_loadsz;
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
if (uart_circ_empty(xmit))
break;
} while (--count > 0);
// ... 原有发送逻辑
/* ===== 发送完成后:等待FIFO清空,再置低GPIO ===== */
if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM)) {
__stop_tx(up);
if (up->rts_gpios > 0) {
// 轮询等待发送完成(最多600ms)
for (cnt = 0; cnt < 200; cnt++) {
mdelay(3);
lsr = serial_in(up, UART_LSR);
if (UART_LSR_TEMT == (lsr & UART_LSR_TEMT))
break;
}
gpio_set_value(up->rts_gpios, 0);  // 切换回接收模式
}
}
}

四、关键设计要点

4.1  为什么要在8250_core.c中传递GPIO?

因为 rts_gpios 定义在 struct uart_8250_port 结构体中,而发送函数 serial8250_tx_chars 操作的是该结构体的实例。如果不通过 serial8250_register_8250_port 函数传递,在 8250_port.c 中引用的 up->rts_gpios 将为0,导致GPIO控制失效。

4.2  发送完成检测策略

方案采用轮询UART_LSR寄存器的方式检测发送完成:

// UART_LSR_TEMT位为1表示发送FIFO和移位寄存器均为空
if (UART_LSR_TEMT == (lsr & UART_LSR_TEMT))
break;

每次轮询间隔3ms

最大轮询200次(总计600ms超时)

适用于最高波特率115200bps下的典型帧传输

4.3  多串口支持

本方案支持对多个UART同时进行RS485改造,只需在设备树中为各串口配置不同的GPIO即可。每个串口的GPIO状态独立维护,互不干扰。

4.4  注意事项

设备树属性名:使用 rts_gpio(单数),勿用 rts-gpios(复数)

GPIO有效电平:根据RS485收发器规格选择 GPIO_ACTIVE_LOW 或 GPIO_ACTIVE_HIGH

超时设置:若波特率较低或数据量较大,需适当延长轮询超时时间

五、总结

本文介绍的驱动层改造方案,通过设备树配置+内核驱动修改,实现了RK3588 RS485的自动控制。相比用户空间轮询方案,具有以下优势:

✅ 时序精确:在数据发送的最开始置高GPIO,在所有数据彻底发送完成后置低,无用户态切换延迟

✅ 应用透明:应用层无需任何修改,直接按普通串口使用

✅ 资源节省:无需外置RS485协议芯片,降低BOM成本

✅ 内核标准:基于8250串口驱动框架,兼容性好,易于维护

相关代码补丁可在眺望电子提供的SDK上应用,适用于眺望电子RK3568、RK3588、RK3576等RK全系列芯片平台。

广州眺望电子科技有限公司专注于嵌入式处理器模组的研发与应用,提供从硬件设计到驱动开发,系统解决方案的全流程技术支持。欢迎关注我们的公众号,获取更多嵌入式项目开发实战经验。

相关推荐