手把手教你學51單片機之十八 RS485通信與Modbus協議

2020-11-22 電子產品世界

(ON/OFF)

(ON/OFF)

8個內部線圈的通斷狀態,這8個線圈的地址由控制器決定,用戶邏輯可以將這些線圈定義,以說明從機狀態,短報文適宜於迅速讀取狀態

484

PC從機邏輯

484

9的報文發送後,本功能碼才發送

ModBus事務處理通信事件記錄。如果某項事務處理完成,記錄會給出有關錯誤

PC從機邏輯

13的報文發送後,本功能碼才得發送

和MICRO84

PC狀態邏輯

程序對功能碼的處理,就是來檢測這個字節的數值,然後根據其數值來做相應的功能處理。

數據:跟在功能代碼後邊的是n個8bit的數據。這個n值的到底是多少,是功能代碼來確定的,不同的功能代碼後邊跟的數據數量不同。舉個例子,如果功能碼是0x03,也就是讀保持寄存器,那麼主機發送數據n的組成部分就是:2個字節的寄存器起始地址,加2個字節的寄存器數量N。從機數據n的組成部分是:1個字節的字節數,因為我們的寄存器的值是2個字節,所以這個字節數也就是2N個,再加上2N個寄存器的值,如圖18-6所示。

圖18-6讀保持寄存器數據結構

CRC校驗:CRC校驗是一種數據算法,是用來校驗數據對錯的。CRC校驗函數把一幀數據除最後兩個字節外,前邊所有的字節進行特定的算法計算,計算完後生成了一個16bit的數據,作為CRC校驗碼,添加在一幀數據的最後。接收方接收到數據後,同樣會把前邊的字節進行CRC計算,計算完了再和發過來的16bit的CRC數據進行比較,如果相同則認為數據正常,沒有出錯,如果比較不相同,則說明數據在傳輸中發生了錯誤,這幀數據將被丟棄,就像沒收到一樣,而發送方會在得不到回應後做相應的處理錯誤處理。

RTU模式的每個字節的位是這樣分布的:1個起始位、8個數據位,最小有效位先發送、1個奇偶校驗位(如果無校驗則沒有這一位)、1位停止位(有校驗位時)或者2個停止位(無校驗位時)。

1.1Modbus多機通信例程

給從機下發不同的指令,從機去執行不同的操作,這個就是判斷一下功能碼即可,和我們前邊學的實用串口例程是類似的。多機通信,無非就是添加了一個設備地址判斷而已,難度也不大。我們找了一個Modbus調試精靈,通過設置設備地址,讀寫寄存器的地址以及數值數量等參數,可以直接替代串口調試助手,比較方便的下發多個字節的數據,如圖18-7所示。我們先來就圖中的設置和數據來對Modbus做進一步的分析,圖中的數據來自於調試精靈與我們接下來要講的例程之間的交互。

圖18-7Modbus調試精靈

如圖,我們的USB轉RS485模塊虛擬出的是COM5,波特率9600,無校驗位,數據位是8位,1位停止位,設備地址假設為1。

寫寄存器的時候,如果我們要把01寫到一個地址是0000的寄存器地址裡,點一下「寫入」,就會出現發送指令:010600000001480A。我們來分析一下這幀數據,其中01是設備地址,06是功能碼,代表寫寄存器這個功能,後邊跟0000表示的是要寫入的寄存器的地址,0001就是要寫入的數據,480A就是CRC校驗碼,這是軟體自動算出來的。而根據Modbus協議,當寫寄存器的時候,從機成功完成該指令的操作後,會把主機發送的指令直接返回,我們的調試精靈會接收到這樣一幀數據:010600000001480A。

假如我們現在要從寄存器地址0002開始讀取寄存器,並且讀取的數量是2個。點一下「讀出」,就會出現發送指令:01030002000265CB。其中01是設備地址,03是功能碼,代表讀寄存器這個功能,0002就是讀寄存器的起始地址,後一個0002就是要讀取2個寄存器的數值,65CB就是CRC校驗。而接收到的數據是:01030400000000FA33。其中01是設備地址,03是功能碼,04代表的是後邊讀到的數據字節數是4個,00000000分別是地址為0002和0003的寄存器內部的數據,而FA33就是CRC校驗了。

