基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)

kostenlosKostenloses technisches Material

Dieser Inhalt ist direkt lesbar und eignet sich für das Grundlagenlernen und die Suche.

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)缩略图
🌐 Diese Seite ist noch nicht auf Deutsch verfügbar. Die chinesische Version wird angezeigt. Zurück zur chinesischen Seite.

Modbus作为开放式的工业通讯Vereinbarung,在各种工业Ausrüstung中应用极其广泛。本人也使用Modbus通讯很多Jahr了,或者用现成的,或者针对具体应用开发,一直以来都想要开发一个比较通用的Vereinbarung栈能在后续的项目中复用,而不必每次都写一遍。现在利用项目研发的机会,开发一个自己的ModbusVereinbarung栈。

Modbus有国际标准,也有国家标准,内容是完全一样的。在标准钟支持2种物理链路:一是基于RS485(RS232)的串行链路;二是基于Ethernet的TCP/IP链路。事实上,ModbusVereinbarung作为一种Anwendungsschichtprotokoll对物理键子并没有特别的要求,光纤、无线等都是可以实现的。

本次主要是开发Modbus RTUModbus TCP两种标准Vereinbarung方式,支持常用的Funktionscode:

FunktionscodeName实现描述
0x01读线圈对可读写型的StatusQuantität进行Lesen
0x02Lesen Sie getrennte Eingabe对只读型的StatusQuantität进行Lesen
0x03Lesen Sie das Register对可读写型的Register.Quantität进行Lesen
0x04Eingangsregister lesen对只读型的Register.Quantität进行Lesen
0x05Schreiben Sie eine einzelne Schleife.对单个的读写型的StatusQuantität进行写入
0x06Schreiben eines einzelnen Registers对单个的读写型的Register.Quantität进行写入
0x0FSchreiben Sie mehrere Schleife.对多个的读写型的StatusQuantität进行写入
0x10Schreiben mehrerer Register对多个的读写型的Register.Quantität进行写入

ModbusVereinbarung是一种主从(或者说客户端/Der Server)模式Vereinbarung,有Hauptstation(客户端)发起事务Bitte,Von der Station(Der Server)Antwort事务Bitte。一般我们想要访问(或者说被访问)Die Ausrüstung即为Von der Station(Der Server),而我们通常去访问别人Die Ausrüstung就是Hauptstation(客户端)。通常在RTU时被称为Hauptstation和Von der Station,而在TCP方式时被称为Der Server和客户端。这种称呼只是叫法不同,但在本质上是没有区别我的。后续这两种称呼我们会同时使用。在这次开发中,我们计划同时实现Hauptstation和Von der Station的Funktion。

1、标准流程

启动MODBUS 事务处理的客户机创建 MODBUS 应用Daten单元。当从客户机向Der ServerAusrüstungSenden SieMeldung时,Funktionscode向Der Server指示将执行哪种Operationen。

从客户机向Der ServerAusrüstungSenden Sie的MeldungDaten Domäne包括附加信息,Der Server使用这个信息执行Funktionscode定义的Operationen。如果在一个Richtig.Empfang的 MODBUS ADU 中,不出现与Bitte MODBUS Funktion有关的schlecht错,那么Der Server至客户机的AntwortDaten Domäne包括BitteDaten。

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图

正常事务处理流程

如果出现与Bitte MODBUS Funktion有关的schlecht错,那么域包括一个Ungewöhnlicher Code,Der Server应用能够使用这个域确定下一个执行的Operationen。当Der Server对客户机Antwort时,它使用Funktionscode域来指示正常(无schlecht错)Antwort或者出现某种schlecht错(称为Ungewöhnliche Reaktion)。对于一个正常Antwort来说,Der Server仅对原始FunktionscodeAntwort。

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图1

异常事务处理流程

2、Operationen设计

根据我们对Modbus标准事务流程的理解,我们来设计Hauptstation和Von der Station的具体Operationen过程。

HauptstationOperationen流程:

(1)、初始化Hauptstation,站标识符,定时器、

(2)、生成访问Befehl

Befehle erzeugenADUUnterteilung两层实现:网络层和链路层。

网络层实现与具体链路无关的PDU(包括Funktionscode和Daten,这里的Daten指传送的Daten,并非指变Quantität的值,事实上Anzahl和Adresse也包含在内)。对应的文件是mbpdu.c/.h,实现对pud单元的Daten处理,包括Befehl和Antwort。

链路层实现ADU的封装。对应的文件是mbrtu.c/.h(和mbtcp.c/.h和mbascii.c/.h),实现ADU单元的封装;同时作为Von der Station时,写Operationen的Antwort也在此处封装。

(3)、定时Senden Sie访问Befehl,读Befehl定是轮询,写Befehl具有更高的优先级。在有写Befehl时暂停读Befehl,

(4)、AnalyseEmpfangzu的Daten,进行相应Operationen

Von der StationOperationen流程:

(1)、Vereinbarung栈初始化:von der Station Adresse设置,创建Daten存储域,访问控制

(2)、Von der StationEmpfang消息并Analyse

Analyse消息后OperationenDaten(读写对应对象的值)

Erstellen von Reaktionen

(3)、Erstellen von ReaktionenDaten并回复

3、BefehlDas Format

不同的Funktionscode具有不同的消息Das Format,我们只简单说一下我们将要实现的以上8中Funktionscode的信息Das Format。

(1)Funktionscode0x01:读线圈

在一个Fern entferntAusrüstung中,使用该FunktionscodeLesen线圈的1至2000个连续Status。Bitte PDU详细说明了Ausgang Adresse,即指定的Die一个Schleife Adresse和线圈编Nr.。从零开始寻址线圈。因此寻址线圈 1-16 为 0-15。

根据Daten Domäne的每个比特将AntwortMeldung中的线圈Unterteilung成为一个线圈。指示Status为 1= ON 和 0= OFF。Die一个DatenBytes的 LSB(最低Wirksam位)包括在询问中寻址的输出。其它线圈依次类推,一直zu这个Bytes的高位端为止,并在后续Bytes中从低位zu高位的顺序。

如果返回的输出Anzahl不是八的倍数,将用零填充最后DatenBytes中的剩余比特(一直zuBytes的高位端)。Anzahl der BytesQuantität域说明了Daten的vollständigAnzahl der Bytes。具体Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图2

(2)Funktionscode0x02:读离散Quantität输入

在一个Fern entferntAusrüstung中,使用该FunktionscodeLesen离散Quantität输入的 1 bis zu 2000 连续Status。Bitte PDU 详细说明了Ausgang Adresse,即指定的Die一个输入Adresse和输入编Nr.。从零开始寻址输入。因此寻址输入 1-16 为 0-15。

