Modbus ist ein offenes industrielles Kommunikationsprotokoll, das in einer Vielzahl von industriellen Geräten weit verbreitet ist. Ich benutze auch Modbus-Kommunikation seit vielen Jahren, entweder mit vorgefertigten oder für spezifische Anwendungen entwickelt, wollte schon immer einen relativ universellen Protokollstack entwickeln, der in späteren Projekten wiederverwendet werden kann, ohne jedes Mal schreiben zu müssen. Jetzt nutzt das Projekt die Gelegenheit, um einen eigenen Modbus-Protokollstack zu entwickeln.
Modbus hat internationale Standards, es gibt nationale Standards, der Inhalt ist genau derselbe. Die Standarduhr unterstützt zwei Arten von physischen Verbindungen: eine basiert aufbasierende serielle Verbindungund eine aufbasierende TCP / IP Verbindung。Tatsächlich hat das Modbus-Protokoll als Anwendungsschichtprotokoll keine speziellen Anforderungen an physische Schlüssel, Glasfaser, drahtlos und so weiter können realisiert werden.
Diesmal geht es hauptsächlich um die Entwicklung.Modbus RTU和Modbus TCPhat zwei Standardprotokolle entwickelt, die häufig verwendeten Funktionscode unterstützen:
| Funktionscode | Name | Implementierung | Beschreibung |
| 0x01 | Lesespule | 是 | Lesen von lesbaren und schreibbaren Zustandgrößen |
| 0x02 | Lesen Sie getrennte Eingabe | 是 | Lesen von nur lesen Zustandvolumen |
| 0x03 | Lesen-Halt - Register | 是 | Lesen von nur lesen Zustandvolumen |
| 0x04 | Leseneingabe Register | 是 | Lesen von nur lesen Zustandvolumen |
| 0x05 | Schreiben einer einzelnen Spule | 是 | Schreiben einer einzelnen Lesen - und Schreib-Statusmenge |
| 0x06 | Schreiben eines einzelnen Registers | 是 | Schreiben einer einzelnen Lesen - und Schreib-Registermenge |
| 0x0F | Schreiben mehrerer Spule. | 是 | Schreiben von mehreren Lese - und Schreib-Status - Quantitäten |
| 0x10 | Schreiben von mehreren Registeren | 是 | Schreiben von mehreren Lese - und Schreib-Register - Quantitäten |
Das Modbus-Protokoll ist einMaster-Slave -(oder Client / Server-Modus - Protokoll) mitMaster-Stationen (Client)Anforderung zu initiieren,Der SlaveBeantwortung von Geschäftsanfragen. Im Allgemeinen ist das Gerät, auf das wir zugreifen möchten (oder auf das wir zugreifen möchten), der Slave (Server), und das Gerät, auf das wir normalerweise zugreifen, der Master (Client). In der Regel werden sie in RTU als Master und Slave bezeichnet, während sie in TCP als Server und Client bezeichnet werden. Diese Bezeichnung ist nur anders, aber im Wesentlichen unterscheidet mich nicht. Beide Bezeichnungen werden wir gleichzeitig verwenden. Bei dieser Entwicklung planen wir, die Funktionen von Master - und Slave-Stationen gleichzeitig zu realisieren.
1Der Standardprozess
Der Client, der eine MODBUS-Transaktion initiiert, erstellt eine MODBUS-Anwendungsdatenunit. Wenn eine Nachricht vom Client an das Servergerät gesendet wird, zeigt der Funktionscode dem Server an, welche Aktion ausgeführt werden soll.
Das Datenfeld einer Nachricht, die vom Client an das Server-Gerät gesendet wird, enthält zusätzliche Informationen, die der Server verwendet, um die von dem Funktionscode definierten Operationen auszuführen. Wenn in einer korrekt empfangenen MODBUS-ADU keine Fehler im Zusammenhang mit der Anforderung der MODBUS-Funktion auftreten, enthält das Antwortdatenfeld vom Server an den Client die Anforderungsdaten.

Normaler Transaktionsfluss
Bei Fehlern im Zusammenhang mit der Anforderung der MODBUS-Funktion enthält die Domäne einen Ausnahmecode, den die Serveranwendung verwenden kann, um die nächste Aktion zu bestimmen. Wenn der Server auf einen Client reagiert, verwendet er das Funktionscode-Feld, um eine normale (fehlerfreie) Reaktion oder eine Art von Fehler (außergewöhnliche Antwort genannt) anzuzeigen. Bei einer normalen Antwort reagiert der Server nur auf den ursprünglichen Funktionscode.

Ausnahme-Transaktionsprozess
2Operationsdesign
Basierend auf unserem Verständnis des Modbus-Standard - Transaktionsprozesses entwerfen wir den spezifischen Betriebsprozess für Master - und Slave-Stationen.
Master-Station - Betriebsabläufe:
(1), Initialisierung der Master-Station, Stations-Identifikation, Timer,
(2), Erstellen von Zugriffsbefehlen
Generate-Befehle ADU wird in zwei Ebenen implementiert: Netzwerk-Ebene und Link-Ebene.
Die Netzwerkschicht implementiert eine PDU, die unabhängig von der spezifischen Verbindung ist (einschließlich Funktionscodes und Daten, wobei sich die Daten auf die übertragenen Daten beziehen, nicht auf den Wert der Variablen, sondern tatsächlich auch auf die Anzahl und die Adresse). Die entsprechende Datei ist mbpdu.c/.h und implementiert die Datenverarbeitung der Pud-Einheit, einschließlich Befehle und Antworten.
Die Linkschicht implementiert die Verkapselung von ADU. Die entsprechende Datei ist mbrtu. c /. h (und mbtcp. c /. h und mbascii. c /. h), die die Enkapselung der ADU-Einheit implementiert; und als Slave-Station werden hier auch die Antworten auf Schreibvorgänge verkapselt.
(3) Senden Sie regelmäßig Zugriffsbefehle, Lesebefehle müssen Polling sein, Schreibbefehle haben höhere Priorität. Befehl zum Lesen unterbrechen, wenn ein Schreibbefehl vorhanden ist,
(4), die empfangenen Daten analysieren, entsprechend handeln
Betriebsprozess von der Station:
(1), Initialisierung des Protokollstacks: Einstellung der Station-Adresse, Erstellung von Datenspeicherdomänen, Zugriffssteuerung
(2), Empfangen von Nachrichten von der Station und analysieren
Nachrichten analysieren und Daten verarbeiten (lesen und schreiben den Wert des entsprechenden Objekts)
Erstellen einer Antwort
(3), Erstellen von Antwortdaten und Antworten
3, Befehlformat
Verschiedene Funktionscodes haben unterschiedliche Nachrichtenformate, wir sagen nur kurz, dass wir die Informationen des Funktionscodes in den oben 8 implementieren werden.
(1) Funktionscode 0x01: Lesespule
In einem Remote-Gerät wird dieser Funktionscode verwendet, um 1 bis 2000 aufeinanderfolgende Zustände der Spule zu lesen. Die Anforderung PDU beschreibt die Startadresse, d.h. die angegebene erste Spule-Adresse und die Spule-Nummer. Beginnen Sie mit der Adresse der Spulen von Null an. Daher ist die Adressspule 1 - 16 auf 0 - 15.
Teilt die Spule in der Antwortnachricht in eine Spule basierend auf jedem Bit des Datenfelds auf. Der Zustand ist 1 = ON und 0 = OFF. Das LSB (Least Significant Bit) des ersten Datenbytes enthält die Ausgabe, die in der Abfrage adressiert wird. Die anderen Spulen verfolgen diese Reihenfolge bis zum oberen Ende dieses Bytes und in der Reihenfolge von der niedrigen bis zu der höheren Position in den folgenden Bytes.
Wenn die Anzahl der zurückgegebenen Ausgänge nicht ein Vielfaches von acht ist, werden die verbleibenden Bits im letzten Datenbyte (bis zum oberen Ende des Bytes) mit Nullen gefüllt. Das Feld Anzahl Bytes beschreibt die vollständige Anzahl der Bytes der Daten. Beispiel für das Format:

(2) Funktionscode0x02: Diskreteneingabe lesen
In einem Remote-Gerät wird dieser Funktionscode verwendet, um den aufeinanderfolgenden Zustand von 1 bis 2000 diskreten Eingängen zu lesen. Die Anforderung PDU beschreibt die Startadresse, d. h. die angegebene erste Eingabedresse und die Eingabenummer. Adresse Eingabe von Null anfangen. Daher ist die Adressierung der Eingabe 1 - 16 0 - 15.
Teilt die diskrete Menge der Eingabe in der Antwortnachricht in einen Eingabe basierend auf jedem Bit des Datenfelds auf. Der Zustand ist 1 = ON und 0 = OFF. Das LSB (Last Significant Bit) des ersten Datenbytes enthält die Eingabe, die in der Abfrage adressiert wird. Die anderen Eingänge sind bis zum oberen Ende dieses Bytes und in der Reihenfolge von niedrigem bis hohem Bits in den folgenden Bytes.
Wenn die Anzahl der zurückgegebenen Eingaben nicht ein Vielfaches von Acht ist, werden die verbleibenden Bits im letzten Datenbyte (bis zum oberen Ende des Bytes) mit Nullen gefüllt. Das Feld Anzahl Bytes beschreibt die vollständige Anzahl der Bytes der Daten.

