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

|
+&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 的关联。
+#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 总线被占用,干扰其他设备。
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 信息,才能控制方向。
+// #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 一直处于发送模式,阻塞总线。
1.应用层彻底解放:无需再写 GPIO 控制代码(如ioctl设 GPIO 电平、猜延时等),调用write()发数据即可,内核自动搞定方向切换;
2.时序绝对精准:通过读取 UART 硬件寄存器(LSR_TEMT)判断发送完成,比应用层usleep(靠经验猜延时)可靠 100%,不会丢包;
3.硬件适配灵活:若换 485 控制引脚,只需改设备树(dtsi)的485_ctrl_gpio,驱动和应用层无需动→符合 “硬件与软件解耦” 的内核设计思想。
1.GPIO 引脚唯一性:uart5用 GPIO3_PD6、uart11用 GPIO3_PD7,需确保这两个 GPIO 没被其他硬件(如 SPI、I2C)占用,否则会导致引脚冲突;
2.超时参数调整:loop_count=200(200ms)是通用值,若 485 波特率极低(如 2400),1 个字节发送时间长,可适当增大loop_count,避免超时误判。
全部0条评论
快来发表一下你的评论吧 !