根据Daten Domäne的每个比特将AntwortMeldung中的离散Quantität输入Unterteilung成为一个输入。指示Status为1=ON 和0=OFF。Die一个DatenBytes的 LSB(最低Wirksam位)包括在询问中寻址的输入。其它输入依次类推,一直zu这个Bytes的高位端为止,并在后续Bytes中从低位zu高位的顺序。

如果返回的输入Anzahl不是八的倍数,将用零填充最后DatenBytes中的剩余比特(一直zuBytes的高位端)。Anzahl der BytesQuantität域说明了Daten的vollständigAnzahl der Bytes。具体Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图3

事实上,Funktionscode0x01和0x02的Datenformate是完全一样的,Operationen方式也是完全一样的,所不同只是Operationen的对象不同。

(3)Funktionscode0x03:Lesen Sie das Register

在一个Fern entferntAusrüstung中,使用该FunktionscodeLesen und halten Register连续块的内容。Bitte PDU 说明了Start-Register - Adresse和Anzahl der Register。从零开始寻址Register.。因此,寻址Register. 1-16 为 0-15。

将AntwortMeldung中的RegisterdatenUnterteilung成每ein Register.有两Bytes,在每个Bytes中直接地调整Binärsystem内容。

对于每ein Register.,Die一个Bytes包括高位比特,并且Die二个Bytes包括低位比特。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图4

Antwort信息的Länge与具体Lesen的DatenQuantität有关。

(4)Funktionscode0x04:Eingangsregister lesen

在一个Fern entferntAusrüstung中,使用该FunktionscodeLesen 1 至大约 125 的连续输入Register.。Bitte PDU 说明了Ausgang Adresse和Anzahl der Register。从零开始寻址Register.。因此,寻址输入Register. 1-16 为 0-15。

将AntwortMeldung中的RegisterdatenUnterteilung成每ein Register.为两Bytes,在每个Bytes中直接地调整Binärsystem内容。

对于每ein Register.,Die一个Bytes包括高位比特,并且Die二个Bytes包括低位比特。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图5

事实上,Funktionscode0x03和0x04的Datenformate是完全一样的,Operationen方式也是完全一样的,所不同只是Operationen的对象不同。Antwort信息的Länge与具体Lesen的DatenQuantität有关。

(5)Funktionscode0x05:Schreiben Sie eine einzelne Schleife.

在一个Fern entferntAusrüstung上,使用该Funktionscode写单个输出为 ON 或 OFF。

BitteDaten Domäne中的常Quantität说明Bitte的ON/OFFStatus。16. System值0xFF00Bitte输出为 ON。16. System值0x0000 Bitte输出为OFF。其它所有值均是非法的,并且对输出不起作用。

Bitte PDU 说明了强制的Schleife Adresse。从零开始寻址线圈。因此,寻址线圈1为0。线圈值域的常Quantität说明Bitte的 ON/OFF Status。16. System值 0XFF00 Bitte线圈为 ON。16. System值 0X0000 Bitte线圈为OFF。其它所有值均为非法的,并且对线圈不起作用。

正常Antwort是Bitte的应答,在写入Zustand der Coil之后返回这个正常Antwort。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图6

(6)Funktionscode0x06:Schreiben eines einzelnen Registers

在一个Fern entferntAusrüstung中,使用该Funktionscode写单个保持Register.。

Bitte PDU 说明了被写入Register.的Adresse。从零开始寻址Register.。因此,寻址Register.1为0。

正常Antwort是Bitte的应答,在写入Register Inhalt之后返回这个正常Antwort。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图7

(7)Funktionscode0x0F:Schreiben Sie mehrere Schleife.

在一个Fern entferntAusrüstung中,使用该Funktionscode强制线圈Sequenz中的每个线圈为ON或OFF。BittePDU说明了强制的线圈参考。从零开始寻址线圈。因此,寻址线圈1为0。

BitteDaten Domäne的内容说明了被Bitte的ON/OFFStatus。域比特位置中的逻辑“1”Bitte相应输出为ON。域比特位置中的逻辑“0”Bitte相应输出为 OFF。

正常Antwort返回Funktionscode、Ausgang Adresse和强制的线圈Anzahl。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图8

(8)Funktionscode0x10:Schreiben mehrerer Register

在一个Fern entferntAusrüstung中,使用该Funktionscode写连续Register.块(1至约120ein Register.)。

在BitteDaten Domäne中说明了Bitte写入的值。每ein Register.将DatenUnterteilung成两Bytes。

正常Antwort返回Funktionscode、Ausgang Adresse和被写入Register.的Anzahl。具体的Das Format举例如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图9

清楚了以上这些说明,我们就可以开始ModbusVereinbarung的开发之旅了。

前面我们已经对Modbus的基本事务作了说明,也据此设计了我们将要实现的主Von der Station的Operationen流程。这Von denen与Modbus直接Related的就是Modbus消息帧的生成。Modbus消息帧也是实现Modbus通讯Vereinbarung的根本。

1Modbus消息帧Unterteilung析

MODBUSVereinbarung在不同的物理链路上的消息帧有一些schlecht异,但我们Unterteilung析一下就会发现,在这些不同的消息帧中具有一下相同的部Unterteilung,这对我们实现统一的DatenOperationen非常重要,具体描述如下:

1)、简单VereinbarungDaten单元

MODBUSVereinbarung定义了一个与基础Kommunikation层无关的简单VereinbarungDaten单元(PDU)。简单VereinbarungDaten单元的结构如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图10

PDU是一个与具体的传输网络无关的部Unterteilung,包含Funktionscode和Daten。对于特定总线或网络上的 MODBUS Vereinbarung只是在PDU的基础上在应用Daten单元(ADU)上引入一些附加域。

Daten单元部Unterteilung的开发是最基本的部Unterteilung,主要是2各方面的类容:一是生成客户端(Hauptstation)访问Der Server(Von der Station)的Befehl部Unterteilung;二是生成Der Server(Von der Station)Antwort客户端(Hauptstation)回复部Unterteilung。

2)、RTU的应用Daten单元

对于在串行链路上Betrieb的ModbusVereinbarung,其应用Daten单元(ADU)是在PDU的基础上,在前面加上Adresse域,后面加上DatenPrüfungen。Das Format如下图所示:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图11

Adresse域就是所访问Von der Station的Adresse,为一个8位无符Nr.数,取值0-255,但0和255有固定含义不能使用。CRC-Test采用的是CRC16Prüfungen方式。

3)、TCP的应用Daten单元

在Ethernet链路上Betrieb的ModbusVereinbarung,其应用Daten单元(ADU)是在PDU的基础上添加上MBAPZeitung Kopf形成的,具体Das Format如下图:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图12

对于MBAP Zeitung Kopf,包括下列域:

