基于STM32和W5500的Modbus TCP通讯



在最近的一个项目中需要实现Modbus TCP通讯,而选用的硬件平台则是STM32F103和W5500,软件平台则选用IAR EWAR6.4来实现。

1、移植前的准备工作

为了实现Modbus TCP通讯首先需要下载W5500的驱动源码,可以到WIZnet的官网下载:products:w5500:driver [Document Wiki]

下载下来的压缩包,解压后如下图:

需要将ethernet文件夹拷贝到我们的项目目录中:

并在IAR的项目下添加相关的文件和路径,主要是socket.c、w5500.c、wizchip_.conf.c三个文件。这三个文件分别实现socket、硬件驱动及相关通讯配置功能,具体可以查看相应的源码级手册。

并在如下图所示的项目选项设置中添加Ethernet和Ethernet\W5500目录。

2、移植过程和代码编写

在完成以上工作后就可以开始真正地移植工作了。具体步骤如下:

  • 硬件配置及初始化。

  • 以太网通讯配置的初始化。

  • 实现具体的通讯过程。

2.1、硬件的配置及初始化

由于W5500通过SPI接口与STM32通讯,所以硬件配置和初始化是非常简单的,与W5500实际上没有关系,使一些通用的操作。事实上就是STM32F103的SPI接口初始化的过程,需要实现RCC、GPIO以及SPI的初始化就可以了。关于这部分可以查看ST的例程。

2.2、以太网通讯配置的初始化

以太网通讯配置的初始化主要有三个方面的内容:

  • 注册TCP通讯相关的回调函数 RegisterFunction();

  • 初始化芯片参数 ChipParametersConfiguration();

  • 初始化网络通讯参数 NetworkParameterConfiguration()

三个函数的具体实现内容如下:

//函数注册,首先,应由用户实现SPI注册回调函数来访问WIZCHIP
void RegisterFunction(void)
{ 
 //临界区回调函数
 reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //注册临界区函数

 //片选回调函数
#if  _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_VDM_

 reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);//注册SPI片选信号函数

#elif _WIZCHIP_IO_MODE_ ==_WIZCHIP_IO_MODE_SPI_FDM_

 reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); // CS必须为低电平.

#else

  #if(_WIZCHIP_IO_MODE_ & _WIZCHIP_IO_MODE_SIP_) != _WIZCHIP_IO_MODE_SIP_

     #error "Unknown _WIZCHIP_IO_MODE_"

  #else

     reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect);

  #endif

#endif

 //SPI的读写回调函数

 reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte);    //注册读写函数

}

注册函数实际上就是函数指针的调用,可参考C语言函数指针部分内容。对于以上注册的函数,SPI_WriteByte需要说明一下,无论是用可函数还是直接操作寄存器,在写完之后都需要再读一下(红色部分),否则就会在客户端出现连接TCPServer超时的报警,没明白什么原因。

//写1字节数据到SPI总线
void SPI_WriteByte(uint8_t TxData)
{                      
// while((SPI2->SR&SPI_I2S_FLAG_TXE)==0);       //等待发送区空             
// SPI2->DR=TxData;                             //发送一个byte
// while((SPI2->SR&SPI_I2S_FLAG_RXNE)==0);      //等待接收完一个byte 
// SPI2->DR;
 while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);       //等待发送区空
 SPI_I2S_SendData(SPI2,TxData);                                       //发送一个byte
 while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET);       //等待接收完一个byte
 SPI_I2S_ReceiveData(SPI2);                                           //返回接收的数据
}

//初始化芯片参数
void ChipParametersConfiguration(void)
{
 uint8_ttmp;
 uint8_tmemsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};
 //WIZCHIPSOCKET缓存区初始化
 if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1){
   //printf("WIZCHIP Initialized fail.\r\n");
 while(1);
 }

 //PHY物理层连接状态检查
 do{
   if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1){
     //printf("Unknown PHY Link stauts.\r\n");
   }
 }while(tmp == PHY_LINK_OFF);
}

以上实现网络物理层的配置。

//初始化WIZCHIP中的网络参数信息
void NetworkParameterConfiguration(void)
{
 uint8_ttmpstr[6];
 ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO);
 ctlnetwork(CN_GET_NETINFO, (void*)&gWIZNETINFO);

 ctlwizchip(CW_GET_ID,(void*)tmpstr);
}

其中gWIZNETINFO是一个wiz_NetInfo类型的结构体变量,该结构体在wizchip_conf.h中定义,用于设置mac地址、IP地址等网络参数,具体如下:

typedef struct wiz_NetInfo_t
{
  uint8_tmac[6]; ///< Source Mac Address
  uint8_tip[4];  ///< Source IP Address
  uint8_tsn[4];  ///< Subnet Mask
  uint8_tgw[4];  ///< Gateway IP Address
  uint8_tdns[4]; ///< DNS server IP Address
  dhcp_mode dhcp; ///< 1 -Static, 2 - DHCP
}wiz_NetInfo;

至此网络部分的初始化就已完成。

2.3、具体通讯过程的实现

经过前面的配置网络已经可以ping通了,下面可以实现具体的应用。对于我这个项目就是可是实现Modbus TCP的编写了。

编写TCP Server,这部分有很多资料,直接附代码:

//TCP服务器数据通讯
int32_t TCPServer(uint8_t sn, uint16_t port)
{
 int32_tret;
 uint8_tsocketStatus=getSn_SR(sn);

 switch(socketStatus)
 {
   caseSOCK_ESTABLISHED :
     {
       if(getSn_IR(sn) & Sn_IR_CON)
       {
         setSn_IR(sn,Sn_IR_CON);
       }
       uint16_t size=0;
       if((size = getSn_RX_RSR(sn)) > 0)
       {
         if(size > DATA_BUFFER_SIZE)
         {
           size = DATA_BUFFER_SIZE;
         }
         uint8_t rxBuffer[DATA_BUFFER_SIZE];
         ret = recv(sn,rxBuffer,size);
         if(ret <= 0)
         {
           return ret;
         }

         //添加数据解析及响应的函数
         uint8_t txBuffer[DATA_BUFFER_SIZE];
         uint16_t length=ReceivedDataParsing(rxBuffer,txBuffer);
         
         uint16_t sentsize=0;
         while(length != sentsize)
         {
           ret = send(sn,txBuffer+sentsize,length-sentsize);
           if(ret < 0)
           {
             close(sn);
             return ret;
           }
           sentsize += ret; //不用管SOCKERR_BUSY,因为它是零.
         }
       }
       break;
     }
   caseSOCK_CLOSE_WAIT :
     if((ret=disconnect(sn)) != SOCK_OK)
     {
       return ret;
     }
     break;
   caseSOCK_INIT :
     if((ret = listen(sn)) != SOCK_OK)
     {
       return ret;
     }
     break;
   caseSOCK_CLOSED:
     if((ret=socket(sn,Sn_MR_TCP,port,0x00)) != sn)
     {
       return ret;
     }
     break;
   default:
     break;
 }
 return 1;//基于STM32和W5500的Modbus TCP通讯
}

其中ReceivedDataParsing(rxBuffer,txBuffer)实现具体的Modbus协议,根据具体的需求而定。

通过Modscan连接测试,结果正确。

欢迎关注:

 实例下载:ModbusExample.zip-其它文档类资源-CSDN下载

实例说明:Modbus协议栈综合实例设计_木南创智-CSDN博客

同时发到Github,其地址为:https://github.com/foxclever/Modbus
同时也发布到码云,其地址为:https://gitee.com/ErichMoonan/Modbus
在开源的协议栈中example文件夹下即是本次发布的例子。

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值