引言
RS485串口通信技术具有传输距离远、成本低、抗干扰能力强等优点,在计算机通信领域占有一席之地。但是由于RS485总线缺少总线仲裁,多对多会导致多个设备在总线上信号冲撞,所以只能构成一对一或一对多的通信网络,使其难以实现较为复杂的工业现场测控网络的构建[1]。控制器局域网网络(Controller Area Network, CAN)是应用最为广泛的工业现场总线之一,具有总线仲裁、高可靠性和高性能等特点,在变电站、矿山煤场、汽车等领域有广泛的应用[2-4]。但是CAN总线最高速率只有1Mbps,难以满足高速数据采集、通信的速率要求。高速串行总线(High-speed Serial Bus, HSB)利用低压差分信号(Low Voltage Deferential Signaling, LVDS )的高速特点(最高1.923Gbps)来改进CAN总线的性能,其中CAN作为控制总线,LVDS作为数据总线,从而实现多点互联的大数据量高速通信的应用需求,在变电站等领域有广泛应用[5-6]。
变电站内一次设备在线监测、火灾消防、安全防卫、动环系统等为变电站监控提供辅助支撑的设备,总称为辅助设备。辅助设备通过RS485接口接入火灾自动报警系统、温湿度传感器、空调控制器等外部监测和控制设备,实现对各种数据的采集接入和设备的控制[7-8]。然而一般的处理器集成的串口资源有限,难以满足使用需求。针对于此,本文实现了一种基于高速串行总线的分布式串口扩展方式,可以根据使用需求快速扩展串口资源。
1 系统概述
基于灵活硬件资源配置和硬件平台设计考虑,典型的变电站辅助设备装置硬件系统采用多板卡的分布式结构,由控制、数字量采集、模拟量采集和RS485通信等板卡组成,每个板卡可以根据需要进行多个配置,装置内部互联总线采用HSB总线,硬件总线架构图如图1所示。控制板卡是辅助设备的核心,通过HSB总线与其他板卡进行通信,对采集的数据进行计算处理,其中的FPGA模块实现HSB总线和千兆以太网总线控制,通过千兆以太网总线与CPU模块互联;CPU模块为ARM Cortex-A7系列处理器,处理器包含4个CPU核心,主频1.2GHz,运行嵌入式Linux操作系统。RS485通信板卡包含8个RS485接口,用于接入RS485接口设备,其中的FPGA模块实现HSB总线控制和RS485总线控制功能。其他板卡与本文内容无关,限于篇幅不再赘述。
图1 硬件总线架构图
RS485通信板卡的FPGA接收RS485报文后,添加HSB报文头后发送到HSB总线,控制板卡的FPGA从HSB总线接收HSB报文后,通过千兆以太网口上送CPU,由CPU进行HSB报文解包和计算处理,至此,完成一帧RS485报文的接收。
控制板卡的CPU接收到RS485报文后,添加HSB报文头后通过千兆以太网口发送给FPGA,FPGA的HSB总线控制模块将HSB报文发送到HSB总线,RS485通信板卡的FPGA从HSB总线接到HSB报文后,从HSB报文中解析出RS485报文,并根据HSB报文头中的相关信息将RS485报文发送到指定的RS485接口,至此,完成一帧RS485报文的发送。
2 通信协议设计
通信双方要进行正常通信,就必须需要一个通信协议。通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。基于通信过程的复杂性和灵活性考虑,通常将通信协议划分为若干个层次,每个层次完成一部分功能,各个层次相互配合共同完成通信功能。
控制板卡与RS485通信板卡间的通信基于HSB总线,依照分层通信体系结构,通信协议从下往上依次划分为数据链路层、HSB通信协议层和串口通信协议层,如图2所示。
图2 HSB通信协议层次图
数据链路层的主要功能是帧编码和误差纠正控制。通过链路层协议来控制数据的传输,以保证被传输数据的正确性。使用千兆以太网协议作为链路层协议,不仅可以利用Linux操作系统现有的千兆以太网驱动和编程接口,减少开发工作量,而且可以利用Linux网络协议栈的NAPI收包、套接字缓存等特性[9],提高通信性能和稳定性。
HSB通信协议层的主要功能是实现控制板卡与RS485通信板卡间的通信,也是本文讨论的重点。HSB通信协议报文的各字段定义如图3所示。报文目标节点地址、报文源节点地址、报文优先级、报文长度5个字段用于HSB的CAN总线控制,其中,报文目标节点地址字段表示通信的接收方,报文源节点地址字段表示通信的发送方,报文优先级字段用于CAN总线的发送优先级控制,报文长度字段用于指定HSB协议数据段的长度。报文类型、串口端口、串口配置3个字段用于HSB的RS485串口报文控制,其中,报文类型字段用于区分发送报文和接收报文,串口端口字段用于确定RS485通信板卡上的串口地址,串口配置字段用于确定串口的波特率和通信协议配置,该通信协议由1个起始位、5-9个数据位、1-2个停止位、1个奇偶校验位组成。
图3 HSB通信协议报文格式图
串口通信协议层的主要功能是实现各种应用功能。作为应用层协议,位于HSB报文的数据部分。常用的通信协议包括ModBus、ProfiBus协议等。
3 通信接口设计
“一切皆是文件”是 Unix/Linux 的基本哲学之一,Linux常用文件接口包括打开、关闭、读、写等。基于此设计思想,RS485串口通信接口设计如下。COMOpen用于打开和初始化RS485通信板卡上的串口设备,其中slotid参数用于指定RS485通信板卡地址,devid参数用于指定串口端口地址,baud参数用于指定串口波特率,flags参数用于指定串口通信协议配置,接口返回打开的串口对象指针;COMClose用于关闭串口;COMWrite、COMRead分别用于串口报文发送和接收,其中dev参数用于指定已打开的串口对象,buf、len参数用于指定串口报文缓冲区的首地址和长度。
struct com_dev * COMOpen(uint32_t slotid, uint32_t devid,uint32_t baud, uint32_t flags);
int COMClose(struct com_dev *dev);
int COMWrite(struct com_dev *dev, uint8_t * buf, uint32_t len);
int COMRead(struct com_dev *dev, uint8_t * buf, uint32_t len);
3.1 串口打开和初始化
因为串口报文通过千兆以太网总线进入控制板卡的CPU,所以CPU通过网络接口设备与RS485通信板卡进行通信。首先通过socket接口实现网络接口设备打开和网络协议绑定,网络协议号由slotid和devid生成,通过网络协议号指定RS485通信板卡的串口端口地址。接着设置报文接收超时时间,避免读接口因为无报文而阻塞。然后创建并初始化串口设备对象,串口设备对象包含socket文件描述符、RS485通信板卡地址、串口地址和串口配置等信息,作为其他接口的参数,提供相关通信信息。最后发送一个空的串口报文,用于配置串口的波特率和通信协议。串口的打开和初始化伪码程序如下。
struct com_dev * COMOpen(uint32_t slotid, uint32_t devid, uint32_t baud, uint32_t flags)
{
// 根据RS485通信板卡地址和串口地址获取网络协议号
proto = htons(COM_PROTO(slotid, devid));
// 打开千兆以太网接口设备,并绑定网络协议号
fd = socket(PF_PACKET, SOCK_RAW, proto);
sock_com.sll_family = PF_PACKET;
sock_com.sll_protocol = proto;
sock_com.sll_ifindex = if_nametoindex(COM_SOCK_DEV);
bind(fd, (struct sockaddr *)&sock_com, sizeof(sock_com));
// 设置串口报文接收超时时间
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
// 创建和初始化串口设备对象
struct com_dev * dev;
dev = malloc(sizeof(struct com_dev));
dev->fd = fd;
dev->slotid = slotid;
dev->devid = devid;
dev->config = com_config_parse(baud, flags);
// 发送一个空的串口报文,配置串口的波特率和通信协议
COMWrite(dev, NULL, 0);
return dev;
}
3.2 串口报文发送
串口报文发送接口的核心工作是为串口报文添加HSB报文首部后发出。首先对串口设备对象、报文缓冲区的首地址和长度进行合法性检查,然后通过串口报文长度加上HSB报文首部长度计算出HSB报文长度,以此申请一个HSB报文缓冲区并初始化此HSB报文的目标节点地址、报文长度、报文类型、串口配置、数据段等各个发送相关字段,最后通过网络通信接口sendto发出HSB报文。串口报文发送伪码程序如下。
int COMWrite(struct com_dev *dev, uint8_t * buf, uint32_t len)
{
// 对串口对象、报文缓冲区的首地址和长度进行合法性检查
if((dev == NULL) || (buf == NULL && (len != 0)) || (len > COM_SOCK_MTU - hsb_hdr_size))
return -EINVAL;
// 计算HSB报文长度,并申请HSB报文缓冲区空间
pkt = malloc(len + hsb_hdr_size);
// 复制串口报文至HSB报文的数据段
memcpy((uint8_t *)pkt+hsb_hdr_size, buf, len);
// 初始化HSB首部报文发送相关字段
pkt->dst=htons(1<< (dev->slotid));
pkt->dlc = htons(len + COM_TX_HSB_HDR_LEN);
pkt->type = htons(COM_SEND_TYPE);
pkt->port = htons(1 << dev->devid);
pkt->config = htonl(dev->config);
// 发送HSB报文
ret = sendto(dev->fd, pkt, len+hsb_hdr_size, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
// 释放HSB报文缓冲区
free(pkt);
return ret;
}
3.3 串口报文接收
串口报文接收接口与串口报文发送接口的处理流程类似。首先对所有参数进行合法性检查,然后计算出HSB报文长度并申请一个HSB报文缓冲区,接着使用网络通信接口recvfrom接收到HSB报文,最后将接收到的HSB报文的数据段数据拷贝到串口报文接收接口的缓冲区。串口报文接收的程序伪码如下。
int COMRead(struct com_dev *dev, uint8_t * buf, uint32_t len)
{
// 对串口对象、报文缓冲区的首地址和长度进行合法性检查
if((dev == NULL) || (buf == NULL && (len != 0)) || (len > COM_SOCK_MTU - hsb_hdr_size))
return -EINVAL;
// 计算HSB报文长度,并申请HSB报文缓冲区空间
pkt = malloc(len + hsb_hdr_size);
// 接收HSB报文
ret = recvfrom(dev->fd, pkt, len + hsb_hdr_size, 0, (struct sockaddr *)&device, &sll_len);
// 将HSB报文数据段拷贝到串口报文缓冲区
memcpy(buf,(uint8_t *)pkt+hsb_hdr_size, ret);
// 释放HSB报文缓冲区
free(pkt);
return ret;
}
3.4 串口关闭
串口关闭接口的主要功能是释放相关系统资源,如关闭socket接口、释放串口设备对象等等。串口关闭接口的程序伪码如下。
int COMClose(struct com_dev *dev)
{
// 释放socke资源
ret = close(dev->fd);
// 释放串口设备对象资源
free(dev);
return ret;
}
4 工程应用
本方案目前已应用到新一代的安防监控、消防信息传输控制等多个变电站辅助设备中。通过RS485通信板卡的RS485接口,与安防监控、火灾自动报警系统等终端的通信功能工作正常,通过RS485接口接入的扩展键盘、探测器和输出前端等设备,能正确实现报警信号、布撤防等数据交互。设备现场运行情况良好,所有功能工作稳定,性能符合装置技术指标要求。
5 结语
本文实现了一种基于高速串行总线的分布式RS485串口通信方案,支持设备根据需要灵活配置RS485通信板卡数量来扩展RS485串口资源。通过对通信协议的分层设计,实现了分布式RS485串口的访问控制和数据传输,并给出了详细的通信接口设计。本方案提高设备的可扩展性,通信协议易于升级与维护,对低速通信总线的扩展具有一定参考价值。
(本文修改稿发表于《工业控制计算机》2021年第11期)