Länge描述客户机Der Server
事务元标识符2 个BytesMODBUS Bitte/Antwort事务处理的识别码客户机启动Der Server从Empfang的Bitte中重新Kopieren.
Protokoll-Bezeichner2 个Bytes0=MODBUS Vereinbarung客户机启动Der Server从Empfang的Bitte中重新Kopieren.
Länge2 个Bytes以下Bytes的Anzahl客户机启动(Bitte)Der Server(Antwort)启动
Einheitenidentifikator1 个Bytes串行链路或其它总线上Verbindung的Fern entferntVon der Station的识别码客户机启动Der Server从Empfang的Bitte中重新Kopieren.

从上表中可知Zeitung Kopf为 7 个Bytes长:

事务处理标识符:用于事务处理配对。在Antwort中,MODBUS Der ServerKopieren.Bitte的事务处理标识符。
Protokoll-Bezeichner:用于Das System内的多路复用。通过值 0 识别 MODBUS Vereinbarung。

Länge:Länge域是下一个域的Anzahl der Bytes,包括Einheitenidentifikator和Daten Domäne。

Einheitenidentifikator:为了Das System内路由,使用这个域。专门用于通过EthernetTCP-IP 网络和MODBUS串行链路之间的网关对MODBUS或MODBUS+串行链路Von der Station的Kommunikation。说的简单点就是串行链路中的Adresse域。MODBUS客户机在Bitte中设置这个域,在Antwort中Der Server必须利用相同的值返回这个域。

2、Daten帧的具体组成Unterteilung析

从以上对简单Vereinbarung基本Daten元、RTU应用Daten单元和TCP应用Daten单元MeldungDas Format的Unterteilung析,我们发现对于基本Daten单元部Unterteilung已一致的,所以我们可以考虑来Unterteilung层封装VereinbarungOperationen部Unterteilung:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图13

最开始实现Modbus基本Daten单元,这是Daten公用部Unterteilung与具体的应用无关,只需要封装一次,对于这部Unterteilung的开发只需要按照Modbus的标准Vereinbarung来开发就好,本次我们计划实现的Funktion有8个:

FunktionscodeName实现描述
0x01读线圈对可读写型的StatusQuantität进行Lesen
0x02Lesen Sie getrennte Eingabe对只读型的StatusQuantität进行Lesen
0x03Lesen Sie das Register对可读写型的Register.Quantität进行Lesen
0x04Eingangsregister lesen对只读型的Register.Quantität进行Lesen
0x05Schreiben Sie eine einzelne Schleife.对单个的读写型的StatusQuantität进行写入
0x06Schreiben eines einzelnen Registers对单个的读写型的Register.Quantität进行写入
0x0FSchreiben Sie mehrere Schleife.对多个的读写型的StatusQuantität进行写入
0x10Schreiben mehrerer Register对多个的读写型的Register.Quantität进行写入

这8个也是ModbusVereinbarung所定义的最主要的Funktion,现在对这几种Funktionscode的MeldungDas Format描述如下:

1)读线圈0x01

读线圈就是都一种可以写的Schalter!Quantität,因为ModbusUrsprung der Vereinbarung于PLC应用,而线圈是对PLC的DO输出的称呼,一般适用于Hauptstation对Von der Station下达OperationenBefehl。读这种具有读写Funktion的StatusQuantität的Datenformate如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图14

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+Anzahl。

2)Lesen Sie getrennte Eingabe0x02

读Status输入是Lesen一种只读Schalter!Quantität信Nr.,对应于PLC中的数字输入Quantität。Lesen这种只读型Schalter!输入Quantität的Das Format如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图15

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+Anzahl。

3)Lesen Sie das Register0x03

保持Register.就是指可以读写的16位Daten,通过单个或多个保持Register.可以用来表示各种Daten,如8位整数、16为整数、32位整数、64位整数以及单双精度浮点数等。Lesen und halten Register的MeldungDas Format如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图16

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+Anzahl。

4)Eingangsregister lesen0x04

输入Register.是一种只读形式的16位Daten。通过单个或多个输入Register.可以表示8位整数、16为整数、32位整数、64位整数以及单双精度浮点数等。Eingangsregister lesen的MeldungDas Format如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图17

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+Anzahl。

5)Schreiben Sie eine einzelne Schleife.0x05

Schreiben Sie eine einzelne Schleife.Quantität就是对单个的可读写的Schalter!Quantität进行Operationen,但是其并非是直接写“0”或者“1”,而是在需要写“1”时Senden Sie0xFF00;而在需要写“0”时Senden Sie0x0000,其具体的MeldungDas Format如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图18

其下发的BefehlDas Format为:域名+Funktionscode+输出Adresse+输出值。Befehl的具体内容与读Operationen有区别但,Das Format却是完全一样,在编程时实际读和写可以封装在一起。

6)Schreiben eines einzelnen Registers0x06

Schreiben eines einzelnen Registers就是对单个的保持Register.进行Operationen,Daten的Das Format依然是一样的,实际应用中只适用于对16位整型Daten的Operationen,对于浮点数等则不可以。

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图19

其下发的BefehlDas Format为:域名+Funktionscode+输出Adresse+输出值。Befehl的具体内容与读Operationen有区别但,Das Format却是完全一样,在编程时实际读和写可以封装在一起。

7)Schreiben Sie mehrere Schleife.0x0F

Schreiben Sie mehrere Schleife.的Operationen对象与Schreiben Sie eine einzelne Schleife.是完全一样的,不同的是Anzahl和Operationen值,特别是值,写“1”就是“1”,写“0”就是 “0”,这是与Schreiben Sie eine einzelne Schleife.的区别。

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图20

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+输出Anzahl+Anzahl der Bytes+输出值。BefehlMeldung与前面的几种读写Operationen有较大的区别,必须要单独处理。

8)Schreiben mehrerer Register0x10

Schreiben mehrerer Register的就是对多个可读写Register.同时进行Operationen,DatenMeldung的Das Format与Schreiben Sie mehrere Schleife.是一致的。

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图21

其下发的BefehlDas Format为:域名+Funktionscode+Ausgang Adresse+输出Anzahl+Anzahl der Bytes+输出值。

3、基本Daten单元的编程

经过上面的Unterteilung析,我们发现不论是在什么样的物理链路上实现的应用Daten,器基本Daten段都是相同的。其实加上域名段的Das Format也是相同的,所以我们就将

域名+PDU一起作为最基本的Daten单元来实现。

对于基本Daten单元的实现由Unterteilung为2种情况:一是作为Hauptstation(客户端)时,对Von der Station(Der Server)的下发Befehl;二是作为Von der Station(Der Server)时,对Hauptstation(客户端)Befehl的Antwort。所以我们将这两种情况Unterteilung别封装为2个基础函数:

(1)、作为RTUHauptstation(TCP客户端)时,生成读写RTUVon der Station(TCPDer Server)对象的Befehl:

uint16_t GenerateReadWriteCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

ParameterUnterteilung别是PDU单元的基本信息,写对象的对应Daten,以及erzeugte Befehle.Bytes。而返回值则是erzeugte Befehle.的Länge。

