在實際編程過程中需要注意以下一些細節。
1、MODBUS 3.5T是如何計算的?
T = 1000毫秒*(1起始位+數據位+奇偶校驗+停止位)/波特率
如果你的通訊方式是:波特率115200(表示每秒傳輸多少字符),數據位8,無奇偶校驗。那麼你發送一個字符的時間是:T=1000* (1起始位+8數據位+0奇偶校驗+1停止位)/ 115200=0.087ms。
發送端:發送一幀後延時7*T(其中3.5T是停止時間,3.5T是起始時間)再發送第二幀,保證一幀數據裡頭各字節間間隔延時不能超過1.5T。
接收端:接收一個字節,查詢2T時間,是否有接收到下一個字節,有則這幀數據未完,繼續循環接收;沒有則默認這幀已經接收完畢。
2、串口超時時間設置
COMMTIMEOUTS timeout;
//填充timeout結構
GetCommTimeouts(m_h232Port,&timeout);
timeout.ReadIntervalTimeout=100;//兩字符之間最大的延時
timeout.ReadTotalTimeoutConstant=500;
timeout.ReadTotalTimeoutMultiplier=0;//讀取每字符間的超時
timeout.WriteTotalTimeoutConstant=2000;
timeout.WriteTotalTimeoutMultiplier=60;//寫入每字符間的超時
BOOLerror=SetCommTimeouts(m_hPort,&timeout);
ReadIntervalTimeout:兩字符之間最大的延時,當讀取串口數據時,一旦兩個字符傳輸的時間差超過該時間,讀取函數將返回現有的數據。設置為0表示該參數不起作用。
ReadTotalTimeoutMultiplier:讀取每字符間的超時。
ReadTotalTimeoutConstant:一次讀取串口數據的固定超時。所以在一次讀取串口的操作中,其超時為ReadTotalTimeoutMultiplier乘以讀取的字節數再加上 ReadTotalTimeoutConstant。將ReadIntervalTimeout設置為MAXDWORD,並將ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant設置為0,表示讀取操作將立即返回存放在輸入緩衝區的字符。
WriteTotalTimeoutMultiplier:寫入每字符間的超時。
WriteTotalTimeoutConstant:一次寫入串口數據的固定超時。所以在一次寫入串口的操作中,其超時為WriteTotalTimeoutMultiplier乘以寫入的字節數再加上 WriteTotalTimeoutConstant。
3、串口編程。
在程序中如果要用到多個串口,而且還要做很多複雜的處理,那麼最好不用MSComm通訊控件,網絡上有很多封裝好的串口類。
C++ class for Win32 serial ports:http://www.naughter.com/serialport.html
CSerialPort:https://github.com/itas109/CSerialPort
4、如何寫穩定的modbus代碼?
需要從穩定性和易讀性來考慮,如果穩定性較差會造成系統控制故障,如果易讀性差就會造成難以維護,這些控制指令之間差別很小,如果一個一個單獨寫命令,非常容易出錯。對應舉措如下:
1)避免每個指令寫一部分代碼,需要統一處理,比如校驗函數,發送函數,接收函數等。通信協議已知,從中可以知道通信的實際數據長度(不包含包頭包尾和校驗的部分),所以可以控制讀寫多少個字節,並且可以知道什麼時候啟動校驗,那些數據參與校驗計算。
2)為指令建立指令列表,這樣後來需要添加功能,就可以直接把指令字加入列表即可。
3)適當抽象。為每個指令的動作寫回調函數,這樣就可以在應用層,用一句簡單的回調函數指針直接操作具體的動作函數,而不是應用邏輯層操作具體的驅動層面的接口。
4)超時,必須有超時機制。通信失敗怎麼處理?通信之後一般線斷了怎麼處理?不能讓系統死等後面的幾個字節發過來。
5)接收超時機制,不能依靠數傳輸的字節個數來停止接收來和區分幀間隔,因為可能通信就是斷掉了,所以要按照協議,串口通信情況下3~5個T空閒就認為一幀結束。也可以通過協議事先計算出接收數據的總時間(字符數*(字符時間+字符間間隔時間)),使用該使用作為等待超時時間,但最長超時時間不能超過5T時間。
6)響應超時機制,Modbus是請求/應答式通信,那麼主機就需要知道到底多久從機才會應答,主機等待從機應答的最長時間就是從機的最大回復間隔,超過這個時間後從機即使已經完成計算也不能回覆,因為此時主機可能已經開始給其他從機發送數據了。
7)如果通信都是由你發起的,那麼無論此次通信是完成還是超時或失敗,都應該關閉通信端口。
8)如果是線程中操作串口,可以提升線程優先級,讓操作系統在一定程度上提高串口讀寫性能。
9)ModbusRTU設備參數設置如下:
(1)內部屬性:單擊“查看設備內部屬性” ,點擊按鈕進入內部屬性
(2)最小採集週期:組態軟件對設備進行操作的時間週期, 單位為 ms, 默認為100ms,根據採集數據量的大小,設置值可適當調整
(3)設備地址:必須和實際設備的地址相一致,範圍為0-255,默認值為 0。
(4)通訊等待時間:通訊數據接收等待時間,默認設置為 200ms,根據採集數據量的大小,設置值可適當調整。
(5)快速採集次數:對選擇了快速採集的通道進行快採的頻率(已不使用,為與老驅動兼容,故保留,無需設置) 。
(6)16位整數解碼順序:調整字元件的解碼順序,對於Modicon PLC 及標準 PLC設備,使用默認值即可。
16 位整數解碼順序 舉例:0x0001
0―12 表示字元件高低字節不顛倒(默認值) 表示 1
1―21 表示字元件高低字節顛倒 表示 256
(7)32位整數解碼順序:調整雙字元件的解碼順序,對於Modicon PLC,請設置為“2-3412”順序解碼。
32 位整數解碼順序 舉例: 0x0000 0001
0―1234 表示雙字元件不做處理直接解碼(默認值) 表示 1
1―2143 表示雙字元件高低字不顛倒,但字內高低字節顛倒 表示256
2—3412表示雙字元件高低字顛倒,但字內高低字節不顛倒 表示65536
3—4321表示雙字元件內4個字節全部顛倒 表示 16777216
(8)32位浮點數解碼順序:調整雙字元件的解碼順序,對於Modicon PLC,請設置為“2-3412”順序解碼。
32 位浮點數解碼順序 舉例:0x3F80 0000
0―1234 表示雙字元件不做處理直接解碼(默認值) 表示 1.0
1―2143 表示雙字元件高低字不顛倒,但字內高低字節顛倒 表示-5.78564e-039
2—3412表示雙字元件高低字顛倒,但字內高低字節不顛倒 表示2.27795e-041
3—4321表示雙字元件內4 個字節全部顛倒 表示 4.60060e-041
(9)校驗方式: 選擇LRC校驗值的組合方式, 對於 Modicon PLC及標準 PLC 設備,
使用默認設置即可。
0—LH[低字節,高字節]:校驗結果為2 個字節,低字節在前,高字節在後。
1—HL[高字節,低字節]:校驗結果為2 個字節,高字節在前,低字節在後。默認值。
(10)分塊採集方式:驅動採集數據分塊的方式,對於Modicon PLC及標準 PLC設備,使用默認設置可以提高採集效率。
0— 按最大長度分塊:採集分塊按最大塊長處理,對地址不連續但地址相近的多個分塊,分為一塊一次性讀取,以優化採集效率。
1— 按連續地址分塊:採集分塊按地址連續性處理,對地址不連續的多個分塊,每次只採集連續地址,不做優化處理。
例如:有4區寄存器地址分別為 1~5,7,9~12的數據需採集,如果選擇“0-按最大長度分塊” ,則兩塊可優化為地址1~12的數據打包1次完成採集;如果選擇“1-按連續地址分塊” ,則需要採集 3 次。
(11)4區16 位寫功能碼選擇:寫 4 區單字時功能碼的選擇,這個屬性主要是針對自己製作設備的用戶而設置的,這樣的設備4區單字寫可能只支持 0x10 功能碼,而不支持0x06 功能碼。
0—0x06:單字寫功能碼使用0x06。
1—0x10:單字寫功能碼使用0x10。
注意:
(1). “解碼順序”及“校驗方式”設置:主要是針對非標準 ModbusRTU 協議的不同解碼及校驗順序。當用戶通過本驅動軟件與設備通訊時,如果出現解析數據值不對,或者通訊校驗錯誤(通訊狀態為3),可與廠家諮詢後對以上兩項進行設置。而對於ModiconPLC及支持標準ModbusRTU 的 PLC 及控制器等設備,一般需將“32位整數解碼順序”和“32位浮點數解碼順序”設置為“2-3412” 。 另外,在使用本驅動與“Modbus 串口數據轉發設備”構件通訊時, “解碼順序”及“校驗方式”均需按默認值設置,否則會導致通訊失敗或解析數據錯誤。
(2). “分塊採集方式”設置:主要是針對非標準 ModbusRTU協議設備。當用戶通過本驅動軟件與設備通訊時,如果按默認“0-按最大長度分塊”時,出現讀取連續地址正常,而不連續地址不正常時,可與廠家諮詢,並設置為“1-按連續地址分塊方式”嘗試是否可正常通訊。 而對於 Modicon PLC 及支持標準 ModbusRTU 的 PLC 及控制器等設備,直接使用默認設置即可,這樣可以提高採集效率。
5、lrc和crc校驗算法
//--------------------------------------------------------------------------
// Constants
//--------------------------------------------------------------------------
static const uint16_t modbus_crc_table[256] = {
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
};
//--------------------------------------------------------------------------
// Modbus functions
//--------------------------------------------------------------------------
uint8_t modbus_lrc_calc(uint8_t *data, uint16_t len)
{
uint8_t lrc = 0U;
int i;
for (i = 0U; i < len; i++)
{
lrc += data[i];
}
lrc = (0xFFU - lrc)+1U;
return(lrc);
}
uint16_t modbus_crc_calc(uint8_t *buffer, uint16_t size)
{
uint16_t crc = 0xFFFFU;
uint8_t nTemp;
while (size--)
{
nTemp = *buffer++ ^ crc;
crc >>= 8;
crc ^= modbus_crc_table[(nTemp & 0xFFU)];
}
return(crc);
}
LRC算出來的是16進制的字節值,若發送的數據是字符串形式,不需任何轉換,直接放在字符串最後位置就行了。若發送的數據是數組形式,則需要轉換為ASCII值,即由1字節的16進制轉換為2字節的ASCII值,4D應該轉換為34 44。
如果你的發送指令是:com.output="xxxxx",就是字符串形式;
如果發送指令形式是:com.output=A[],就是數組形式。
但不管是哪種形式,ModBus ASCII模式最終在數據線上的數據都是ASCII值。字符串形式的數據,編譯軟件會自動轉換。
6、調試工具
Modbus Poll是一個Modbus管理模擬器軟件,幫助開發人員進行管理和監控的Mod bus數據區在同一時間和模擬Mod bus協議。支持Modbus RTU / ASCII和Modbus TCP / IP協議。
下載地址:http://www.downcc.com/soft/25945.html
modbus slave是一款功能強大的modbus子設備模擬工具,可以幫助modbus通訊設備開發人員進行modbus通訊協議的模擬和測試,用於模擬、測試、調試modbus通訊設備。
下載地址:http://www.ddooo.com/softdown/70166.htm
7、modbus開源項目
https://github.com/stephane/libmodbus