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