您好,欢迎来到九壹网。
搜索
您的当前位置:首页通讯接口应用笔记3:使用W5500实现ModbusTCP服务器

通讯接口应用笔记3:使用W5500实现ModbusTCP服务器

来源:九壹网
通讯接⼝应⽤笔记3:使⽤W5500实现ModbusTCP服务器

  前⾯我们设计实现了W5500的驱动程序,也讲解了驱动的使⽤⽅式。在最近⼀次的项⽬应⽤中,正好有⼀个使⽤W5500实现TCP通讯的需求,所以我们就使⽤该驱动程序轻松实现。这⼀篇中我们就来说⼀说基于我们W5500通讯驱动程序实现TCP通讯的过程。

1、应⽤需求

  在本次应⽤中,要求实现⼀个基于W5500的Modbus TCP服务器。这个需求的描述虽然只有⼀句话,但是这个需求的内容可不简单。我们⾸先来分析⼀下这个需求的具体内容。

  为了实现基于W5500的Modbus TCP服务器,我们必先须基于W5500实现⼀个TCP服务器。W5500本⾝是带硬件协议栈的,但却并不带TCP服务器。不过在我们前⾯的关于外设驱动库的系列⽂章中已经封装了W5500的驱动,其中就带有⼀个TCP服务器,我们可以直接采⽤就可以了。

  其次我们要在TCP服务器的基础上实现Modbus TCP协议。关于Modbus协议栈,我们以前的⽂章就讲述过Modbus通讯协议栈的开发问题。⽽且我们已经将我们开发的Modbus通讯协议栈开源。其中已经封装了Modbus TCP服务器对象,所以我们直接采⽤这⼀Modbus通讯协议栈就可以了。

  有了驱动和协议栈,我们还需要考虑应⽤层⾯的具体问题,⽽且也只需要考虑应⽤层⾯的具体问题。这⾥就看出我们前⾯封装外设驱动和Modbus通讯协议栈的价值所在了。关于应⽤层⾯的问题我们主要需要重点考虑⼏个问题:

  第⼀,数据的存储类型及地址范围。我们知道Modbus协议常见的数据类型有4种。我们需要考虑在系统中需要使⽤到的类型及地址,这将决定Modbus协议数据处理回调函数的实现。

  第⼆,⽹络配置问题,我们需要通过⽹络访问这台下位机就需要要为其配置⽹络。这存在静态配置,动态配置和系统⾃动分配的问题。作为服务器,我们⼀般不会希望让系统⾃动分配。所以我们需要考虑的是如何⽅便使⽤者为其分配地址的问题。

  第三,并发访问的问题。挂载在⽹络上的服务器肯定⾯临多个客户端来访问的问题。W5500可以实现8个Socket,⽽Modbus TCP通⽤的默认端⼝号是502,当然也可以使⽤其它端⼝,只要不冲突就好。所以我们可以考虑使⽤不同的Socket和不同的端⼝号来实现并发访问。

2、功能设计

  我们分析了基于W5500实现Modbus TCP服务器的需求。我们现在从硬件和软件两个⽅⾯来分析器功能的实现。

2.1、硬件功能设计

  我们知道W5500带有硬件协议栈,集成有以太⽹控制器和物理层,所以对外我们只需要实现以太⽹变压器和硬件接⼝就好了。但与控制器部分的连接则采⽤SPI接⼝,除此之外还需要提供中断输⼊和模式设定的相关接⼝。在这⾥我们设计器硬件连接如下:

  在上图中,我们将中断输⼊引⼊到MCU的GPIO端⼝,⽽模式设定PMODE0、PMODE1、PMODE2均通过电阻上拉到电源。对于W5500来说PMODE0、PMODE1、PMODE2均为⾼电平表⽰开启全部功能,所以我们直接拉⾼⽽不是引⼊到MCU引脚来控制。

2.2、软件功能设计

  从需求来说,软件的功能⾮常简单,就是实现⼀个Modbus TCP服务器。但实际上,如我们前⾯所描述的那样,软件需要考虑的问题还是⽐较多的。从功能实现上主要有3个⽅⾯需要考虑:

  第⼀,实现TCP服务器,这个服务器⽤于在系统中轮询处理,从W5500获取数据和发送数据给W5500都需要通过这部分来实现。  第⼆,TCP服务器得到数据后,我们需要解析数据,并根据解析的上位数据决定进⼀步的动作,还需要⽣成返回信息。这部分对应功能就是Modbus TCP服务器的实现。

  第三,根据Modbus TCP服务器解析出的Modbus消息,需要决定下⼀步的动作,这个具体动作根据功能码的不同可能有不同需求,所以我们需要根据具体的要求实现不同功能码的动作。

  根据上述的设计,我们可以简单的将需要实现的软件功能图⽰如下:

  上图中,因为W5500的TCP服务器以及Modbus TCP协议栈的相关函数我们都做了封装,所以它们之间的调⽤都将以回调函数的⽅式实现。除了上述的软件实现外,还需要注意必要的初始化配置。

