RK3568/RK3588之修改8250驱动实现RS485收发的自动切换
0.需求背景:
基于RS485单向通信的模式,在外接485设备进行使用时,应用层收发数据需要控制一个收发引脚,这样做在实际使用时很不方便。
解决思路:
应用层把485当作串口来使用,无需关注控制引脚,这样做需要在内核串口驱动里适配下485:
该方法的逻辑就是:
默认情况下485一直处于接收状态,当需要发送数据时,引脚切换为发送模式,发送完数据的瞬间将引脚切换为接收模式,
引脚切换时间的长短决定了485的丢包率的大小,所以这个时间必须短。
1.修法方法:
因为开发板硬件固定(可在硬件设计层直接实现),所以在驱动程序中直接根据设备树里的控制管脚来判断是否进行管脚的拉高拉低。
主要修改有2个地方:一是设备树,二是驱动程序;
1.1:设备树的修改:
修改前485设备树节点:
&uart3 {status = "okay";pinctrl-names = "default";pinctrl-0 = <&uart3m2_xfer>;
};
修改后485设备树节点:
&uart3 {status = "okay";pinctrl-names = "default";pinctrl-0 = <&uart3m2_xfer>;rts-gpio = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>; //收发控制引脚,根据实际情况来进行修改
};
1.2:驱动文件的修改:
RK3568/3588使用的是8250的串口驱动,这里一共需要修改以下3个文件:
kernel/orange-pi-5.10-rk3588/include/uapi/linux/serial.h
kernel/orange-pi-5.10-rk3588/drivers/tty/serial/8250/8250_dw.c
kernel/orange-pi-5.10-rk3588/drivers/tty/serial/8250/8250_port.c
修改serial.h文件:
文件serial.h,在结构体serial_rs485的定义中,增加一个rts_gpio的整型变量,用来存储上收发控制引脚的gpio号:
struct serial_rs485 {__u32 flags; /* RS485 feature flags */
#define SER_RS485_ENABLED (1 << 0) /* If enabled */
#define SER_RS485_RTS_ON_SEND (1 << 1) /* Logical level forRTS pin whensending */
#define SER_RS485_RTS_AFTER_SEND (1 << 2) /* Logical level forRTS pin after sent*/
#define SER_RS485_RX_DURING_TX (1 << 4)
#define SER_RS485_TERMINATE_BUS (1 << 5) /* Enable bustermination(if supported) */__u32 delay_rts_before_send; /* Delay before send (milliseconds) */__u32 delay_rts_after_send; /* Delay after send (milliseconds) */__u32 padding[5]; /* Memory is cheap, new structsare a royal PITA .. */__u32 rts_gpio; // 新增
};
修改8250_dw.c
文件8250_dw.c,一共需要修改2处,一处是增加头文件,另一处是获取收发切换的管脚编号。
1、增加头文件包含:
#include <linux/gpio.h>
#include <linux/of_gpio.h>
2、在static int dw8250_probe()函数里,增加如下代码,用来获取收发控制引脚的编号。
#if 1uart.port.rs485.rts_gpio = of_get_named_gpio(p->dev->of_node, "rts-gpio", 0);if(28 == uart.port.rs485.rts_gpio){gpio_direction_output(uart.port.rs485.rts_gpio, 0);gpio_set_value(uart.port.rs485.rts_gpio, 0); // defalut low-level}
#endif
修改8250_port.c
文件8250_port.c,一共需要修改2处,一处是增加头文件,一处是发送前拉高,发送后拉低的控制。.
1、增加头文件包含:
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
2、增加收发切换控制代码。在函数serial8250_tx_chars中增加以下代码:
发送前先拉高:
int lsr; // 用于获取状态
int i; // 用于循环计数if(28 == up->port.rs485.rts_gpio)
{gpio_set_value(up->port.rs485.rts_gpio, 1);udelay(2); // 这个延时好像可以不用
}
发送后,循环判断是否结束,结束后拉低:
if(28 == up->port.rs485.rts_gpio)
{for(i = 0; i < 200; i++){mdelay(3);lsr = serial_in(up, UART_LSR);if(UART_LSR_TEMT == (lsr & UART_LSR_TEMT)){printk("[%d] wait finished: %d, lsr: %d\n", i, (lsr & UART_LSR_TEMT) == UART_LSR_TEMT, lsr);break;}}gpio_set_value(up->port.rs485.rts_gpio, 0);
}
2.总结:
在串口发送前,若为485,则将控制引脚拉为发送状态,在发送后,循环查看是否发送完成,若发送完成则将引脚置回读状态,如此一来,就能实现在内核测控制方向切换,而不会因为延时造成数据丢失,经过测试此办法可行。
全部修改完成后,重新编译生成内核文件和设备树文件,烧写到板子上即可。
3.扩展:如何查找到RK3568/3588对应的串口驱动文件:
第一步:查看设备树串口的 “compatible”属性,其中有两个名称。
kernel/orange-pi-5.10-rk3588/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
uart3: serial@feb60000 {compatible = "rockchip,rk3588-uart", "snps,dw-apb-uart";reg = <0x0 0xfeb60000 0x0 0x100>;interrupts = <GIC_SPI 334 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru SCLK_UART3>, <&cru PCLK_UART3>;clock-names = "baudclk", "apb_pclk";reg-shift = <2>;reg-io-width = <4>;dmas = <&dmac0 12>, <&dmac0 13>;pinctrl-names = "default";pinctrl-0 = <&uart3m1_xfer>;status = "disabled";};
先在源码搜索"rockchip,rk3568-uart",没有搜索到,再搜索"snps,dw-apb-uart"。在8250_dw.c文件中找到了匹配的名称,采用的是8250的串口驱动。
static const struct of_device_id dw8250_of_match[] = {{ .compatible = "snps,dw-apb-uart" },
#ifndef CONFIG_ROCKCHIP_MINI_KERNEL{ .compatible = "cavium,octeon-3860-uart" },{ .compatible = "marvell,armada-38x-uart" },{ .compatible = "renesas,rzn1-uart" },
#endif{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw8250_of_match);
参考:
https://blog.csdn.net/qq_41696915/article/details/132173191
https://blog.csdn.net/qq_44179040/article/details/140825658
https://blog.csdn.net/bing328924/article/details/134503353