似乎越來越明朗了,所謂的Modbus通信協議,無非就是主機下發了不同的指令,從機根據指令的判斷來執行不同的操作而已。由於我們的開發板沒有Modbus功能碼那麼多相應的功能,我們在程序中定義了一個數組regGroup[5],相當於5個寄存器,此外又定義了第6個寄存器,控制蜂鳴器,通過下發不同的指令我們改變寄存器組的數據或者改變蜂鳴器的開關狀態。在Modbus協議裡寄存器的地址和數值都是16位的,即2個字節,我們默認高字節是0x00,低字節就是數組regGroup對應的值。其中地址0x0000到0x0004對應的就是regGroup數組中的元素,我們寫入的同時把數字又顯示到1602液晶上,而0x0005這個地址,寫入0x00,蜂鳴器就不響,寫入任何其它數值,蜂鳴器就報警。我們單片機的主要工作也就是解析串口接收的數據執行不同操作。

/*Lcd1602.c文件程序原始碼*/

(此處省略,可參考之前章節的代碼)

/RS485.c文件程序原始碼*/

(此處省略,可參考之前章節的代碼)

/CRC16.c文件程序原始碼/

/*CRC16計算函數,ptr-數據指針,len-數據長度,返回值-計算出的CRC16數值*/

unsignedintGetCRC16(unsignedchar*ptr,unsignedcharlen)

{

unsignedintindex;

unsignedcharcrch=0xFF;//高CRC字節

unsignedcharcrcl=0xFF;//低CRC字節

unsignedcharcodeTabH[]={//CRC高位字節值表

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,

0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,

0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,

0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,

0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,

0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,

0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,

0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,

0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,

0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,

0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,

0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,

0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,

0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,

0x80,0x41,0x00,0xC1,0x81,0x40

};

unsignedcharcodeTabL[]={//CRC低位字節值表

0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,

0x07,0xC7,0x05,0xC5,0xC4,0x04,0xCC,0x0C,0x0D,0xCD,

0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,

0x08,0xC8,0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,

0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC,0x14,0xD4,

0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,

0x11,0xD1,0xD0,0x10,0xF0,0x30,0x31,0xF1,0x33,0xF3,

0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4,

0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,

0x3B,0xFB,0x39,0xF9,0xF8,0x38,0x28,0xE8,0xE9,0x29,

0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,0x2D,0xED,

0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,

0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,

0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,

0xA5,0x65,0x64,0xA4,0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,

0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xA8,0x68,

0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,0x7E,

0x7F,0xBF,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,

0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,

0x70,0xB0,0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,

0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,0x9C,0x5C,

0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,

0x99,0x59,0x58,0x98,0x88,0x48,0x49,0x89,0x4B,0x8B,

0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C,

0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,

0x43,0x83,0x41,0x81,0x80,0x40

};

while(len--)//計算指定長度的CRC

{

index=crch^*ptr++;

crch=crcl^TabH[index];

crcl=TabL[index];

}

return((crch<<8)|crcl);

}

關於CRC校驗的算法,如果不是專門學習校驗算法本身,大家可以不去研究這個程序的細節,直接使用現成的函數即可。

/*main.c文件程序原始碼/

#include

sbitBUZZ=P1^6;

bitflagBuzzOn=0;//蜂鳴器啟動標誌

unsignedcharT0RH=0;//T0重載值的高字節

unsignedcharT0RL=0;//T0重載值的低字節

unsignedcharregGroup[5];//Modbus寄存器組,地址為0x00~0x04

voidConfigTimer0(unsignedintms);

externvoidUartDriver();

externvoidConfigUART(unsignedintbaud);

externvoidUartRxMonitor(unsignedcharms);

externvoidUartWrite(unsignedchar*buf,unsignedcharlen);

externunsignedintGetCRC16(unsignedchar*ptr,unsignedcharlen);

externvoidInitLcd1602();

externvoidLcdShowStr(unsignedcharx,unsignedchary,unsignedchar*str);

voidmain()

{

EA=1;//開總中斷

ConfigTimer0(1);//配置T0定時1ms

ConfigUART(9600);//配置波特率為9600

InitLcd1602();//初始化液晶

while(1)

{

UartDriver();//調用串口驅動

}

}

/*串口動作函數,根據接收到的命令幀執行響應的動作

buf-接收到的命令幀指針,len-命令幀長度*/

