• 基于STM32和W5500的Modbus TCP通讯


    在最近的一个项目中需要实现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和EthernetW5500目录。

    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_t tmp;
    
      uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};
    
      //WIZCHIP SOCKET缓存区初始化
    
      if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1){
    
        //printf("WIZCHIP Initialized fail.
    ");
    
      while(1);
    
      }
    
     
    
      //PHY物理层连接状态检查
    
      do{
    
        if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1){
    
          //printf("Unknown PHY Link stauts.
    ");
    
        }
    
      }while(tmp == PHY_LINK_OFF);
    
    }

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

    初始化WIZCHIP中的网络参数信息:

    //初始化WIZCHIP中的网络参数信息
    
    void NetworkParameterConfiguration(void)
    
    {
    
      uint8_t tmpstr[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_t mac[6];  ///< Source Mac Address
    
       uint8_t ip[4];   ///< Source IP Address
    
       uint8_t sn[4];   ///< Subnet Mask
    
       uint8_t gw[4];   ///< Gateway IP Address
    
       uint8_t dns[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_t ret;
    
      uint8_t socketStatus=getSn_SR(sn);
    
     
    
      switch(socketStatus)
    
      {
    
        case SOCK_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;
    
          }
    
        case SOCK_CLOSE_WAIT :
    
          if((ret=disconnect(sn)) != SOCK_OK)
    
          {
    
            return ret;
    
          }
    
          break;
    
        case SOCK_INIT :
    
          if( (ret = listen(sn)) != SOCK_OK)
    
          {
    
            return ret;
    
          }
    
          break;
    
        case SOCK_CLOSED:
    
          if((ret=socket(sn,Sn_MR_TCP,port,0x00)) != sn)
    
          {
    
            return ret;
    
          }
    
          break;
    
        default:
    
          break;
    
      }
    
      return 1;
    
    }

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

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

    欢迎关注:

  • 相关阅读:
    剑指OFFER之包含min函数的栈
    剑指OFFER之二叉树的镜像
    关于【最长递增子序列(LIS)】
    题目1113:二叉树
    剑指OFFER之字符串的排列
    题目1120:全排列
    题目1460:Oil Deposit
    题目1459:Prime ring problem
    剑指OFFER之二叉树中和为某一值的路径
    python 线程、进程
  • 原文地址:https://www.cnblogs.com/foxclever/p/5717844.html
Copyright © 2020-2023  润新知