Tatsächlich ist das Datenformat der Funktionscodes 0x01 und 0x02 genau das gleiche, und die Betriebsmethode ist auch genau die gleiche, der Unterschied ist nur das Objekt der Operation.
(3) Funktionscode 0x03: Read Hold Register
In einem Remote-Gerät wird der Inhalt eines aufeinanderfolgenden Blocks des Hold Registers mit diesem Funktionscode gelesen. Die Anforderung PDU beschreibt die Start-Register - Adresse und die Anzahl der Register. Adressieren Sie das Register von Null an. Das Adressregister 1 - 16 ist also 0 - 15.
Teilt die Registerdaten in der Antwortnachricht in zwei Byte pro Register und passt den Binärinhalt direkt in jedem Byte an.
Für jedes Register enthält das erste Byte die hohen Bits und das zweite Byte die niedrigen Bits. Ein konkretes Format ist wie folgt beispielhaft:

Die Länge der Antwortinformationen hängt von der Menge an Daten ab, die speziell gelesen werden.
(4) Funktionscode0x04: Eingaberegister lesen
In einem Remote-Gerät wird dieser Funktionscode verwendet, um die aufeinanderfolgenden Eingaberegister von 1 bis ca. 125 zu lesen. Die Anforderung PDU gibt die Startadresse und die Anzahl der Register an. Adressieren Sie das Register von Null an. Daher ist das Adress-Eingabe - Register 1 - 16 0 - 15.
Teilt die Registerdaten in der Antwort-Nachricht in zwei Byte für jedes Register und passt den Binärinhalt direkt in jedem Byte.
Für jedes Register enthält das erste Byte die hohen Bits und das zweite Byte die niedrigen.

Tatsächlich ist das Datenformat der Funktionscodes 0x03 und 0x04 genau das gleiche, und die Betriebsmethode ist auch genau die gleiche, der Unterschied ist nur das Objekt der Operation. Die Länge der Antwortinformationen hängt von der Menge an Daten ab, die speziell gelesen werden.
(5) Funktionscode 0x05: Schreiben einer einzelnen Spule
Auf einem Remote-Gerät, verwenden Sie diesen Funktionscode zum Schreiben eines einzelnen Ausgangs als ON oder OFF.
Die Konstante im Anforderungsdatenfeld beschreibt den ON / OFF-Status der Anforderung. Hexadezimalwert 0xFF00 Anforderung Ausgabe ON. Hexadezimalwert 0x0000 Die Ausgabe ist OFF angefordert. Alle anderen Werte sind illegal und wirken nicht auf die Ausgabe.
Die PDU-Anforderung beschreibt die obligatorische Spule-Adresse. Beginnen Sie mit der Adresse der Spulen von Null an. Daher ist die Adressspule 1 0. Die Konstante des Spule-Wertfelds beschreibt den angeforderten ON / OFF-Zustand. Der Hexadezimalwert 0XFF00 verlangt, dass die Spule ON ist. Der Hexadezimalwert 0X0000 verlangt, dass die Spule OFF ist. Alle anderen Werte sind illegal und wirken nicht auf die Spule.
Eine normale Antwort ist eine Antwort auf die Anforderung, die nach dem Schreiben des Spulenstatus zurückgegeben wird. Ein Beispiel für das spezifische Format lautet:

(6) Funktionscode0x06: Schreiben eines einzelnen Registers
Schreiben eines einzelnen Hold-Registers mit diesem Funktionscode in einem Remote-Gerät.
Die Anforderung PDU beschreibt die Adresse, die in das Register geschrieben wird. Adressieren Sie das Register von Null an. Daher ist das Adressregister 1 0.
Eine normale Antwort ist eine Antwort auf die Anforderung, die nach dem Schreiben des Registerinhalts zurückgegeben wird. Beispiel für das spezifische Format:

(7) Funktionscode0x0F: Schreiben mehrerer Spulen
In einem Remote-Gerät erzwingen Sie mit diesem Funktionscode, dass jede Spule in der Spule-Sequenz ON oder OFF ist. Anforderung PDU beschreibt die obligatorische Spule Referenz. Beginnen Sie mit der Adresse der Spulen von Null an. Daher ist die Adressspule 1 0.
Der Inhalt des angeforderten Datenfelds beschreibt den angeforderten ON / OFF-Status. Die Logik "1" in der Feld-Bit - Position fordert die entsprechende Ausgabe ON. Die logische "0" in der Feld-Bit - Position verlangt, dass der entsprechende Ausgang OFF ist.
Die normale Antwort gibt den Funktionscode, die Startadresse und die Anzahl der erzwungenen Spulen zurück. Ein Beispiel für das spezifische Format lautet:

(8) Funktionscode0x10: Schreiben mehrerer Register
Verwenden Sie diesen Funktionscode, um in einem Remote-Gerät aufeinanderfolgende Registerblöcke (1 bis etwa 120 Register) zu schreiben.
Der Wert, der angefordert wird, wird im Anfrage-Datenfeld angegeben. Jedes Register teilt die Daten in zwei Bytes.
Die normale Antwort gibt den Funktionscode, die Startadresse und die Anzahl der in das Register geschriebenen Register zurück.

Nachdem wir diese Anweisungen klar haben, können wir die Entwicklung des Modbus-Protokolls beginnen.
Wir haben die grundlegenden Transaktionen von Modbus bereits beschrieben und daraufhin den Betriebsfluss der Master-Slaven - Station entworfen, die wir implementieren werden. Dies ist direkt mit Modbus verbunden ist die Erzeugung von Modbus-Nachrichtenrahmen. Modbus-Nachrichtenframe ist auch die Grundlage für die Implementierung des Modbus-Kommunikationsprotokolls.
1、ModbusNachrichtenrahmenanalyse
MODBUS-Protokoll gibt es einige Unterschiede zwischen Nachrichtenrahmen auf verschiedenen physischen Links, aber wir analysieren, dass es in diesen verschiedenen Nachrichtenrahmen die gleichen Teile gibt, was für uns sehr wichtig ist, eine einheitliche Datenoperation zu erreichen, wie folgt beschrieben:
(1), Simple Protocol Data Unit
Das MODBUS-Protokoll definiert eine einfache Protokoll-Daten - Einheit (PDU), die unabhängig von der zugrunde liegenden Kommunikationsschicht ist. Die einfache Protokoll-Daten - Einheit ist wie folgt aufgebaut:

Die PDU ist ein Teil des spezifischen Übertragungsnetzes und enthält Funktionscodes und Daten. Das MODBUS-Protokoll für einen bestimmten Bus oder ein Netzwerk führt nur zusätzliche Domänen auf der Anwendungseinheit (ADU) auf der Basis der PDU ein.
Die Entwicklung des Dateneinheiten-Teils ist der grundlegendste Teil, hauptsächlich zwei Aspekte der Klasse: Erstens ist es, den Client (Master) zu generieren, um den Server (Slave) zu besuchen, den Kommandenteil zu generieren; Zweitens ist es, den Server (Slave) zu generieren, um den Client (Master) zu beantworten.
(2)、RTUAnwendungsdaten-Einheiten
für Modbus-Protokoll, das über eine serielle Verbindung ausgeführt wird, basiert die Application Data Unit (ADU) auf einer PDU, vor der Adressfeld und gefolgt von Datenüberprüfung. Das Format ist in der folgenden Abbildung dargestellt:

Das Adressfeld ist die Adresse der aufgerufenen Slave-Station, eine 8 - stellige unsignierte Zahl, die den Wert 0 - 255, aber 0 und 255 haben eine feste Bedeutung, die nicht verwendet werden kann. Die CRC-Prüfungen werden durch die CRC16 - Prüfmethode durchgeführt.
(3)、TCPist ein Modbus-Protokoll, das über eine Ethernet-Verbindung ausgeführt wird, und die Anwendungseinheit (ADU) wird durch die Hinzufügung eines MBAP-Headers auf der Basis der PDU gebildet, und das Format ist wie folgt:
von

