Linux-485收发切换延迟的解决方法

RS-485(亦称TIA-485, EIA-485)作为一种半双工总线,其收发过程不能同时进行。
RS-485通信的具体硬件原理可查阅其他资料,此处不详述。本文仅描述其控制方法及相关问题。

通常由CPU引出三根管脚:两个UART管脚(记作PIN_RX、PIN_TX)和一个485收发方向控制管脚(记作PIN_DIR)。
这三根管脚会接在板上的485芯片上,485芯片再向板外引出“D+、D-”两根差分信号总线(差分信号具有搞干扰、传输距离远的优势)。

应用程序编写时,在原来的普通串口通信基础上,加上485收发方向控制即可。
具体说来,UART发送过程中,将PIN_DIR脚拉高,发送完毕再将PIN_DIR脚拉低,使485总线可以接收数据。
对于无操作系统的裸机程序来说,485通信非常简单。
但在Linux应用程序编写中,这个方向切换存在延迟问题。

Linux应用层485控制接口伪代码如下:

// 初始化串口 fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY); init_serial(fd, 9600, 8, 1, 'N'); set_485_dir(LOW); // 默认为接收状态 // 发送数据 set_485_dir(HIGH); write(fd, buf, sizeof(buf)); tcdrain(fd); // 此句判断时刻不准,延时约10-20ms set_485_dir(LOW); // 接收数据 read();

经测试,set_485_dir()改变PIN_DIR脚的延迟很小,可忽略不计。tcdrain()却总是存在10-20ms的延迟。
tcdrain()是等待fd所引用的串口设备数据传输完毕。在物理上数据已传输完毕时,因为操作系统延迟原因,导致tcdrain()多停留了10-20ms,从而导致数据发送完后,PIN_DIR不能及时切换。
如果对接的485设备,接收和应答的延迟小于20ms,那方向切换不及时将导致数据接收丢失。这就是问题所在。

二、解决方法 1. 解决思路

关于收发方向延迟问题,解决思路有如下几种:

由硬件自动控制收发方向的切换,这种方式不需要软件参与,硬件实现也很简单,推荐使用

尝试将操作系统HZ由默认的100改为1000,经测,tcdrain()延迟降为几个ms,实际仍然不能满足要求,而且比较影响系统性能

应用层控制方向切换,应用程序里使用ioctl()方法,利用Linux串口驱动里自带的485功能。此方法需要全串口里的RTS管脚作为方向脚。时间所限,此方法未研究明白

驱动层控制方向切换,修改串口驱动使支持485方向切换,此方法验证可行

最后一种方法就是本文要描述的方法。

2. 知识储备

解决此问题,需要有如下知识储备:

了解485通信原理

了解Linux终端设备驱动架构,搞清楚板上串口对应的实际驱动源文件

掌握Linux设备驱动中的中断处理机制:顶半部、底半部(tasklet、工作队列、软中断)

3. 实现方法

本应用中对应的串口设备驱动文件为linux/drivers/tty/serial/8250/8250_core.c

3.1 由应用程序控制是否打开串口设备的485功能

在串口驱动里切换485方向对性能有一些影响。
而某些应用可能只需要标准串口,不需要支持485模式。
因此最好由应用程序来控制,是使用标准串口还是支持485模式的串口。
这主要利用ioctl()实现。

应用程序在初始化打开串口时,禁用/使能串口的485模式

fd = open(...); init_serial(fd, ...); struct serial_rs485 rs485conf; rs485conf.flags |= SER_RS485_ENABLED; // 使能本串口485模式,默认禁用 ioctl(fd, TIOCSRS485, &rs485conf);

驱动程序中对使能了485模式的串口作特殊处理。
利用struct uart_8250_port结构体中的struct serial_rs485 rs485成员判断串口是否支持485模式。
在serial_8250.h中有定义rs485数据成员,以及设置此数据成员的成员函数rs485_config

// noted by xx@xx: in serial_8250.h /* * This should be used by drivers which want to register * their own 8250 ports without registering their own * platform device. Using these will make your driver * dependent on the 8250 driver. */ struct uart_8250_port { struct uart_port port; struct timer_list timer; /* "no irq" timer */ struct list_head list; /* ports on this IRQ */ unsigned short capabilities; /* port capabilities */ unsigned short bugs; /* port bugs */ bool fifo_bug; /* min RX trigger if enabled */ unsigned int tx_loadsz; /* transmit fifo load size */ unsigned char acr; unsigned char fcr; unsigned char ier; unsigned char lcr; unsigned char mcr; unsigned char mcr_mask; /* mask of user bits */ unsigned char mcr_force; /* mask of forced bits */ unsigned char cur_iotype; /* Running I/O type */ unsigned int rpm_tx_active; /* * Some bits in registers are cleared on a read, so they must * be saved whenever the register is read but the bits will not * be immediately processed. */ #define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS unsigned char lsr_saved_flags; #define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA unsigned char msr_saved_flags; struct uart_8250_dma *dma; struct serial_rs485 rs485; /* 8250 specific callbacks */ int (*dl_read)(struct uart_8250_port *); void (*dl_write)(struct uart_8250_port *, int); ***int (*rs485_config)(struct uart_8250_port *, struct serial_rs485 *rs485);*** };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyyfyx.html