Quote by rage
Guten Morgen,
ich muss einen Sensor an unsere Hardware anschließen und halt auch Daten davon lesen. Zwar sieht Modbus RTU nicht so wirklich dramatisch aus, aber ich frage trotzdem mal. Hat irgendwer das schon mal erledigt und könnte den Sourcecode zur Verfügung stellen?
Ein schönen Tag noch
Ralf
Hallo Ralf,
also hast Du einen Sensor der Modbus-RTU liefert und willst diesen verwenden?
Also ich habe schon mit AVRco einige Steuerungen mit Modbus-RTU (Slave) im Einsatz.
Als Master und Slave habe ich Modbus-ASCII im Einsatz, ist einfacher zu verarbeiten und ist nicht so zeitkritisch wie das RTU. Beim Slave nutze ich die vorhandenen Modbusfunktionen vom AVRco. Beim Master habe ich mir mal einge Routinen gebaut um mir diese entsprechend zu benutzen.
Im Prinzip recht einfach du sendest ein Telegram an die Slave Adr und wartest auf die Antwort.
Sobald die Anwort kommt, Prüfst Du die Gültigkeit, Länge und Checksumme und verarbeitest die Daten.
Die Routinen die ich mir gebaut habe sind aus 2015/2016 und eigentlich genau auf meine eigenen Slaves aufgebaut. Aber diese könntest Du Dir dann für Deinen Slave anpassen, aber es sind nur MODBUS-ASCII Funktionen.
Diese Steuerungen laufen schon seit 2015/2016 im 24h Betrieb ohne einen einzigen Ausfall, schaue Dir die Routinen einfach mal an da kannst Du bestimmt was passendes ableiten.
Mittlerweile würde ich einige Sachen anderst aufbauen und programmieren, aber der Code funktioniert so.
Die Meisten Endgeräte/Sensoren können RTU und ASCII, meistens kann man diese umschalten.
Im Mainloop:
Code
...
loop
SetData; // Daten für ModbusSlave
ReadInput;
// COMTimer, TimeOut, RestartTimer
SlaveCom(6, 8, 8);
// Schreibe Ausgänge an Hardware
WriteOutput;
endloop;
...
Im SlaveCom ist die eigentliche Verbindung und Verarbeitung drinnen:
Code
procedure SlaveCom( bComTime : word; bTOTime : byte; bStartTime : word);
var
strTest : String[100];
bCh : char;
begin
// Sende an Adresse 1
//
if ((ComState=0)AND isSysTimerZero( ComTimer ))then
ComState:= 1;
Toggle( OUT_LED_COM );
SetSysTimer( ComTimer, bComTime);
endif;
if ((ComState = 2) ) then
if SerInp_TO2( bCh, bTOTime ) then
if bCh=CHAR($0A) then
elsif bCh=CHAR($0D) then
else
strTest:=strTest+bCh;
endif;
if (bCh=CHAR($0A)) then
// DebugOut('Read vom Slave : '+strTest);
ComErr1:=0;
ComState:= 3;
SetSysTimer( ComTimer, bComTime);
Toggle( OUT_LED_COM );
bComErr1:=false;
if ModWriteAnsw( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncWrite, SlaveData.Slave[0].WriteRegister, SlaveData.Slave[0].WriteBuffer, strTest) then
//DebugOut('1.OK, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr));
else
bComErr1:=true;
DebugOut('1.Fehler, von Adr. '+ ByteToStr(SlaveData.Slave[0].Adr));
endif;
strTest:='';
endif; // Ende Telegramm
else
if ComErr1 < 1 then
ComState:= 1;
SetSysTimer( ComTimer, bStartTime); //+50
ComErr1:=ComErr1+1;
else
ComState:= 3;
ComErr1:= 0;
SetSysTimer( ComTimer, bStartTime); //+50
bComErr1:=true;
DebugOut('1.TimneOut, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr)+' => '+strTest);
endif;
endif; // Ende SerInp
endif; // ComState2
if ((ComState = 1) AND isSysTimerZero( ComTimer )) then
//DebugOut('1.Write zum Slave: '+ModWriteTel( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncWrite, SlaveData.Slave[0].WriteRegister, SlaveData.Slave[0].WriteBuffer));
Ser_Enable2 (true);
WriteLn (SerOut2, ModWriteTel( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncWrite, SlaveData.Slave[0].WriteRegister, SlaveData.Slave[0].WriteBuffer));
Ser_Enable2 (false);
strTest:='';
ComState:= 2;
Toggle( OUT_LED_COM );
// SetSysTimer( ComTimer, bComTime);
endif;
// Teil 2
if ((ComState = 4) ) then
if SerInp_TO2( bCh, bTOTime ) then
if bCh=CHAR($0A) then
elsif bCh=CHAR($0D) then
else
strTest:=strTest+bCh;
endif;
if (bCh=CHAR($0A)) then
//DebugOut('Read vom Slave : '+strTest);
ComState:= 5;
ComErr2:= 0;
bComErr2:=false;
SetSysTimer( ComTimer, bComTime);
Toggle( OUT_LED_COM );
if ModWriteAnsw( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncWrite, SlaveData.Slave[1].WriteRegister, SlaveData.Slave[1].WriteBuffer, strTest) then
//DebugOut('2.OK, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr));
else
DebugOut('2.Fehler, von Adr. '+ ByteToStr(SlaveData.Slave[1].Adr));
bComErr2:=true;
endif;
strTest:='';
endif; // Ende Telegramm
else
if ComErr2 < 1 then // Fehler
ComState:= 3;
SetSysTimer( ComTimer, bStartTime); //+50
ComErr2:=ComErr2+1;
else //2. Fehler
ComState:= 5;
ComErr2:= 0;
SetSysTimer( ComTimer, bStartTime); //+50
DebugOut('2.TimneOut, von Adr. '+ ByteToStr( SlaveData.Slave[1].Adr)+' => '+strTest);
bComErr2:=true;
endif;
endif; // Ende SerInp
endif; // ComState4
if ((ComState = 3) AND isSysTimerZero( ComTimer )) then
//DebugOut('2.Write zum Slave: '+ModWriteTel( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncWrite, SlaveData.Slave[1].WriteRegister, SlaveData.Slave[1].WriteBuffer));
Ser_Enable2 (true);
WriteLn (SerOut2, ModWriteTel( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncWrite, SlaveData.Slave[1].WriteRegister, SlaveData.Slave[1].WriteBuffer));
Ser_Enable2 (false);
strTest:='';
ComState:= 4;
Toggle( OUT_LED_COM );
// SetSysTimer( ComTimer, bComTime);
endif;
// ab hier Read
//--------------------------------------------------------------------------
//
// ´Knoten 1
//
if ((ComState = 6) ) then
if SerInp_TO2( bCh, bTOTime ) then
if bCh=CHAR($0A) then
elsif bCh=CHAR($0D) then
else
strTest:=strTest+bCh;
endif;
if (bCh=CHAR($0A)) then
//DebugOut('2. Read vom Slave : '+strTest);
ComState:= 7;
ComErr2:= 0;
bComErr2:=False;
SetSysTimer( ComTimer, bComTime);
Toggle( OUT_LED_COM );
if ModReadAnsw( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncRead, SlaveData.Slave[1].ReadNum * 2, strTest, false, 1) then
//DebugOut('3.OK, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr));
else
DebugOut('3.Fehler, von Adr. '+ ByteToStr(SlaveData.Slave[1].Adr));
bComErr2:=true;
endif;
strTest:='';
endif; // Ende Telegramm
else
if ComErr2 < 1 then
ComState:= 5;
SetSysTimer( ComTimer, bStartTime); //+50
ComErr2:= ComErr2+1;
else
ComState:= 7;
ComErr2:= 0;
SetSysTimer( ComTimer, bStartTime); //+50
DebugOut('3.TimneOut, von Adr. '+ ByteToStr( SlaveData.Slave[1].Adr)+' => '+strTest);
bComErr2:=true;
endif;
endif; // Ende SerInp
endif; // ComState4
// Read Start
if ((ComState = 5) AND isSysTimerZero( ComTimer )) then
//DebugOut('3. Ausgabe: '+ModReadTel( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncRead, SlaveData.Slave[1].ReadRegister, SlaveData.Slave[1].ReadNum));
Ser_Enable2 (true);
WriteLn (SerOut2, ModReadTel( SlaveData.Slave[1].Adr, SlaveData.Slave[1].FuncRead, SlaveData.Slave[1].ReadRegister, SlaveData.Slave[1].ReadNum));
Ser_Enable2 (false);
strTest:='';
ComState:= 6;
Toggle( OUT_LED_COM );
// SetSysTimer( ComTimer, bComTime);
endif;
//--------------------------------------------------------------------------
//
// ´Knoten 0
//
if ((ComState = 8) ) then
if SerInp_TO2( bCh, bTOTime ) then
if bCh=CHAR($0A) then
elsif bCh=CHAR($0D) then
else
strTest:=strTest+bCh;
endif;
if (bCh=CHAR($0A)) then
//DebugOut('3. Read vom Slave : '+strTest);
ComState:= 0;
ComErr1:= 0;
bComErr1:=false;
SetSysTimer( ComTimer, bComTime);
Toggle( OUT_LED_COM );
if ModReadAnsw( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncRead, SlaveData.Slave[0].ReadNum * 2, strTest, false,0 ) then
//DebugOut('4.OK, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr));
else
DebugOut('4.Fehler, von Adr. '+ ByteToStr(SlaveData.Slave[0].Adr));
bComErr1:=true;
endif;
strTest:='';
endif; // Ende Telegramm
else
if ComErr1 < 1 then
ComState:= 7;
SetSysTimer( ComTimer, bStartTime); //+50
ComErr1:= ComErr1+1;
else
ComState:= 0;
ComErr1:= 0;
SetSysTimer( ComTimer, bStartTime); //+50
DebugOut('4.TimneOut, von Adr. '+ ByteToStr( SlaveData.Slave[0].Adr)+' => '+strTest);
bComErr1:=true;
endif;
endif; // Ende SerInp
endif; // ComState4
// Read Start
if ((ComState = 7) AND isSysTimerZero( ComTimer )) then
//DebugOut('4. Ausgabe: '+ModReadTel( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncRead, SlaveData.Slave[0].ReadRegister, SlaveData.Slave[0].ReadNum));
Ser_Enable2 (true);
WriteLn (SerOut2, ModReadTel( SlaveData.Slave[0].Adr, SlaveData.Slave[0].FuncRead, SlaveData.Slave[0].ReadRegister, SlaveData.Slave[0].ReadNum));
Ser_Enable2 (false);
strTest:='';
ComState:= 8;
Toggle( OUT_LED_COM );
SetSysTimer( ComTimer, bComTime);
endif;
end; // Ende Slave Com
Die Funktion ModWriteAnsw:
Code
// :01 10 001E 0001 D0
function ModWriteAnsw( Adr: byte; Func : byte; Register : word; wWrite : word; strAntwort : String[100] ) : boolean;
var
StrBufferTel: String[100];
LRC : word;
inBufferArr : Array[0..99] of integer;
ix : integer;
str2 : string[2];
str4 : string[4];
begin
// clear
for ix := 0 to 99 do
inBufferArr[ix]:=0;
endfor;
if Length(strAntwort) < 14 then // Antwort zu klein -> Raus
DebugOut('Telegram länge: '+ strAntwort);
return (false);
endif;
if strAntwort[1]<>':' then // Keine : am Anfang, dann mit false raus
DebugOut('Telegram Falsch: '+ strAntwort);
return (false);
endif;
//Berechne wie Antwort aussehen sollte
LRC:= 0;
LRC:= LRC+ Word(Adr);
LRC:= LRC+ Word(Func);
LRC:= LRC+ Word(Register);
LRC:= LRC+ Word(1);
LRC:= (LRC XOR $FF) +1;
LRC:= LRC AND $FF;
StrBufferTel:=':'+ByteToHex(Adr)+ByteToHex(Func)+IntToHex(Register)+IntToHex(1)+ByteToHex( Byte(LRC));
//DebugOut('Telegram SOLL: '+ StrBufferTel);
//DebugOut('Telegram: '+ strAntwort);
if StrBufferTel = strAntwort then // Telegramm gleich?
return (true);
else
return (false);
endif;
end;
Antwort vom Slave verarbeiten:
Code
//------------------------------------------------------------------------------
//
// Antwort vom Slave:
// HEX:
// 3A 30 31 30 33 30 38 30 30 30 30 30 30 30 30 30 30 30 33 30 30 38 33 36 45 0D 0A
//
// ASCII:
// :01 03 08 0000 0000 0003 0081 70 CR LF
// 01 03 08 0000 0000 0003 0081 70 CR LF
// :01 03 08 0000 0000 0003 0083 6E CR LF
// | | | --- --- --- --- |
// | | | | | | | |- LRC
// | | | | | | |-- 4. Register
// | | | | | |-- 3. Register
// | | | | |-- 2. Register
// | | | |--- 1. Register
// | | |---Anzahl der Bytes
// | |---Functionscode
// |---Adresse
//
function ModReadAnsw( Adr: byte; Func : byte; Count : Byte; strAntwort : String[100]; Print : boolean; Knoten : byte ) : boolean;
var
StrBufferTel: String[100];
LRC : word;
// inBufferArr : Array[0..99] of integer;
ix : integer;
str2 : string[2];
str4 : string[4];
str20 : string[20];
iBuffer : WORD;
bBuffer : byte;
iZiel : integer;
wReg1 : word;
wReg2 : word;
wReg3 : word;
wReg4 : word;
begin
// clear
// for ix := 0 to 99 do
// inBufferArr[ix]:=0;
// endfor;
if Length(strAntwort) < 25 then // Antwort zu klein -> Raus
DebugOut('Telegram länge read: '+ strAntwort);
return (false);
endif;
if strAntwort[1]<>':' then // Keine : am Anfang, dann mit false raus
DebugOut('Telegram falsch Read: '+ strAntwort);
return (false);
endif;
// Checke ob richtige Adresse
str2:= strAntwort[2]+strAntwort[3];
bBuffer:= HexToInt( str2 );
if bBuffer <> Adr then // Antwort von falscher Adresse
DebugOut('Telegram falsche Adresse: '+ strAntwort);
return (false);
endif;
// Checke ob richtiger Functionscode
str2:= strAntwort[4]+strAntwort[5];
bBuffer:= HexToInt( str2 );
if bBuffer <> Func then // Antwort von falscher Functionscode
DebugOut('Telegram falscher Functionscode: '+ strAntwort);
return (false);
endif;
// Checke ob richtige Anzahl der Bytes
str2:= strAntwort[6]+strAntwort[7];
bBuffer:= HexToInt( str2 );
if bBuffer <> Count then // Falsche Anzahl an Bytes
DebugOut('Telegram falsche Anzahl der Bytes: '+ strAntwort);
return (false);
endif;
//Berechne LRC
//
LRC:= 0;
// Adresse
str2:= strAntwort[2]+strAntwort[3];
LRC:= LRC + word(HexToInt( str2 ));
// Functionscode
str2:= strAntwort[4]+strAntwort[5];
LRC:= LRC + word(HexToInt( str2 ));
// Anzahl der Bytes
str2:= strAntwort[6]+strAntwort[7];
LRC:= LRC + word(HexToInt( str2 ));
iZiel:= HexToInt( str2 ); // Anzahl der Bytes für Schleife
// Register
str2:= strAntwort[8]+strAntwort[9];
LRC:= LRC + word(HexToInt( str2 ));
// Register
str2:= strAntwort[10]+strAntwort[11];
LRC:= LRC + word(HexToInt( str2 ));
// Register zur Übertragung vorbereiten
str4:= strAntwort[8]+strAntwort[9]+strAntwort[10]+strAntwort[11];
wReg1 := word(HexToInt( str4 ));
// Register
str2:= strAntwort[12]+strAntwort[13];
LRC:= LRC + word(HexToInt( str2 ));
// Register
str2:= strAntwort[14]+strAntwort[15];
LRC:= LRC + word(HexToInt( str2 ));
// Register zur Übertragung vorbereiten
str4:= strAntwort[12]+strAntwort[13]+strAntwort[14]+strAntwort[15];
wReg2 := word(HexToInt( str4 ));
// Register
str2:= strAntwort[16]+strAntwort[17];
LRC:= LRC + word(HexToInt( str2 ));
// Register
str2:= strAntwort[18]+strAntwort[19];
LRC:= LRC + word(HexToInt( str2 ));
// Register zur Übertragung vorbereiten
str4:= strAntwort[16]+strAntwort[17]+strAntwort[18]+strAntwort[19];
wReg3 := word(HexToInt( str4 ));
// Register
str2:= strAntwort[20]+strAntwort[21];
LRC:= LRC + word(HexToInt( str2 ));
// Register
str2:= strAntwort[22]+strAntwort[23];
LRC:= LRC + word(HexToInt( str2 ));
// Register zur Übertragung vorbereiten
str4:= strAntwort[20]+strAntwort[21]+strAntwort[22]+strAntwort[23];
wReg4 := word(HexToInt( str4 ));
// End Berechnung
LRC:= (LRC XOR $FF) +1;
LRC:= LRC AND $FF;
if Print then
DebugOut('---------');
ix:= 7 + (iZiel*2)+1;
str2 := strAntwort[ix]+ strAntwort[ix+1];
iBuffer := HexToInt( str2 );
str20:= IntToStr( iBuffer );
DebugOut('LRC in : '+ str20);
DebugOut('LRC calc: '+ IntToStr( LRC ) );
else
ix:= 7 + (iZiel*2)+1;
str2 := strAntwort[ix]+ strAntwort[ix+1];
iBuffer := HexToInt( str2 );
str20:= IntToStr( iBuffer );
endif;
if LRC = iBuffer then // LRC OK ?
// Nur wenn LRC ok, dann Daten einsortieren
//
SlaveData.Slave[Knoten].ReadBuffer1 := wReg1;
SlaveData.Slave[Knoten].ReadBuffer2 := wReg2;
SlaveData.Slave[Knoten].ReadBuffer3 := wReg3;
SlaveData.Slave[Knoten].ReadBuffer4 := wReg4;
return (true);
else
return (false);
endif;
end;
ModWriteTel:
Code
// WRITE:
// Modbus Ascii alles EIN:
// HEX:
// 3A 30 31 31 30 30 30 31 45 30 30 30 31 30 32 30 30 46 46 43 46 0D 0A
// ASCII:
// Test
// :01 10 00 1E 00 01 02 00 FF CF <CR><LF>
// | | | | | --- |- LRC
// | | | | | | Register Inhalt
// | | | | |- Anzahl der Bytes
// | | | |-- Anzahl der Register
// | | |-- Ab Regsiter Hex
// | |--- Funktionscode
// |--- Adresse
function ModWriteTel( Adr: byte; Func : byte; Register : word; wWrite : word ) : String[100];
var
StrBufferTel: String[100];
LRC : word;
begin
LRC:= 0;
LRC:= LRC+ Word(Adr);
LRC:= LRC+ Word(Func);
LRC:= LRC+ Word(Register);
LRC:= LRC+ Word(1);
LRC:= LRC+ Word(2);
// LRC:= LRC+ Word(wWrite);
LRC:= LRC+ Word( WordByte(wWrite, true) );
LRC:= LRC+ Word( WordByte(wWrite, false) );
LRC:= (LRC XOR $FF) +1;
LRC:= LRC AND $FF;
StrBufferTel:=':'+ByteToHex(Adr)+ByteToHex(Func)+IntToHex(Register)+IntToHex(1)+ByteToHex(2)+IntToHex(wWrite)+ByteToHex( Byte(LRC));
return (StrBufferTel);
end;
ModReadTel:
Code
//---------------------------------------------------------------------
//HEX:
//3A 30 31 30 33 30 30 31 30 30 30 30 34 45 38 0D OA
//
//ASCII:
//:01 03 00 10 00 04 E8 CR LF
// | | | | |- LRC
// | | | |-- Anzahl der Register
// | | |-- Ab Regsiter Hex
// | |--- Funktionscode
// |--- Adresse
function ModReadTel( Adr: byte; Func : byte; Register : word; Count : byte ) : String[100];
var
StrBufferTel: String[100];
LRC : word;
begin
LRC:= 0;
LRC:= LRC+ Word(Adr);
LRC:= LRC+ Word(Func);
LRC:= LRC+ Word(Register);
LRC:= LRC+ Word(Count);
LRC:= (LRC XOR $FF) +1;
LRC:= LRC AND $FF;
StrBufferTel:=':'+ByteToHex(Adr)+ByteToHex(Func)+IntToHex(Register)+IntToHex( Word(Count))+ByteToHex( Byte(LRC));
return (StrBufferTel);
end;
Thorsten