【前言】
在工業通信場景中,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/8250/8250_core.cindex 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)#ifdefCONFIG_ARCH_ROCKCHIPuart->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.cindex 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+#include/* Offsets for the DesignWare specific registers */#defineDW_UART_USR 0x1f /* UART Status Register */#defineDW_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 *pdev)#ifdefCONFIG_ARCH_ROCKCHIPdata->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/8250/8250_port.cindex 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/* Nuvoton NPCM timeout register */#defineUART_NPCM_TOR 7#defineUART_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 *up)__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 *up)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.hindex 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.hindex 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字段:
structuart_8250_port {// ... 原有字段/* Serial port overrun backoff */structdelayed_work overrun_backoff;u32 overrun_backoff_time_ms;intrts_gpios; /* 新增:RS485方向控制GPIO */};
3.2平臺驅動初始化
修改 drivers/tty/serial/8250/8250_dw.c,在probe函數中解析設備樹GPIO:
#include#includestaticintdw8250_probe(structplatform_device *pdev){// ... 原有代碼intrts_gpios;// 從設備樹獲取GPIOrts_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:
intserial8250_register_8250_port(conststructuart_8250_port *up){// ... 原有代碼#ifdefCONFIG_ARCH_ROCKCHIPuart->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 函數:
#includevoidserial8250_tx_chars(structuart_8250_port *up){structuart_port*port = &up->port;structcirc_buf*xmit = &port->state->xmit;intcount;intlsr, 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全系列芯片平臺。
廣州眺望電子科技有限公司專注于嵌入式處理器模組的研發與應用,提供從硬件設計到驅動開發,系統解決方案的全流程技術支持。歡迎關注我們的公眾號,獲取更多嵌入式項目開發實戰經驗。
-
RS485
+關注
關注
40文章
1347瀏覽量
86140 -
串口
+關注
關注
15文章
1625瀏覽量
83153 -
無線收發控制
+關注
關注
0文章
2瀏覽量
746 -
RK3588
+關注
關注
8文章
575瀏覽量
7486
發布評論請先 登錄
RK3588 PCB推薦疊層及阻抗設計
HaaS100通過RS485串口控制380V電機
RS485 232串口通信數據解析
(RS485 232串口通信數據解析實用干貨(1)
工業RS485串口網關實現485接口數據采集
RK3588串口RS485自動收發控制:內核驅動層改造實戰
評論