基於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和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_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.\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中的網絡參數信息:

//初始化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連接測試,結果正確。

歡迎關注:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM