In deze les

Wat deze les behandelt

  • De zeven bytes van de MBAP-header en wie elk veld invult
  • De Length-byte narekenen, en waarom er geen CRC in zit
  • Welke unit id bij een native apparaat hoort en welke achter een gateway

Lees eerst: Exception codes lezen en de juiste function code kiezen, Het Modbus RTU frame byte voor byte, plus CRC

De MBAP header is de envelop van zeven bytes waarin Modbus TCP precies dezelfde PDU stopt die op RS485 tussen een adresbyte en een CRC zit. Vier velden, meer is het niet: transaction id, protocol id, length en unit id. Na deze les ontleed je elk Modbus TCP frame byte voor byte, reken je de Length zelf na, en weet je welke unit id je invult bij een native apparaat en welke achter een gateway.

Hetzelfde verzoek, nu over ethernet

Dit is een leesverzoek over Modbus TCP, precies zoals het over de kabel gaat:

00 01 00 00 00 06 FF 03 00 6B 00 03

Twaalf bytes. Zo lees je ze:

BytesVeldWaardeWat het betekent
00 01transaction id1volgnummer dat de client kiest
00 00protocol id0altijd nul, dit is Modbus
00 06length6er volgen nog zes bytes
FFunit id255native TCP-apparaat, geen gateway
03function codeFC03Read Holding Registers
00 6Bstartadres107PDU-adres 107, in datasheettaal register 40108
00 03aantal registers3drie registers achter elkaar

Het RTU-frame voor dezelfde vraag, uit de les over het RTU-frame en de CRC, telt acht bytes:

01 03 00 6B 00 03 74 17

De laatste vijf bytes zijn in beide frames identiek: 03 00 6B 00 03. Dat is de PDU, en die verandert niet. Weg zijn het serveradres 01 en de CRC 74 17, en daarvoor in de plaats staat een header van zeven bytes. Modbus TCP als transport staat compleet in de uitleg over Modbus TCP; deze les ontleedt die zeven bytes.

De zeven bytes van de MBAP-header

De header heeft vier velden, en alle vier staan ze big-endian op de lijn, high byte eerst.

OffsetVeldBytesWie vult hem in
0transaction identifier2de client kiest, de server kopieert
2protocol identifier2de client zet 0, de server kopieert
4length2allebei zelf berekend
6unit identifier1de client kiest, de server kopieert

Daar zit een geheugensteun in: van de zeven headerbytes zijn er zes identiek in vraag en antwoord. Alleen Length verandert, want dat is het enige veld dat de server zelf uitrekent in plaats van kopieert.

MBAP header, 7 bytesPDU, function code en dataTID2 BPID2 BLEN2 BUID1 BFC1 BDATAN BCRC-16
TIDTransaction id, 2 bytes
PIDProtocol id, 2 bytes, altijd 0
LENLength, 2 bytes
UIDUnit id, 1 byte
FCFunction code
DATAData

Geen CRC: Ethernet en TCP doen de integriteitscontrole al.

Zeven bytes header, daarna exact dezelfde PDU als op RS485.

Het protocol id gedraagt zich anders dan je verwacht. Staat er iets anders dan 0x0000 in, dan stuurt de server geen exception-antwoord terug maar gooit hij het bericht stilzwijgend weg. Je ziet dan niets terugkomen en loopt in een timeout.

Waarom er een transaction id is

Omdat op TCP meerdere verzoeken tegelijk onderweg mogen zijn. Een client mag een tweede vraag versturen voordat het antwoord op de eerste binnen is, en die antwoorden hoeven niet in dezelfde volgorde terug te komen. De transaction id lost dat op: de client geeft elk lopend verzoek op die verbinding een eigen nummer, en de server kopieert dat nummer ongewijzigd naar het antwoord. Vraag en antwoord horen bij elkaar zodra de nummers matchen.

Op RS485 bestaat dat probleem niet, want daar praat er per bus een apparaat tegelijk en wacht de client op antwoord of timeout voordat hij verdergaat. Hoe je het nummer kiest is aan de client: een teller die per verzoek met 1 omhoog gaat is genoeg. De enige eis is dat het nummer uniek is zolang het verzoek loopt.

De Length-byte narekenen

Length telt precies twee dingen: de ene unit id byte plus alle bytes van de PDU. In het verzoek hierboven is de PDU 03 00 6B 00 03, dus vijf bytes, en 1 plus 5 is 6. Dat is de 00 06 in de header. Het complete frame is 12 bytes, altijd zes meer dan Length.

Het antwoord op datzelfde verzoek:

00 01 00 00 00 09 FF 03 06 02 2B 00 00 00 64
BytesVeldWaardeWat het betekent
00 01transaction id1gekopieerd uit het verzoek
00 00protocol id0gekopieerd uit het verzoek
00 09length91 unit id plus 8 PDU-bytes, zelf berekend
FFunit id255gekopieerd uit het verzoek
03function codeFC03dezelfde code, dus geen fout
06byte count6drie registers maal twee bytes
02 2Bregister 1555
00 00register 20
00 64register 3100

De PDU is hier 8 bytes (1 function code, 1 byte count en 6 databytes), dus Length is 9 en het frame 15 bytes. Nu een voorbeeld dat op precies een punt afwijkt:

00 01 00 00 00 03 FF 83 02

Zelfde header, maar Length is 3: 1 unit id plus een PDU van 2 bytes. De function code is 0x83, dat is 0x03 plus 0x80, en de byte erna is exception 02. Een exception-antwoord herken je dus aan twee dingen tegelijk. Wat die codes betekenen staat in de les over function codes en exception codes.

Volledig frame (ADU)

MBAP header (7 bytes)

00010000000601

PDU (function code en data)

030000000A

Length telt 1 byte unit id plus 5 bytes PDU, samen 6.

Transaction id00 011Volgnummer dat de client kiest. De server stuurt het ongewijzigd terug, zodat je vraag en antwoord kunt koppelen.
Protocol id00 000Altijd 0 voor Modbus. Een andere waarde gooit de server stilzwijgend weg.
Length00 066Aantal bytes dat hierna nog volgt, de unit id meegerekend.
Unit id011Achter een gateway het echte serveradres op de RS485-bus. Bij een native TCP-apparaat 255 of 0.
Function code033 (Read Holding Registers)Welke bewerking je vraagt.
Startadres00 000Eerste register, 0-gebaseerd zoals het over de draad gaat.
Aantal00 0A10Aantal registers of coils in dit ene verzoek.
Verander de function code en het aantal, en kijk wat er met de Length-byte gebeurt.

Waarom er geen CRC in zit

Omdat de lagen eronder het werk al doen. Ethernet zet achter elk frame een CRC-32 en gooit beschadigde frames weg voordat ze boven komen. TCP heeft daarnaast een eigen checksum en stuurt gesneuvelde segmenten opnieuw. Een extra controle in de Modbus-laag kost alleen bandbreedte en rekentijd.

Er zit een tweede reden onder. Op RS485 markeert 3,5 karaktertijden stilte het einde van een frame. TCP kent die stilte niet, want het is een bytestroom zonder berichtgrenzen: een bericht kan over twee segmenten verdeeld raken of samen met het volgende in een segment zitten. Het Length-veld vertelt de ontvanger waar het bericht ophoudt, en is daarmee de directe tegenhanger van die stilte.

Wat je in het veld unit id invult

Bij een apparaat dat rechtstreeks aan het netwerk hangt vul je 0xFF in, dus 255. De waarde 0x00 mag ook. Het IP-adres wijst het apparaat immers al aan, dus het byte heeft geen adresfunctie meer. Achter een gateway is het juist wel het echte serveradres op de RS485-bus, tussen 1 en 247. De gateway leest dat byte en stuurt je verzoek naar die ene server door.

Waarom uitgerekend 255? Dat is bescherming. Wordt een IP-adres later opnieuw uitgedeeld en zit er ineens een gateway op, dan routeert die een betekenisvol serveradres door naar een apparaat dat je nooit bedoeld hebt. Bij 255 gooit de gateway het verzoek gewoon weg.

Fabrikanten volgen die aanbeveling lang niet altijd: het SunSpec-profiel van SMA vraagt unit id 126, een Fronius GEN24-omvormer wil 1 en de energiemeter erachter 200, en SolarEdge over ethernet gebruikt 1. Vertrouw hier dus niet op de standaard maar op het datasheet.

De inrichting aan gateway-kant staat in de handleiding voor het installeren van een Modbus TCP gateway, de bredere afweging in de vergelijking van Modbus RTU en Modbus TCP.

Waarom poort 502 vaststaat

Omdat de poort al sinds 1996 bij IANA geregistreerd staat, met de servicenaam mbap op naam van modbus.org. De standaard is er streng over: een apparaat moet standaard op poort 502 luisteren. Een tweede, instelbare poort mag ernaast, maar 502 hoort beschikbaar te blijven.

