手把手教你學(xué)單片機(jī)之十八 RS通信與Modbus協(xié)議_第1頁
手把手教你學(xué)單片機(jī)之十八 RS通信與Modbus協(xié)議_第2頁
手把手教你學(xué)單片機(jī)之十八 RS通信與Modbus協(xié)議_第3頁
手把手教你學(xué)單片機(jī)之十八 RS通信與Modbus協(xié)議_第4頁
手把手教你學(xué)單片機(jī)之十八 RS通信與Modbus協(xié)議_第5頁
已閱讀5頁,還剩30頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、手把手教你學(xué)51單片機(jī)之十八 RS485通信與Modbus協(xié)議 HYPERLINK / / HYPERLINK / 郵件群發(fā) 在工業(yè)控制、電力通訊、智能儀表等領(lǐng)域,通常情況下是采用串口通信的方式進(jìn)行數(shù)據(jù)交換。最初采用的方式是RS232接口,由于工業(yè)現(xiàn)場比較復(fù)雜,各種電氣設(shè)備會在環(huán)境中產(chǎn)生比較多的電磁干擾,會導(dǎo)致信號傳輸錯誤。除此之外,RS232接口只能實(shí)現(xiàn)點(diǎn)對點(diǎn)通信,不具備聯(lián)網(wǎng)功能,最大傳輸距離也只能達(dá)到十幾米,不能滿足遠(yuǎn)距離通信要求。而RS485則解決了這些問題,數(shù)據(jù)信號采用差分傳輸方式,可以有效的解決共模干擾問題,最大距離可達(dá)1200米,并且允許多個收發(fā)設(shè)備接到同一條總線上。隨著工業(yè)應(yīng)用通

2、信越來越多,1979年施耐德電氣制定了一個用于工業(yè)現(xiàn)場的總線協(xié)議Modbus協(xié)議,現(xiàn)在工業(yè)中使用RS485通信場合很多都采用Modbus協(xié)議,本節(jié)課我們就來講解一下RS485通信和Modbus協(xié)議。 單單使用一塊KST-51開發(fā)板是不能夠進(jìn)行RS485實(shí)驗(yàn)的,應(yīng)很多同學(xué)的要求,把這節(jié)課作為擴(kuò)展課程講一下,如果要做本課相關(guān)實(shí)驗(yàn),需要自行購買USB轉(zhuǎn)RS485通信模塊,或連接其它的RS485主控設(shè)備進(jìn)行。1.1RS485通信RS232標(biāo)準(zhǔn)是誕生于RS485之前的,但是RS232有幾處不足的地方:1、接口的信號電平值較高,達(dá)到十幾V,使用不當(dāng)容易損壞接口芯片,電平標(biāo)準(zhǔn)也與TTL電平不兼容。2、傳輸速

3、率有局限,不可以過高,一般到一兩百千比特每秒(Kb/s)就到極限了。3、接口使用信號線和GND與其它設(shè)備形成共地模式的通信,這種共地模式傳輸容易產(chǎn)生干擾,并且抗干擾性能也比較弱。4、傳輸距離有限,最多只能通信幾十米。5、通信的時候只能兩點(diǎn)之間進(jìn)行通信,不能夠?qū)崿F(xiàn)多機(jī)聯(lián)網(wǎng)通信。 針對RS232接口的不足,就不斷出現(xiàn)了一些新的接口標(biāo)準(zhǔn),RS485就是其中之一,它具備以下的特點(diǎn):1、采用差分信號。我們在講A/D的時候,講過差分信號輸入的概念,同時也介紹了差分輸入的好處,最大的優(yōu)勢是可以抑制共模干擾。尤其當(dāng)工業(yè)現(xiàn)場環(huán)境比較復(fù)雜,干擾比較多時,采用差分方式可以有效的提高通信可靠性。RS485采用兩根通信

4、線,通常用A和B或者D+和D-來表示。邏輯“1”以兩線之間的電壓差為+(0.26)V表示,邏輯“0”以兩線間的電壓差為-(0.26)V來表示,是一種典型的差分通信。2、RS485通信速率快,最大傳輸速度可以達(dá)到10Mb/s以上。3、RS485內(nèi)部的物理結(jié)構(gòu),采用的是平衡驅(qū)動器和差分接收器的組合,抗干擾能力也大大增加。4、傳輸距離最遠(yuǎn)可以達(dá)到1200米左右,但是它的傳輸速率和傳輸距離是成反比的,只有在100Kb/s以下的傳輸速度,才能達(dá)到最大的通信距離,如果需要傳輸更遠(yuǎn)距離可以使用中繼。5、可以在總線上進(jìn)行聯(lián)網(wǎng)實(shí)現(xiàn)多機(jī)通信,總線上允許掛多個收發(fā)器,從現(xiàn)有的RS485芯片來看,有可以掛32、64、

