mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat: QO-100 beacon AO-40 FEC decode end-to-end (Milestone 2)
Full chain now decodes the QO-100 central beacon to 256-byte AO-40 frames on-air (frames>0, RS errors=0). Builds on M1 (stable BPSK demod). FEC backend (BeaconFEC.pas, TBeaconFEC): - AO-40 FEC: distributed sync (65 bit, step 80) -> 80x65 deinterleave -> Viterbi r=1/2 k=7 -> CCSDS descramble -> 2x RS(160,128). Links system libfec (Karn) load-time (libfec.so/.dll/.dylib). Viterbi AO-40 [0x4F,-0x6D] remapped to libfec [0x6D,0x4F]. Validated against gr-satellites reference vectors (fectest2: exact 256-byte frame, 0 mismatches). DBPSK Manchester frontend (BeaconDecoder.pas): - Chip-rate (800) processing: RRC matched filter -> ML signal-times-slope timing recovery -> Costas -> Manchester combine (block phase select) -> differential decode -> soft symbols to FEC. - Carrier acquisition: squaring-FFT estimates the residual (carrier at +-pi/chip defeats decision-directed Costas), one-shot pulls it to DC via residual NCO. - Costas frequency offload into pre-RRC NCO (with deadband) tracks LNB drift without hitting the +-pi Costas clamp. - ML TED replaces Gardner, which degenerates on the Manchester chip stream. Temporary on-air diagnostics retained (IQ dump + STATE log) pending wider signal validation; to be removed before final cleanup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+104
@@ -0,0 +1,104 @@
|
||||
program fectest2;
|
||||
{$MODE Delphi}
|
||||
{
|
||||
Валидация TBeaconFEC против эталонных тест-векторов gr-satellites
|
||||
(python/components/deframers/qa_ao40_fec_deframer_*). Гоним поток мягких
|
||||
символов через PushSoftSymbol и сверяем выданный 256-байтовый кадр с эталоном.
|
||||
Решает вопрос конвенции Viterbi (libfec [0x6D,0x4F] vs AO-40 [0x4F,-0x6D]).
|
||||
}
|
||||
uses SysUtils, Classes, BeaconFEC;
|
||||
|
||||
const
|
||||
GR = '/home/vladimir/Документы/source/gr-satellites/python/components/deframers/';
|
||||
SymPath = GR + 'qa_ao40_fec_deframer_symbols.f32';
|
||||
|
||||
// qa_ao40_fec_deframer.py: финальный кадр (256 байт)
|
||||
FrameRef: array[0..255] of Byte = (
|
||||
137, 0, 0, 0, 0, 0, 0, 0, 0, 31, 204, 0, 206, 2, 209, 0, 0,
|
||||
7, 8, 9, 9, 0, 0, 5, 1, 1, 0, 64, 19, 47, 200, 242, 92,
|
||||
143, 52, 35, 243, 186, 11, 93, 98, 116, 81, 199, 234, 250,
|
||||
105, 74, 154, 159, 0, 9, 239, 160, 31, 244, 167, 234, 74,
|
||||
198, 143, 17, 64, 17, 30, 16, 247, 1, 62, 32, 100, 0, 215,
|
||||
139, 248, 215, 148, 200, 147, 168, 42, 218, 82, 166, 14, 88,
|
||||
14, 200, 15, 78, 1, 29, 32, 90, 0, 219, 148, 168, 170, 138,
|
||||
152, 19, 172, 105, 10, 166, 168, 16, 230, 16, 146, 15, 184,
|
||||
1, 80, 32, 100, 0, 215, 150, 168, 193, 139, 72, 37, 171, 169,
|
||||
202, 206, 157, 16, 118, 15, 201, 16, 85, 1, 58, 32, 90, 0, 215,
|
||||
151, 41, 8, 140, 72, 79, 169, 106, 90, 242, 164, 16, 57, 15,
|
||||
123, 15, 134, 1, 73, 32, 100, 0, 215, 148, 8, 208, 138, 216,
|
||||
42, 173, 106, 90, 126, 180, 14, 83, 14, 155, 14, 183, 1, 9, 32,
|
||||
90, 0, 219, 153, 168, 242, 143, 232, 56, 175, 170, 138, 194,
|
||||
158, 14, 222, 15, 72, 14, 49, 1, 49, 32, 90, 0, 206, 155, 200,
|
||||
255, 136, 104, 27, 178, 106, 90, 202, 167, 15, 195, 14, 116, 14,
|
||||
88, 1, 52, 32, 90, 0, 215, 155, 57, 27, 151, 184, 197, 176, 43,
|
||||
58, 214, 181, 1, 107, 0, 106, 2, 158, 0, 3, 32, 19, 0);
|
||||
|
||||
type
|
||||
TFrameChecker = class
|
||||
Got: Boolean;
|
||||
Match: Boolean;
|
||||
NErr: Integer;
|
||||
RSErr: Integer;
|
||||
procedure OnFrame(const Frame: TBeaconFrame; RSErrors: Integer);
|
||||
end;
|
||||
|
||||
procedure TFrameChecker.OnFrame(const Frame: TBeaconFrame; RSErrors: Integer);
|
||||
var i, e: Integer;
|
||||
begin
|
||||
if Got then Exit; // берём первый кадр
|
||||
Got := True;
|
||||
RSErr := RSErrors;
|
||||
e := 0;
|
||||
for i := 0 to 255 do
|
||||
if Frame[i] <> FrameRef[i] then Inc(e);
|
||||
NErr := e;
|
||||
Match := (e = 0);
|
||||
end;
|
||||
|
||||
var
|
||||
fec: TBeaconFEC;
|
||||
chk: TFrameChecker;
|
||||
fs: TFileStream;
|
||||
n, i: Integer;
|
||||
buf: array of Single;
|
||||
begin
|
||||
if not FileExists(SymPath) then
|
||||
begin
|
||||
WriteLn('symbols file not found: ', SymPath); Halt(2);
|
||||
end;
|
||||
fs := TFileStream.Create(SymPath, fmOpenRead);
|
||||
try
|
||||
n := fs.Size div 4;
|
||||
SetLength(buf, n);
|
||||
fs.ReadBuffer(buf[0], n * 4);
|
||||
finally
|
||||
fs.Free;
|
||||
end;
|
||||
WriteLn('loaded ', n, ' soft symbols');
|
||||
|
||||
chk := TFrameChecker.Create;
|
||||
fec := TBeaconFEC.Create;
|
||||
try
|
||||
fec.OnFrame := chk.OnFrame;
|
||||
for i := 0 to n - 1 do
|
||||
fec.PushSoftSymbol(buf[i]);
|
||||
|
||||
WriteLn('frames decoded: ', fec.Frames);
|
||||
if not chk.Got then
|
||||
begin
|
||||
WriteLn('==> FAIL: no frame decoded (sync not found / RS uncorrectable)');
|
||||
Halt(1);
|
||||
end;
|
||||
WriteLn(Format('frame: %d/256 byte mismatches, RS errors=%d', [chk.NErr, chk.RSErr]));
|
||||
if chk.Match then
|
||||
WriteLn('==> PASS: frame matches gr-satellites reference')
|
||||
else
|
||||
begin
|
||||
WriteLn('==> FAIL: frame does not match reference');
|
||||
Halt(1);
|
||||
end;
|
||||
finally
|
||||
fec.Free;
|
||||
chk.Free;
|
||||
end;
|
||||
end.
|
||||
Reference in New Issue
Block a user