(2)、作为Von der Station(Der Server)时,生成Hauptstation读访问的Antwort。对于Antwort因为写Operationen的Antwort实际上就是Kopieren.Hauptstation(客户端)的Befehl的一部Unterteilung,所以我们实际需要生成的Antwort是包括0x01、0x02、0x03、0x04Funktionscode的情形。

uint16_t GenerateMasterAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

ParameterUnterteilung别是Empfangzu的信息,Lesen的对象的Daten,以及返回的Antwort消息。而返回值则是返回的Antwort消息的Länge。

4RTU应用Daten单元的编程

对于RTU应用Daten单元来说,其MeldungDas Format就是:“域名+PDU+CRC”,而域名+PDU我们在上一节中已经实现了,所以要实现RTU的Daten单元实际上我们只需要加上CRC-Test就已经abgeschlossen.了。

对于RTUDaten单元的实现由Unterteilung为2种情况:一是作为Hauptstation时,对Von der Station的下发Befehl;二是作为Von der Station时,对HauptstationBefehl的Antwort。所以我们将这两种情况Unterteilung别封装为2个基础函数:

(1)、作为RTUHauptstation时,生成读写RTUVon der Station对象的Befehl:

/*生成读写Von der StationDaten对象的Befehl,BefehlLänge包括2个PrüfungenBytes*/