In de praktijk houdt niet iedereen zich daaraan. Het bekendste voorbeeld is SolarEdge: op toestellen met SetApp staat Modbus TCP standaard op poort 1502. Kom je op 502 niet binnen, controleer dan eerst de poort in het datasheet voordat je de firewall verdenkt.

Veelgemaakte fouten

De unit id verwarren met het RS485-serveradres. Bij een native TCP-apparaat is dat byte betekenisloos en vul je 255 of 0 in. Achter een gateway is het het echte serveradres, en dan is 255 gegarandeerd fout. Kijk eerst waar je tegen praat.

Length lezen als de totale framelengte. Length telt alleen wat er na dat veld komt: 1 byte unit id plus de PDU. Het complete frame is altijd zes bytes langer. Wie die twee verwart, knipt bij het parsen van een berichtenstroom steeds op de verkeerde plek.

Een CRC verwachten. Laat je een RTU-decoder los op een TCP-frame, dan leest die de laatste twee databytes als CRC en meldt een fout die er niet is. In het antwoord hierboven zou 00 64, de meetwaarde 100, ineens een kapotte checksum heten.

Aannemen dat het altijd poort 502 is. Een afwijkende poort mag en komt voor. Controleer hem in het datasheet voordat je in de firewall gaat graven.

Aan de slag

Je bouwt zelf een frame en controleert of de Length klopt. Dit werkt zonder hardware, want de builder in deze les rekent in de pagina.

  1. 1

    Zet de builder op het voorbeeld

    Kies in de MbapBuilder hierboven function code FC03, unit id 255, startadres 107 en aantal 3.

  2. 2

    Vergelijk met het referentieframe

    Leg het resultaat naast 00 01 00 00 00 06 FF 03 00 6B 00 03. Alleen de transaction id mag afwijken, want dat nummer kiest de client zelf.

  3. 3

    Zet het aantal registers op 10

    Kijk wat er met de Length-byte gebeurt, en vooral wat er niet mee gebeurt. Length blijft 6, want een verzoek is even lang of je nu 3 of 10 registers vraagt. Length gaat over dit frame, niet over het antwoord.

  4. 4

    Wissel naar FC16

    Zet de function code op FC16 met drie registers. De PDU groeit van 5 naar 12 bytes, want er komen een byte count en zes databytes bij. Length wordt 13 en het frame 19 bytes.

  5. 5

    Reken het antwoord op papier na

    Hoe lang wordt het antwoord op stap 2? 7 bytes MBAP, plus 1 function code, plus 1 byte count, plus 6 databytes is 15 bytes. Controleer dat tegen het antwoordframe hierboven.

  6. 6

    Wil je het echt over de lijn zien

    Lees later in de cursus met een simulator een register uit en vang het verkeer af. Je ziet dan dezelfde twaalf bytes voorbijkomen, met een eigen transaction id.

Verwacht resultaat: een frame dat op de transaction id na identiek is aan het referentieframe, en een uitgerekende antwoordlengte van 15 bytes.

Samenvatting

  • De MBAP-header is zeven bytes lang: transaction id (2), protocol id (2), length (2) en unit id (1), allemaal high byte eerst.
  • Length telt 1 byte unit id plus de lengte van de PDU, dus het complete frame is altijd zes bytes langer dan die waarde.
  • Van de zeven headerbytes zijn er zes identiek in vraag en antwoord; alleen Length berekent de server zelf opnieuw.
  • Modbus TCP heeft geen CRC omdat ethernet en TCP de integriteit al bewaken, terwijl het Length-veld de berichtgrens markeert.
  • Bij een native TCP-apparaat vul je unit id 255 of 0 in, achter een gateway het echte serveradres tussen 1 en 247.

Controleer jezelf

Vier vragen over deze les. Je krijgt bij elk antwoord uitleg.

Vraag 1 van 4
In het frame 00 01 00 00 00 06 FF 03 00 6B 00 03: wat betekenen de bytes 00 06?
Je leest een energiemeter uit die achter een RS485-naar-Ethernet-gateway hangt. Je vult unit id 255 in en krijgt exception 0B terug. Wat betekent dat?
Waarom heeft Modbus TCP geen CRC terwijl Modbus RTU die wel heeft?
Twee verzoeken van dezelfde client zijn tegelijk onderweg over een TCP-verbinding. Waaraan koppelt de client elk antwoord aan het juiste verzoek?

Loopt het vast op een project?

Beschrijf wat je ziet en waar het misgaat. We kijken mee en zeggen wat we zouden doen.

Verder lezen in de kennisbank