voidUartAction(unsignedchar*buf,unsignedcharlen)

{

unsignedchari;

unsignedcharcnt;

unsignedcharstr[4];

unsignedintcrc;

unsignedcharcrch,crcl;

if(buf[0]!=0x01)//本例中的本機地址設定為0x01,

{//如數據幀中的地址字節與本機地址不符,

return;//則直接退出,即丟棄本幀數據不做任何處理

}

//地址相符時,再對本幀數據進行校驗

crc=GetCRC16(buf,len-2);//計算CRC校驗值

crch=crc>>8;

crcl=crc&0xFF;

if((buf[len-2]!=crch)||(buf[len-1]!=crcl))

{

return;//如CRC校驗不符時直接退出

}

//地址和校驗字均相符後,解析功能碼,執行相關操作

switch(buf[1])

{

case0x03://讀取一個或連續的寄存器

if((buf[2]==0x00)&&(buf[3]<=0x05))//只支持0x0000~0x0005

{

if(buf[3]<=0x04)

{

i=buf[3];//提取寄存器地址

cnt=buf[5];//提取待讀取的寄存器數量

buf[2]=cnt*2;//讀取數據的字節數,為寄存器數*2

len=3;//幀前部已有地址、功能碼、字節數共3個字節

while(cnt--)

{

buf[len++]=0x00;//寄存器高字節補0

buf[len++]=regGroup[i++];//寄存器低字節

}

}

else//地址0x05為蜂鳴器狀態

{

buf[2]=2;//讀取數據的字節數

buf[3]=0x00;

buf[4]=flagBuzzOn;

len=5;

}

break;

}

else//寄存器地址不被支持時,返回錯誤碼

{

buf[1]=0x83;//功能碼最高位置1

buf[2]=0x02;//設置異常碼為02-無效地址

len=3;

break;

}

case0x06://寫入單個寄存器

if((buf[2]==0x00)&&(buf[3]<=0x05))//只支持0x0000~0x0005

{

if(buf[3]<=0x04)

{

i=buf[3];//提取寄存器地址

regGroup[i]=buf[5];//保存寄存器數據

cnt=regGroup[i]>>4;//顯示到液晶上

if(cnt>=0xA)

str[0]=cnt-0xA+A;

else

str[0]=cnt+0;

cnt=regGroup[i]&0x0F;

if(cnt>=0xA)

str[1]=cnt-0xA+A;

else

str[1]=cnt+0;

str[2]=;

LcdShowStr(i*3,0,str);

}

else//地址0x05為蜂鳴器狀態

{

flagBuzzOn=(bit)buf[5];//寄存器值轉為蜂鳴器的開關

}

len-=2;//長度-2以重新計算CRC並返回原幀

&nb

break;

}

else//寄存器地址不被支持時,返回錯誤碼

{

buf[1]=0x86;//功能碼最高位置1

buf[2]=0x02;//設置異常碼為02-無效地址

len=3;

break;

}

default://其它不支持的功能碼

buf[1]|=0x80;//功能碼最高位置1

buf[2]=0x01;//設置異常碼為01-無效功能

len=3;

break;

}

crc=GetCRC16(buf,len);//計算返回幀的CRC校驗值

buf[len++]=crc>>8;//CRC高字節

buf[len++]=crc&0xFF;//CRC低字節

UartWrite(buf,len);//發送返回幀

}

/*配置並啟動T0,ms-T0定時時間*/

voidConfigTimer0(unsignedintms)

