首页 > 其他分享 >RK3568之修改8250驱动实现RS485收发的自动切换

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

时间:2024-08-01 10:54:32浏览次数:13  
标签:set 8250 RK3568 RS485 value 发送 fd gpio 485

最近项目需求,要用到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时所遇到的问题、调试过程和解决办法,也参考了其他博主的帖子,综合之后做出此修改,希望对你们有所帮助。若有不对的地方也可指出。

标签:set,8250,RK3568,RS485,value,发送,fd,gpio,485
From: https://blog.csdn.net/qq_44179040/article/details/140825658

相关文章

  • [米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-26 RS485串口程序收发环路设计
    软件版本:Anlogic-TD5.9.1-DR1_ES1.1操作系统:WIN1064bit硬件平台:适用安路(Anlogic)FPGA实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板板卡获取平台:https://milianke.tmall.com/登录"米联客"FPGA社区http://www.uisrc.com视频课程、答疑解惑! 1概述在前面的课程中,我......
  • RK3568驱动指南|第十六篇 SPI-第195章 实践:移植官方mcp2515驱动
    瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和MaliG522EE图形处理器。RK3568支持4K解码和1080P编码,支持SATA/PCIE/USB3.0外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568支持安卓11和linux系统,主要面向......
  • 【RK3568】点亮eDP屏幕+双屏异显
    一、驱动eDP屏幕    一般来说,屏幕的规格书中会找到屏幕的相关参数,如没有,也可直接找屏幕厂商要,首先打开屏幕的规格书,搜索EDIDTable,可找到如下信息:    (1)显示时序配置        将这些参数对应到设备树中,即可完成下面修改,关键节点就是显示时序配置的d......
  • 案例源码公开!分享瑞芯微RK3568J与FPGA的PCIe通信案例,嵌入式必读!
    ARM+FPGA架构有何种优势近年来,随着中国新基建、中国制造2025的持续推进,单ARM处理器越来越难满足工业现场的功能要求,特别是能源电力、工业控制、智慧医疗等行业通常需要ARM+FPGA架构的处理器平台来实现特定的功能,例如多路/高速AD采集、多路网口、多路串口、多路/高速并行DI/DO......
  • RS485浅析(硬件原理,软件配置)
    485是一种最常用的通信接口,在单片机裸机和Linux系统中都有应用。并且又分为收发电平自动转换和手动转换两种。本文将分别进行举例说明。485通信原理差分信号传输RS485通信采用差分信号传输,通常情况下只需要两根信号线就可以进行正常的通信。在差分信号中,逻辑0和逻辑1是用两根信......
  • 基于RS485的Modbus协议
    RS485:用来传输数据,RS485是一种差分传输的串行通信标准,以其强大的抗干扰能力、长距离传输和多点通信能力,在工业控制领域得到广泛应用。RS485使用一对差分信号线(A和B)来传输数据,差分信号能有效抵抗共模干扰,提高通信的可靠性。RS485通信可以是半双工或全双工,具体取决于应用配置。......
  • 关于武汉芯景科技有限公司的RS485通信接口芯片XJ13488EESA开发指南(兼容MAX13488EESA)
    一、芯片引脚介绍及应用电路1.芯片引脚图​2.引脚的定义及描述​3.芯片的逻辑关系二、自动收发控制电路        当检测到输入数据有变化时,电路会自动切换到发送模式,将数据发送到通信线上。当没有数据需要发送时,电路会自动切换回接收模式,监听通信线上的数......
  • RK3568 gps N303-5Q
    修改目录kernel-5.10device/rockchip/rk356x/frameworks/basesystem/coredts配置kernel-5.10&uart4{       status="okay";       pinctrl-0=<&uart4m0_xfer>;+      gnss{+               //compatible="u-blox,ne......
  • TTL 、RS232和RS485
    通信方式通信系统的分类1、按信道信号特征分类:模拟通信和数字通信2、按传输媒质分类:有线通信和无线通信3、按传输方式分类:基带传输和带通传输4、按通信业务分类:电话通信、数据通信、图像通信和遥控通信等5、按工作波段分类:长波通信、短波通信、微波通信和光通信等6、按复用......
  • RK3568平台(基础篇)开机LOGO
    一.开机LOGO概述rk3568开机logo由uboot和kerne两阶段所用图片构成uboot启动阶段主动加载logo.bmp,kernel启动阶段会加载logo.bmp和logokernel.bmp。在内核编译时这两张图片会打包到resource.img镜像,然后再合并到boot.img;logo文件直接关系boot.img大小boot.img分区超出分......