uint16_t SyntheticReadWriteSlaveCommand(ObjAccessInfo slaveInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

ParameterUnterteilung别是Von der Station基本信息,下发的Daten列表,以及最终erzeugte Befehle.数组。返回值是是Befehl的Länge。

(2)、作为Von der Station时,生成Hauptstation读访问的Antwort:

/*生成Von der Station应答Hauptstation的Antwort*/

uint16_t SyntheticSlaveAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

ParameterUnterteilung别是Empfangzu的信息,返回的Daten列表,生成的Antwort信息列表。返回值是Antwort信息列表的Länge。

5TCP应用Daten单元的编程

而对于TCP应用Daten单元来说,与RTU类式,起MeldungDas Format是:“MBAP Kopf+PDU”,而PDU单元就是前面定义的,所以只需要加上MBAP Kopf部就可以了,事实上MBAP Kopf部的实现Das Format是固定的。

对于TCP应用Daten单元的实现同样Unterteilung为2中情况:一是作为客户端时,对Der Server的下发Befehl;二是作为Der Server时,对客户端Befehl的Antwort。所以我们将这两种情况Unterteilung别封装为2个基础函数:

(1)、作为TCP客户端时,生成读写TCPDer Server对象的Befehl:

/*生成读写Der Server对象的Befehl*/

uint16_t SyntheticReadWriteTCPServerCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

(2)、作为(Der Server时,生成客户端读写访问的Antwort:

/*合成对Der Server访问的Antwort,返回值为BefehlLänge*/

uint16_t SyntheticServerAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

6、Ende语

其实zu这里我们对Modbus基本Vereinbarung已经基本实现,甚至使用这些grundlegenden Betrieb也能实现Modbus的通讯。事实上很多人在应用写的Modbus通讯Vereinbarung比这还要简单,也能实现部Unterteilung的Modbus通讯Funktion。当然这不是我们的目标,否则就不需要专门开发库了,我们要进一步封装,让其更通用也更易用才是我们需要的。

在abgeschlossen.了前面的工作后,我们就可以实现有针对性的应用了,首先我们来实现Modbus TCP的Der Server端应用。当然我们不是做具体的应用,而是对Modbus TCP的Der Server端应用进行封装以供有需要时调用。

这里我们不涉及TCP的Vereinbarung,这部Unterteilung与Modbus没有必然联系,我们只是在其应用层BetriebModbusVereinbarung而已。

对于Modbus TCP的Der Server我们需要实现几个Funktion:首先是对Empfangzu客户端Befehl进行Analyse,我们只实现前面提zu的8中常用的Funktion吗的支持。其次在Analyseabgeschlossen.后,我们要实现对应各种Funktionscode的Operationen。具体架构如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图22

1、BefehlAnalyse

Der Server作为被动端Empfangzu客户端的Bitte后,安装Bitte进行处理。所以Der ServerEmpfangzu信息后首先对其进行Analyse,这里我们只需要一个Analyse函数就可以abgeschlossen.,当在具体应用中时只要将Empfangzu的信息调用这个函数Analyse就可以了。

/*AnalyseEmpfangzu的信息,Zurück zur AntwortBefehl的Länge*/

uint16_t ParsingClientAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes)

其实对外来说只有这一个函数时可见的。当我们要开发一个TCP Server的应用时,就调用这个函数。

2、Befehl处理

BefehlAnalyse出来了之后,按不同的Funktionscode来进行不同的处理,我们支持8种Funktionscode,每种Funktion吗都对应处理部Unterteilung,很多时候大家都会在Analyse的时候根据Analyseergebnisse处理,这势必需要一个很大的Analyse函数。为了简化Operationen,我们将每个Funktionscode对应的处理部Unterteilung都封装为一个函数。然后使用一个函数指针数组来动态调用这些函数,从而简化这些函数。

首先我们需要8个处理对应个FunktionscodeOperationen的函数:

/*处理Lesen Sie den ZustandBefehl*/

static uint16_t HandleReadCoilStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理读Importieren StatusBefehl*/

static uint16_t HandleReadInputStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Lesen Sie das RegisterBefehl*/

static uint16_t HandleReadHoldingRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Eingangsregister lesenBefehl*/

static uint16_t HandleReadInputRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben Sie eine einzelne Schleife.Befehl*/

static uint16_t HandleWriteSingleCoilCommand(uint16_t coilAddress,uint16_t coilValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben eines einzelnen RegistersBefehl*/

static uint16_t HandleWriteSingleRegisterCommand(uint16_t registerAddress,uint16_t registerValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben Sie mehrere Schleife.Status*/

static uint16_t HandleWriteMultipleCoilCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben mehrerer RegisterStatus*/

static uint16_t HandleWriteMultipleRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

然后我们定义一个函数指针数组来调用这下函数:

uint16_t (*HandleClientCommand[])(uint16_t,uint16_t,uint8_t *,uint8_t *)={HandleReadCoilStatusCommand,

                                                                          HandleReadInputStatusCommand,

                                                                          HandleReadHoldingRegisterCommand,

                                                                          HandleReadInputRegisterCommand,

                                                                          HandleWriteSingleCoilCommand,

                                                                          HandleWriteSingleRegisterCommand,

                                                                          HandleWriteMultipleCoilCommand,

                                                                          HandleWriteMultipleRegisterCommand};

3、Antwort的生成

对于AntwortBefehl的生成其实在Die二篇中已经说过了,需要说明的是各种Schreiben von Daten的具体Zahlenwert以及获得Lesen von Daten的各种具体Zahlenwert我们将在单独的文件中去实现,因为这部UnterteilungTCP和RTU是相同的,我们将在后续的篇Kapitel中说明。

这一次我们封装Modbus TCP Client应用。同样的我们也不是做具体的应用,而是实现TCP客户端的基本Funktion。我们将TCP客户端的Funktion封装为函数,以便在开发具体应用时调用。

对于TCP客户端我们主要实现的Funktion有两个:其一是生成访问TCPDer Server的Befehl,总共支持8中Funktionscode。其二是对TCPDer Server端返回的信息进行Analyse并根据Ergebnis进行各种Operationen,同样也是支持8中Funktion吗的Operationen。具体软件访问结构如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图23

1、访问Befehl的生成

客户端作为主动交互端,需要向Der Server发各种OperationenBitteBefehl。所以对于TCP客户端来说,首先要生成访问Der Server的Befehl。Befehle erzeugen只需要按标准的VereinbarungDas Format来生成即可,目前我们只支持前面提zu的8个Funktionscode。

/*生成访问Der Server的Befehl*/

uint16_t CreateAccessServerCommand(ObjAccessInfo objInfo,void *dataList,uint8_t *commandBytes)

这样在开发具体的客户端应用时只需要调用这个函数来生成访问Der Server的Befehl就可以了。

2、Antwort信息的Analyse

如前一节所述,Der Server收zuBefehl后,根据Befehl进行处理并Erstellen von Reaktionen信息返回给客户端。客户端接zuBefehl后首先要对Antwort信息进行Analyse,Analyse的过程其实与Der Server端是一致的。所不同的是,不需要再根据AnalyseergebnisseErstellen von Reaktionen信息了。

/*Analyse收zu的Der Server相应信息*/

void ParsingServerRespondMessage(uint8_t *recievedMessage)

这样在开发客户端应用时,我们调用这一函数来AnalyseAntwort信息就可以了。

3、Antwort处理

对于Analyse出来的信息,我们需要根据情况实现Operationen,比如修改变Quantität的值等,应为主要支持的Operationen码是8个,理论上对应的每种Funktionscode都会有不同的Operationen,但事实上,由于写OperationenBefehl已经不需要做任何Operationen了,所以对应的Operationen实际上只有读Operationen的4种Funktionscode。

/*处理读Von der StationStatusQuantität返回信息,Lesen Sie den Zustand位0x01Funktionscode*/

static void HandleReadCoilStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationStatusQuantität返回信息,读Importieren Status位0x02Funktionscode*/

static void HandleReadInputStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationRegisterwert的返回信息,Lesen Sie das Register0x03Funktionscode)*/

static void HandleReadHoldingRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationRegisterwert的返回信息,Eingangsregister lesen0x04Funktionscode*/

static void HandleReadInputRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

同样的,我们也定义一个函数指针数组来实现这些函数的调用:

void (*HandleServerRespond[])(uint8_t *,uint16_t,uint16_t)={HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};

如此,TCP客户端的封装就abgeschlossen.了,当然具体的Daten处理部Unterteilung需要在开发具体应用是才能确定。

Modbus在串行链路上Unterteilung为Slave和Master,这一节我们就来开发Slave。对于Modbus RTUVon der Station来说,需要实现的Funktion其实与Modbus TCP的Der Server端是一样的。其Operationen过程也是一样的。首先EmpfangzuHauptstation的访问Befehl,对该BefehlMeldung进行Analyse,这里我们也只是实现前面提zu的8种Funktionscode。其次我们根据Analyse的Ergebnis进行对应的Operationen,具体的软件访问结构如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图24

从上图中不难发现实际是3步:

Die1步、接zuBefehl后先Analyse。Analyse的方式与前面几节中的类式。

Die2步、根据Analyseergebnisse进行Operationen。包括更具Befehl修改或者获取变Quantität的值。

Die3步、Erstellen von Reaktionen并返回给主机。

1、Analyse主机Befehl

从机在Empfangzu主机的BefehlMeldung后,对其进行Analyse,我们暂且支持上图所示的8种Funktionscode。

/*AnalyseEmpfangzu的信息,并返回合成的回复信息和信息的BytesLänge,通过回调函数*/

uint16_t ParsingMasterAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes,uint16_t rxLength)

该函数Empfang接受zu的Meldung,并Erstellen von Reaktionen信息Meldung,返回值是AntwortMeldung的Länge。在开发应用是将Empfangzu的MeldungSenden Sie个该函数,并将生成的Antwort信息返回个客户端就可以了。

2、ErgebnisOperationen

Analyse之后无论是读Befehl还是写Befehl都需要进行相应的Operationen。我们根据不同的Funktionscode封装不同的Operationen:

/*处理Lesen Sie den ZustandBefehl*/

static uint16_t HandleReadCoilStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理读Importieren StatusBefehl*/

static uint16_t HandleReadInputStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Lesen Sie das RegisterBefehl*/

static uint16_t HandleReadHoldingRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Eingangsregister lesenBefehl*/

static uint16_t HandleReadInputRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben Sie eine einzelne Schleife.Befehl*/

static uint16_t HandleWriteSingleCoilCommand(uint16_t coilAddress,uint16_t coilValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben eines einzelnen RegistersBefehl*/

static uint16_t HandleWriteSingleRegisterCommand(uint16_t registerAddress,uint16_t registerValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben Sie mehrere Schleife.Status*/

static uint16_t HandleWriteMultipleCoilCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/*处理Schreiben mehrerer RegisterStatus*/

static uint16_t HandleWriteMultipleRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

同样我们也是定义一个函数指针数组来实现这8个函数的调用:

uint16_t (*HandleMasterCommand[])(uint16_t,uint16_t,uint8_t *,uint8_t *)={HandleReadCoilStatusCommand,

                                                                          HandleReadInputStatusCommand,

                                                                          HandleReadHoldingRegisterCommand,

                                                                          HandleReadInputRegisterCommand,

                                                                          HandleWriteSingleCoilCommand,

                                                                          HandleWriteSingleRegisterCommand,

                                                                          HandleWriteMultipleCoilCommand,

                                                                          HandleWriteMultipleRegisterCommand};

3、生成从机Antwort

处理完还需要生成从机的相应信息,无论是读OperationenBefehl还是写OperationenBefehl,我们均在对应的Funktionscode处理时Erstellen von ReaktionenMeldung。这么一来在开发应用时,只需要调用Analyse函Daten可以实现Alles的Funktion了。

这一节我们来封装最后一种应用(Modbus RTU Master应用),RTUHauptstation的开发与TCP客户端的开发是一致的。同样的我们也不是做具体的应用,而是实现RTUHauptstation的基本Funktion。我们将RTUHauptstation的Funktion封装为函数,以便在开发具体应用时调用。

对于RTUHauptstation我们主要实现的Funktion有两个:其一是生成访问RTUVon der Station的Befehl,总共支持8中Funktionscode。其二是对RTUVon der Station端返回的信息进行Analyse并根据Ergebnis进行各种Operationen,同样也是支持8中Funktion吗的Operationen。具体软件访问结构如下:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图25

1、访问Befehl的生成

客户端作为主动交互端,需要向Der Server发各种OperationenBitteBefehl。所以对于RTUHauptstation来说,首先要生成访问Der Server的Befehl。Befehle erzeugen只需要按标准的VereinbarungDas Format来生成即可,目前我们只支持前面提zu的8个Funktionscode。

/*生成访问Von der Station的Befehl*/

uint16_t CreateAccessSlaveCommand(ObjAccessInfo objInfo,void *dataList,uint8_t *commandBytes)

这样在开发具体的客户端应用时只需要调用这个函数来生成访问Der Server的Befehl就可以了。

2、Antwort信息的Analyse

如前一节所述,Der Server收zuBefehl后,根据Befehl进行处理并Erstellen von Reaktionen信息返回给客户端。客户端接zuBefehl后首先要对Antwort信息进行Analyse,Analyse的过程其实与Der Server端是一致的。所不同的是,不需要再根据AnalyseergebnisseErstellen von Reaktionen信息了。

/*Analyse收zu的Von der Station相应信息*/

void ParsingSlaveRespondMessage(uint8_t *recievedMessage)

这样在开发客户端应用时,我们调用这一函数来AnalyseAntwort信息就可以了。

3、Antwort处理

对于Analyse出来的信息,我们需要根据情况实现Operationen,比如修改变Quantität的值等,应为主要支持的Operationen码是8个,理论上对应的每种Funktionscode都会有不同的Operationen,但事实上,由于写OperationenBefehl已经不需要做任何Operationen了,所以对应的Operationen实际上只有读Operationen的4种Funktionscode。

/*处理读Von der StationStatusQuantität返回信息,Lesen Sie den Zustand位0x01Funktionscode*/

static void HandleReadCoilStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationStatusQuantität返回信息,读Importieren Status位0x02Funktionscode*/

static void HandleReadInputStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationRegisterwert的返回信息,Lesen Sie das Register0x03Funktionscode)*/

static void HandleReadHoldingRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/*处理读Von der StationRegisterwert的返回信息,Eingangsregister lesen0x04Funktionscode*/

static void HandleReadInputRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

同样的,我们也定义一个函数指针数组来实现这些函数的调用:

void (*HandleServerRespond[])(uint8_t *,uint16_t,uint16_t)={HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};

zu这里,RTUHauptstation的封装就abgeschlossen.了,当然具体的Daten处理部Unterteilung需要在开发具体应用是才能确定。

前面开发了各种应用,但是却一直没有提zu一个问题,你就是对具体的Daten进行读写Operationen。对于Modbus来说标准的Daten有4种:Daten der Coil(Adresse:0000x)、Importieren StatusQuantitätDaten(Adresse:1000x)、保持Registerdaten(Adresse:4000x)和输入Registerdaten(Adresse:3000x)。我们通讯的目的就是为了对这些Daten进行Operationen,可是我们前面的封装中并没有提zuDaten处理。事实上,也没办法考虑这一点,因为具体的应用Daten千schlecht万别,是没办法封装的。那我们怎么解决这一问题呢?接下来我们将解决这一类问题。

1、Daten处理函数的封装

我们考虑zu,不论是在RTUHauptstation、RTUVon der Station、TCP客户端、还是在TCPDer Server对Daten的处理本质上是一样的,只要具体应用的Daten结构确定后处理方法也就确定了。鉴于此,我们采用的方法是定义弱化Typus的函数。如下:

/*获取想要Lesen的CoilQuantität的值*/

__weak void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*获取想要Lesen的InputStatusQuantität的值*/

__weak void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*获取想要Lesen的保持Register.的值*/

__weak void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*获取想要Lesen的输入Register.的值*/

__weak void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*设置单个线圈的值*/

__weak void SetSingleCoil(uint16_t coilAddress,bool coilValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*设置单ein Register.的值*/

__weak void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*设置多个线圈的值*/

__weak void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*设置多ein Register.的值*/

__weak void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

  //如果需要Modbus TCP Server/RTU Slave应用中实现具体内容

}

/*更新读回来的Zustand der Coil*/

__weak void UpdateCoilStatus(uint16_t startAddress,uint16_t quantity,bool *stateValue)

{

  //在客户端(Hauptstation)应用中实现

}

/*更新读回来的Importieren Status值*/

__weak void UpdateInputStatus(uint16_t startAddress,uint16_t quantity,bool *stateValue)

{

  //在客户端(Hauptstation)应用中实现

}

/*更新读回来的Zustand der Coil*/

__weak void UpdateHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

  //在客户端(Hauptstation)应用中实现

}

/*更新读回来的Zustand der Coil*/

__weak void UpdateInputResgister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

  //在客户端(Hauptstation)应用中实现

}

