RK3568之修改8250驱动实现RS485收发的自动切换

最近项目需求,要用到RK3568搭配自制底板。整个软硬件联调过程并不顺利,特立此系列帖,记录调试中发生的一些问题和解决办法。


前言

由于硬件在选485芯片的时候,没有选择自动控制收发io的芯片,也没有在电路上进行优化,导致在使用自制板的RS485通讯时,需要在应用层控制485收发方向,但在应用层控制可能会导致切换不及时而造成数据丢包。下面会介绍其中问题细节和我的解决办法。


调试过程及问题

自制板在RS485部分,使用的芯片是TDH301D485H-E,UART_RE为收发方向IO,RX,TX即收发引脚。部分电路图如下所示:
在这里插入图片描述
相比于普通的UART,在485的使用上多出了一个收发IO口。Linux提供了相关的控制接口,最初的想法是在串口发送数据之前加上一个GPIO的电平控制。默认电平为高(485读状态),在发送前将电平拉低,进入发送状态,发送后再将电平拉高进入读状态。
GPIO导出控制相关函数:

int set_gpio_export(unsigned int gpio)
{
    int fd, len;
    char buf[128];
    fd = open( "/sys/class/gpio/export", O_WRONLY);
    if (fd < 0) 
    {
        perror("gpio/export");
        return -1;
    }
 
    len = snprintf(buf, sizeof(buf), "%d", gpio);//从数字变换为字符串,即1 变为”1“
    write(fd, buf, len);//将需要导出的GPIO引脚编号进行写入
    close(fd);
 
    return 0;
}
int set_gpio_direction(unsigned int gpio, unsigned int io_flag)
{
    int fd, len;
    char buf[128];
    len = snprintf(buf, sizeof(buf), "/sys/class/gpio"  "/gpio%d/direction", gpio);
 
    fd = open(buf, O_WRONLY);
    if (fd < 0) 
    {
        perror(buf);
        return -1;
    }
    
    if (io_flag)//为1,则写入“out",即设置为输出
        write(fd, "out", 4);
    else//为0,则写入“in",即设置为输入
        write(fd, "in", 3);
 
    close(fd);
    return 0;
}
int set_gpio_value(unsigned int gpio, unsigned int value)
{
    int fd, len;
    char buf[128];
    len = snprintf(buf, sizeof(buf), "/sys/class/gpio" "/gpio%d/value", gpio);
 
    fd = open(buf, O_WRONLY);
    if (fd < 0) 
    {
        perror(buf);
        return -1;
    }
 
    if (value)//为1,则写入“1",即设置为输出高电平
        write(fd, "1", 2);
    else//为0,则写入“0",即设置为输出低电平
        write(fd, "0", 2);
 
    close(fd);
    return 0;
}

发送485时拉高,发送完立即拉低,示例:

   set_gpio_export(gpio_num);
   set_gpio_direction(gpio_num, GPIO_OUT_MODE);
   set_gpio_value(gpio_num, RS485_SEND_MODE);
   write(fd, data, sizeof(data));
   set_gpio_value(gpio_num, RS485_RECV_MODE);

但是经测试使用此方式发送完后,接收方收不到数据或者只能收到几个字节的数据,原因在于应用层调用write函数后,数据会交给内核处理,假设115200波特率下发送一包256个字节的数据,理论上的发送时间是256101000/115200大约22ms左右,而应用层write函数调用时间是us级,在调用完后如果立即切换引脚状态,缓冲区中的数据还没有发送完,就会导致数据丢失。
在此现象下,想到了一个函数tcdrain()。既然需要等到缓冲区发送完毕后才能切换状态,那就在调用完write后,检查缓冲区,直到为空时再去切换。
发送485时拉高,发送完等待缓冲区数据发送完毕再拉低,示例:

   set_gpio_export(gpio_num);
   set_gpio_direction(gpio_num, GPIO_OUT_MODE);
   set_gpio_value(gpio_num, RS485_SEND_MODE);
   write(fd, data, sizeof(data));
   tcdrain(fd);
   set_gpio_value(gpio_num, RS485_RECV_MODE);

经过优化后,再进行测试,只是单测收发确实发送的数据能被接收到了。
但在模拟485的使用场景(连接板卡的485_1和485_2,485_1发送数据,后等待回复,485_2等待接收,接收到数据后立即返回数据。)测试时,又发现1能成功发送并且2能接收到,但是2返回的数据已经丢了。

在这里插入图片描述
在这里插入图片描述
经调试发现,tcdrain这个函数,实际阻塞的时间要远大于理论发送时间,在上述波特率和数据包大小情况下,tcdrain阻塞最高等到32ms,而在发送方阻塞时,接收方已经接收到了数据并将数据返回了,这才导致了数据丢失。如此在应用层来控制收发在实际使用上还是有很大可能会造成丢包,在项目上是不允许的,下面说一下解决办法。

解决办法

1.硬件修改

其实硬件上修改为自动收发控制电路或使用自动收发控制的芯片是最好的解决办法。例如更换为TDH301D485H-A型号
在这里插入图片描述

当然,芯片更换如果封装不同可能会很麻烦,成本也可能会增加。也可以通过优化电路来实现,例如正点原子的参考电路:
在这里插入图片描述
通过增加一个三极管电路,实现自动切换方向,详细的电路原理这里不再展开。

2.软件解决

