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,那方向切换不及时将导致数据接收丢失。这就是问题所在。
关于收发方向延迟问题,解决思路有如下几种:
由硬件自动控制收发方向的切换,这种方式不需要软件参与,硬件实现也很简单,推荐使用
尝试将操作系统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