回答

收藏

[转]基于STM32和W5500的Modbus TCP通讯

STMCU STMCU 4988 人阅读 | 0 人回复 | 2017-08-02

在最近的一个项目中需要实现Modbus TCP通讯,而选用的硬件平台则是STM32F103和W5500,软件平台则选用IAR EWAR6.4来实现。
1、移植千的准备工作
为了实现Modbus TCP通讯首先需要下载W5500的驱动源码,可以到WIZnet的官网下载:
http://wizwiki.net/wiki/doku.php?id=products:w5500:driver
下载下来的压缩包,解压后如下图:

需要将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()
三个函数的具体实现内容如下:
  1. //函数注册,首先,应由用户实现SPI注册回调函数来访问WIZCHIP
  2. void RegisterFunction(void)
  3. {  
  4.   //临界区回调函数
  5.   reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit);    //注册临界区函数
  6.   //片选回调函数
  7. #if   _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_VDM_
  8.   reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);//注册SPI片选信号函数
  9. #elif _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_FDM_
  10.   reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);  // CS必须为低电平.
  11. #else
  12.    #if (_WIZCHIP_IO_MODE_ & _WIZCHIP_IO_MODE_SIP_) != _WIZCHIP_IO_MODE_SIP_
  13.       #error "Unknown _WIZCHIP_IO_MODE_"
  14.    #else
  15.       reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect);
  16.    #endif
  17. #endif
  18.   //SPI的读写回调函数
  19.   reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte);    //注册读写函数
  20. }
复制代码
注册函数实际上就是函数指针的调用,可参考C语言函数指针部分内容。对于以上注册的函数,SPI_WriteByte需要说明一下,无论是用可函数还是直接操作寄存器,在写完之后都需要再读一下(红色部分),否则就会在客户端出现连接TCPServer超时的报警,没明白什么原因。
  1. //写1字节数据到SPI总线

  2. void SPI_WriteByte(uint8_t TxData)

  3. {                       

  4. //  while((SPI2->SR&SPI_I2S_FLAG_TXE)==0);        //等待发送区空              

  5. //  SPI2->DR=TxData;                              //发送一个byte

  6. //  while((SPI2->SR&SPI_I2S_FLAG_RXNE)==0);       //等待接收完一个byte

  7. //  SPI2->DR;

  8.   while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);        //等待发送区空

  9.   SPI_I2S_SendData(SPI2,TxData);                                        //发送一个byte

  10.   while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET);        //等待接收完一个byte

  11.   SPI_I2S_ReceiveData(SPI2);                                            //返回接收的数据

  12. }
复制代码
初始化芯片参数:
  1. //初始化芯片参数

  2. void ChipParametersConfiguration(void)

  3. {

  4.   uint8_t tmp;

  5.   uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};

  6.   //WIZCHIP SOCKET缓存区初始化

  7.   if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1){

  8.     //printf("WIZCHIP Initialized fail.\r\n");

  9.   while(1);

  10.   }



  11.   //PHY物理层连接状态检查

  12.   do{

  13.     if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1){

  14.       //printf("Unknown PHY Link stauts.\r\n");

  15.     }

  16.   }while(tmp == PHY_LINK_OFF);

  17. }
复制代码
以上实现网络物理层的配置。
初始化WIZCHIP中的网络参数信息:
  1. //初始化WIZCHIP中的网络参数信息

  2. void NetworkParameterConfiguration(void)

  3. {

  4.   uint8_t tmpstr[6];

  5.   ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO);

  6.   ctlnetwork(CN_GET_NETINFO, (void*)&gWIZNETINFO);


  7.   ctlwizchip(CW_GET_ID,(void*)tmpstr);

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

  2. {

  3.    uint8_t mac[6];  ///< Source Mac Address

  4.    uint8_t ip[4];   ///< Source IP Address

  5.    uint8_t sn[4];   ///< Subnet Mask

  6.    uint8_t gw[4];   ///< Gateway IP Address

  7.    uint8_t dns[4];  ///< DNS server IP Address

  8.    dhcp_mode dhcp;  ///< 1 - Static, 2 - DHCP

  9. }wiz_NetInfo;
复制代码
至此网络部分的初始化就已完成。
2.3、具体通讯过程的实现
经过前面的配置网络已经可以ping通了,下面可以实现具体的应用。对于我这个项目就是可是实现Modbus TCP的编写了。
编写TCP Server,这部分有很多资料,直接附代码:
  1. //TCP服务器数据通讯

  2. int32_t TCPServer(uint8_t sn, uint16_t port)

  3. {

  4.   int32_t ret;

  5.   uint8_t socketStatus=getSn_SR(sn);



  6.   switch(socketStatus)

  7.   {

  8.     case SOCK_ESTABLISHED :

  9.       {

  10.         if(getSn_IR(sn) & Sn_IR_CON)

  11.         {

  12.           setSn_IR(sn,Sn_IR_CON);

  13.         }

  14.         uint16_t size=0;

  15.         if((size = getSn_RX_RSR(sn)) > 0)

  16.         {

  17.           if(size > DATA_BUFFER_SIZE)

  18.           {

  19.             size = DATA_BUFFER_SIZE;

  20.           }

  21.           uint8_t rxBuffer[DATA_BUFFER_SIZE];

  22.           ret = recv(sn,rxBuffer,size);

  23.           if(ret <= 0)

  24.           {

  25.             return ret;

  26.           }

  27.           //添加数据解析及响应的函数

  28.           uint8_t txBuffer[DATA_BUFFER_SIZE];

  29.           uint16_t length=ReceivedDataParsing(rxBuffer,txBuffer);

  30.          

  31.           uint16_t sentsize=0;

  32.           while(length != sentsize)

  33.           {

  34.             ret = send(sn,txBuffer+sentsize,length-sentsize);

  35.             if(ret < 0)

  36.             {

  37.               close(sn);

  38.               return ret;

  39.             }

  40.             sentsize += ret; // 不用管SOCKERR_BUSY, 因为它是零.

  41.           }

  42.         }

  43.         break;

  44.       }

  45.     case SOCK_CLOSE_WAIT :

  46.       if((ret=disconnect(sn)) != SOCK_OK)

  47.       {

  48.         return ret;

  49.       }

  50.       break;

  51.     case SOCK_INIT :

  52.       if( (ret = listen(sn)) != SOCK_OK)

  53.       {

  54.         return ret;

  55.       }

  56.       break;

  57.     case SOCK_CLOSED:

  58.       if((ret=socket(sn,Sn_MR_TCP,port,0x00)) != sn)

  59.       {

  60.         return ret;

  61.       }

  62.       break;

  63.     default:

  64.       break;

  65.   }

  66.   return 1;

  67. }
复制代码
其中ReceivedDataParsing(rxBuffer,txBuffer)实现具体的Modbus协议,根据具体的需求而定。
通过Modscan连接测试,结果正确。



关注下面的标签,发现更多相似文章
分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

10637 积分
601 主题
+ 关注
关闭

站长推荐上一条 /3 下一条