在开发具体应用时,我们只需要在应用中实现对应的函数就可以使Funktionvollständig,至于具体的Daten如何处理,就要看具体应用中的Datenformate了。当然这些函数并非都需要实现,只需要根据自己的需要实现就可以了。

2、关于大小端的问题

提zuDaten通讯,有一个问题是没有办法回避的,那就是大小端的问题。对于ModbusVereinbarung来说,采用的是大端模式,就是低位Adresse存高位Anzahl der Bytes据,高位Adresse存低位Anzahl der Bytes据。

在进行多Anzahl der Bytes据通讯时,大小端的问题就明显了,比如一个浮点数在不同的Das System中存储的顺序是有schlecht别的,你读上来或者写下去的Daten就会出现Fehler.的Analyse。所以我们在处理DatenMeldung时是必须考虑这一点的。

谈zuModbus通讯自然免不了循环冗余Prüfungen(CRC),特别是在标准的串行RTU链路上是必不可少的。不仅如此在其他开发中,也经常要用zuCRC 算法对各种Daten进行Prüfungen。这样一来,我们就需要研究一下这个循环冗余Prüfungen(CRC)算法。

1CRC简述

      循环冗余检查(CRC)是一种Daten传输检错Funktion,对Daten进行多项式Berechnung,并将得zu的Ergebnis附在帧的后面,EmpfangAusrüstung也执行类似的算法,以GarantieDaten传输的Richtig.性和vollständig性。

CRC-Test的基本思想是利用线性Codierung理论,在Senden Sie端根据要传送的k位Binärsystem码Sequenz,以一定的Regeln产生一个Prüfungen用的监督码(既CRC码)r位,并附在信息后边,构成一个新的Binärsystem码Sequenz数共(k+r)位,最后Senden Sie出去。在Empfang端,则根据信息码和CRC码之间所遵循的Regeln进行检验,以确定传送中是否出错。