3、应⽤实现

  根据我们前⾯的设计,接下来我们考虑⼀下这⼀需求的具体实现过程。我们将这⼀过程分为4个部分来分别描述。

3.1、系统的初始化

  在实现具体的功能之前,我们需要对硬件以及软件环境做必要的初始化配置。具体到这⾥就是对W5500作必要的软硬件配置,包括接⼝、⽹络以及回调函数等。具体实例代码如下:

/* 以太⽹通讯配置 */

void McEthernetConfiguration(void){

uint8_t mac[6]={0x01, 0x08, 0xdc,0x00, 0xab, 0xcd}; //本地Mac地址 uint8_t ip[4]={192, 168, 1, 190}; //本地IP地址 uint8_t sn[4]={255,255,255,0}; //⼦⽹掩码 uint8_t gw[4]={192, 168, 1, 1}; //⽹关地址 uint8_t dns[4]={0,0,0,0}; //DNS服务器地址

/* 以太⽹使⽤GPIO初始化 */ GPIO_Init_Configuration();

/* SPI1端⼝初始化 */

SPI1_Init_Configuration();

/*W5500对象初始化函数*/

W5500Initialization(&w5500, //W5500对象 mac, //本地Mac地址 ip, //本地IP地址 sn, //⼦⽹掩码 gw, //⽹关地址

dns, //DNS服务器地址

NETINFO_STATIC, //DHCP类型 EnterCritical, //进⼊临界区 ExitCritical, //退出临界区 EnableChipSelect, //⽚选使能 DisableChipSelect, //⽚选失能 ReadByteBySPI, //SPI读字节 WriteByteBySPI, //SPI写字节

W5500DataParsing, //报⽂解析函数 NULL //数据请求函数 );}

  在这个实例中,我们对⽹络部分采⽤的是静态配置,就是说⽹络参数是固定不变的,⽽且我们的测试环境只限于局域⽹内。

3.2、数据处理函数

  数据处理函数是最灵活的,因为每个项⽬及每个⼈对数据处理的要求都是不⼀样的,只要能符合应⽤要求就没问题。需要说⼀下的是,这部分是Modbus协议栈对处理数据的要求,想要详细了解的话,可以看我们以前关于Modbus协议站的⽂章。对于这个实例,数据处理函数如下:

/*获取想要读取的Coil量的值*/

void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList){

uint16_t start; uint16_t count;

/*先判断地址是否处于合法范围*/

start=(startAddress>CoilStartAddress)?((startAddress<=CoilEndAddress)?startAddress:CoilEndAddress):CoilStartAddress; count=((start+quantity-1)<=CoilEndAddress)?quantity:(CoilEndAddress-start);

for(int i=0;istatusList[i]=dPara.coil[start+i]; }}

/*获取想要读取的保持寄存器的值*/

void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue){

uint16_t start; uint16_t count;

/*先判断地址是否处于合法范围*/

start=(startAddress>HoldingRegisterStartAddress)?((startAddress<=HoldingRegisterEndAddress)?startAddress:HoldingRegisterEndAddress):HoldingRegisterStartAddress; count=((start+quantity-1)<=HoldingRegisterEndAddress)?quantity:(HoldingRegisterEndAddress-start);

for(int i=0;iregisterValue[i]=aPara.holdingRegister[start+i]; }}

/*设置单个线圈的值*/

void SetSingleCoil(uint16_t coilAddress,bool coilValue){

/*先判断地址是否处于合法范围*/ if(coilAddress<=12)

{

dPara.coil[coilAddress]=coilValue; }}

/*设置多个线圈的值*/

void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue){

uint16_t endAddress=startAddress+quantity-1; if((startAddress<=12)&&(endAddress<=12)) {

for(int i=0;idPara.coil[i+startAddress]=statusValue[i]; } }}

/*设置单个寄存器的值*/

void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue){

bool noError=(bool)(((50<=registerAddress)&&(registerAddress<=59)) ||((73<=registerAddress)&&(registerAddress<=74)) ||((90<=registerAddress)&&(registerAddress<=91)));

if(noError) {

aPara.holdingRegister[registerAddress]=registerValue; } }

/*设置多个寄存器的值*/