{

unsignedlongtmp;//臨時變量

tmp=11059200/12;//定時器計數頻率

tmp=(tmp*ms)/1000;//計算所需的計數值

tmp=65536-tmp;//計算定時器重載值

tmp=tmp+33;//補償中斷響應延時造成的誤差

T0RH=(unsignedchar)(tmp>>8);//定時器重載值拆分為高低字節

T0RL=(unsignedchar)tmp;

TMOD&=0xF0;//清零T0的控制位

TMOD|=0x01;//配置T0為模式1

TH0=T0RH;//加載T0重載值

TL0=T0RL;

ET0=1;//使能T0中斷

TR0=1;//啟動T0

}

/*T0中斷服務函數,執行串口接收監控和蜂鳴器驅動*/

voidInterruptTimer0()interrupt1

{

TH0=T0RH;//重新加載重載值

TL0=T0RL;

if(flagBuzzOn)//執行蜂鳴器鳴叫或關閉

BUZZ=~BUZZ;

else

BUZZ=1;

UartRxMonitor(1);//串口接收監控

}

大家可以看到負責解析協議的UartAction函數很長,因為協議解析本來就是一件很繁瑣的事情。我們的例程僅解析執行了兩個功能命令,就已經有近百行程序了,如果你需要解析更多的功能命令的話,那麼建議把每個功能都做一個函數,然後在相應的case分支裡調用即可,這樣就不會使單個函數過於龐大而難以維護。

1.1練習題

1、了解RS485通信以及和RS232的不同用法。

2、了解Modbus協議以及RTU數據幀的規則。

3、寫一個電子鐘程序,並且可以通過485調試器校時。

