在最近的一個項目中需要實現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連接測試,結果正確。

歡迎關注:

