?前面我們設計實現了W5500的驅動程序,也講解了驅動的使用方式。在最近一次的項目應用中,正好有一個使用W5500實現TCP通訊的需求,所以我們就使用該驅動程序輕松實現。這一篇中我們就來說一說基于我們W5500通訊驅動程序實現TCP通訊的過程。
1、應用需求
??在本次應用中,要求實現一個基于W5500的Modbus TCP服務器。這個需求的描述雖然只有一句話,但是這個需求的內容可不簡單。我們首先來分析一下這個需求的具體內容。
??為了實現基于W5500的Modbus TCP服務器,我們必先須基于W5500實現一個TCP服務器。W5500本身是帶硬件協議棧的,但卻并不帶TCP服務器。不過在我們前面的關于外設驅動庫的系列文章中已經封裝了W5500的驅動,其中就帶有一個TCP服務器,我們可以直接采用就可以了。
??其次我們要在TCP服務器的基礎上實現Modbus TCP協議。關于Modbus協議棧,我們以前的文章就講述過Modbus通訊協議棧的開發問題。而且我們已經將我們開發的Modbus通訊協議棧開源。其中已經封裝了Modbus TCP服務器對象,所以我們直接采用這一Modbus通訊協議棧就可以了。
??有了驅動和協議棧,我們還需要考慮應用層面的具體問題,而且也只需要考慮應用層面的具體問題。這里就看出我們前面封裝外設驅動和Modbus通訊協議棧的價值所在了。關于應用層面的問題我們主要需要重點考慮幾個問題:
??第一,數據的存儲類型及地址范圍。我們知道Modbus協議常見的數據類型有4種。我們需要考慮在系統中需要使用到的類型及地址,這將決定Modbus協議數據處理回調函數的實現。
??第二,網絡配置問題,我們需要通過網絡訪問這臺下位機就需要要為其配置網絡。這存在靜態配置,動態配置和系統自動分配的問題。作為服務器,我們一般不會希望讓系統自動分配。所以我們需要考慮的是如何方便使用者為其分配地址的問題。
??第三,并發訪問的問題。掛載在網絡上的服務器肯定面臨多個客戶端來訪問的問題。W5500可以實現8個Socket,而Modbus TCP通用的默認端口號是502,當然也可以使用其它端口,只要不沖突就好。所以我們可以考慮使用不同的Socket和不同的端口號來實現并發訪問。
2、功能設計
??我們分析了基于W5500實現Modbus TCP服務器的需求。我們現在從硬件和軟件兩個方面來分析器功能的實現。
2.1、硬件功能設計
??我們知道W5500帶有硬件協議棧,集成有以太網控制器和物理層,所以對外我們只需要實現以太網變壓器和硬件接口就好了。但與控制器部分的連接則采用SPI接口,除此之外還需要提供中斷輸入和模式設定的相關接口。在這里我們設計器硬件連接如下:
??在上圖中,我們將中斷輸入引入到MCU的GPIO端口,而模式設定PMODE0、PMODE1、PMODE2均通過電阻上拉到電源。對于W5500來說PMODE0、PMODE1、PMODE2均為高電平表示開啟全部功能,所以我們直接拉高而不是引入到MCU引腳來控制。
2.2、軟件功能設計
??從需求來說,軟件的功能非常簡單,就是實現一個Modbus TCP服務器。但實際上,如我們前面所描述的那樣,軟件需要考慮的問題還是比較多的。從功能實現上主要有3個方面需要考慮:
??第一,實現TCP服務器,這個服務器用于在系統中輪詢處理,從W5500獲取數據和發送數據給W5500都需要通過這部分來實現。
??第二,TCP服務器得到數據后,我們需要解析數據,并根據解析的上位數據決定進一步的動作,還需要生成返回信息。這部分對應功能就是Modbus TCP服務器的實現。
??第三,根據Modbus TCP服務器解析出的Modbus消息,需要決定下一步的動作,這個具體動作根據功能碼的不同可能有不同需求,所以我們需要根據具體的要求實現不同功能碼的動作。
??根據上述的設計,我們可以簡單的將需要實現的軟件功能圖示如下:
??上圖中,因為W5500的TCP服務器以及Modbus TCP協議棧的相關函數我們都做了封裝,所以它們之間的調用都將以回調函數的方式實現。除了上述的軟件實現外,還需要注意必要的初始化配置。
3、應用實現
??根據我們前面的設計,接下來我們考慮一下這一需求的具體實現過程。我們將這一過程分為4個部分來分別描述。
3.1、系統的初始化
??在實現具體的功能之前,我們需要對硬件以及軟件環境做必要的初始化配置。具體到這里就是對W5500作必要的軟硬件配置,包括接口、網絡以及回調函數等。具體實例代碼如下:
/* 以太網通訊配置 */
void McEthernetConfiguration(void)
{
uint8_t mac[6]={0x01, 0x08, 0xdc,0x00, 0xab, 0xcd}; //本地Mac地址
uint8_t ip[4]={192, 168, 1, 190}; //本地IP地址
uint8_t sn[4]={255,255,255,0}; //子網掩碼
uint8_t gw[4]={192, 168, 1, 1}; //網關地址
uint8_t dns[4]={0,0,0,0}; //DNS服務器地址
/* 以太網使用GPIO初始化 */
GPIO_Init_Configuration();
/* SPI1端口初始化 */
SPI1_Init_Configuration();
/*W5500對象初始化函數*/
W5500Initialization(&w5500, //W5500對象
mac, //本地Mac地址
ip, //本地IP地址
sn, //子網掩碼
gw, //網關地址
dns, //DNS服務器地址
NETINFO_STATIC, //DHCP類型
EnterCritical, //進入臨界區
ExitCritical, //退出臨界區
EnableChipSelect, //片選使能
DisableChipSelect, //片選失能
ReadByteBySPI, //SPI讀字節
WriteByteBySPI, //SPI寫字節
W5500DataParsing, //報文解析函數
NULL //數據請求函數
);
}
??在這個實例中,我們對網絡部分采用的是靜態配置,就是說網絡參數是固定不變的,而且我們的測試環境只限于局域網內。
3.2、數據處理函數
??數據處理函數是最靈活的,因為每個項目及每個人對數據處理的要求都是不一樣的,只要能符合應用要求就沒問題。需要說一下的是,這部分是Modbus協議棧對處理數據的要求,想要詳細了解的話,可以看我們以前關于Modbus協議站的文章。對于這個實例,數據處理函數如下:
/*獲取想要讀取的Coil量的值*/
void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList)
{
uint16_t start;
uint16_t count;
/*先判斷地址是否處于合法范圍*/
start=(startAddress>CoilStartAddress)?((startAddress<=CoilEndAddress)?startAddress:CoilEndAddress):CoilStartAddress;
count=((start+quantity-1)<=CoilEndAddress)?quantity:(CoilEndAddress-start);
for(int i=0;i/*獲取想要讀取的保持寄存器的值*/
void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)
{
uint16_t start;
uint16_t count;
/*先判斷地址是否處于合法范圍*/
start=(startAddress>HoldingRegisterStartAddress)?((startAddress<=HoldingRegisterEndAddress)?startAddress:HoldingRegisterEndAddress):HoldingRegisterStartAddress;
count=((start+quantity-1)<=HoldingRegisterEndAddress)?quantity:(HoldingRegisterEndAddress-start);
for(int i=0;i/*設置單個線圈的值*/
void SetSingleCoil(uint16_t coilAddress,bool coilValue)
{
/*先判斷地址是否處于合法范圍*/
if(coilAddress<=12)
{
dPara.coil[coilAddress]=coilValue;
}
}
/*設置多個線圈的值*/
void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue)
{
uint16_t endAddress=startAddress+quantity-1;
if((startAddress<=12)&&(endAddress<=12))
{
for(int i=0;i/*設置單個寄存器的值*/
void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue)
{
bool noError=(bool)(((50<=registerAddress)&&(registerAddress<=59))
||((73<=registerAddress)&&(registerAddress<=74))
||((90<=registerAddress)&&(registerAddress<=91)));
if(noError)
{
aPara.holdingRegister[registerAddress]=registerValue;
}
}
/*設置多個寄存器的值*/
void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)
{
uint16_t endAddress=startAddress+quantity-1;
bool noError=(bool)(((18<=startAddress)&&(startAddress<=28)&&(18<=endAddress)&&(endAddress<=28))
||((50<=startAddress)&&(startAddress<=59)&&(50<=endAddress)&&(endAddress<=59))
||((73<=startAddress)&&(startAddress<=74)&&(73<=endAddress)&&(endAddress<=74))
||((90<=startAddress)&&(startAddress<=91)&&(90<=endAddress)&&(endAddress<=91)));
if(noError)
{
for(int i=0;i
3.2、數據解析函數
??大家可能在前面的初始化函數中發現有一個名為W5500DataParsing的數據解析函數。這個函數是W5500驅動中,TCP服務器的要求,實現對數據的解析。因為具體的應用層協議解析多不勝數,所以設計成了回調函數,其函數原型如下:
/*解析接收到的數據*/
typedef uint16_t (*W5500DataParsingType)(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer);
??對于我們來說,我們需要根據具體的應用層協議來實現這一函數。不過我們采用的Modbus TCP協議,在我們的Modbus協議棧中已經實現了解析函數,所以我們調用如下:
/*報文解析函數*/
static uint16_t W5500DataParsing(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer)
{
/*解析接收到的信息,返回響應命令的長度*/
return ParsingClientAccessCommand(rxBuffer,txBuffer);
}
3.3、TCP服務器
??我們在前面已經說過了,需要對服務器進行輪詢。所以我們需要在一個進程中輪詢訪問W5500的TCP服務器。同樣我們也要考慮多客戶端同時訪問的問題,我們將輪詢函數實現如下:
/* 以太網通訊處理 */
void McEthernetProcess(void)
{
/*TCP服務器數據通訊*/
W5500TCPServer(&w5500,Socket0,502);
W5500TCPServer(&w5500,Socket1,503);
W5500TCPServer(&w5500,Socket2,504);
W5500TCPServer(&w5500,Socket3,505);
W5500TCPServer(&w5500,Socket4,506);
W5500TCPServer(&w5500,Socket5,507);
W5500TCPServer(&w5500,Socket6,508);
W5500TCPServer(&w5500,Socket7,509);
}
??事實上使用同一個Socket和不同的端口也是可以實現多客戶端訪問的,但既然有8個Socket,用起來自然更好一點。
4、應用驗證
??我們已經根據需求實現了一個Modbus TCP服務器,究竟效果如何呢?我們還需要測試一下,以確認設計的正確性。
4.1、通訊測試
??我們將目標板連接到局域網中,使用著名的Modbus Poll軟件來測試一下我們設計的程序是否符合要求。
??我們首先在一臺機器上連接端口為504的Modbus TCP服務器,連接正常且數據獲取也完全正確。具體如下圖所示:
??同時,我們采用局域網內的另一臺機器連接端口為502的Modbus TCP服務器,連接正常且數據獲取也完全正確。具體如下圖所示:
??經過上述測試,我們可以確定我們實現的Modbus TCP服務器是可行的,而且在多客戶端并行訪問下也可以正確工作。
4.2、小結
??這一篇中,我們實現了可以支持多客戶端訪問的Modbus TCP服務器,經測試運行也符合設計預期。這里我們將需要考慮的幾個問題總結如下:
??關于初始化配置的問題,在這個例子中,我們對網絡的配置是直接在軟件上固定死的,這樣做雖然簡單直接但并不是一個好的選擇。更好的辦法是可以讓使用者自己配置,方法有多種,可以根據自己的實際情況,在軟件上進一步的考慮。
??關于數據處理的問題,具體的數據處理與實際的應用需求有關,也與應用層協議的要求有關,這個例子中實現的Modbus的數據處理函數并不是唯一的,但可參考其思路。
??關于數據解析的問題,在本例中實現的是Modbus TCP服務器的解析函數。對于不同的應用協議需要編寫不同的解析函數,這部分是靈活性最大的,支持所有可運行于TCP應用層的通訊協議。
??關于多客戶端訪問的問題,W5500可以實現8個Socket,而Modbus TCP默認端口號是502,當然也可以使用其它端口。所以我們可以考慮使用不同的Socket和不同的端口號來實現并發訪問。事實上,經過我們測試使用同一個Socket和不同的端口也是可以實現多客戶端訪問的,有興趣的同仁可以試試。
-
MODBUS
+關注
關注
28文章
1799瀏覽量
76950 -
服務器
+關注
關注
12文章
9123瀏覽量
85324 -
TCP
+關注
關注
8文章
1353瀏覽量
79055 -
ModBus協議
+關注
關注
3文章
177瀏覽量
33416 -
W5500
+關注
關注
5文章
45瀏覽量
17582
發布評論請先 登錄
相關推薦
評論