Für den MBAP-Headers enthält die folgenden Felder:
| 域 | Länge: | Beschreibung | Client | Server |
| Transaktions-Meta - Identifier | 2 Byte | ID für die MODBUS-Anforderung / - Antwort-Transaktion | Client-Start | Server aus empfangenen Anfragen erneut repliziert |
| Protokoll-Identifikation | 2 Bytes | 0 = MODBUS-Protokoll | Clientstart | Server repliziert erneut aus empfangenen Anfragen |
| Länge | 2 Bytes | Anzahl der Bytes nach | Client-Start (Anforderung) | Server-Start (Antwort) |
| Einheit-Identifikator | 1 Byte | Identifikationsnummer des Remote-Slaven, der über eine serielle Verbindung oder einen anderen Bus angeschlossen ist | Client-Start | Server kopiert aus der empfangenen Anforderung |
Aus der obigen Tabelle kann man wissen, dass der Header sieben Bytes lang ist:
Transaktionsbezeichner: verwendet für die Paarung von Transaktionen. In der Antwort kopiert der MODBUS-Server die Transaktionsidentifikatoren der Anforderung.
Protokoll-Identifikator: für die Multiplexation innerhalb des Systems. Das MODBUS-Protokoll wird durch den Wert 0 erkannt.
Länge: Das Feld Länge ist die Anzahl der Bytes des nächsten Felds, einschließlich der Einheit-Kennzeichnung und Datenfeld.
Einheit-Identifikator: Verwenden Sie diese Domain für das Routing innerhalb des Systems. Speziell für die Kommunikation von MODBUS - oder MODBUS + - seriellen Verbindungen über das Gateway zwischen TCP-IP - Netzwerk über Ethernet und MODBUS-seriellen Verbindungen. Der einfache Punkt, der gesagt wird, ist das Adressfeld in einer seriellen Verbindung. Der MODBUS-Client legt diese Domäne in der Anforderung fest, und der Server muss diese Domäne mit demselben Wert in der Antwort zurückgeben.
2- Analyse der spezifischen Komponenten des Datenrahmens
Aus der Analyse der folgenden einfachen Protokoll-Grundelemente, RTU-Anwendungs - Dateneinheiten und TCP-Anwendungs - Dateneinheiten-Nachrichtenformate, finden wir, dass die grundlegenden Dateneinheiten-Teile konsistent sind, so dass wir in Betracht ziehen können, die Operations-Teile des Protokolls in Schichten zu verkapseln:

Zuerst implementiert Modbus-Grundeinheit, die Daten-Community - Teil hat nichts mit der konkreten Anwendung zu tun, nur einmal kapseln, für die Entwicklung dieses Teils nur nach Modbus-Standard - Protokoll zu entwickeln, diesmal haben wir 8 Funktionen, die wir implementieren wollen:
| Funktion Code | Name | Implementierung | Beschreibung |
| 0x01 | Lesespule | 是 | Lesen von lesbaren und schreibbaren Zustandsgrößen |
| 0x02 | Lesen von diskreten Eingängen | 是 | Lesen von nur lesen Zustandvolumen |
| 0x03 | Lesen-Halt - Register | 是 | Lesen von nur lesen-schreibbaren Registervolumen |
| 0x04 | Leseneingangsregister | 是 | Lesen von nur lesen-Registervolumen |
| 0x05 | Schreiben Sie eine einzelne Schleife. | 是 | Schreiben Sie eine einzelne Lese - und Schreib-Status - Menge |
| 0x06 | Schreiben Sie ein einzelnes Register | 是 | Schreiben Sie eine einzelne Lese - und Schreib-Register - Menge |
| 0x0F | Schreiben Sie mehrere Spule | 是 | Schreiben Sie mehrere Lese - und Schreib-Status - Menge |
| 0x10 | Schreiben mehrerer Registers | 是 | Schreiben mehrerer Lese - und Schreibregisters |
Diese acht Funktionen sind die wichtigsten Funktionen, die im Modbus-Protokoll definiert sind, und das Nachrichtenformat für diese Funktionscode wird wie folgt beschrieben:
(1Lesespule0x01
Lesespule ist eine Art Schalter, die geschrieben werden kann, weil das Modbus-Protokoll aus der PLC-Anwendung entstanden ist, und die Spule ist die Bezeichnung der DO-Ausgabe der PLC, die allgemein für die Master-Station an die Slave-Station verwendet wird. Das Format des Lesens dieser Statusmenge mit Lese - und Schreibfunktionen lautet wie folgt:

Das Format des Befehls ist: Domänenname + Funktionscode + Startadresse + Anzahl.
(2) Discrete Eingabe lesen0x02
Der Lesestatus-Eingang ist das Lesen eines reinen Schaltsignal, das dem digitalen Eingabe in der SPS entspricht. Die Eingabe für diesen reinen Schalter kann im folgenden Format gelesen werden:

Das Befehlsformat ist: Domänenname + Funktionscode + Startadresse + Anzahl.
(3Lesen Sie das Register0x03
Hold Register bezieht sich auf 16 - Bit-Daten, die gelesen und geschrieben werden können, und kann durch ein oder mehrere Hold Registers verwendet werden, um verschiedene Daten darzustellen, wie 8 - Bit-Ganzzahlen, 16 - Ganzzahlen, 32 - Bit-Ganzzahlen, 64 - Bit-Ganzzahlen und Single-Doppel - Genauigkeit-Floating - Komma-Zahlen. Das Format der Nachricht zum Lesen des Haltregisters lautet wie folgt:

Das Format des Befehls ist: Domänenname + Funktionscode + Startadresse + Anzahl.
(4) Read-Input - Register0x04
Das Eingangsregister ist ein 16 - Bit-Daten in Lese-nur - Form. Durch ein oder mehrere Eingangsregister können 8 - Bit-Ganzzahlen, 16 als Ganzzahlen, 32 - Bit-Ganzzahlen, 64 - Bit-Ganzzahlen und Single-Doppel - Präzision-Floating - Komma-Zahlen usw. dargestellt werden. Das Format der Nachricht zum Lesen des Eingaberegisters lautet wie folgt:

Das Format des Befehls ist: Domänenname + Funktionscode + Startadresse + Anzahl.
(5Schreiben Sie eine einzelne Schleife0x05
Schreiben einer einzelnen Spule beträgt die Betätigung eines einzelnen lesbaren Schaltervolumes, aber es ist nicht direkt zu schreiben "0" oder "1", sondern 0xFF00 zu senden, wenn es notwendig ist, "1" zu schreiben; und 0x0000 zu senden, wenn es notwendig ist, "0" zu schreiben, das spezifische Nachrichtenformat ist wie folgt:

Das Befehlformat ist: Domain + Funktionscode + Ausgabeadresse + Ausgabewert. Der spezifische Inhalt des Befehls unterscheidet sich vom Lesenvorgang, aber das Format ist genau das gleiche, und beim Programmieren kann das tatsächliche Lesen und Schreiben zusammen verpackt werden.
(6) Schreiben eines einzigen Registers0x06
Schreiben eines einzigen Registers ist die Operation eines einzigen Aufbewahrungsregisters, das Format der Daten ist immer noch das gleiche, in der praktischen Anwendung nur für die 16 - Bit-ganzwertige Daten, für Gleitkommazahlen und so weiter nicht.

Das Format des Befehls lautet: Domain + Funktionscode + Ausgabeadresse + Ausgabewert. Der spezifische Inhalt des Befehls unterscheidet sich vom Lesenvorgang, aber das Format ist genau das gleiche, und beim Programmieren kann das tatsächliche Lesen und Schreiben zusammen verpackt werden.
(7Schreiben Sie mehrere Schleife0x0F
Schreiben mehrerer Spule ist genau das gleiche wie das Schreiben einer einzelnen Spule, der Unterschied ist die Anzahl und den Betriebswert, insbesondere der Wert, Schreiben "1" ist "1", Schreiben "0" ist "0", das ist der Unterschied zum Schreiben einer einzelnen Spule.

Das Format des Befehls ist: Domänenname + Funktionscode + Startadresse + Anzahl der Ausgabe + Anzahl der Bytes + Ausgabewert. Die Befehlnachricht unterscheidet sich von den vorherigen mehreren Lese - und Schreiboperationen und muss separat verarbeitet werden.
(8) Schreiben mehrerer Register0x10
Schreiben mehrerer Register bedeutet gleichzeitig mehrere lesbare und schreibbare Register, wobei das Format der Datennachricht mit dem Schreiben mehrerer Spulen übereinstimmt.

Der Befehl lautet: Domänenname + Funktionscode + Startadresse + Anzahl der Ausgabe + Anzahl der Bytes + Ausgabewert.
3, Programmierung der grundlegenden Dateneinheiten
Nach der obigen Analyse haben wir festgestellt, dass die grundlegenden Dateneinheiten gleich sind, unabhängig von der Art von physikalischen Links, die Anwendungsdaten implementiert werden. In der Tat ist das Format des Domainnamen-Segments auch das gleiche, so dass wir
Der Domänenname + die PDU werden zusammen als grundlegende Dateneinheit implementiert.
Die Implementierung der grundlegenden Dateneinheiten wird in zwei Szenarien unterteilt: Erstens ist es als Master-Station (Client) an die Slave-Station (Server) Befehle ausgegeben; zweitens ist es als Slave-Station (Server) auf die Master-Station (Client) Befehle zu antworten. Wir haben diese beiden Szenarien in zwei Grundfunktionen zusammengefasst:
(1) Als RTU-Master (TCP-Client) generieren Sie einen Befehl zum Lesen und Schreiben von Objekten von RTU-Slaven (TCP-Server):
uint16_t GenerateReadWriteCommand (ObjAccessInfo objInfo, bool * statusList, uint16_t * registerList, uint8_t * commandBytes)
Die Parameter sind die grundlegenden Informationen der PDU-Einheit, die entsprechenden Daten des Schreibobjekts und das generierte Befehlsbyte. Der Rückgabewert ist die Länge des generierten Befehls.
(2) Als Slave-Station (Server) generiert die Antwort des Lesenzugriffs der Master-Station. Da die Antwort des Schreibvorgangs tatsächlich Teil des Befehls ist, der den Master (Client) kopiert, müssen wir die Antwort generieren, die den Funktionscode 0x01, 0x02, 0x03 und 0x04 enthält.
uint16_t GenerateMasterAccessRespond(uint8_t * receivedMessage, bool * statusList, uint16_t * registerList, uint8_t * respondBytes)
Die Parameter sind die empfangenen Informationen, die Daten des gelesenen Objekts und die zurückgegebene Antwortnachricht. Der Rückgabewert ist die Länge der zurückgegebenen Antwortnachricht.
4、RTUProgrammierung der Anwendungseinheit
Für die RTU-Anwendungseinheit ist das Nachrichtenformat: "Domänenname + PDU + CRC", und Domänenname + PDU haben wir im vorherigen Abschnitt implementiert, so dass wir die RTU-Dateneinheit in der Tat nur mit der CRC-Prüfung abgeschlossen haben müssen.
Die Implementierung der RTU-Dateinheit wird in zwei Szenarien unterteilt: Erstens ist es als Master-Station, wenn die Befehle an die Slave-Station ausgegeben werden; zweitens ist es als Slave-Station, wenn die Antwort auf die Master-Station - Befehle. Wir haben diese beiden Szenarien in zwei Grundfunktionen zusammengefasst:
(1) Erstellen Sie Befehle zum Lesen und Schreiben von RTU-Sklavenobjekten als RTU-Master:
/ * Generiert einen Befehl zum Lesen und Schreiben von Slave-Datenobjekten mit einer Länge von 2 Check-Bytes * /
uint16_t SyntheticReadWriteSlaveCommand (ObjAccessInfo slaveInfo, bool * statusList, uint16_t * registerList, uint8_t * commandBytes)
Die Parameter sind die grundlegenden Informationen von der Station, die Datenliste, die ausgegeben werden, und das endgültig generierte Array von Befehlen. Der Rückgabewert ist die Länge der Befehle.
(2) Als Slave-Station erzeugt die Antwort des Lesebesuchs der Master-Station:
/ * Erstellen Sie eine Antwort auf die Master-Station * /
uint16_t SyntheticSlaveAccessRespond (uint8_t * receivedMessage, bool * statusList, uint16_t * registerList, uint8_t * respondBytes)
Die Parameter sind die empfangenen Informationen, die zurückgegebene Datenliste und die generierte Antwortinformationsliste. Der Rückgabewert ist die Länge der Antwortinformationenliste.
5、TCPProgrammierung von Anwendungseinheiten
Und für die TCP-Anwendungseinheit, und RTU-Klasse, das Nachrichtenformat ist: "MBAP-Header + PDU", und die PDU-Einheit ist zuvor definiert, so dass Sie nur den MBAP-Header hinzufügen müssen, in der Tat ist das Implementierungsformat des MBAP-Headers fest.
Die Implementierung der TCP-Anwendungseinheit ist auch in zwei Situationen unterteilt: Erstens ist es als Client, wenn der Server einen Befehl erteilt; zweitens ist es als Server, wenn der Client-Befehl reagiert. Wir haben diese beiden Szenarien in zwei Grundfunktionen zusammengefasst:
(1) Erzeugt als TCP-Client Befehle zum Lesen und Schreiben von TCP-Server - Objekten:
/ * Befehl zum Erstellen von Lese - und Schreib-Server - Objekten * /
uint16_t SyntheticReadWriteTCPServerCommand (ObjAccessInfo objInfo, bool * statusList, uint16_t * registerList, uint8_t * commandBytes)
(2), als (Server, generiert eine Antwort für den Lese - und Schreibzugriff des Clients:
/ * Synthetisiert die Antwort auf einen Serverzugriff, der den Befehllänge zurückgibt * /
uint16_t SyntheticServerAccessRespond(uint8_t * receivedMessage, bool * statusList, uint16_t * registerList, uint8_t * respondBytes)
6, Schlussfolgerung
In der Tat haben wir hier das Modbus-Grundprotokoll im Grunde implementiert, und sogar mit diesen grundlegenden Operationen können wir die Modbus-Kommunikation implementieren. In der Tat, viele Leute in der Anwendung Schreiben Modbus-Kommunikationsprotokoll ist einfacher als dies, kann auch ein Teil der Modbus-Kommunikationsfunktion zu implementieren. Natürlich ist das nicht unser Ziel, sonst brauchen wir keine spezielle Entwicklung der Bibliothek, wir müssen sie weiter verkapseln, um sie allgemeiner und benutzerfreundlicher zu machen, was wir brauchen.
Nachdem wir die vorherige Arbeit abgeschlossen haben, können wir gezielte Anwendungen implementieren, zunächst werden wir die Serverseitige Anwendung von Modbus TCP implementieren. Natürlich machen wir keine spezifischen Anwendungen, sondern kapseln die Serverseitige Anwendung von Modbus TCP für den Aufruf bei Bedarf.
Hier geht es nicht um das TCP-Protokoll, das nicht unbedingt mit Modbus verbunden ist, sondern um das Modbus-Protokoll in seiner Anwendungsschicht auszuführen.
Für Modbus TCP-Server müssen wir mehrere Funktionen implementieren: Zuerst müssen wir die empfangenen Client-Befehle analysieren, und wir implementieren nur die Unterstützung der oben genannten 8 häufig verwendeten Funktionen. Zweitens, nachdem die Analyse abgeschlossen ist, müssen wir die entsprechenden Operationen verschiedener Funktionscodes implementieren. Die spezifische Architektur lautet:

1Befehl-Analyse
Nachdem der Server als passive Seite die Anfrage des Clients empfängt, verarbeitet er die Installationsanfrage. So empfängt der Server die Informationen zuerst analysieren sie, hier brauchen wir nur eine Analyse-Funktion, um es zu erledigen, wenn in einer konkreten Anwendung, nur die empfangenen Informationen aufrufen diese Funktion analysieren kann.
/ * Analyse der empfangenen Informationen und Rückgabe der Länge des Befehls * /
uint16_t ParsingClientAccessCommand(uint8_t *receivedMessage, uint8_t *respondBytes)
Diese Funktion ist nur von außen sichtbar. Wenn wir eine Anwendung für den TCP-Server entwickeln wollen, rufen wir diese Funktion auf.
2Befehl, Bearbeitung
Nach dem Befehl analysiert, nach verschiedenen Funktionscodes, um verschiedene Verarbeitung, unterstützen wir 8 Arten von Funktionscodes, jede Funktion entspricht den Verarbeitungsteilen, viele Male werden wir bei der Analyse nach den Ergebnissen der Analyse verarbeitet, das erfordert eine sehr große Analyse-Funktion. Um die Operation zu vereinfachen, haben wir den Verarbeitungsteil jedes Funktionscodes in eine Funktion kapseln. Dann verwenden Sie ein Funktionszeiger-Array, um diese Funktionen dynamisch aufzurufen, wodurch sie vereinfacht werden.
Zuerst benötigen wir 8 Funktionen, die eine Funktionscode-Operation verarbeiten:
/ * Befehl zum Lesen von Spule-Status * /
statische uint16_t HandleReadCoilStatusCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Befehl zum Lesen des Eingabestatus verarbeiten * /
statisch uint16_t HandleReadInputStatusCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Read-Hold - Register-Befehlen * /
statisch uint16_tRead HandleHoldingRegisterCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Read-Eingabe - Register-Befehlen * /
statisch uint16_tRead-Handle - Input-Register - Command (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Schreibbefehlen für einzelne Spulen * /
statische uint16_t HandleWriteSingleCoilCommand (uint16_t coilAddress,uint16_t coilValue,uint8_t * receivedMessage,uint8_t * respondBytes)
/ * Verarbeitung von Schreibbefehlen für einzelne Register * /
statisch uint16_t HandleWriteSingleRegisterCommand (uint16_t registerAddress,uint16_t registerValue,uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung des Schreibens von mehreren Spulen * /
static uint16_t HandleWriteMultipleCoilCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung des Schreibens von mehreren Registerzuständen * /
static uint16_t HandleWriteMultipleRegisterCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
Dann definieren wir ein Array von Funktionszeigern, um die folgende Funktion aufzurufen:
uint16_t (* HandleClientCommand[])(uint16_t, uint16_t, uint8_t *, uint8_t *) ={HandleReadCoilStatusCommand,
HandleReadInputStatusCommand
HandleReadHoldRegisterCommand
HandleReadInputRegisterCommand,
HandleWriteSingleCoilCommand,
HandleWriteSingleRegisterCommand
HandleWriteMultipleCoilCommand,
HandleWriteMultipleRegisterCommand}
3, Erstellung von Antworten
Für die Erstellung von Befehlen für Antworten in der Tat bereits im zweiten Kapitel gesagt, die Notwendigkeit ist, die spezifischen Werte für die verschiedenen Schreibdaten und die verschiedenen spezifischen Werte für die Lesedaten zu erhalten, die wir in einer separaten Datei implementieren werden, da dieser Teil TCP und RTU identisch sind, werden wir in den folgenden Kapiteln erläutern.
Dieses Mal haben wir die Modbus TCP Client-Anwendung kapseln. Auch wir machen keine spezifischen Anwendungen, sondern implementieren die grundlegenden Funktionen des TCP-Clients. Wir kapseln die Funktionen des TCP-Clients in Funktionen, die bei der Entwicklung einer spezifischen Anwendung aufgerufen werden können.
Für den TCP-Client haben wir zwei Hauptfunktionen implementiert: Die erste ist die Generierung von Befehlen für den Zugriff auf den TCP-Server, die insgesamt 8 Funktionscodes unterstützen. Die zweite ist die Analyse der Informationen, die vom TCP-Server zurückgegeben werden, und die Durchführung verschiedener Operationen basierend auf den Ergebnissen, die ebenfalls die Funktionen in 8 unterstützen. Die spezifische Struktur des Zugriffs auf die Software ist wie folgt:

1, die Erzeugung von Zugriffsbefehlen
Als aktive Interaktion muss der Client verschiedene Operationsanfragen an den Server senden. Für den TCP-Client müssen Sie zuerst einen Befehl generieren, der auf den Server zugreifen soll. Die Erstellung von Befehlen muss nur in einem Standardprotokollformat generiert werden, und wir unterstützen derzeit nur die oben genannten 8 Funktionscodes.
/ * Befehle für den Zugriff auf den Server erstellen * /
uint16_t CreateAccessServerCommand(ObjAccessInfo objInfo, void * dataList, uint8_t * commandBytes)
Auf diese Weise müssen Sie bei der Entwicklung einer speziellen Client-Anwendung nur diese Funktion aufrufen, um Befehle für den Zugriff auf den Server zu generieren.
2Analyse der Antwortinformationen
Wie im vorherigen Abschnitt beschrieben, verarbeitet der Server nach Erhalt des Befehls eine Antwort und erzeugt eine Antwort, die an den Client zurückgegeben wird. Nachdem der Client den Befehl empfängt, muss er zuerst die Antwortinformationen analysieren, der Prozess der Analyse ist in der Tat mit dem Server übereinstimmt. Der Unterschied besteht darin, dass keine Antwortinformationen auf der Grundlage des Analyseergebnisses mehr generiert werden müssen.
/ * Resolviert die empfangenen Server-Informationen * /
void ParsingServerRespondMessage(uint8_t * receivedMessage)
So können wir diese Funktion bei der Entwicklung von Client-Anwendungen aufrufen, um die Antwort-Informationen zu analysieren.
3, Antwort Verarbeitung
Für die Informationen, die wir analysieren müssen, müssen wir die Operation nach der Situation, wie zum Beispiel ändern Variablen Wert, sollte die wichtigsten unterstützten Operationscode ist 8, theoretisch korrespondiert jede Art von Funktionscode wird unterschiedliche Operationen haben, aber in der Tat, weil die Schreiboperationsbefehle nicht mehr brauchen, um eine Operation zu tun, so dass die korresponsive Operation in der Tat nur 4 Arten von Funktionscodes lesen Operationen.
/ * Verarbeitung des Lesens der Rückgabeinformationen vom Zustand der Station, Lesen der Funktionscode 0x01
static void HandleReadCoilStatusRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung von Ausgabeinformationen über den Zustand der Slave-Station, Ausgabe von Eingabe-Status - Bit0x02 Funktionscode * /
static void HandleReadInputStatusRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung von Rückgabeinformationen für das Lesen von Registerwerten von Slaves, Lesen von Hold Register 0x03 Funktionscode) * /
static void HandleReadHoldingRegisterRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung der Rückgabe von Slave-Register - Werten, Lesen des Eingabe-Registers 0x04 Funktionscode * /
static void HandleReadInputRegisterRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
Ebenso definieren wir auch ein Array von Funktionszeigern, um die Aufrufe dieser Funktionen zu implementieren:
void (* HandleServerRespond []) (uint8_t *, uint16_t, uint16_t) = {HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};
Damit ist die Verpackung des TCP-Clients abgeschlossen, natürlich müssen die spezifischen Datenverarbeitungsteile in der Entwicklung der spezifischen Anwendung festgelegt werden.
Modbus ist in Slave und Master unterteilt, in diesem Abschnitt werden wir Slave entwickeln. Für Modbus-RTU - Slaves müssen die Funktionen genau die gleichen wie auf der Serverseite von Modbus TCP implementiert werden. Auch ihr Betriebsprozess ist identisch. Zuerst empfangen Sie den Zugriff Befehl der Masterstation, um die Befehl-Nachricht zu analysieren, hier sind wir nur die zuvor erwähnten 8 Funktionscodes zu implementieren. Zweitens führen wir entsprechend den Ergebnissen der Analyse durch, und die spezifische Software-Zugriffsstruktur ist wie folgt:

Aus der obigen Abbildung ist es nicht schwer zu finden, dass es tatsächlich 3 Schritte ist:
Schritt 1: Nach Erhalt des Befehls analysieren. Die Methoden der Analyse und die Formen in den vorherigen Abschnitten.
Schritt 2 - Anhand der Analyseergebnisse. Enthält mehr Befehle, um den Wert einer Variablen zu modifizieren oder zu erhalten.
Schritt 3: Erstellen Sie eine Antwort und geben Sie sie an den Host zurück.
1, Resolve Host-Befehl
Der Slave analysiert die Befehlnachricht des Hostes nach dem Empfang, und wir unterstützen vorübergehend die 8 Funktionscodes wie oben gezeigt.
/ * analysiert die empfangene Nachricht und gibt die synthetische Antwort und die Bytelänge der Nachricht zurück, indem die Rückruffunktion * /
uint16_t ParsingMasterAccessCommand(uint8_t *receivedMessage, uint8_t *respondBytes, uint16_t rxLength)
Diese Funktion empfängt die empfangene Nachricht und erzeugt eine Antwortinformationsnachricht, wobei der Rückgabewert die Länge der Antwortnachricht ist. In der Entwicklung der Anwendung ist es, die empfangene Nachricht an die Funktion zu senden und die erzeugten Antworten an den Client zurückzugeben.
2Ergebnis Operationen
Nach der Auswertung müssen entweder Lesen - oder Schreiben-Befehle entsprechend ausgeführt werden Wir verpacken verschiedene Operationen je nach verschiedenen Funktionscodes:
/ * Verarbeitung des Befehls zum Lesen der Spule * /
statisch uint16_t HandleReadCoilStatusCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von ReadInputStatusCommand * /
statisch uint16_t HandleReadInputStatusCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Read-Holding - Register-Befehlen * /
statisch uint16_tRead-Handle - Holding-Register-Command (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Read-Eingabe - Register-Befehlen * /
statisch uint16_tRead-Handle - Input-Register - Command (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung von Schreibbefehlen für einzelne Spulen * /
statische uint16_t HandleWriteSingleCoilCommand (uint16_t coilAddress,uint16_t coilValue,uint8_t * receivedMessage,uint8_t * respondBytes)
/ * Verarbeitung des Schreibbefehls für ein einzelnes Register * /
statisch uint16_t HandleWriteSingleRegisterCommand (uint16_t registerAddress,uint16_t registerValue,uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung des Schreibens mehrerer Spulenzustände * /
static uint16_t HandleWriteMultipleCoilCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
/ * Verarbeitung des Schreibens von mehreren Registerzuständen * /
statisch uint16_t HandleWriteMultipleRegisterCommand (uint16_t startAddress, uint16_t quantity, uint8_t * receivedMessage, uint8_t * respondBytes)
Wir definieren auch ein Array von Funktionszeiger, um die Aufrufe dieser acht Funktionen zu implementieren:
uint16_t (* HandleMasterCommand [])(uint16_t, uint16_t, uint8_t *, uint8_t *) = {HandleReadCoilStatusCommand,
HandleReadInputStatusCommand
HandleReadHoldRegisterCommand
HandleReadInputRegisterCommand
HandleWriteSingleCoilCommand,
HandleWriteSingleRegisterCommand,
HandleWriteMultipleCoilCommand,
HandleWriteMultipleRegisterCommand}
3Erstellen von Reaktionen
Die Verarbeitung muss auch die entsprechenden Informationen des Slaves erzeugen, ob es sich um einen Lesebefehl oder einen Schreibbefehl handelt, erzeugen wir eine Antwortmeldung bei der Verarbeitung des entsprechenden Funktionscodes. Bei der Entwicklung von Anwendungen müssen Sie also nur die Analyse von Daten aufrufen, um alle Funktionen zu implementieren
In diesem Abschnitt beschreiben wir die letzte Anwendung (Modbus RTU Master-Anwendung), die mit der Entwicklung des TCP-Clients übereinstimmt. Auch wir machen keine spezifischen Anwendungen, sondern realisieren die grundlegenden Funktionen der RTU-Hauptstation. Wir haben die Funktionen der RTU-Master - Station in Funktionen verpackt, die bei der Entwicklung spezifischer Anwendungen aufgerufen werden können.
Für die RTU-Master - Station haben wir zwei Hauptfunktionen implementiert: Die erste ist die Generierung von Befehlen für den Zugriff auf die RTU-Slave - Station, die insgesamt 8 Funktionscodes unterstützen. Die zweite ist die Analyse der von der RTU von der Seite zurückgegebenen Informationen und die Durchführung verschiedener Operationen basierend auf den Ergebnissen, die auch die Unterstützung der Funktionen in 8 - Operationen. Die spezifische Software-Zugangsstruktur lautet wie folgt:

1und die Erzeugung von Zugriffskommandos
Als aktive Interaktion muss der Client verschiedene Operationsanfragen an den Server senden. Für die RTU-Masterstation müssen Sie zuerst einen Befehl für den Zugriff auf den Server generieren. Die Erstellung von Befehlen muss nur in einem Standardprotokollformat generiert werden, und wir unterstützen derzeit nur die oben genannten 8 Funktionscodes.
/ * Erstellen von Befehlen für den Zugriff auf die Station * /
uint16_t CreateAccessSlaveCommand(ObjAccessInfo objInfo, void * dataList, uint8_t * commandBytes)
So müssen Sie diese Funktion nur aufrufen, um Befehle für den Zugriff auf den Server zu generieren, wenn Sie eine spezifische Client-Anwendung entwickeln.
2, Analyse von Antwortinformationen
Wie im vorherigen Abschnitt beschrieben, erhält der Server einen Befehl, verarbeitet er den Befehl und generiert eine Antwort, die an den Client zurückgegeben wird. Nachdem der Client den Befehl empfängt, muss er zuerst die Antwortinformationen analysieren, der Prozess der Analyse ist in der Tat mit dem Server übereinstimmt. Der Unterschied besteht darin, dass keine Antwortinformationen auf der Grundlage des Analyseergebnisses mehr generiert werden müssen.
/ * Auswertung der von der Station empfangenen Informationen * /
/ * ParsingSlaveRespondMessage (uint8_t * receivedMessage)
So können wir diese Funktion bei der Entwicklung von Client-Anwendungen aufrufen, um die Antwort zu analysieren.
3, Antwort Verarbeitung
Für die Informationen, die wir analysieren müssen, müssen wir den Betrieb entsprechend der Situation zu implementieren, wie beispielsweise den Wert der Variablen ändern, sollte die wichtigsten unterstützten Operationscodes sind 8, theoretisch wird jede Art von Funktionscode unterschiedliche Operationen haben, aber in der Tat, weil die Schreiboperationsbefehle haben keine Operation, so dass die entsprechenden Operationen tatsächlich nur 4 Arten von Funktionscodes lesen.
/ * Verarbeitung des Lesens der Rückgabeinformationen vom Zustand der Station, Lesen der Funktionscode 0x01
static void HandleReadCoilStatusRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung von Ausgabeinformationen über den Zustand der Slave-Station, Ausgabe von Eingabe-Status - Bit0x02 Funktionscode * /
static void HandleReadInputStatusRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung von Rückgabeinformationen für das Lesen von Registerwerten von Slaves, Lesen von Hold Register 0x03 Funktionscode) * /
static void HandleReadHoldingRegisterRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
/ * Verarbeitung der Rückgabe von Slave-Register - Werten, Lesen des Eingabe-Registers 0x04 Funktionscodes * /
static void HandleReadInputRegisterRespond (uint8_t * receivedMessage, uint16_t startAddress, uint16_t quantity)
Ebenso definieren wir auch ein Array von Funktionszeigern, um die Aufrufe dieser Funktionen zu implementieren:
void (* HandleServerRespond []) (uint8_t *, uint16_t, uint16_t) = {HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};
Hier ist die Verpackung der RTU-Masterstation abgeschlossen, natürlich muss der spezifische Datenverarbeitungsteil bei der Entwicklung einer spezifischen Anwendung festgelegt werden.
Viele Anwendungen wurden zuvor entwickelt, aber es wurde kein Problem erwähnt, dass Sie spezifische Daten lesen und schreiben. Für Modbus gibt es vier Standard-Datenarten: Spule-Daten (Adresse: 0000x), Eingabe-Status - Daten (Adresse: 1000x), Hold-Register - Daten (Adresse: 4000x) und Eingabe-Register - Daten (Adresse: 3000x). Der Zweck unserer Kommunikation ist es, diese Daten zu verarbeiten, aber die Datenverarbeitung ist in unserer vorherigen Verpackung nicht erwähnt. Tatsächlich kann dies nicht berücksichtigt werden, da die spezifischen Anwendungsdaten so verschieden sind, dass sie nicht kapseln können. Wie lösen wir das Problem? Als nächstes werden wir diese Art von Problem lösen.
1, Verpackung der Datenverarbeitung Funktionen
Wir berücksichtigen, dass die Datenverarbeitung im Wesentlichen gleich ist, ob im RTU Master, RTU Slave, TCP-Client oder auf dem TCP-Server, solange die spezifische Anwendung der Datenstruktur nach der Verarbeitung Methode festgelegt ist. In Anbetracht dessen ist unser Ansatz, Funktionen mit geschwächten Typen zu definieren. Wie folgt:
/ * Holt den Wert der Coil-Menge ab, die gelesen werden soll * /
__weak void GetCoilStatus(uint16_t startAddress, uint16_t quantity, bool * statusList)
{
/ / Wenn Sie spezifische Inhalte in der Modbus TCP Server / RTU Slave-Anwendung implementieren müssen
}
/ * Holen Sie den Wert des gewünschten InputStatus * /
__weak void GetInputStatus(uint16_t startAddress, uint16_t quantity, bool * statusValue)
{
/ / Wenn Sie eine spezifische Implementierung in der Modbus TCP Server / RTU Slave-Anwendung benötigen
}
/ * Holen Sie den Wert des gehaltenen Registers, den Sie lesen möchten * /
__ weak void GetHoldingRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue)
{
/ / Wenn Sie einen spezifischen Inhalt in der Modbus TCP Server / RTU Slave-Anwendung implementieren möchten
}
/ * Holen Sie den Wert des Eingangsregisters ab, das Sie lesen möchten * /
GetInputRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue)
{
/ / Wenn es in der Modbus TCP Server / RTU Slave Anwendung spezifisch implementiert werden muss
}
/ * Stellen Sie den Wert für eine einzelne Spule * /
__ weak void SetSingleCoil(uint16_t coilAddress, bool coilValue)
{
/ / Wenn Sie spezifische Inhalte in der Modbus TCP Server / RTU Slave Anwendung implementieren möchten
}
/ * Stellen Sie einen Wert für ein einzelnes Register * /
__ weak void SetSingleRegister(uint16_t registerAddress, uint16_t registerValue)
{
/ / Wenn Sie spezifische Inhalte in der Modbus TCP Server / RTU Slave-Anwendung implementieren müssen
}
/ * Setzen Sie mehrere Werte für Spulen * /
__ weak void SetMultipleCoil(uint16_t startAddress,uint16_t quantity, bool * statusValue)
{
/ / Wenn Sie eine spezifische Implementierung in der Modbus TCP Server / RTU Slave-Anwendung benötigen
}
/ * setzen Sie Werte für mehrere Register * /
__ weak void SetMultipleRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue)
{
/ / Bei Bedarf in der Modbus TCP Server / RTU Slave-Anwendung
}
/ * Aktualisieren Sie den gelesenen CoilStatus * /
__ weak void UpdateCoilStatus(uint16_t startAddress, uint16_t quantity, bool * stateValue)
{
Implementiert in der Client-Anwendung (Mainstream)
}
/ * Aktualisieren des Eingabestatuswertes * /
__ weak void UpdateInputStatus(uint16_t startAddress, uint16_t quantity, bool * stateValue)
{
/ / Implementieren von
}
/ * Aktualisieren des Lesestatus der Spule * /
__ weak void UpdateHoldingRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue)
{
/ / Implementieren von
}
in der Client-Anwendung (Master-Station) / * Aktualisieren des zurückgelesenen Spulenstatus * /
UpdateInputResgister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue)
{
In der Client-Anwendung (Master-Station) implementieren
}
Bei der Entwicklung einer konkreten Anwendung müssen wir nur die entsprechende Funktion in der Anwendung implementieren, um die Funktion vollständig zu machen, wie die spezifischen Daten verarbeitet werden, hängt das Datenformat in der spezifischen Anwendung ab. Natürlich müssen diese Funktionen nicht alle implementiert werden, sondern nur nach eigenen Bedürfnissen.
2Das Problem der Größe
, Fragen über die Größe
Bei der Multi-Byte - Datenkommunikation ist das Problem der Größe offensichtlich, wie z. B. eine Gleitkommazahl in verschiedenen Systemen gespeichert Reihenfolge ist unterschiedlich, Sie lesen oder schreiben Daten werden eine falsche Analyse auftreten. Das müssen wir bei der Verarbeitung von Datensätzen berücksichtigen.
Die Modbus-Kommunikation ist natürlich unverzichtbar auf die zyklische Redundanzprüfung (CRC), insbesondere auf standardmäßigen seriellen RTU-Verbindungen. Nicht nur in anderen Entwicklungen wird häufig der CRC- Algorithmus verwendet.Prüfen Sie verschiedene Daten. Hierfür müssen wir den Algorithmus Cyclic Redundancy Checking (CRC) kennenlernen.
1、CRCZusammenfassung
Die zyklische Redundanzprüfung (CRC) ist eine Funktion zur Fehlerüberprüfung bei der Datenübertragung, die polynomiale Berechnungen der Daten durchführt und das resultierende Ergebnis hinter dem Frame beigefügt, und das Empfangsgerät führt ähnliche Algorithmen durch, um die Richtigkeit und Integrität der Datenübertragung zu gewährleisten.
Die grundlegende Idee der CRC-Check ist die Verwendung der linearen Kodierungstheorie, am Sendeende basierend auf der k-Bit - Binärcode-Sequenz zu übertragen, mit bestimmten Regeln zu erzeugen, um einen Überprüfungs-Code (sowohl CRC-Code) r-Bits, und angehängt an die Nachricht, um eine neue Binärcode-Sequenz insgesamt (k + r) Bits zu bilden, schließlich zu senden. Am Empfangsende wird eine Überprüfung nach den Regeln durchgeführt, die zwischen dem Informationscode und dem CRC-Code befolgt werden, um festzustellen, ob ein Fehler bei der Übertragung aufgetreten ist.
Das Wesen des CRC ist der Rest der Modulo - 2 - Teilung, wobei die Teiler unterschiedlich sind und der CRC-Typ unterschiedlich ist. In der Regel wird der CRC-Teiler durch ein erzeugendes Polynomium dargestellt. Die am häufigsten verwendeten CRC-Code - Erzeugungspolynome sind folgende:

Es gibt unterschiedliche Polynome, so dass das Ergebnis natürlich auch anders ist. In der Modbus-Kommunikation wird CRC - 16 verwendet.
2- Algorithmus-Analyse
Die Codierung des CRC-Prüfcodes wird durch die Teilung der zu übertragenen Binärdaten t (x) durch das Polynomial g (x) erzeugt, wobei der letzte Rest als CRC-Prüfcode verwendet wird. Die Implementierungsschritte sind folgende:
Nehmen wir an, dass der zu übersendende Datenblock ein binäres Polynom t (x) mit m-Bit ist, und erzeugen wir ein Polynom g (x) mit r-Reihe. Fügen Sie am Ende des Blocks r 0s hinzu, erhöht sich die Länge des Blocks auf m + r-Bits, und das entsprechende binäre Polynom ist. Entfernen Sie mit dem erzeugenden Polynom g (x) und erhalten Sie das binäre Polynom y (x) mit der Ordnung r - 1. Dieses binäre Polynom y (x) ist der CRC-Prüfcode für t (x), der durch das generierte Polynom g (x) kodiert wurde. Subtrahieren Sie y (x) im Modul 2 und erhalten Sie ein binäres Polynom. Es handelt sich um eine zu versendende Zeichenfolge, die einen CRC-Prüfcode enthält.
Aus den CRC-Codierungsregeln kann man sehen, dass die CRC-Codierung tatsächlich das m-Bit - binäre Polynomial t (x) in das m + r-Bit - binäre Polynomial konvertiert, das durch g (x) geteilt werden kann, so dass bei der Decodierung g (x) mit den empfangenen Daten entfernt werden kann, wenn der Rest Null ist, dann gibt es keinen Fehler im Übertragungsprozess; Wenn der Rest nicht Null ist, dann gibt es sicherlich einen Fehler im Übertragungsprozess. Viele Hardware-Decodierungsschaltungen von CRCs werden auf diese Weise Fehlerprüfung durchgeführt. Es kann auch als eine Kombination von t (x) und CRC-Checkcodes betrachtet werden, so dass bei der Decodierung die empfangenen Binärdaten die R-Bit - Daten entfernen und die ursprünglichen Daten erhalten.
Tatsächlich unterscheidet sich die tatsächliche CRC-Berechnung normalerweise etwas von der oben beschriebenen. Dies liegt daran, dass diese grundlegendste CRC-Teilung einen offensichtlichen Mangel hat, dass das Hinzufügen von Nullen zu Beginn des Datenstroms das Ergebnis der letzten Überprüfung nicht beeinflusst. Um diesen Mangel auszugleichen, wurden zwei Begriffe eingeführt: der eine ist der "Rest-Initial - Wert" und der andere ist der "Result-Exclusive - OR-Wert". Der sogenannte „Rest-Initial - Wert" ist der Anfangswert, der einer Speichervariablen zugewiesen wird, bevor der CRC-Wert berechnet wird. Der entsprechende "Exclusive OR-Wert des Ergebnisses" ist, dass nach der Berechnung der Variablenwert und dieser Wert die letzte Exclusive OR-Operation durchführen, um das Prüfergebnis zu erhalten.
| Name | Prüfsumme Bitbreite | Polynomial erzeugen | Teiler (Polynomial) | Rest anfänglicher Wert | Ergebnis Exclusive OR-Wert |
| CRC - 4 | 4 | x4 + x + 1 | 3 | ||
| CRC - 8 | 8 | x8 + x5 + x4 + 1 | 0x31 | ||
| CRC - 8 | 8 | x8 + x2 + x1 + 1 | 0x07 | ||
| CRC - 8 | 8 | x8 + x6 + x4 + x3 + x2 + x1 | 0x5E | ||
| CRC - 12 | 12 | CRC - 12 | CRC - 16 | ||
| CRC - 16 | 16 | x16 + x15 + x2 + 1 | 0x8005 | 0x000000 | 0x000000 |
| CRC-CCITT | 16 | x16 + x12 + x5 + 1 | 0x1021 | 0xFFFF | 0x000000 |
| CRC-32 | 32 | x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+1 | 0x04C11DB7 | 0xFFFFFF | 0xFFFFFF |
| CRC-32c | 32 | x32+x28+x27 +... + x8 + x6 + 1 | 1EDC6F41 |
Dabei können wir die Implementierung dieses Algorithmus beschreiben:
Schritt 1: Definieren Sie die CRC-Speichervariable und geben Sie ihr den Wert „Rest-Initial - Wert" zu.
Schritt 2: Das erste 8 - Bit-Zeichen der Daten wird mit der CRC-Speichervariable exorisiert und das Ergebnis in die CRC-Speichervariable gespeichert.
Schritt 3: Verschieben Sie die CRC-Speichervariable ein Bit nach rechts, füllen Sie die MSB Nullen, bewegen Sie sich aus und überprüfen Sie die LSB.
Schritt 4: Wiederholen Sie den dritten Schritt, wenn LSB 0 ist; wenn LSB 1 ist, unterscheidet sich das CRC-Register von 0x31.
Schritt 5: Wiederholen Sie die Schritte 3 und 4, bis alle 8 Schritte abgeschlossen sind. Die 8 - Bit-Datenverarbeitung ist abgeschlossen.
Schritt 6: Wiederholen Sie die Schritte 2 bis 5, bis alle Daten verarbeitet wurden.
Schritt 7: Der Inhalt der endgültigen CRC-Speichervariablen wird CRC-Wert, wenn der Inhalt der Variablen mit dem Ergebnis-Exclusive - OR-Wert oder Nicht-Operation durchgeführt wird.
3, Codeimplementierung
Mit der vorherigen Vorbereitung in der Tat wollen wir den CRC-Check - Code zu implementieren ist sehr einfach, es gibt verschiedene Methoden, um diesen Prozess zu implementieren, sagen wir häufig verwendet 2 Arten: Erstens ist die direkte Berechnung, die nach den vorherigen Schritten berechnet wird; Zweitens ist die Treibtabelle-Methode, die einige Daten zu speichern, um die Berechnung direkt zu erhalten. Da in Modbus CRC - 16 verwendet wird, haben wir es als Beispiel implementiert.
(1) direkte Berechnung
direkte Berechnung ist einfach und direkt, das Schreiben von Programmen ist auch relativ einfach, wir nehmen CRC - 16 als Beispiel, dessen Polynomial als 0x8005 beschrieben wird, weil es den Wert von 0x0000 beschrieben hat, so dass es nicht hinzugefügt werden kann. Der Code lautet:
#define Initial_Value. 0x0000
#define EOR 0x0000
#define POLY16 0x8005
uint16_t CRC16(uint8_t * buf, uint16_t Länge)
{
uint16_t crc16,data,val;
crc16 = Initial_Value;
for(int i=0;i<length;i++)
{
if((i % 8) == 0)
{
data = (* buf + +) < <8;
}
val = crc16 ^ Daten;
crc16 = crc16 < < 1;
data = data < < 1;
if(val&0x8000)
{
crc16 = crc16 ^ POLY16;
}
}
return crc16;
}
(2) Treiber-Tabellen - Methode
Bei direkten Berechnungen ist es zwar einfach und direkt, aber manchmal ist die Effizienz ein Problem, so dass wir in der Modbus-Kommunikation normalerweise die Treibertabellenmethode verwenden, um zu implementieren:
/ / CRC_16 Hoch 8 - Bit-Datenbereich
const uint8_t auch CRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41
0x00, 0x1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0x01,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x001, 0x
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x001, 0x
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0x
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0x01,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0x
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0x40, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x80, 0x41 und
0x00, 0x1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0x01,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0x01,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0x
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
/ / CRC Low-Byte - Wert-Tabelle
/ / CRC Low-Byte - Wert-Tabelle
0x00, 0xC0, 0xC1, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC4, 0x04, 0xCC, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, Siehe auch:
0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC 0x14, 0xD4, 0xD4
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF1, 0
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x34, 0xF4, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0xE8, 0xE9, 0x29,
0xEB, 0x2A, 0xEA, 0xEE, 0x2E, 0x2E, Siehe auch:
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 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, 0xA9, 0x78, 0x79, 0xBB, 0x78, 0x79, 0xBA, 0xBE, 0x7E, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 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, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 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 und 0x40
};
/ * Funktion: CRC-Prüfcode erzeugen
Die Eingabeparameter: puchMsgg ist die Nachricht, die CRC überprüft werden soll, usDataLen ist die Anzahl der Bytes in der Nachricht.
Funktionsausgabe: berechneter CRC-Prüfcode
GenerateCRC16CheckCode Tabellensuche Berechnungsfunktion * /
static uint16_t GenerateCRC16CheckCode(uint8_t * puckMsg,uint8_t usDataLen)
{
uint8_t uchCRCHi = 0xFF; / / hohe CRC-Byte - Initialisierung
uint8_t uchCRCLo = 0xFF; / / Initialisierung niedriger CRC-Bytes
uint32_t uIndex; / / Index in der CRC-Schleife
/ / Übertragung von Nachrichtenpuffer
während (usDataLen - -)
{
/ / CRC berechnen
/ / gibt das Ergebnis zurück, das höchste zuvor
uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex];
uchCRCHi = auchCRCLo[uIndex];
}
/ / Rückkehr des Ergebnisses, oben vorher
return (uchCRCLo < < 8| (auchch)
}
4Abschließende Worte
CRC-Anwendungen sind sehr weit verbreitet, vor allem bei der Kommunikation ist es häufiger zu sehen, so dass es sehr notwendig ist, es zu beherrschen, zumindest es zu verwenden. Während der Entwicklung der Modbus-Bibliothek - Funktion haben wir nur ein paar relativ oberflächliche Verständnisse davon, die hier beschrieben werden, um gemeinsam voranzukommen.
Wir haben bereits die Entwicklung des Modbus-Stacks abgeschlossen, aber das ist nicht unser Ziel. Wir haben es natürlich entwickelt, um es zu nutzen, um unsere praktischen Probleme zu lösen. Als nächstes entwickeln wir eine Modbus-TCP - Server-Anwendung mit dem soeben entwickelten Modbus-Protokollstack.
Die Entwicklung von Modbus TCP-Servern erfordert zuerst die Unterstützung des TCP-Servers und die Konfiguration des Netzwerks, aber dies hat nichts mit Modbus selbst zu tun, und wir werden es wieder nicht diskutieren. Wir stellen sicher, dass das Netzwerk und der TCP-Server ordnungsgemäß konfiguriert sind. Im Folgenden werden wir den Implementierungsprozess des Modbus TCP-Servers diskutieren.
Basierend auf der vorherigen Verkapselung des Protokollstacks müssen wir auf die entsprechenden Verkapselungen des Modbus TCP-Servers verweisen Der Prototyp der Parser-Funktion:
uint16_t ParsingClientAccessCommand(uint8_t * receivedMessage, uint8_t * respondBytes);
Diese Funktion wird als Funktionszeiger an den TCP-Server übergeben und ruft die Liste der empfangenen Informationen zurück.
Darüber hinaus müssen wir 8 Callback-Funktionen implementieren, um echte Operationen mit Modbus-Objekten zu erledigen, die Prototypen dieser 8 Funktionen sind wie folgt:
/ * Erhält den Wert der Coil-Menge * /
void GetCoilStatus (uint16_t startAddress, uint16_t quantity, bool * statusList);
/ * Erhält den Wert des zu lesenen InputStatus * /
void GetInputStatus (uint16_t startAddress, uint16_t quantity, bool * statusValue);
/ * Holt den Wert des zu lesen gehaltenen Registers * /
void GetHoldingRegister (uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
/ * Holt den Wert des Eingabe-Registers ab, das gelesen werden soll * /
void GetInputRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
/ * Stellen Sie den Wert für eine einzelne Spule * /
void SetSingleCoil (uint16_t coilAddress, bool coilValue);
/ * Setze den Wert für einzelne Register * /
void SetSingleRegister (uint16_t registerAddress, uint16_t registerValue);
/ * Setze den Wert für mehrere Spulen * /
void SetMultipleCoil (uint16_t startAddress, uint16_t quantity, bool * statusValue);
/ * setzt den Wert für mehrere Register * /
void SetMultipleRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
Diese acht Funktionen implementieren den Betrieb von Datenobjekten entsprechend den spezifischen Anforderungen der Anwendung, wobei mehrere Implementierungen entsprechend den jeweiligen Anforderungen der Anwendung und Datenstrukturen festgelegt werden.
Im vorherigen Abschnitt haben wir eine Modbus TCP Server-Anwendung mit dem Protokoll erstellt. Als nächstes haben wir den Protokollstack für die Entwicklung einer seriellen Link-basierten Mosbus RTU-Slaven - Anwendung verwendet.
Basierend auf der vorhergehenden Verkapselung des Protokollstacks müssen wir auf die entsprechenden Verkapselungen des Modbus TCP-Servers verweisen. Nach Erhalt des Befehls wird die Parserfunktion aufgerufen, um die Parserfunktion zu analysieren, der Prototyp der Parserfunktion:
ParsingMasterAccessCommand(uint8_t * receivedMesasage, uint8_t * respondBytes, uint16_t rxLength);
Der RTU Slave verwendet den seriellen Anschluss zum Empfangen von Informationen, verwendet die oben genannten Funktionen, um die Informationen nach dem Empfang zu analysieren und den Betrieb gemäß den empfangenen Informationen zu beenden.
Natürlich müssen wir zusätzlich zu dem Aufruf der Parserfunktion 8 Callback-Funktionen implementieren, um die tatsächliche Operation des Modbus-Objekts abzuschließen, wobei der Prototyp dieser 8 Funktionen wie folgt ist:
/ * den Wert der Coil-Menge abrufen * /
void GetCoilStatus(uint16_t startAddress, uint16_t quantity, bool * statusList);
/ * Holt den Wert des zu lesen gewünschten InputStatus * /
void GetInputStatus (uint16_t startAddress, uint16_t quantity, bool * statusValue);
void GetHoldingRegister (uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
null GetHoldingRegister(uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
/ * Holt den Wert des Eingaberegisters ab, das gelesen wird * /
void GetInputRegister (uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
/ * Wert für eine einzelne Spule festlegen * /
void SetSingleCoil(uint16_t coilAddress, bool coilValue);
/ * Setze den Wert für ein einzelnes Register * /
void SetSingleRegister (uint16_t registerAddress, uint16_t registerValue);
/ * Stellen Sie den Wert für mehrere Spulen * /
void SetMultipleCoil (uint16_t startAddress, uint16_t quantity, bool * statusValue);
/ * Stellen Sie den Wert für mehrere Register * /
void SetMultipleRegister (uint16_t startAddress, uint16_t quantity, uint16_t * registerValue);
Diese acht Funktionen implementieren den Betrieb von Datenobjekten entsprechend den spezifischen Anforderungen der Anwendung, wobei mehrere Implementierungen entsprechend den jeweiligen Anforderungen der Anwendung und Datenstrukturen festgelegt werden.
Natürlich ist es nicht notwendig, Informationen zu empfangen unterbrechen, können Sie auch Abfragen und andere Methoden verwenden, aber die Verwendung von Unterbrechungen ist eine relativ gute Methode, weil der Senden von Master-Stationen - Befehlen im Allgemeinen nicht regelmäßig ist, die Unterbrechungsmethode kann sowohl eine rechtzeitige Antwort auf Informationen gewährleisten, sondern auch nicht zu viele Ressourcen von Slave-Stationen verbrauchen, und es gibt mehr Ressourcen, um andere Aufgaben zu verarbeiten.
Antwort veröffentlichen