void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue){

uint16_t endAddress=startAddress+quantity-1;

bool noError=(bool)(((18<=startAddress)&&(startAddress<=28)&&(18<=endAddress)&&(endAddress<=28)) ||((50<=startAddress)&&(startAddress<=59)&&(50<=endAddress)&&(endAddress<=59)) ||((73<=startAddress)&&(startAddress<=74)&&(73<=endAddress)&&(endAddress<=74)) ||((90<=startAddress)&&(startAddress<=91)&&(90<=endAddress)&&(endAddress<=91))); if(noError) {

for(int i=0;iaPara.holdingRegister[startAddress+i]=registerValue[i]; } } }

3.2、数据解析函数

  ⼤家可能在前⾯的初始化函数中发现有⼀个名为W5500DataParsing的数据解析函数。这个函数是W5500驱动中,TCP服务器的要求,实现对数据的解析。因为具体的应⽤层协议解析多不胜数,所以设计成了回调函数,其函数原型如下:

/*解析接收到的数据*/

typedef uint16_t (*W5500DataParsingType)(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer);

  对于我们来说,我们需要根据具体的应⽤层协议来实现这⼀函数。不过我们采⽤的Modbus TCP协议,在我们的Modbus协议栈中已经实现了解析函数,所以我们调⽤如下:

/*报⽂解析函数*/

static uint16_t W5500DataParsing(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer){

/*解析接收到的信息,返回响应命令的长度*/

return ParsingClientAccessCommand(rxBuffer,txBuffer);}

3.3、TCP服务器

  我们在前⾯已经说过了,需要对服务器进⾏轮询。所以我们需要在⼀个进程中轮询访问W5500的TCP服务器。同样我们也要考虑多客户端同时访问的问题,我们将轮询函数实现如下:

/* 以太⽹通讯处理 */

void McEthernetProcess(void){

/*TCP服务器数据通讯*/

W5500TCPServer(&w5500,Socket0,502);

W5500TCPServer(&w5500,Socket1,503);

W5500TCPServer(&w5500,Socket2,504);

W5500TCPServer(&w5500,Socket3,505);

W5500TCPServer(&w5500,Socket4,506);

W5500TCPServer(&w5500,Socket5,507);

W5500TCPServer(&w5500,Socket6,508);

W5500TCPServer(&w5500,Socket7,509);}

  事实上使⽤同⼀个Socket和不同的端⼝也是可以实现多客户端访问的,但既然有8个Socket,⽤起来⾃然更好⼀点。

4、应⽤验证

  我们已经根据需求实现了⼀个Modbus TCP服务器,究竟效果如何呢?我们还需要测试⼀下,以确认设计的正确性。

4.1、通讯测试

  我们将⽬标板连接到局域⽹中,使⽤著名的Modbus Poll软件来测试⼀下我们设计的程序是否符合要求。

  我们⾸先在⼀台机器上连接端⼝为504的Modbus TCP服务器,连接正常且数据获取也完全正确。具体如下图所⽰:

  同时,我们采⽤局域⽹内的另⼀台机器连接端⼝为502的Modbus TCP服务器,连接正常且数据获取也完全正确。具体如下图所⽰:

  经过上述测试,我们可以确定我们实现的Modbus TCP服务器是可⾏的,⽽且在多客户端并⾏访问下也可以正确⼯作。

4.2、⼩结

  这⼀篇中,我们实现了可以⽀持多客户端访问的Modbus TCP服务器,经测试运⾏也符合设计预期。这⾥我们将需要考虑的⼏个问题总结如下:

  关于初始化配置的问题,在这个例⼦中,我们对⽹络的配置是直接在软件上固定死的,这样做虽然简单直接但并不是⼀个好的选择。更好的办法是可以让使⽤者⾃⼰配置,⽅法有多种,可以根据⾃⼰的实际情况,在软件上进⼀步的考虑。

  关于数据处理的问题,具体的数据处理与实际的应⽤需求有关,也与应⽤层协议的要求有关,这个例⼦中实现的Modbus的数据处理函数并不是唯⼀的,但可参考其思路。

  关于数据解析的问题,在本例中实现的是Modbus TCP服务器的解析函数。对于不同的应⽤协议需要编写不同的解析函数,这部分是灵活性最⼤的,⽀持所有可运⾏于TCP应⽤层的通讯协议。

  关于多客户端访问的问题,W5500可以实现8个Socket,⽽Modbus TCP默认端⼝号是502,当然也可以使⽤其它端⼝。所以我们可以考虑使⽤不同的Socket和不同的端⼝号来实现并发访问。事实上,经过我们测试使⽤同⼀个Socket和不同的端⼝也是可以实现多客户端访问的,有兴趣的同仁可以试试。

欢迎关注:

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 91gzw.com 版权所有 湘ICP备2023023988号-2

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务