相關焦點

  • modbus與rs485的關係_modbus與rs485的區別和聯繫
    打開APP modbus與rs485的關係_modbus與rs485的區別和聯繫 發表於 2018-01-03 18:36:42
  • 深度介紹rs485總線接口通訊協議定義標準以及管腳引腳
    本文引用地址:http://www.eepw.com.cn/article/201808/385592.htmRS485通信網絡接口是一種總線式的結構,上位機(以個人電腦為例)和下位機(以51系列單片機http://www.51hei.com為例)都掛在通信總線上,RS485物理層的通信協議由RS485標準和51單片機的多機通訊方式。
  • Modbus與PROFIBUS-DP協議比較
    Modbus的協議內容是完全公開的,內容是簡單滴,實現起來是非常容易滴,單片機、PLC、DCS統統都能輕易實現Profibus則要複雜一些,關鍵是需要專用晶片進行二次開發,
  • 淺析六種常用的單片機通信協議
    打開APP 淺析六種常用的單片機通信協議 佚名 發表於 2019-11-08 16:36:46 在單片機的應用中,通信協議是必不可少的一部分,上位機與下位機,單片機與單片機,單片機與外設模塊之間的通信都需要通信協議實現信息交換和資源共享。
  • 485通訊協議程序怎麼寫(51單片機的485通信程序案例)
    RS-485收發器採用平衡發送和差分接收,因此具有抑制共模幹擾的能力,加上收發器具有高的靈敏度,能檢測到低達200mv的電壓,可靠通信的傳輸距離可達數千米。使用RS-485總線組網,只需一對雙絞線就可實現多系統聯網構成分布式系統、設備簡單、價格低廉、通信距離長。
  • modbus通信協議,profibus、FF、CAN總線等幾種現場總線知識合集
    modbus通信協議,profibus、FF、CAN總線等幾種現場總線知識合集 李倩 發表於 2018-04-29 17:47:00 在工業的發展中,現場總線起著非常重要的作用
  • 手把手教你學ELISA、PCR、免疫組化
    1.手把手教你學ELISAELISA的基礎是抗原或抗體的固相化及抗原或抗體的酶標記。結合在固相載體 表面的抗原或抗體仍保持其免疫學活性,酶標記的抗原或抗體既保留其免疫學活性,又保留酶的活性。2.手把手教你學PCR實時螢光定量PCR,簡稱RT-QPCR,屬於Q-PCR的一種,目前該技術已得到廣泛應用,如:擴增特異性分析、基因定量分析、基因分型、SNP分析等。
  • 示波器調試RS485通信波形圖
    由於筆者在調試STM32移植的MODBUS協議的時候遇到了一些問題,需要藉助示波器來看波形,而關於485通信波形相關的資料網上不多,所以把自己調試過程中的波形記錄下來。 硬體:STM32單片機,1個主機和10個從機,移植MODBUS協議,採用輪詢機制,主機挨個詢問從機,從機採集一些信息回復給主機。
  • Modbus協議的常用基本定義——(RS485總線系統應用之2)
    Modbus協議簡介Modbus協議是一種應用於電子控制器的通用通信規約。是由MODICON公司最先倡導並於1979年開發的,後來逐漸發展成為國際通用的通信標準。在我國,GB/Z 19582《基於Modbus協議的工業自動化網絡規範》於2004年9月發布,該指導性技術文件發布後得到了國內自動化產品生產廠家、自動化系統集成商和廣大用戶的高度關注和重視,並極大地推動了基於Modbus協議的產品的開發和應用。
  • 基於C8051F040單片機的CAN總線和RS-232串口通信設計
    為了實現對CAN總線和RS-232串口雙向通信需求,提出了一種基於C8051F040單片機的數據通信方案,並完成系統設計。分析了CAN總線和RS-232串口的通信特點,介紹了單片機硬體,並對軟體的設計思路與流程做了詳盡描述,完成功能檢測。實驗結果表明,該設計達到了要求。
  • 關於STC單片機的幾點建議
    51系列中,STC用的是最多的。但問題是:1、51的最大好處無非就是學習資料眾多,學習條件比較好,但51單片機性能偏弱且價格高,因此性價比其實並不好。2、現在比較熱門的是STM8或STM32,大有代替51之勢,它的價格比STC51更便宜,性能也強得多(特別是STM32,性能非常高,根本不是STC這種8位單片機能比得了),穩定性也好。其他的還有PIC、AVR、MSP430系列單片機,飛思卡爾(去年已經被高通收購)也是很不錯的單片機,工業上也有一定應用。
  • 基於51單片機的TFT液晶顯示設計
    0 引言本文引用地址:http://www.eepw.com.cn/article/173037.htm  51單片機作為一種常見的通用單片機, 雖然其內部資源
  • 串行通信與協議(RS232、RS485)
    串行通信是PLC網絡常用的通信方式,正確選擇接口類型和協議標準,對保證通信可靠性具有重要意義。1.RS232接口一種標準的串行物理接口,232是標識號。
  • 《電子發燒友網51單片機設計方案TOP10》
    該系統利用單片機的雙機通信功能,設計出的具有呼叫、顯示、應答等功能的多路呼叫系統,就是為滿足中小型醫院中,醫護人員與病人之間能及時準確地進行半雙工通信,達到既方便病人又方便醫護人員,更利於提高醫院護理水平的目的而設計的。該系統適用於新老病房及門診,且能隨時擴充床位及遷移。針對目前整體化護理要求,取消了通話功能,便於任何情況都能到床邊交流。
  • cmos電平與rs485_rs485通信與DP的區別
    最大的通信距離約為1219m,最大傳輸速率為10Mbps,傳輸速率與傳輸距離成反比,在100KbpS的傳輸速率下,才可以達到最大的通信距離,如果需傳輸更長的距離,需要加485中繼器。RS-485總線一般最大支持32個節點,如果使用特製的485晶片,可以達到128個或者256個節點,最大的可以支持到400個節點。
  • 基於單片機和CPLD的PLC背板總線協議接口晶片設計(一)
    目前,PLC 大多採用串行通信技術實現背板總線,串行總線引線少、硬體成本低,跟並行總線相比不容易受幹擾,串行總線可以提高在惡劣的工廠和工業環境下自動化設備的可靠性。用於串行通信技術的可選類型包括I2C、UART、SPI、USB 和乙太網等,一般來說,很多作為PLC 主晶片的單片機自身都集成了這些外設部件。
  • 異步串行通信協議的設計與實現
    引言      在單片機控制系統中,CPU和外部通信主要有兩種方 式:並行通信和串行通信。並行通信,即數據的各位同時傳 送;串行通信,即數據按位次序傳送。