除了修改硬件,在软件上,我们也可以通过修改linux的内核,在内核侧来控制IO口,会大大的缩短延时,从而达到避免切换延时带来的数据丢包问题。下面以我使用的RK3568作为参考,介绍一下修改步骤。

1.修改设备树文件

在设备书中串口节点中,增加485_ctrl_gpio信息,gpio根据实际使用的io号修改

&uart3 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&uart3m1_xfer>;
	485_ctrl_gpio = <&gpio1, RK_PB2, GPIO_ACTIVE_HIGH>;
};

2.查找设备树对应的串口驱动文件

在rk3568.dts中可以找到串口相关的节点:

	uart1: serial@fe650000 {
		compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
		reg = <0x0 0xfe650000 0x0 0x100>;
		interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&cru SCLK_UART1>, <&cru PCLK_UART1>;
		clock-names = "baudclk", "apb_pclk";
		reg-shift = <2>;
		reg-io-width = <4>;
		dmas = <&dmac0 2>, <&dmac0 3>;
		pinctrl-names = "default";
		pinctrl-0 = <&uart1m0_xfer>;
		status = "disabled";
	};

在内核代码中搜索compatible的两个字符串,可以找到snps,dw-apb-uart对应的8250驱动
在这里插入图片描述

3.修改serial.h

在/kernel/include/uapi//linux/serial.h中定义有serial_rs485结构体,在结构体中增加485的控制引脚信息

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 for
							   RTS pin when
							   sending */
#define SER_RS485_RTS_AFTER_SEND	(1 << 2)	/* Logical level for
							   RTS pin after sent*/
#define SER_RS485_RX_DURING_TX		(1 << 4)
#define SER_RS485_TERMINATE_BUS		(1 << 5)	/* Enable bus
							   termination
							   (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 structs
					   are a royal PITA .. */

	__u32   rts_gpio;//485的控制io
};

2.修改8250_dw.c

8250使用的是platform框架,找到dw8250_probe函数,增加头文件信息,在p->private_data = data后增加以下内容

	#include <linux/gpio.h>
	#include <linux/of_gpio.h>

	//p->private_data = data后增加的内容
	struct device_node *nd = dev->of_node;
	int gpio_ctrl = of_get_named_gpio(nd, "485_ctrl_gpio", 0);
	if (gpio_ctrl > 0)
	{
		p->rs485.flags = 0xabcd;
		p->rs485.rts_gpio = gpio_ctrl;
		gpio_direction_output(gpio_ctrl, 0);
		gpio_set_value(gpio_ctrl, 1);
	}

当匹配到串口信息后,提取设备树中485的控制信息,若有该属性则该串口为485,保存io号和标志,导出控制IO,将电平设置为默认读状态。

2.修改8250_port.c

通过struct uart_ops serial8250_pops可以知道,串口发送使用的函数serial8250_start_tx,调用了__start_tx,最后再调用serial8250_tx_chars,找到该函数,在函数中增加以下代码

	if(0xabcd == port->rs485.flags)
	{
		if (gpio_get_value(port->rs485.rts_gpio) != 0)
		{
			gpio_set_value(port->rs485.rts_gpio, 0);
			printk("this uart is 485, set rts gpio %d value 0\n", port->rs485.rts_gpio);
		}	
	}

最后结束发送后

		if(0xabcd == port->rs485.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 %d\n", port->rs485.rts_gpio);
			} 
			gpio_set_value(port->rs485.rts_gpio, 1);
			printk("this uart is 485,send over set rts gpio %d value 1\n", port->rs485.rts_gpio);
		}

在这里插入图片描述
在串口发送前,若为485,则将控制引脚拉为发送状态,在发送后,循环查看是否发送完成,若发送完成则将引脚置回读状态,如此一来,就能实现在内核测控制方向切换,而不会因为延时造成数据丢失,经过测试此办法可行。

总结

以上则是本次调试RK3568的485时所遇到的问题、调试过程和解决办法,也参考了其他博主的帖子,综合之后做出此修改,希望对你们有所帮助。若有不对的地方也可指出。

  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于RK3568芯片,USB驱动的安装可以按照以下步骤进行: 1. 下载USB驱动程序:首先,你需要在RK3568芯片的官方网站或相关厂商的支持网站上下载适用于RK3568的USB驱动程序。确保下载的驱动程序与你的操作系统版本相匹配。 2. 解压驱动程序:下载完驱动程序后,将其解压缩到一个临时文件夹中。 3. 连接设备:将RK3568设备通过USB数据线连接到计算机上,并确保设备处于正常的工作状态。 4. 安装驱动程序:打开设备管理器(在Windows系统中,可以通过右键点击“我的电脑”或“此电脑”,选择“管理”,然后在左侧导航栏中找到“设备管理器”),找到与RK3568设备相关的项(可能标记为未知设备或其他类别),右键点击它,选择“更新驱动程序软件”。 5. 手动安装驱动程序:在驱动程序更新对话框中,选择“浏览计算机以查找驱动程序软件”,然后浏览到你之前解压缩的驱动程序文件夹,选择合适的驱动程序文件进行安装。 6. 完成安装:按照提示完成驱动程序的安装过程。安装完成后,设备管理器中的RK3568设备应该不再显示为未知设备,并且可以正常使用USB功能。 请注意,以上步骤仅供参考,具体的安装过程可能因驱动程序版本、操作系统版本等因素而略有不同。建议在安装驱动程序之前,先参考相关文档或与设备厂商进行沟通,以确保正确安装驱动程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值