Modbus学习总结2

在实际编程过程中需要注意以下一些细节。

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


分享到:


相關文章: