转载地址:http://blog.sina.com.cn/s/blog_5d4d58a90100uqci.html
基于串口的Modbus软件开发
--------------------华X北X电X力X大X学 秦X宇X飞----2008年春节前于北京定福庄-----
----------------------八股--------------------
一、开发背景:
1、保密
2、GAT垃圾项目的DCS要通过Modbus与PLC进行通讯,我们的DCS仅进行过简单的测试。要连鬼子的炉排PLC,还需进行更正式的测试,秦SUN为了体现ABB的软件水平,特决定自己写一个ABoBo公司的MB测试软件。
3、秦PHD要毕业,以后的毕业设计里也许能用上这个东东。
二、开发意义:
1、地球上又多了,一个MB测试的软件,本人决定部分开源(不能挤公司的饭碗)
2、捡回丢掉的尊严
3、写完就能回家了!!
三、国际国内最新动态
1、已经有好多个测试软件了,但是没有开源。如modbusscan 7.0(ModScan32.exe),Modbus Poll version, 4.3.1, Modbustester.exe,ModLink,ModbusSimulator
2、协议比较老了,但是很管用。
----------------------八股--------------------
----------------------N步走--------------------
第一步:图书馆借N本VC串口编程的书
第二步:上网下N个源代码
第三步:上网下个虚拟串口的程序。(http://www.sudt.com/cn/sn/download.htm)
第四步:学习Modbus协议
主从方式,主站初始化传输。从站根据主设备查询提供的数据作出反应。
主站查询格式:站(或广播)地址、功能代码、要发送的数据、错误检测域。
从站回应格式:确认要行动的域、返回的数据、错误检测域。如果在消息接收过程中发生错误,或从站不能执行其命令,从站将建立错误消息并把它作为回应发送出去。
从站地址是0...247,0为广播地址
ASCII模式
: |
地址 |
功能代码 |
数据数量 |
数据1 |
... |
数据n |
LRC高字节 |
LRC低字节 |
回车 |
换行 |
代码系统:
十六进制,ASCII字符0...9,A...F
消息中的每个ASCII字符都是一个十六进制字符组成
每个字节的位:
1个起始位
7个数据位,最小的有效位先发送
1个奇偶校验位,无校验则无
1个停止位(有校验时),2个Bit(无校验时)
错误检测域
LRC(纵向冗长检测)
使用ASCII模式,消息以冒号(:)字符(ASCII码 3AH)开始,以回车换行符结束(ASCII码 0DH,0AH)。
其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测“:”字符,当有一个冒号接收到时,每个设备都解码下个域(地址域)来判断是否发给自己的。
消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误。
RTU模式
地址 |
功能代码 |
数据数量 |
数据1 |
... |
数据n |
CRC低字节 |
CRC高字节 |
|
|
代码系统:
8位二进制,十六进制数0...9,A...F
消息中的每个8位域都是一个两个十六进制字符组成
每个字节的位:
1个起始位
8个数据位,最小的有效位先发送
1个奇偶校验位,无校验则无
1个停止位(有校验时),2个Bit(无校验时)
错误检测域
CRC(循环冗长检测)
深入理解ModBus功能码
Modbus主要功能码
功能码 |
名称 |
注释 |
|
01 |
Read Coil Status |
(线圈状态0x)(读PLC的开出状态) |
bit 读位 ,与5对应, 可读可写DO |
02 |
Read Input Status |
(输入状态1x) (读PLC的开入状态) |
bit 读位,只读DI |
03 |
Read Holding Register |
(保持寄存器4x HR) (读模出状态) |
读整形、状态字、浮点型、字符型,与16对应 |
04 |
Read Input Register |
(输入寄存器3x AR) (读PLC模入状态) |
读整形、状态字、浮点型 只读AI |
05 |
Write Single Coil |
(强制单路开出,给PLC写数据) |
写单个位 可读可写DO |
06 |
Write Single Register |
(强制单路模出,给PLC写数据) |
写单个整形、状态字、浮点型、字符型,写HR4x的地址区 |
15 |
Write Multiple Coil |
(强制多路开出,给PLC写数据) |
写多个位 |
16(0x10H) |
Write Multiple Register |
(强制多路模出,给PLC写数据) |
写多个整形、状态字、浮点型、字符型 |
15和16可能是用于一次写一串数据的,不允许单个写。如时间的世纪、年、月、日、时、分、秒要一次写下去
设备和Modbus 地址范围对应表 |
||||
设备地址 |
Modbus地址 |
描述 |
功能 |
R/W |
1...10000* |
address - 1 |
Coils (outputs) |
0 |
Read/Write |
10001...20000* |
address - 10001 |
Discrete Inputs |
01 |
Read |
40001...50000* |
address - 40001 |
Holding Registers |
03 |
Read/Write |
30001...40000* |
address - 30001 |
Input Registers |
04 |
Read |
*最大值与设备相关
注:设备地址是从1开始的,所以Modbus写入地址要在设备地址上减1。
协议格式
功能码:01 (线圈状态0x)(读开出状态)
例:从4站读10…22(Coil 11…23),从A开始,共D(13)个数据
从站地址 |
功能码 |
H 地址 |
L 地址 |
H Coils |
L Coils |
CRC |
|
04 |
01 |
00 |
0A |
00 |
0D |
DD |
98 |
主站
从站地址 |
功能码 |
字节数 |
Coils 7..10 |
Coils 27..20 |
CRC |
|
04 |
01 |
02 |
0A |
11 |
50 |
B3 |
从站
功能码:02 (输入状态1x) (读开入状态)
例:从4站读10…22(Input 10011…10023),从A开始,共D(13)个数据
从站地址 |
功能码 |
H 地址 |
L 地址 |
H Input |
L Input |
CRC |
|
04 |
02 |
00 |
0A |
00 |
0D |
99 |
98 |
主站
从站地址 |
功能码 |
字节数 |
Input 7..10 |
Input 27..20 |
CRC |
|
04 |
02 |
02 |
0A |
11 |
14 |
B3 |
从站
功能码:03 (保持寄存器4x HR) (读模出状态)
例:从1站读0…1(寄存器 40001…40002),共2个数据
从站地址 |
功能码 |
H 地址 |
L 地址 |
H 数据 |
L 数据 |
CRC |
|
01 |
03 |
00 |
00 |
00 |
02 |
C4 |
0B |
主站
从站地址 |
功能码 |
字节数 |
H数据 |
L数据 |
H数据 |
L数据 |
CRC |
|
01 |
03 |
04 |
00 |
06 |
00 |
05 |
DA |
31 |
从站
功能码:04 (输入寄存器3x AR) (读模入状态)
例:从1站读0…1(寄存器 30001…30002),共2个数据
从站地址 |
功能码 |
H 地址 |
L 地址 |
H 数据 |
L 数据 |
CRC |
|
01 |
04 |
00 |
00 |
00 |
02 |
71 |
CB |
主站
从站地址 |
功能码 |
字节数 |
H数据 |
L数据 |
H数据 |
L数据 |
CRC |
|
01 |
04 |
04 |
00 |
06 |
00 |
05 |
DB |
86 |
从站
功能码:05 强制单路开出,给PLC写数据
例:给17站173单线圈写ON
从站地址 |
功能码 |
H 地址 |
L 地址 |
H 数据 |
L 数据 |
CRC |
|
11 |
05 |
00 |
AC |
FF |
00 |
4E |
8B |
主站
注:写0xFF00表示ON,写0x0000表示OFF
从站地址 |
功能码 |
H 地址 |
L 地址 |
H数据 |
L数据 |
CRC |
|
11 |
05 |
00 |
AC |
FF |
00 |
4E |
8B |
从站
注:返回帧与主站相同
功能码:06 强制单路模出,给PLC写数据
例:给17站40002变量写0x00 03H。帧的地址是0x0001H
从站地址 |
功能码 |
H 地址 |
L 地址 |
H 数据 |
L 数据 |
CRC |
|
11 |
06 |
00 |
01 |
00 |
03 |
9A |
9B |
主站
从站地址 |
功能码 |
H 地址 |
L 地址 |
H数据 |
L数据 |
CRC |
|
11 |
06 |
00 |
01 |
00 |
03 |
9A |
9B |
从站
注:返回帧与主站相同
功能码:15 强制多路开出,给PLC写数据
例:给17站从20号线圈开始的10个单线圈写ON,MB地址:20-1=19=0x13H
数据:
帧中位 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
位置 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
本例位置 |
27 |
26 |
25 |
24 |
23 |
22 |
21 |
20 |
- |
- |
- |
- |
- |
- |
29 |
28 |
从站地址 |
功能码 |
H 地址 |
L 地址 |
H线圈数量 |
L线圈数量 |
字节数 |
H数据 |
L数据 |
CRC |
|
11 |
0F |
00 |
13 |
00 |
0A |
02 |
CD |
01 |
BF |
0B |
主站
从站地址 |
功能码 |
H 地址 |
L 地址 |
H线圈数量 |
L线圈数量 |
CRC |
|
11 |
05 |
00 |
13 |
00 |
0A |
4E |
8B |
从站
注:变量写的从站反馈没有数据
功能码:16 强制多路模出到保持寄存器,给PLC写数据
每个寄存器的数据为2个字节。
例:给17站从40002HR开始的2个寄存器,数据为000A和0102,MB地址:40002-1=40001=MB1
从站地址 |
功能码 |
H地址 |
L地址 |
H模入数量 |
L模入数量 |
字节数 |
H |
L |
H |
L |
CRC |
|
11 |
10 |
00 |
01 |
00 |
02 |
04 |
00 |
0A |
01 |
02 |
C6 |
F0 |
主站
从站地址 |
功能码 |
H 地址 |
L 地址 |
H线圈数量 |
L线圈数量 |
CRC |
|
11 |
10 |
00 |
01 |
00 |
02 |
12 |
98 |
从站
注:变量写的从站反馈没有数据
秦批:Modbus的从站反馈数据没有数据帧的编号(不像TCP协议那样),所以不会显示这个反馈数据是主站的哪次请求的。若主站一次发送N个数据,从站而没有及时反馈,这样就乱了,主站就不知道从站发过来的数据是哪次命令的。
单变量与多变量:读可以读单读多,命令不分读几个。但是写要区分写一个还是写多个,所以写命令有写单线圈和写多线圈。
Modbus所有功能码
功能码 |
名称 |
作用 |
01 |
读取线圈状态 |
取得一组逻辑线圈的当前状态(ON/OFF) |
02 |
读取输入状态 |
取得一组开关输入的当前状态(ON/OFF) |
03 |
读取保持寄存器 |
在一个或多个保持寄存器中取得当前的二进制值 |
04 |
读取输入寄存器 |
在一个或多个输入寄存器中取得当前的二进制值 |
05 |
强置单线圈 |
强置一个逻辑线圈的通断状态 |
06 |
预置单寄存器 |
把具体二进值装入一个保持寄存器 |
07 |
读取异常状态 |
取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态 |
08 |
回送诊断校验 |
把诊断校验报文送从机,以对通信处理进行评鉴 |
09 |
编程(只用于484) |
使主机模拟编程器作用,修改PC从机逻辑 |
10 |
控询(只用于484) |
可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送 |
11 |
读取事件计数 |
可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时 |
12 |
读取通信事件记录 |
可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误 |
13 |
编程(184/384 484 584) |
可使主机模拟编程器功能修改PC从机逻辑 |
14 |
探询(184/384 484 584) |
可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送 |
15 |
强置多线圈 |
强置一串连续逻辑线圈的通断 |
16 |
预置多寄存器 |
把具体的二进制值装入一串连续的保持寄存器 |
17 |
报告从机标识 |
可使主机判断编址从机的类型及该从机运行指示灯的状态 |
18 |
(884和MICRO 84) |
可使主机模拟编程功能,修改PC状态逻辑 |
19 |
重置通信链路 |
发生非可修改错误后,是从机复位于已知状态,可重置顺序字节 |
20 |
读取通用参数(584L) |
显示扩展存储器文件中的数据信息 |
21 |
写入通用参数(584L) |
把通用参数写入扩展存储文件,或修改之 |
22~64 |
保留作扩展功能备用 |
|
65~72 |
保留以备用户功能所用 |
留作用户功能的扩展编码 |
73~119 |
非法功能 |
|
120~127 |
保留 |
留作内部作用 |
128~255 |
保留 |
用于异常应答 |
ModBus功能码与数据类型对应表
代码 |
功能 |
数据类型 |
01 |
读 |
位 |
02 |
读 |
位 |
03 |
读 |
整型、字符型、状态字、浮点型 |
04 |
读 |
整型、状态字、浮点型 |
05 |
写 |
位 |
06 |
写 |
整型、字符型、状态字、浮点型 |
08 |
N/A |
重复“回路反馈”信息 |
15 |
写 |
位 |
16 |
写 |
整型、字符型、状态字、浮点型 |
17 |
读 |
字符型 |
Modbus变量地址
映射地址 |
功能 |
地址类型 |
存取方式 |
描述 |
0xxxx |
01,05,15 |
Coil |
读写 |
|
1xxxx |
02 |
离散输入 |
只读 |
|
2xxxx |
03,04,06,16 |
浮点寄存器 |
读写 |
两个连续16位寄存器表示一个浮点数(ieee754格式32位) |
3xxxx |
04 |
输入寄存器 |
只读 |
每个寄存器表示一个16位无符号整数(0~65535)同上 |
4xxxx |
03,06,16 |
保持寄存器 |
读写 |
|
5xxxx |
03,04,06,16 |
ascii字符 |
读写 |
每个寄存器表示两个ascii字符 |
FAQ0:ABB组态软件有功能块读线圈-1,读线圈-8,读寄存器-1,读寄存器-8的原因是什么?
因为Modbus功能码的读操作可以指定数据长度。如果从站的地址连续,则可以用一次读八个变量的操作。如果不连续,可以一次读一个。读八个的效率要高一些。
FAQ1 :MB地址的问题:MB经常用30001,40001这样的地址,WHY?给个理由。
ANS1 :地址以3开头,如IFIX的mb1中地址用30001、3001、300001表示的是功能码4的操作,即读输入寄存器。同理, 地址以4开头表示的是功能码3的操作,即对指的是对输出寄存器/内部寄存器进行的操作。虽然Modbus有两个字节表示地址0xFFFF,共64K,但是一般用不了那么多的。
例:高安屯垃圾电厂DCS Modbus通讯点表
DCS-TAG_№ |
DESCRIPTION |
Type |
Data |
Addr. |
XA 0FG10-1 |
ACTIVATED CARBON STORAGE SILO VENTING FILTER FAN FAULT |
DI |
BOOL |
00407 |
HC #RC10-O |
No.# INLET DAMPER FOR NID SYSTEM A IN SERVICE COMMAND |
DO |
BOOL |
10001 |
ATO #FG03-S |
No.# SET POINT SO2 EMISSION |
AO |
INT |
30002 |
AI #RC01 |
No.# REACTOR INLET GAS SO2 ANALYZER |
AI |
INT |
40001 |
注:地址以2开头表示浮点数,以0,1,3,4开头表示整数。
FAQ2 : Modbus功能码的名称来源是什么?
ANS2: 因为Modbus主要用于与PLC通讯,所以Modbus的地址空间命名也服从PLC的方式。我一起在弄DCS,没玩过PLC。所以没明白这个道理之前,根本搞不明白Modbus的功能码要那么叫,经过两天多的研究研究研究,终于大彻大悟了。
功能码 |
名称 |
注释 |
|
01 |
Read Coil Status |
读PLC的开出状态,可能是内部量 |
可读可写 |
02 |
Read Input Status |
读PLC的开入DI点状态 |
只读DI,通道来 |
03 |
Read Holding Register |
读PLC内部模拟量 |
|
04 |
Read Input Register |
读PLC的模入AI状态 |
只读AI,通道来 |
05 |
Write Single Coil |
给PLC写开关量数据 |
写单个位 |
06 |
Write Single Register |
给PLC写模拟量数据 |
写模拟量 |
15 |
Write Multiple Coil |
给PLC写多个开关量数据 |
写多个位 |
16(0x10H) |
Write Multiple Register |
给PLC写多个模拟量数据 |
写多个模拟量 |
列出PLC的线圈与寄存器的分配如下,以三菱FX2n介绍:
一般 |
500点 |
M0到M499 |
|
锁定 |
2572点 |
M384至M3071 |
|
特殊 |
256点 |
M8000至8255 |
|
状态继电器 |
一般 |
490点 |
S0至S499 |
锁定 |
400点 |
S500至S899 |
|
初始 |
10点 |
S0至S9 |
|
信号报警器 |
100点 |
S900至S999 |
|
数据寄存器(D) |
一般 |
200点 |
D0至D199 |
锁定 |
7800点 |
D200至D7999 |
|
文件寄存器 |
7000点 |
D1000至D7999通过14块500程式步的参数设置类型:16位数据存储寄存器 |
|
特殊 |
256点 |
从D8000至D8255 |
|
变址 |
16点 |
V0至V7和Z0至Z7 |
松下FP1—C40寄存器I/O配置表
名称 |
符号 |
编号(地址) |
功能说明 |
|
外部 输入/ 输出 继电 器 |
X(位) |
X0~X12F (主机X0~X17) |
输入继电器 总点数208点,主机24点,用来存储外部输入信号 |
|
WX(字) |
WX0~WX12(13个字) |
|||
Y(位) |
Y0~Y12F(主机Y0~YF) |
输出继电器 总点数208点,主机16点,用来存储程序运行结果并输出 |
||
WY(字) |
WY0~WY12(13个字) |
|||
内 部 继 电 器 |
R(位) |
R0~R62F |
通用内部继电器 只能在PLC内部供用户编程使用,不能用于输出 |
|
WR(字) |
WR0~WR62 |
|||
R(位) |
R9000~R903F |
特殊内部继电器 每个继电器均具有特殊用途,用 户只能使用其接点,不能用程序 控制其状态,不能用于输出 |
||
数据 寄存器 |
DT(字) |
DT0~DT1659(1,660字) |
通用数据寄存器 用来存储PLC内处理的数据 |
|
DT9000~DT9069(70字) |
特殊数据寄存器 具有特殊用途的数据寄存器,不能存储用户数据 |