5、128、256等不同個設(shè)備的驅(qū)動器。6、RS485的接口非常簡單,與RS232所使用的MAX232是類似的,只需要一個RS485轉(zhuǎn)換器,就可以直接與單片機(jī)的UART串口連接起來,并且使用完全相同的異步串行通信協(xié)議。但是由于RS485是差分通信,因此接收數(shù)據(jù)和發(fā)送數(shù)據(jù)是不能同時進(jìn)行的,也就是說它是一種半雙工通信。那我們?nèi)绾闻袛嗍裁磿r候發(fā)送,什么時候接收呢? RS485轉(zhuǎn)換芯片很多,這節(jié)課我們以典型的MAX485為例講解RS485通信,如圖18-1所示。圖18-1MAX485硬件接口 MAX485是美信(Maxim)推出的一款常用RS485轉(zhuǎn)換器。其中5腳和8腳是電源引腳;6腳和7腳就是RS485

6、通信中的A和B兩個引腳;1腳和4腳分別接到單片機(jī)的RXD和TXD引腳上,直接使用單片機(jī)UART進(jìn)行數(shù)據(jù)接收和發(fā)送;2腳和3腳是方向引腳,其中2腳是低電平使能接收器,3腳是高電平使能輸出驅(qū)動器,我們把這兩個引腳連到一起,平時不發(fā)送數(shù)據(jù)的時候,保持這兩個引腳是低電平,讓MAX485處于接收狀態(tài),當(dāng)需要發(fā)送數(shù)據(jù)的時候,把這個引腳拉高,發(fā)送數(shù)據(jù),發(fā)送完畢后再拉低這個引腳就可以了。為了提高RS485的抗干擾能力,需要在靠近MAX485的A和B引腳之間并接一個電阻,這個電阻阻值從100歐到1K都是可以。 在這里我們還要介紹一下如何使用KST-51單片機(jī)開發(fā)板進(jìn)行外圍擴(kuò)展實(shí)驗(yàn)。我們的開發(fā)板只能把基本的功能給

7、同學(xué)們做出來提供實(shí)驗(yàn)練習(xí),但是同學(xué)們學(xué)習(xí)的腳步不應(yīng)該停留在這個實(shí)驗(yàn)板上。如果想進(jìn)行更多的實(shí)驗(yàn),就可以通過單片機(jī)開發(fā)板的擴(kuò)展接口進(jìn)行擴(kuò)展實(shí)驗(yàn)。大家可以看到藍(lán)綠色的單片機(jī)座周圍有32個插針,這32個插針就是把單片機(jī)的32個IO引腳全部都引出來了。在原理圖上體現(xiàn)出來的就是J4、J5、J6、J7這4個器件,如圖18-2所示。圖18-2單片機(jī)擴(kuò)展接口 這32個IO口中并不是所有的都可以用來對外擴(kuò)展,其中既作為數(shù)據(jù)輸出,又可以作為數(shù)據(jù)輸入的引腳是不可以用的,比如P3.2、P3.4、P3.6引腳,這三個引腳是不可用的。比如P3.2這個引腳,如果我們用來擴(kuò)展,發(fā)送的信號如果和DS18B20的時序吻合,會導(dǎo)致D

8、S18B20拉低引腳,影響通信。除這3個IO口以外的其它29個,都可以使用杜邦線接上插針,擴(kuò)展出來使用。當(dāng)然了,如果把當(dāng)前的IO口應(yīng)用于擴(kuò)展功能了,板子上的相應(yīng)功能就實(shí)現(xiàn)不了了,也就是說需要擴(kuò)展功能和板載功能之間二選一。 在進(jìn)行RS485實(shí)驗(yàn)中,我們通信用的引腳必須是P3.0和P3.1,此外還有一個方向控制引腳,我們使用杜邦線將其連接到P1.7上去。RS485的另外一端,大家可以使用一個USB轉(zhuǎn)RS485模塊,用雙絞線把開發(fā)板和模塊上的A和B分別對應(yīng)連起來,USB那頭插入電腦,然后就可以進(jìn)行通信了。 學(xué)習(xí)了第13章實(shí)用的串口通信方法和程序后,做這種串口通信的方法就很簡單了,基本是一致的。我們使

9、用實(shí)用串口通信例程的思路,做了一個簡單的程序,通過串口調(diào)試助手下發(fā)任意個字符,單片機(jī)接收到后在末尾添加“回車+換行”符后再送回,在調(diào)試助手上重新顯示出來,先把程序貼出來。 程序中需要注意的一點(diǎn)是:因?yàn)槠匠6际菍AX485設(shè)置為接收狀態(tài),只有在發(fā)送數(shù)據(jù)的時候才將MAX485改為發(fā)送狀態(tài),所以在UartWrite()函數(shù)開頭將MAX485方向引腳拉高,函數(shù)退出前再拉低。但是這里有一個細(xì)節(jié),就是單片機(jī)的發(fā)送和接收中斷產(chǎn)生的時刻都是在停止位的一半上,也就是說每當(dāng)停止位傳送了一半的時候,RI或TI就已經(jīng)置位并且馬上進(jìn)入中斷(如果中斷使能的話)函數(shù)了,接收的時候自然不會存在問題,但發(fā)送的時候就不一樣了:

10、當(dāng)緊接著向SBUF寫入一個字節(jié)數(shù)據(jù)時,UART硬件會在完成上一個停止位的發(fā)送后,再開始新字節(jié)的發(fā)送,但如果此時不是繼續(xù)發(fā)送下一個字節(jié),而是已經(jīng)發(fā)送完畢了,要停止發(fā)送并將MAX485方向引腳拉低以使MAX485重新處于接收狀態(tài)時就有問題了,因?yàn)檫@時候最后的這個停止位實(shí)際只發(fā)送了一半,還沒有完全完成,所以就有了UartWrite()函數(shù)內(nèi)DelayX10us(5)這個操作,這是人為的增加了50us的延時,這50us的時間正好讓剩下的一半停止位完成,那么這個時間自然就是由通信波特率決定的了,為波特率周期的一半。/RS485.c文件程序源代碼*/#include#includesbitRS485_DI

11、R=P17;/RS485方向選擇引腳bitflagFrame=0;/幀接收完成標(biāo)志,即接收到一幀新數(shù)據(jù)bitflagTxd=0;/單字節(jié)發(fā)送完成標(biāo)志,用來替代TXD中斷標(biāo)志位unsignedcharcntRxd=0;/接收字節(jié)計數(shù)器unsignedcharpdatabufRxd64;/接收字節(jié)緩沖區(qū)externvoidUartAction(unsignedchar*buf,unsignedcharlen);/*串口配置函數(shù),baud-通信波特率*/voidConfigUART(unsignedintbaud)RS485_DIR=0;/RS485設(shè)置為接收方向SCON=0 x50;/配置串口為模