CRC的本质是模2Außer Gesetz的余数,采用的除数不同,CRC的Typus也就不一样。通常,CRC的除数用生成多项式来表示。am häufigsten verwendet的CRC码的生成多项式有下面几种:

基于mnModbusVereinbarung栈的Modbus开发Unterricht(vollständig版)插图26

      不一样的生成多项式,所以得zu的Ergebnis自然也是不一样的。事实上在Modbus通讯中采用的是CRC-16的方式。

2、算法Unterteilung析

      CRC-Test码的Codierung方法是用待Senden Sie的BinärsystemDatent(x)除以生成多项式g(x),将最后的余数作为CRC-Test码。其实现步骤如下:

      设待Senden Sie的Daten块是m位的Binärsystem多项式t(x),生成多项式为r阶的g(x)。在Daten块的末尾添加r个0,Daten块的Länge增加zum+r位,对应的Binärsystem多项式为 。用生成多项式g(x)去除 ,求得余数为阶数为r-1的Binärsystem多项式y(x)。此Binärsystem多项式y(x)就是t(x)经过生成多项式g(x)Codierung的CRC-Test码。用 以模2的方式减去y(x),得zuBinärsystem多项式 。 就是包含了CRC-Test码的待Senden SieCharaktere串。

      从CRC的CodierungRegeln可以看出,CRCCodierung实际上是将代Senden Sie的m位Binärsystem多项式t(x)转换成了可以被g(x)除尽的m+r位Binärsystem多项式,所以解码时可以用Empfangzu的Daten去除g(x),如果余数位零,则表示传输过程没有Fehler.;如果余数不为零,则在传输过程中肯定ExistenzFehler.。许多CRC的硬件解码电路就是按这种方式进行检错的。同时可以看作是由t(x)和CRC-Test码的组合,所以解码时将Empfangzu的BinärsystemDaten去掉尾部的r位Daten,得zu的就是Originaldaten。

      实际上,真正的CRC Berechnung通常与上面描述的还有些不同。这是因为这种最基本的CRCAußer GesetzExistenz一个很明显的缺陷,就是Daten流的开头添加一些0并不影响最后Prüfungen的Ergebnis。为了弥补这一缺陷所以引入了两个概念:一个是“余数初始值”,另一个是“ErgebnisUnterschiedlich oder值”。所谓 “余数初始值”就是在Berechnung von CRC值前,为存储变Quantität所赋的初值。对应的“ErgebnisUnterschiedlich oder值”就是在Berechnungabgeschlossen.后,将变Quantität值与这个值作最后的Unterschiedlich oder运算而得zuPrüfungenErgebnis。

NamePrüfungen und位宽生成多项式除数(多项式)余数初始值ErgebnisUnterschiedlich oder值
CRC-44x4+x+13  
CRC-88x8+x5+x4+10x31  
CRC-88x8+x2+x1+10x07  
CRC-88x8+x6+x4+x3+x2+x10x5E  
CRC-1212x12+x11+x3+x+180F  
CRC-16 16x16+x15+x2+10x80050x00000x0000
CRC-CCITT16x16+x12+x5+10x10210xFFFF0x0000
CRC-3232x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+10x04C11DB70xFFFFFFFF0xFFFFFFFF
CRC-32c32x32+x28+x27+…+x8+x6+11EDC6F41  

      说zu这里我们已经可以描述一下这个算法的实现过程:

Die1步:定义CRC存储变Quantität,并给其Zuweisung为“余数初始值”。

Die2步:将Daten的Die一个8-bitCharaktere与CRC存储变Quantität进行Unterschiedlich oder,并把Ergebnis存入CRC存储变Quantität。

Die3步:CRC存储变Quantität向右移一位,MSB补零,移出并检查LSB。

Die4步:如果LSB为0,重复Die三步;若LSB为1,CRCRegister.与0x31相Unterschiedlich oder。

Die5步:重复Die3与Die4步直zu8次移位Allesabgeschlossen.。此时一个8-bitDaten处理完毕。

Die6步:重复Die2至Die5步直zu所有DatenAlles处理abgeschlossen.。

Die7步:最终CRC存储变Quantität的内容与“ErgebnisUnterschiedlich oder值”进行或非Operationen后即为CRC值。

3、代码实现

      有了前面的准备实际上我们要实现CRC-Test的代码已经很简单了,实现这一过程有各种方法我们说常用的2种:一是直接Berechnung法,就是按照前面的步骤Berechnung出来;二是驱动表法,就是将一些Daten储存起来直接获取Berechnung。因为在Modbus中使用的是CRC-16,所以我们一次为例来实现它。

(1)直接Berechnung法

直接Berechnung法简单直接,便写程序也比较简单,我们以CRC-16为例,其多项式记为0x8005,因为其记过Unterschiedlich oder值为0x0000,所以可以不添加。具体代码如下:

#define Initial_Value    0x0000

#define EOR 0x0000

#define POLY16 0x8005

uint16_t CRC16(uint8_t *buf,uint16_t length)

{

  uint16_t crc16,data,val;

  crc16 = Initial_Value;

  for(int i=0;i<length;i++)

  {

    if((i % 8) == 0)

    {

      data = (*buf++)<<8;

     }

    val = crc16 ^ data;

    crc16 = crc16<<1;

    data = data <<1;

    if(val&0x8000)

    {

      crc16 = crc16 ^ POLY16;

    }

  }

  return crc16;

}

(2)驱动表法

     对于直接Berechnung法,虽然简单直接,但有时候效率却是个问题,所以在Modbus通讯中我们通常采用驱动表法来实现:

//CRC_16高8位Datenzone

const uint8_t auchCRCHi[] = {

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

};

//CRC低位Bytes值表

const uint8_t auchCRCLo[] = {//CRC_16低8位Datenzone

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

};

/*函数Funktion:CRC-Test码生成

  输入Parameter:puchMsgg是要进行CRC-Test的消息,usDataLen是消息中Anzahl der Bytes

  函数输出:Berechnung出来的CRC-Test码

  GenerateCRC16CheckCode查表Berechnung函数*/

static uint16_t GenerateCRC16CheckCode(uint8_t *puckMsg,uint8_t usDataLen)

{

  uint8_t uchCRCHi = 0xFF ; //高CRCBytes初始化

  uint8_t uchCRCLo = 0xFF ; //低CRC Bytes初始化

  uint32_t  uIndex ; //CRC循环中的索引

  //传输消息缓冲区

  while (usDataLen–)

  {

    //Berechnung von CRC

    uIndex = uchCRCLo ^ *puckMsg++ ; 

    uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex] ;

    uchCRCHi = auchCRCLo[uIndex] ;

  }

  //返回Ergebnis,高位在前

  return (uchCRCLo << 8 |uchCRCHi) ;

}

