因實際使用需求我們測試一下網絡通訊,在NUCLEO-F412ZG測試板上沒有以太網部分,我們選擇外接一個W5500的實驗板。W5500支持SPI接口通訊,DC3.3V供源。而NUCLEO-F412ZG測試板已經將SPI1的各引腳SPI1_NSS(CN7_17)、SPI1_SCK(CN7_10)、SPI1_MISO(CN7_12)、SPI1_MOSI(CN7_14)引出到CN7,3.3VDC和GND也引導了CN8,可以方便的實現測試。如下圖紅框所示:
1、測試的准備工作
為了實現以太網通訊首先需要下載W5500的驅動源碼,可以到WIZnet的官網下載:http://www.iwiznet.cn/
目前最新版本為V1.1,我選用的是V1.03下載下來的壓縮包解壓后如下圖:
需要將ethernet文件夾拷貝到我們的項目目錄中:
並在IAR的項目下添加相關的文件和路徑,主要是socket.c、w5500.c、wizchip_.conf.c三個文件。這三個文件分別實現socket、硬件驅動及相關通訊配置功能,具體可以查看相應的源碼級手冊。
2、編寫測試代碼
在完成以上工作后就可以開始真正地移植工作了。具體步驟如下:
- 硬件配置及初始化。
- 以太網通訊配置的初始化。
- 實現具體的通訊過程。
2.1、硬件的配置及初始化
由於W5500通過SPI接口與STM32通訊,所以硬件配置和初始化是非常簡單的,與W5500實際上沒有關系,使一些通用的操作。事實上就是STM32F412ZG的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中的網絡參數信息
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 TCP協議。
3、測試結果
完成編寫,調試無誤后,將程序下載到目標板,首先既然是以太網通訊,我們就在上位機上機簡單的網絡測試。在目標板上我們設定的IP地址為:192.168.1.100,在CMD中使用簡單的Ping命令測試如下:
由上圖可以看到,網路沒有問題。接下來我們用TCP&UDP測試工具進行進一步的測試,並使用Microsoft Network Monitor監視數據包。打開TCP&UDP測試工具,創建一個TCP客戶端,目標IP還是目標板IP,端口用502(我們在軟件中設定了)。
創建完成后,連接無誤,在發送欄中以16進制發送一條指令,可以看到下方的接收欄中收到數據。
我們再看看Microsoft Network Monitor中捕獲到的數據包核對一下數據是否正確。
由於我們實現了簡單的Modbus TCP協議,所以我們在使用ModScan32來測試一下通訊是否正確。首先啟動ModScan32,並做如下配置:
連接后數據顯示出來,持續更新一段時間后沒有問題。
同樣,我們再看看Microsoft Network Monitor中捕獲到的數據更新與ModScan32做一對比,數據包完整而且正確。
完成測試,通訊沒有問題STM32F412與W5500實現以太網通訊簡單方便。