12、式1TMOD&=0 x0F;/清零T1的控制位TMOD|=0 x20;/配置T1為模式2TH1=256-(11059200/12/32)/baud;/計算T1重載值TL1=TH1;/初值等于重載值ET1=0;/禁止T1中斷ES=1;/使能串口中斷TR1=1;/啟動T1/*軟件延時函數(shù),延時時間(t*10)us*/voidDelayX10us(unsignedchart)do_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();while(-t);/*串口數(shù)據(jù)寫入,即串口發(fā)送函數(shù),buf-待發(fā)送數(shù)據(jù)的指針,len-指定的發(fā)

13、送長度*/voidUartWrite(unsignedchar*buf,unsignedcharlen)RS485_DIR=1;/RS485設(shè)置為發(fā)送while(len-)/循環(huán)發(fā)送所有字節(jié)flagTxd=0;/清零發(fā)送標(biāo)志SBUF=*buf+;/發(fā)送一個字節(jié)數(shù)據(jù)while(!flagTxd);/等待該字節(jié)發(fā)送完成DelayX10us(5);/等待最后的停止位完成,延時時間由波特率決定RS485_DIR=0;/RS485設(shè)置為接收/*串口數(shù)據(jù)讀取函數(shù),buf-接收指針,len-指定的讀取長度,返回值-實(shí)際讀到的長度*/unsignedcharUartRead(unsignedchar*buf,

14、unsignedcharlen)unsignedchari;if(lencntRxd)/指定讀取長度大于實(shí)際接收到的數(shù)據(jù)長度時,/讀取長度設(shè)置為實(shí)際接收到的數(shù)據(jù)長度len=cntRxd;for(i=0;i*buf+=bufRxdi;cntRxd=0;/接收計數(shù)器清零returnlen;/返回實(shí)際讀取長度/*串口接收監(jiān)控,由空閑時間判定幀結(jié)束,需在定時中斷中調(diào)用,ms-定時間隔*/voidUartRxMonitor(unsignedcharms)staticunsignedcharcntbkp=0;staticunsignedcharidletmr=0;if(cntRxd0)/接收計數(shù)器大于零時

15、,監(jiān)控總線空閑時間if(cntbkp!=cntRxd)/接收計數(shù)器改變,即剛接收到數(shù)據(jù)時,清零空閑計時cntbkp=cntRxd;idletmr=0;else/接收計數(shù)器未改變,即總線空閑時,累積空閑時間if(idletmridletmr+=ms;if(idletmr=30)/空閑時間達(dá)到30ms時,即判定為一幀接收完畢f(xié)lagFrame=1;/設(shè)置幀接收完成標(biāo)志elsecntbkp=0;/*串口驅(qū)動函數(shù),監(jiān)測數(shù)據(jù)幀的接收,調(diào)度功能函數(shù),需在主循環(huán)中調(diào)用*/voidUartDriver()unsignedcharlen;unsignedcharpdatabuf40;if(flagFrame)/

16、有命令到達(dá)時,讀取處理該命令flagFrame=0;len=UartRead(buf,sizeof(buf)-2);/將接收到的命令讀取到緩沖區(qū)中UartAction(buf,len);/傳遞數(shù)據(jù)幀,調(diào)用動作執(zhí)行函數(shù)/*串口中斷服務(wù)函數(shù)*/voidInterruptUART()interrupt4if(RI)/接收到新字節(jié)RI=0;/清零接收中斷標(biāo)志位if(cntRxd/保存接收字節(jié),并遞增計數(shù)器bufRxdcntRxd+=SBUF;if(TI)/字節(jié)發(fā)送完畢TI=0;/清零發(fā)送中斷標(biāo)志位flagTxd=1;/設(shè)置字節(jié)發(fā)送完成標(biāo)志/*main.c文件程序源代碼/#includeunsigned

17、charT0RH=0;/T0重載值的高字節(jié)unsignedcharT0RL=0;/T0重載值的低字節(jié)voidConfigTimer0(unsignedintms);externvoidUartDriver();externvoidConfigUART(unsignedintbaud);externvoidUartRxMonitor(unsignedcharms);externvoidUartWrite(unsignedchar*buf,unsignedcharlen);voidmain()EA=1;/開總中斷ConfigTimer0(1);/配置T0定時1msConfigUART(9600);

18、/配置波特率為9600while(1)UartDriver();/調(diào)用串口驅(qū)動/*串口動作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動作buf-接收到的命令幀指針,len-命令幀長度*/voidUartAction(unsignedchar*buf,unsignedcharlen)/在接收到的數(shù)據(jù)幀后添加換車換行符后發(fā)回buflen+=r;buflen+=n;UartWrite(buf,len);/*配置并啟動T0,ms-T0定時時間*/voidConfigTimer0(unsignedintms)unsignedlongtmp;/臨時變量tmp=11059200/12;/定時器計數(shù)頻率tmp=(tm

19、p*ms)/1000;/計算所需的計數(shù)值tmp=65536-tmp;/計算定時器重載值tmp=tmp+33;/補(bǔ)償中斷響應(yīng)延時造成的誤差T0RH=(unsignedchar)(tmp8);/定時器重載值拆分為高低字節(jié)T0RL=(unsignedchar)tmp;TMOD&=0 xF0;/清零T0的控制位TMOD|=0 x01;/配置T0為模式1TH0=T0RH;/加載T0重載值TL0=T0RL;ET0=1;/使能T0中斷TR0=1;/啟動T0/*T0中斷服務(wù)函數(shù),執(zhí)行串口接收監(jiān)控*/voidInterruptTimer0()interrupt1TH0=T0RH;/重新加載重載值TL0=T0RL

20、;UartRxMonitor(1);/串口接收監(jiān)控 現(xiàn)在看這種串口程序,是不是感覺很簡單了呢?串口通信程序我們反反復(fù)復(fù)的使用,加上隨著學(xué)習(xí)的模塊越來越多,實(shí)踐的越來越多,原先感覺很復(fù)雜的東西,現(xiàn)在就會感到簡單了。從設(shè)備管理器里可以查看所有的COM口號,我們下載程序用的是COM4,而USB轉(zhuǎn)RS485虛擬的是COM5,通信的時候我們用的是COM5口,如圖18-3所示。圖18-3RS485通信試驗(yàn)設(shè)置和結(jié)果1.2Modbus通信協(xié)議介紹我們前邊學(xué)習(xí)UART、I2C、SPI這些通信協(xié)議,都是最底層的協(xié)議,是“位”級別的協(xié)議。而我們在學(xué)習(xí)13章做實(shí)用串口通信程序的時候,我們通過串口發(fā)給單片機(jī)三條指令,

21、讓單片機(jī)做了三件不同的事情,分別是“buzzon”、“buzzoff”和“showstr”。隨著系統(tǒng)復(fù)雜性的增加,我們希望可以實(shí)現(xiàn)更多的指令。而指令越來越多,帶來的后果就是非常雜亂無章,尤其是這個人喜歡寫成“buzzon”、“buzzoff”,而另外一個人喜歡寫成“onbuzz”、“offbuzz”。導(dǎo)致不同開發(fā)人員寫出來的程序代碼不兼容,不同廠家的產(chǎn)品不能掛到一條總線上通信。隨著這種矛盾的日益嚴(yán)重,就會有聰明人提出更合理的解決方案,提出一些標(biāo)準(zhǔn)來,今后我們的編程必須按照這個標(biāo)準(zhǔn)來,這種標(biāo)準(zhǔn)也是一種通信協(xié)議,但是和UART、I2C、SPI通信協(xié)議不同的是,這種通信協(xié)議是字節(jié)級別的,叫做應(yīng)用層通

22、信協(xié)議。在1979年由Modicon(現(xiàn)為施耐德電氣公司的一個品牌)提出了全球第一個真正用于工業(yè)現(xiàn)場總線的協(xié)議,就是Modbus協(xié)議。1.2.1Modbus協(xié)議特點(diǎn) Modbus協(xié)議是應(yīng)用于電子控制器上的一種通用語言。通過此協(xié)議,控制器相互之間、控制器經(jīng)由網(wǎng)絡(luò)(例如以太網(wǎng))和其他設(shè)備之間可以通信,已經(jīng)成為一種工業(yè)標(biāo)準(zhǔn)。有了它,不同廠商生產(chǎn)的控制設(shè)備可以連成工業(yè)網(wǎng)絡(luò),進(jìn)行集中監(jiān)控。這種協(xié)議定義了一種控制器能夠認(rèn)識使用的數(shù)據(jù)結(jié)構(gòu),而不管它們是經(jīng)過何種網(wǎng)絡(luò)進(jìn)行通信的。它描述了控制器請求訪問其它設(shè)備的過程,如何回應(yīng)來自其它設(shè)備的請求,以及怎樣偵測錯誤記錄,它制定了通信數(shù)據(jù)的格局和內(nèi)容的公共格式。 在進(jìn)

23、行多機(jī)通信的時候,Modbus協(xié)議規(guī)定每個控制器必須要知道它們的設(shè)備地址,識別按照地址發(fā)送過來的數(shù)據(jù),決定是否要產(chǎn)生動作,產(chǎn)生何種動作,如果要回應(yīng),控制器將生成的反饋信息用Modbus協(xié)議發(fā)出。 Modbus協(xié)議允許在各種網(wǎng)絡(luò)體系結(jié)構(gòu)內(nèi)進(jìn)行簡單通信,每種設(shè)備(PLC、人機(jī)界面、控制面板、驅(qū)動程序、輸入輸出設(shè)備等)都能使用Modbus協(xié)議來啟動遠(yuǎn)程操作,一些網(wǎng)關(guān)允許在幾種使用Modbus協(xié)議的總線或網(wǎng)絡(luò)之間的通信,如圖18-4所示。圖18-4Modbus網(wǎng)絡(luò)體系結(jié)構(gòu)實(shí)例 Modbus協(xié)議的整體架構(gòu)和格式比較復(fù)雜和龐大,在我們的課程里,我們重點(diǎn)介紹數(shù)據(jù)幀結(jié)構(gòu)和數(shù)據(jù)通信控制方式,作為一個入門級別的了

24、解。如果大家要詳細(xì)了解,或者使用Modbus開發(fā)相關(guān)設(shè)備,可以查閱相關(guān)的國標(biāo)文件再進(jìn)行深入學(xué)習(xí)。1.1.1RTU協(xié)議幀數(shù)據(jù) Modbus有兩種通信傳輸方式,一種是ASCII模式,一種是RTU模式。由于ASCII模式的數(shù)據(jù)字節(jié)是7bit數(shù)據(jù)位,51單片機(jī)無法實(shí)現(xiàn),而且實(shí)際應(yīng)用的也比較少,所以這里我們只用RTU模式。兩種模式相似,會用一種另外一種也就會了。一條典型的RTU數(shù)據(jù)幀如圖18-5所示。圖18-5RTU數(shù)據(jù)幀 與之前我們講解實(shí)用串口通信程序時用的原理相同,一次發(fā)送的數(shù)據(jù)幀必須是作為一個連續(xù)的數(shù)據(jù)流進(jìn)行傳輸。我們在實(shí)用串口通信程序中采用的方法是定義30ms,如果數(shù)據(jù)接收時超過了30ms還沒有

25、接收到下一個字節(jié),我們就認(rèn)為這次的數(shù)據(jù)結(jié)束。而Modbus的RTU模式規(guī)定不同數(shù)據(jù)幀之間的間隔是3.5個字節(jié)通信時間以上。如果在一幀數(shù)據(jù)完成之前有超過3.5個字節(jié)時間的停頓,接收設(shè)備將刷新當(dāng)前的消息并假定下一個字節(jié)是一個新的數(shù)據(jù)幀的開始。同樣的,如果一個新消息在小于3.5個字節(jié)時間內(nèi)接著前邊一個數(shù)據(jù)開始,接收設(shè)備將會認(rèn)為它是前一幀數(shù)據(jù)的延續(xù)。這將會導(dǎo)致一個錯誤,因此大家看RTU數(shù)據(jù)幀最后還有16bit的CRC校驗(yàn)。 起始位和結(jié)束符:圖18-5上代表的是一個數(shù)據(jù)幀,前后都至少有3.5個字節(jié)的時間間隔,起始位和結(jié)束符實(shí)際上沒有任何數(shù)據(jù),T1-T2-T3-T4代表的是時間間隔3.5個字節(jié)以上的時間,

26、而真正有意義的第一個字節(jié)是設(shè)備地址。 設(shè)備地址:很多同學(xué)不理解,在多機(jī)通信的時候,數(shù)據(jù)那么多,我們依靠什么判斷這個數(shù)據(jù)幀是哪個設(shè)備的呢?沒錯,就是依靠這個設(shè)備地址字節(jié)。每個設(shè)備都有一個自己的地址,當(dāng)設(shè)備接收到一幀數(shù)據(jù)后,程序首先對設(shè)備地址字節(jié)進(jìn)行判斷比較,如果與自己的地址不同,則對這幀數(shù)據(jù)直接不予理會,如果與自己的地址相同,就要對這幀數(shù)據(jù)進(jìn)行解析,按照之后的功能碼執(zhí)行相應(yīng)的功能。如果地址是0 x00,則認(rèn)為是一個廣播命令,就是所有的從機(jī)設(shè)備都要執(zhí)行的指令。 功能代碼:在第二個字節(jié)功能代碼字節(jié)中,Modbus規(guī)定了部分功能代碼,此外也保留了一部分功能代碼作為備用或者用戶自定義,這些功能碼大家不需

27、要去記憶,甚至都不用去看,直到你用到的那天再過來查這個表格即可,如表18-1所示。 表18-1Modbus功能碼(ON/OFF)(ON/OFF)8個內(nèi)部線圈的通斷狀態(tài),這8個線圈的地址由控制器決定,用戶邏輯可以將這些線圈定義,以說明從機(jī)狀態(tài),短報文適宜于迅速讀取狀態(tài)484PC從機(jī)邏輯4849的報文發(fā)送后,本功能碼才發(fā)送ModBus事務(wù)處理通信事件記錄。如果某項事務(wù)處理完成,記錄會給出有關(guān)錯誤PC從機(jī)邏輯13的報文發(fā)送后,本功能碼才得發(fā)送和MICRO84PC狀態(tài)邏輯 程序?qū)δ艽a的處理,就是來檢測這個字節(jié)的數(shù)值,然后根據(jù)其數(shù)值來做相應(yīng)的功能處理。 數(shù)據(jù):跟在功能代碼后邊的是n個8bit的數(shù)據(jù)。這

28、個n值的到底是多少,是功能代碼來確定的,不同的功能代碼后邊跟的數(shù)據(jù)數(shù)量不同。舉個例子,如果功能碼是0 x03,也就是讀保持寄存器,那么主機(jī)發(fā)送數(shù)據(jù)n的組成部分就是:2個字節(jié)的寄存器起始地址,加2個字節(jié)的寄存器數(shù)量N。從機(jī)數(shù)據(jù)n的組成部分是:1個字節(jié)的字節(jié)數(shù),因?yàn)槲覀兊募拇嫫鞯闹凳?個字節(jié),所以這個字節(jié)數(shù)也就是2N個,再加上2N個寄存器的值,如圖18-6所示。圖18-6讀保持寄存器數(shù)據(jù)結(jié)構(gòu) CRC校驗(yàn):CRC校驗(yàn)是一種數(shù)據(jù)算法,是用來校驗(yàn)數(shù)據(jù)對錯的。CRC校驗(yàn)函數(shù)把一幀數(shù)據(jù)除最后兩個字節(jié)外,前邊所有的字節(jié)進(jìn)行特定的算法計算,計算完后生成了一個16bit的數(shù)據(jù),作為CRC校驗(yàn)碼,添加在一幀數(shù)據(jù)的最

29、后。接收方接收到數(shù)據(jù)后,同樣會把前邊的字節(jié)進(jìn)行CRC計算,計算完了再和發(fā)過來的16bit的CRC數(shù)據(jù)進(jìn)行比較,如果相同則認(rèn)為數(shù)據(jù)正常,沒有出錯,如果比較不相同,則說明數(shù)據(jù)在傳輸中發(fā)生了錯誤,這幀數(shù)據(jù)將被丟棄,就像沒收到一樣,而發(fā)送方會在得不到回應(yīng)后做相應(yīng)的處理錯誤處理。 RTU模式的每個字節(jié)的位是這樣分布的:1個起始位、8個數(shù)據(jù)位,最小有效位先發(fā)送、1個奇偶校驗(yàn)位(如果無校驗(yàn)則沒有這一位)、1位停止位(有校驗(yàn)位時)或者2個停止位(無校驗(yàn)位時)。1.1Modbus多機(jī)通信例程 給從機(jī)下發(fā)不同的指令,從機(jī)去執(zhí)行不同的操作,這個就是判斷一下功能碼即可,和我們前邊學(xué)的實(shí)用串口例程是類似的。多機(jī)通信,無

30、非就是添加了一個設(shè)備地址判斷而已,難度也不大。我們找了一個Modbus調(diào)試精靈,通過設(shè)置設(shè)備地址,讀寫寄存器的地址以及數(shù)值數(shù)量等參數(shù),可以直接替代串口調(diào)試助手,比較方便的下發(fā)多個字節(jié)的數(shù)據(jù),如圖18-7所示。我們先來就圖中的設(shè)置和數(shù)據(jù)來對Modbus做進(jìn)一步的分析,圖中的數(shù)據(jù)來自于調(diào)試精靈與我們接下來要講的例程之間的交互。圖18-7Modbus調(diào)試精靈 如圖,我們的USB轉(zhuǎn)RS485模塊虛擬出的是COM5,波特率9600,無校驗(yàn)位,數(shù)據(jù)位是8位,1位停止位,設(shè)備地址假設(shè)為1。 寫寄存器的時候,如果我們要把01寫到一個地址是0000的寄存器地址里,點(diǎn)一下“寫入”,就會出現(xiàn)發(fā)送指令:0106000

31、00001480A。我們來分析一下這幀數(shù)據(jù),其中01是設(shè)備地址,06是功能碼,代表寫寄存器這個功能,后邊跟0000表示的是要寫入的寄存器的地址,0001就是要寫入的數(shù)據(jù),480A就是CRC校驗(yàn)碼,這是軟件自動算出來的。而根據(jù)Modbus協(xié)議,當(dāng)寫寄存器的時候,從機(jī)成功完成該指令的操作后,會把主機(jī)發(fā)送的指令直接返回,我們的調(diào)試精靈會接收到這樣一幀數(shù)據(jù):010600000001480A。 假如我們現(xiàn)在要從寄存器地址0002開始讀取寄存器,并且讀取的數(shù)量是2個。點(diǎn)一下“讀出”,就會出現(xiàn)發(fā)送指令:01030002000265CB。其中01是設(shè)備地址,03是功能碼,代表讀寄存器這個功能,0002就是讀寄

32、存器的起始地址,后一個0002就是要讀取2個寄存器的數(shù)值,65CB就是CRC校驗(yàn)。而接收到的數(shù)據(jù)是:01030400000000FA33。其中01是設(shè)備地址,03是功能碼,04代表的是后邊讀到的數(shù)據(jù)字節(jié)數(shù)是4個,00000000分別是地址為0002和0003的寄存器內(nèi)部的數(shù)據(jù),而FA33就是CRC校驗(yàn)了。 似乎越來越明朗了,所謂的Modbus通信協(xié)議,無非就是主機(jī)下發(fā)了不同的指令,從機(jī)根據(jù)指令的判斷來執(zhí)行不同的操作而已。由于我們的開發(fā)板沒有Modbus功能碼那么多相應(yīng)的功能,我們在程序中定義了一個數(shù)組regGroup5,相當(dāng)于5個寄存器,此外又定義了第6個寄存器,控制蜂鳴器,通過下發(fā)不同的指令

33、我們改變寄存器組的數(shù)據(jù)或者改變蜂鳴器的開關(guān)狀態(tài)。在Modbus協(xié)議里寄存器的地址和數(shù)值都是16位的,即2個字節(jié),我們默認(rèn)高字節(jié)是0 x00,低字節(jié)就是數(shù)組regGroup對應(yīng)的值。其中地址0 x0000到0 x0004對應(yīng)的就是regGroup數(shù)組中的元素,我們寫入的同時把數(shù)字又顯示到1602液晶上,而0 x0005這個地址,寫入0 x00,蜂鳴器就不響,寫入任何其它數(shù)值,蜂鳴器就報警。我們單片機(jī)的主要工作也就是解析串口接收的數(shù)據(jù)執(zhí)行不同操作。/*Lcd1602.c文件程序源代碼*/(此處省略,可參考之前章節(jié)的代碼)/RS485.c文件程序源代碼*/(此處省略,可參考之前章節(jié)的代碼)/CRC1

34、6.c文件程序源代碼/*CRC16計算函數(shù),ptr-數(shù)據(jù)指針,len-數(shù)據(jù)長度,返回值-計算出的CRC16數(shù)值*/unsignedintGetCRC16(unsignedchar*ptr,unsignedcharlen)unsignedintindex;unsignedcharcrch=0 xFF;/高CRC字節(jié)unsignedcharcrcl=0 xFF;/低CRC字節(jié)unsignedcharcodeTabH=/CRC高位字節(jié)值表0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC

35、1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x4

36、0,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC

37、0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x4

38、0,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC

39、1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40,0 x01,0 xC0,0 x80,0 x41,0 x01,0 xC0,0 x80,0 x41,0 x00,0 xC1,0 x81,0 x40;unsignedcharcodeTabL=/CRC低位字節(jié)值表0 x00,0 xC0,0 x

40、C1,0 x01,0 xC3,0 x03,0 x02,0 xC2,0 xC6,0 x06,0 x07,0 xC7,0 x05,0 xC5,0 xC4,0 x04,0 xCC,0 x0C,0 x0D,0 xCD,0 x0F,0 xCF,0 xCE,0 x0E,0 x0A,0 xCA,0 xCB,0 x0B,0 xC9,0 x09,0 x08,0 xC8,0 xD8,0 x18,0 x19,0 xD9,0 x1B,0 xDB,0 xDA,0 x1A,0 x1E,0 xDE,0 xDF,0 x1F,0 xDD,0 x1D,0 x1C,0 xDC,0 x14,0 xD4,0 xD5,0 x15,0 x

41、D7,0 x17,0 x16,0 xD6,0 xD2,0 x12,0 x13,0 xD3,0 x11,0 xD1,0 xD0,0 x10,0 xF0,0 x30,0 x31,0 xF1,0 x33,0 xF3,0 xF2,0 x32,0 x36,0 xF6,0 xF7,0 x37,0 xF5,0 x35,0 x34,0 xF4,0 x3C,0 xFC,0 xFD,0 x3D,0 xFF,0 x3F,0 x3E,0 xFE,0 xFA,0 x3A,0 x3B,0 xFB,0 x39,0 xF9,0 xF8,0 x38,0 x28,0 xE8,0 xE9,0 x29,0 xEB,0 x2B,0 x

42、2A,0 xEA,0 xEE,0 x2E,0 x2F,0 xEF,0 x2D,0 xED,0 xEC,0 x2C,0 xE4,0 x24,0 x25,0 xE5,0 x27,0 xE7,0 xE6,0 x26,0 x22,0 xE2,0 xE3,0 x23,0 xE1,0 x21,0 x20,0 xE0,0 xA0,0 x60,0 x61,0 xA1,0 x63,0 xA3,0 xA2,0 x62,0 x66,0 xA6,0 xA7,0 x67,0 xA5,0 x65,0 x64,0 xA4,0 x6C,0 xAC,0 xAD,0 x6D,0 xAF,0 x6F,0 x6E,0 xAE,0 x

43、AA,0 x6A,0 x6B,0 xAB,0 x69,0 xA9,0 xA8,0 x68,0 x78,0 xB8,0 xB9,0 x79,0 xBB,0 x7B,0 x7A,0 xBA,0 xBE,0 x7E,0 x7F,0 xBF,0 x7D,0 xBD,0 xBC,0 x7C,0 xB4,0 x74,0 x75,0 xB5,0 x77,0 xB7,0 xB6,0 x76,0 x72,0 xB2,0 xB3,0 x73,0 xB1,0 x71,0 x70,0 xB0,0 x50,0 x90,0 x91,0 x51,0 x93,0 x53,0 x52,0 x92,0 x96,0 x56,0 x

44、57,0 x97,0 x55,0 x95,0 x94,0 x54,0 x9C,0 x5C,0 x5D,0 x9D,0 x5F,0 x9F,0 x9E,0 x5E,0 x5A,0 x9A,0 x9B,0 x5B,0 x99,0 x59,0 x58,0 x98,0 x88,0 x48,0 x49,0 x89,0 x4B,0 x8B,0 x8A,0 x4A,0 x4E,0 x8E,0 x8F,0 x4F,0 x8D,0 x4D,0 x4C,0 x8C,0 x44,0 x84,0 x85,0 x45,0 x87,0 x47,0 x46,0 x86,0 x82,0 x42,0 x43,0 x83,0 x

45、41,0 x81,0 x80,0 x40;while(len-)/計算指定長度的CRCindex=crch*ptr+;crch=crclTabHindex;crcl=TabLindex;return(crch關(guān)于CRC校驗(yàn)的算法,如果不是專門學(xué)習(xí)校驗(yàn)算法本身,大家可以不去研究這個程序的細(xì)節(jié),直接使用現(xiàn)成的函數(shù)即可。/*main.c文件程序源代碼/#includesbitBUZZ=P16;bitflagBuzzOn=0;/蜂鳴器啟動標(biāo)志unsignedcharT0RH=0;/T0重載值的高字節(jié)unsignedcharT0RL=0;/T0重載值的低字節(jié)unsignedcharregGroup5;/

46、Modbus寄存器組,地址為0 x000 x04voidConfigTimer0(unsignedintms);externvoidUartDriver();externvoidConfigUART(unsignedintbaud);externvoidUartRxMonitor(unsignedcharms);externvoidUartWrite(unsignedchar*buf,unsignedcharlen);externunsignedintGetCRC16(unsignedchar*ptr,unsignedcharlen);externvoidInitLcd1602();exter

47、nvoidLcdShowStr(unsignedcharx,unsignedchary,unsignedchar*str);voidmain()EA=1;/開總中斷ConfigTimer0(1);/配置T0定時1msConfigUART(9600);/配置波特率為9600InitLcd1602();/初始化液晶while(1)UartDriver();/調(diào)用串口驅(qū)動/*串口動作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動作buf-接收到的命令幀指針,len-命令幀長度*/voidUartAction(unsignedchar*buf,unsignedcharlen)unsignedchari;unsi

48、gnedcharcnt;unsignedcharstr4;unsignedintcrc;unsignedcharcrch,crcl;if(buf0!=0 x01)/本例中的本機(jī)地址設(shè)定為0 x01,/如數(shù)據(jù)幀中的地址字節(jié)與本機(jī)地址不符,return;/則直接退出,即丟棄本幀數(shù)據(jù)不做任何處理/地址相符時,再對本幀數(shù)據(jù)進(jìn)行校驗(yàn)crc=GetCRC16(buf,len-2);/計算CRC校驗(yàn)值crch=crc8;crcl=crc&0 xFF;if(buflen-2!=crch)|(buflen-1!=crcl)return;/如CRC校驗(yàn)不符時直接退出/地址和校驗(yàn)字均相符后,解析功能碼,執(zhí)行相關(guān)操作switch(buf1)case0 x03:/讀取一個或連續(xù)的寄存器if(buf2=0 x00)&(b

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論