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