RK3576内核485控制引脚修改解析

电子说

1.4w人已加入

描述

 

 

 

一、先明确核心目标:修改要解决什么问题?

 

RS-485 是半双工通信,需要 个 方向控制 GPIO”(高电平 发送,低电平 接收)。传统方案中,应用层必须手动:

 

 

1.发送前设置 GPIO 高电平切发送模式;

 

 

2.发送完成后等数据发完再设 GPIO 低电平切接收模式;

 

 

若时序错(如没等数据发完就切接收),必然丢包。

 

 

修改核心是:让内核在发送数据” 的关键节点自动控制这个 GPIO,应用层只需要调用write()发数据,无需管方向切换。

RS-485

 

 

 

二、逐文件拆解修改:改了什么?为什么这么改?

 

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>;

 

 

+};

 

 

改了什么?

 

uart5uart11两个串口,各加 1 485_ctrl_gpio属性:

 

 

uart5绑定gpio3PD6引脚uart11绑定gpio3PD7引脚

 

 

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 +#include @@ -570,9 +572,11 @@ static 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;@@ -610,6 +614,14 @@ 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.hof_gpio.h是内核操作 GPIO 的必备接口;

 

 

2.读取设备树 GPIO:通过of_get_named_gpio(nd, "485_ctrl_gpio", 0),从设备树读取你定义的485_ctrl_gpio引脚号;

 

 

3.初始化 GPIO 状态:

 

 

若读取到有效 GPIOgpio_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 控制状态

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
@@ -50,6 +50,8 @@ struct dw8250_data { #endif         unsigned 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 +#include @@ -1833,6 +1835,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;+        struct dw8250_data* p_data = (struct dw8250_data*)(port->private_data);@@ -1848,6 +1851,14 @@ 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);+                }        +        }@@ -1884,7 +1895,30 @@ 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位(发送移位寄存器空)→ 确保硬件已把最后 个字节发完(避免数据残留);

 

 

最多等 200msloop_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_PD6uart11 GPIO3_PD7,需确保这两个 GPIO 没被其他硬件(如 SPII2C)占用,否则会导致引脚冲突;

 

 

2.超时参数调整loop_count=200200ms)是通用值,若 485 波特率极低(如 2400),个字节发送时间长,可适当增大loop_count,避免超时误判。

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分