一、先明确核心目标:修改要解决什么问题?
RS-485是半双工通信,需要1个“方向控制GPIO”(高电平=发送,低电平=接收)。传统方案中,应用层必须手动:
1.发送前设置GPIO高电平→切发送模式;
2.发送完成后等数据发完→再设GPIO低电平→切接收模式;
若时序错(如没等数据发完就切接收),必然丢包。
修改核心是:让内核在“发送数据”的关键节点自动控制这个GPIO,应用层只需要调用write()发数据,无需管方向切换。

二、逐文件拆解修改:改了什么?为什么这么改?
1.设备树修改(rk3576-evb1.dtsi):给UART绑定485控制GPIO
|
+&uart5 {
+ status = "okay";
+ pinctrl-names = "default";
+ 485_ctrl_gpio = <&gpio3 RK_PD6 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&uart5m0_xfer>;
+};
+
+&uart11 {
+ status = "okay";
+ pinctrl-names = "default";
+ 485_ctrl_gpio = <&gpio3 RK_PD7 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&uart11m0_xfer>;
+};
|
改了什么?
•给uart5和uart11两个串口,各加1个485_ctrl_gpio属性:
◦uart5绑定gpio3的PD6引脚,uart11绑定gpio3的PD7引脚;
◦GPIO_ACTIVE_HIGH表示:GPIO高电平时,485进入“发送模式”。
•启用串口(status = "okay")并指定引脚配置(pinctrl-0 = <&uart5m0_xfer>)。
为什么这么改?
•设备树是“硬件与驱动的桥梁”:需要告诉内核“哪个UART对应哪个GPIO”,否则驱动不知道该控制哪个引脚;
•后续驱动代码(8250_dw.c)会通过of_get_named_gpio读取这个属性,建立UART与485控制GPIO的关联。
2.驱动初始化修改(8250_dw.c):读取GPIO配置并初始化
+#include+#includestatic int dw8250_probe(struct platform_device *pdev)struct device *dev = &pdev->dev;struct dw8250_data *data;struct resource *regs;+ struct device_node *nd = dev->of_node;int irq;int err;u32 val;+ int gpio_ctrl;static int dw8250_probe(struct platform_device *pdev)data->data.dma.fn = dw8250_fallback_dma_filter;data->pdata = device_get_match_data(p->dev);p->private_data = &data->data;+ gpio_ctrl = of_get_named_gpio(nd, "485_ctrl_gpio", 0);+ if (gpio_ctrl > 0)+ {+ data->flags = 0xabcd;+ data->dir_gpio_pin = gpio_ctrl;+ gpio_direction_output(gpio_ctrl, 0);+ gpio_set_value(gpio_ctrl, 0);+ }
改了什么?
1.新增头文件:gpio.h和of_gpio.h是内核操作GPIO的必备接口;
2.读取设备树GPIO:通过of_get_named_gpio(nd, "485_ctrl_gpio", 0),从设备树读取你定义的485_ctrl_gpio引脚号;
3.初始化GPIO状态:
◦若读取到有效GPIO(gpio_ctrl > 0),给dw8250_data结构体设标记(data->flags = 0xabcd,用于后续识别“这是485串口”);
◦存储GPIO引脚号(data->dir_gpio_pin = gpio_ctrl);
◦设GPIO为输出模式(gpio_direction_output),并初始化为低电平(gpio_set_value(gpio_ctrl, 0))→初始是“接收模式”,避免上电就误发。
为什么这么改?
•这是“驱动层与硬件建立连接”的关键:设备树只是“声明”,驱动需要通过probe函数“读取声明并初始化硬件”;
•初始设为低电平(接收模式)是安全设计:防止设备上电时GPIO随机电平导致485总线被占用,干扰其他设备。
3.数据结构扩展(8250_dwlib.h):存储485控制状态
struct dw8250_data {#endifunsigned int skip_autocfg:1;unsigned int uart_16550_compatible:1;+ int flags;+ int dir_gpio_pin;};
改了什么?
在dw8250_data结构体(RK平台UART驱动的核心数据结构)中,新增两个字段:
•flags:标记是否为485串口(用0xabcd作为识别值);
•dir_gpio_pin:存储485方向控制GPIO的引脚号。
为什么这么改?
•内核驱动的“数据结构是状态的载体”:dw8250_data原本只存UART基础配置,现在要控制485,必须新增字段存储“是否是485”和“控制哪个GPIO”;
•后续发送数据时(8250_port.c),需要通过这个结构体获取GPIO信息,才能控制方向。
4.发送逻辑修改(8250_port.c):自动切换收发方向
+// #include "8250.h"+#include "8250_dwlib.h"+#include+#includevoid serial8250_tx_chars(struct uart_8250_port *up)struct uart_port *port = &up->port;struct circ_buf *xmit = &port->state->xmit;int count;+ struct dw8250_data* p_data = (struct dw8250_data*)(port->private_data);void serial8250_tx_chars(struct uart_8250_port *up)}count = up->tx_loadsz;+ if(0xabcd == p_data->flags)+ {+ if (gpio_get_value(p_data->dir_gpio_pin) != 1)+ {+ gpio_set_value(p_data->dir_gpio_pin, 1);+ printk("this uart is 485, set rts gpio %d value 1n", p_data->dir_gpio_pin);+ }+ }void serial8250_tx_chars(struct uart_8250_port *up)if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM))- __stop_tx(up);+ {+ __stop_tx(up);+ if(0xabcd == p_data->flags)+ {+ unsigned int lsr;+ int loop_count = 200;+ while(loop_count)+ {+ loop_count--;+ lsr=serial_port_in(port,UART_LSR);+ if(((lsr & UART_LSR_TEMT) == UART_LSR_TEMT))+ break;+ mdelay(1);+ }+ if(loop_count<0)+ {+ printk("timeout wait 485 send %dn",p_data->dir_gpio_pin);+ }++ gpio_set_value(p_data->dir_gpio_pin, 0);+ printk("this uart is 485, set rts gpio %d value 0n", p_data->dir_gpio_pin);++ }+ }
改了什么?
这是最核心的“自动控制”逻辑,分两个阶段:
1.发送前:切到发送模式:
◦先通过port->private_data拿到dw8250_data结构体(之前在probe函数中绑定);
◦检查flags == 0xabcd(确认是485串口),且GPIO当前不是高电平→设GPIO为高电平(gpio_set_value(1));
◦打印日志,提示“485串口已切发送模式”。
1.发送后:切回接收模式:
◦当发送缓冲区为空(uart_circ_empty(xmit)),先调用__stop_tx停止发送;
◦然后循环检查UART的LSR寄存器(serial_port_in(port,UART_LSR)):
等待UART_LSR_TEMT位(发送移位寄存器空)→确保硬件已把最后1个字节发完(避免数据残留);
最多等200ms(loop_count=200),超时打印错误日志;
◦最后设GPIO为低电平(gpio_set_value(0)),切回接收模式,打印日志。
为什么这么改?
•解决传统应用层控制的“时序痛点”:应用层无法精确判断“硬件是否真的发完数据”,而内核能直接读UART寄存器(LSR),确保数据发完再切接收;
•200ms超时是容错设计:防止硬件异常时GPIO一直处于发送模式,阻塞总线。
三、修改带来的3个核心优势(纯代码层面总结)
1.应用层彻底解放:无需再写GPIO控制代码(如ioctl设GPIO电平、猜延时等),调用write()发数据即可,内核自动搞定方向切换;
2.时序绝对精准:通过读取UART硬件寄存器(LSR_TEMT)判断发送完成,比应用层usleep(靠经验猜延时)可靠100%,不会丢包;
3.硬件适配灵活:若换485控制引脚,只需改设备树(dtsi)的485_ctrl_gpio,驱动和应用层无需动→符合“硬件与软件解耦”的内核设计思想。
四、开发者需注意的2个细节
1.GPIO引脚唯一性:uart5用GPIO3_PD6、uart11用GPIO3_PD7,需确保这两个GPIO没被其他硬件(如SPI、I2C)占用,否则会导致引脚冲突;
2.超时参数调整:loop_count=200(200ms)是通用值,若485波特率极低(如2400),1个字节发送时间长,可适当增大loop_count,避免超时误判。
-
内核
+关注
关注
4文章
1474浏览量
43088 -
RS-485
+关注
关注
11文章
751浏览量
86987 -
rk3576
+关注
关注
1文章
292浏览量
1668
发布评论请先 登录
如何移植EtherCAT Igh--基于米尔RK3576开发板
米尔RK3576和RK3588怎么选?-看这篇就够了
【米尔RK3576开发板评测】+项目名称值得购买的米尔RK3576开发板
RK3576 vs RK3588:为何越来越多的开发者转向RK3576?
Mpp支持RK3576么
RK这2款旗舰芯片RK3588 PK RK3576,谁是最优选
【作品合集】米尔RK3576开发板测评
【作品合集】灵眸科技EASY EAI Orin Nano(RK3576)开发板测评
新品体验 | RK3576开发板
RK3576单板发布倒计时:RK3399与RK3576对比
RK3588与RK3576区别解析
瑞芯微RK3576与RK3576S有什么区别,性能参数配置与型号差异解析
RK3576内核485控制引脚修改解析
评论