Files
f.bittiandClaude Opus 5 4a01a2ec88 Plancia configurabile su Modbus RTU
Due programmi che condividono uModbusRTU e uGauge:

- Console/PlanciaConsole: plancia nautica descritta da file XML, con
  modalita' plancia e modalita' configurazione. Comandi, spie, selettori,
  strumenti e allarmi sonori; il bus gira in un thread suo perche' la
  finestra non si fermi mai.
- ProjectPlancia: il programma di prova piu' vecchio, usato per collaudare
  i canali.

Le plance sono in Console/*.xml, la documentazione in Console/LEGGIMI.md.
Esclusi dal versionamento i compilati (.exe, .dcu), i file dell'IDE e un
audio da 17 MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:56:34 +02:00

129 lines
3.6 KiB
ObjectPascal

program dumpframe;
{
Manda una singola richiesta Modbus e stampa i byte grezzi della risposta,
senza interpretarli. Serve quando una lettura fallisce e non si sa se il
problema sia nel modulo, nel cablaggio o nel codice che decodifica.
Uso: dumpframe [porta] [baud] [slave] [funzione] [start] [quantita]
Es.: dumpframe COM7 9600 1 2 0 8
}
{$APPTYPE CONSOLE}
uses
Winapi.Windows, System.SysUtils;
function CRC16(const ABuf: array of Byte; ALen: Integer): Word;
var
I, J: Integer;
begin
Result := $FFFF;
for I := 0 to ALen - 1 do
begin
Result := Result xor ABuf[I];
for J := 0 to 7 do
if (Result and 1) <> 0 then
Result := (Result shr 1) xor $A001
else
Result := Result shr 1;
end;
end;
function Hex(const ABuf: TBytes; ALen: Integer): string;
var
I: Integer;
begin
Result := '';
for I := 0 to ALen - 1 do
Result := Result + IntToHex(ABuf[I], 2) + ' ';
end;
var
Port: string;
Baud, Slave, Func, Start, Qty: Integer;
H: THandle;
DCB: TDCB;
TO_: TCommTimeouts;
Req: array[0..7] of Byte;
C: Word;
Buf: TBytes;
Written, Got: DWORD;
Expected, I: Integer;
begin
Port := 'COM7'; Baud := 9600; Slave := 1; Func := 2; Start := 0; Qty := 8;
if ParamCount >= 1 then Port := ParamStr(1);
if ParamCount >= 2 then Baud := StrToIntDef(ParamStr(2), 9600);
if ParamCount >= 3 then Slave := StrToIntDef(ParamStr(3), 1);
if ParamCount >= 4 then Func := StrToIntDef(ParamStr(4), 2);
if ParamCount >= 5 then Start := StrToIntDef(ParamStr(5), 0);
if ParamCount >= 6 then Qty := StrToIntDef(ParamStr(6), 8);
H := CreateFile(PChar('\\.\' + Port), GENERIC_READ or GENERIC_WRITE, 0, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if H = THandle(-1) then
begin
Writeln(Format('Impossibile aprire %s (errore %d)', [Port, GetLastError]));
Readln; Halt(1);
end;
try
FillChar(DCB, SizeOf(DCB), 0);
DCB.DCBlength := SizeOf(DCB);
GetCommState(H, DCB);
DCB.BaudRate := Baud;
DCB.ByteSize := 8;
DCB.Parity := NOPARITY;
DCB.StopBits := ONESTOPBIT;
DCB.Flags := 0;
SetCommState(H, DCB);
TO_.ReadIntervalTimeout := 50;
TO_.ReadTotalTimeoutMultiplier := 10;
TO_.ReadTotalTimeoutConstant := 500;
TO_.WriteTotalTimeoutMultiplier := 10;
TO_.WriteTotalTimeoutConstant := 500;
SetCommTimeouts(H, TO_);
PurgeComm(H, PURGE_RXCLEAR or PURGE_TXCLEAR);
Req[0] := Slave;
Req[1] := Func;
Req[2] := Hi(Word(Start)); Req[3] := Lo(Word(Start));
Req[4] := Hi(Word(Qty)); Req[5] := Lo(Word(Qty));
C := CRC16(Req, 6);
Req[6] := Lo(C); Req[7] := Hi(C);
Write ('RICHIESTA (8 byte): ');
for I := 0 to 7 do Write(IntToHex(Req[I], 2), ' ');
Writeln;
WriteFile(H, Req[0], 8, Written, nil);
// Legge molto piu' del necessario: se arriva un eco della richiesta o
// byte di troppo si devono vedere, non nascondere troncando.
Expected := 64;
SetLength(Buf, Expected);
Sleep(150); // lascia il tempo allo slave di rispondere per intero
ReadFile(H, Buf[0], Expected, Got, nil);
Writeln(Format('RISPOSTA (%d byte): ', [Got]) + Hex(Buf, Got));
if Got >= 4 then
begin
C := CRC16(Buf, Got - 2);
Writeln(Format('CRC calcolata sui primi %d byte: %s %s - nel frame: %s %s -> %s',
[Got - 2, IntToHex(Lo(C), 2), IntToHex(Hi(C), 2),
IntToHex(Buf[Got - 2], 2), IntToHex(Buf[Got - 1], 2),
BoolToStr((Lo(C) = Buf[Got - 2]) and (Hi(C) = Buf[Got - 1]), True)]));
end;
finally
CloseHandle(H);
end;
Writeln;
Write('Premi INVIO per chiudere.');
Readln;
end.