4、Ende语

      CRC的应用非常广泛,特别是在做通讯时更是经Häufigzu,所以掌握它是非常有必要的,至少会使用它。我们在开发Modbus库函数的过程中,对它也不过是有了一些比较粗浅的理解,在此记述以求共进。

前面我们已经abgeschlossen.了ModbusVereinbarung栈的开发,但这不是我们的目的。我们开发它的目的当然是要使用它来解决我们的实际问题。接下来我们就使用刚开发的ModbusVereinbarung栈开发一个Modbus TCP Server应用。

开发Modbus TCP Server首先需要有TCP Server的支持以及网络的配置等,但这些与Modbus本身没有什么关系,我们再次不作讨论。我们规定网络和TCP Server已经配置妥当。接下来我们讨论Modbus TCP Server的实现过程。

根据前面对Vereinbarung栈的封装,我们需要引用Modbus TCP Server的Related封装。在EmpfangzuBefehl后调用Analyse函数进行Analyse,Analyse函数的原型:

uint16_t ParsingClientAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes);

该函数作为函数指针传递给TCP Server,并回调AnalyseEmpfangzu的信息列表。

除此之外,我们要根据具体的需要实现8个回调函数,以abgeschlossen.真正的对Modbus对象的Operationen,这8个函数的原型如下:

/*获取想要Lesen的CoilQuantität的值*/

void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList);

 /*获取想要Lesen的InputStatusQuantität的值*/

void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /*获取想要Lesen的保持Register.的值*/

void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /*获取想要Lesen的输入Register.的值*/

void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /*设置单个线圈的值*/

void SetSingleCoil(uint16_t coilAddress,bool coilValue);

 /*设置单ein Register.的值*/

void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue);

 /*设置多个线圈的值*/

void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /*设置多ein Register.的值*/

void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

这8个函数根据应用的具体需求来实现Daten对象的Operationen,实现几个如何实现根据各自的应用需求和Daten结构来确定。

上一节我们使用Vereinbarung占开发了一个Modbus TCP Server应用。接下来我们使用Vereinbarung栈在开发一个基于串行链路的Mosbus RTU Slave应用。

根据前面对Vereinbarung栈的封装,我们需要引用Modbus TCP Server的Related封装。在EmpfangzuBefehl后调用Analyse函数进行Analyse,Analyse函数的原型:

ParsingMasterAccessCommand(uint8_t *receivedMesasage,uint8_t *respondBytes,uint16_t rxLength);

RTU Slave使用Die Serie中断Empfang信息,Empfangzu信息后使用上述函数Analyse信息,根据Empfang的信息Befehlabgeschlossen.Operationen。

当然,除了调用Analyse函数外,我们要根据具体的需要实现8个回调函数,以abgeschlossen.真正的对Modbus对象的Operationen,这8个函数的原型如下:

/*获取想要Lesen的CoilQuantität的值*/

void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList);

 /*获取想要Lesen的InputStatusQuantität的值*/

void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /*获取想要Lesen的保持Register.的值*/

void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /*获取想要Lesen的输入Register.的值*/

void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /*设置单个线圈的值*/

void SetSingleCoil(uint16_t coilAddress,bool coilValue);

 /*设置单ein Register.的值*/

void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue);

 /*设置多个线圈的值*/

void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /*设置多ein Register.的值*/

void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

这8个函数根据应用的具体需求来实现Daten对象的Operationen,实现几个如何实现根据各自的应用需求和Daten结构来确定。

当然,并非必须使用中断Empfang信息,也可以使用查询等方式,但是使用中断是一个比较好的方法,因为HauptstationBefehl的Senden Sie一般并无规律,中断方式既可Garantie信息及时Antwort,也无须耗费太多的Von der Station资源,而有mehr.资源去处理其他的任务。

Techniken术语(共 12 个)—— Klick auf展开
Modbus RTU基于串行链路的ModbusVereinbarung,使用BinärsystemCodierung和CRC-Test
Modbus TCP基于Ethernet的ModbusVereinbarung变体,使用TCP/IP传输
RS485工业常用的schlechtUnterteilung串行Kommunikation标准,支持多点Kommunikation
RS232点对点串行Kommunikation标准,常用于短距离AusrüstungKommunikation
FunktionscodeModbusFunktionscode指定读/写OperationenTypus,如01读线圈、03Lesen Sie das Register
Register.Modbus Register.存储Daten单元,Unterteilung线圈/离散输入/保持/输入Register.四类
CRC-Test循环冗余Prüfungen,用于检测Daten传输中的Fehler.
PLC可编程逻辑Der Controller,Industrielle Automatisierung控制的核心Ausrüstung
网关Vereinbarung转换Ausrüstung,如 Modbus RTU ↔ Modbus TCP
Die SerieBerechnung机与外部Ausrüstung进行串行Kommunikation的物理Schnittstelle
线圈Modbus位可读Schreiben von Daten,Adresse从00001开始
保持Register.Modbus 16位可读Schreiben von Daten,Adresse从40001开始
来源/Werkzeuge信息 —— Klick auf展开
来源 Modbus Chinesisches Netzwerk(modbus.cn) —— Inländisch führend.ModbusKommunikationsprotokoll Technologie Gemeinschaft Klassifizierung Modbus Programmierung 字数 32712 字 · 阅读约 82 Unterteilung钟 更新 2024-04-25 永久链接 https://www.modbus.cn/27778.html
Empfohlene Werkzeuge: Modbus Debugger-Assistent WeChat-Applet
Modbus Chinesisches Netzwerk官方推出的Modbus Debugging-Tools,支持 Modbus RTU/TCP 实时KommunikationTests、Register.读写、线圈控制、Daten监控和MeldungUnterteilung析。 keine Installation erforderlich,Mikro-Suche「Modbus DebuggingAssistenten.」Benutzt werden kann.。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
Sollte man diese Informationen für ein echtes Projekt verwenden?

Gehen Sie zum Tool Center, um Nachrichten zu analysieren, CRC-Prüfungen und Geräte-Debugging zu erledigen, oder senden Sie Anforderungen für Auswahl - und Zugangsvorschläge.

Ingenieur Mitglied

Verwandeln Sie diesen Artikel in ein umsetzbares Debuggermaterial

Erweiterte Nachrichtenanalyse, Paket-Downloads, Codebeispiele, Engineering-Szenarien und Priority-Technischer Support sind für die Real-Projekt - Bereitstellung möglich.

Unbegrenzte Werkzeuge
Datenpaket und Codepaket
Vollständige Engineering Case-Basis
Vorrangiger Zugang zum technischen Support

Antwort veröffentlichen

Ihre E-Mail - Adresse wird nicht öffentlich gemacht. Erforderliche Elemente wurden verwendet * markiert.