esp32-ogn-tracker/main/ogn2.h

455 wiersze
23 KiB
C++

#ifndef __OGN2_H__
#define __OGN2_H__
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "intmath.h"
#include "ognconv.h"
#include "bitcount.h"
#include "nmea.h"
#include "mavlink.h"
#include "format.h"
// uint32_t OGN2_SYNC = 0xF56D3738;
// the packet description here is how it look on the little-endian CPU before sending it to the RF chip
// nRF905, CC1101, SPIRIT1, RFM69 chips actually reverse the bit order within every byte
// thus on the air the bits appear MSbit first for every byte transmitted
class OGN2_Packet // Packet structure for the OGN tracker
{ public:
static const int Words = 5;
static const int Bytes = 20;
union
{ uint32_t HeaderWord; // ECNR POTT AAAA AAAA AAAA AAAA AAAA AAAA
// E=Emergency, C=enCrypt/Custom, R=Relay, P=Parity, O=Other, TT=address Type, AA..=Address:24-bit
// When enCrypt/Custom is set the data (position or whatever) can only be decoded by the owner
// This option is indented to pass any type of custom data not foreseen otheriwse
struct
{ unsigned int Address :24; // aircraft address
unsigned int AddrType : 2; // address type: 0 = random, 1 = ICAO, 2 = FLARM, 3 = OGN
unsigned int Relay : 1; // 0 = direct packet, 1 = relayed packet
unsigned int Parity : 1; // parity takes into account bits 0..27 thus only the 28 lowest bits
unsigned int NonPos : 1; // 0 = position packet, 1 = other information like status
// unsigned int Auth : 2; // Authentication: 00 = no auth. 01 = auth. this packet, 10/11 = auth response
// Auth:NonPos: 000 = position, 010 = position, to be followed by crypto-response,
// 101 = response #0, 111 = response #1
// 001 = non-position: status, info, etc.
// 100 = ???, 110 = ???, 011 = ???
unsigned int NonOGN : 1; // 0 = OGN packet, 1 = other systems, like MAVlink
unsigned int Encrypted : 1; // packet is encrypted
unsigned int Emergency : 1; // aircraft in emergency (not used for now)
} Header ;
} ;
union
{ uint32_t Data[4];
struct
{ unsigned int AcftType: 4; // [0..15] // type of aircraft: 1 = glider, 2 = towplane, 3 = helicopter, ...
unsigned int Heading:10; // [0.35deg] // Ground track in cordic units
unsigned int ClimbRate: 9; // [0.1m/s] VR
unsigned int BaroAltDiff: 9; // [m] // lower 8 bits of the altitude difference between baro and GPS
unsigned int Altitude:14; // [m] VR // Altitude
unsigned int Speed:10; // [0.1m/s] VR // Ground speed
unsigned int TurnRate: 8; // [0.1deg/s] VR // Ground heading (track) rate
unsigned int Latitude:24; // [1.2m] // Latitude in cordic units
unsigned int Time: 6; // [sec] // time, just second thus ambiguity every every minute
unsigned int FixQuality: 2; // // 0 = no fix, 1 = GPS, 2 = diff. GPS, 3 = other
unsigned int Longitude:25; // [1.2m] // Longitude in cordic units
unsigned int DOP: 6; // // GPS Dilution of Precision
unsigned int FixMode: 1; // [2-D/3-D]
} Position;
struct
{
unsigned int ReportType: 4; // [0] // 0 for the status report
unsigned int TxPower : 4; // [dBm] // RF trancmitter power
unsigned int Firmware : 8; // [ ] // firmware version
unsigned int Hardware : 8; // [ ] // hardware version
unsigned int Voltage : 8; // [1/64V] VR // supply voltager
unsigned int Altitude :14; // [m] VR // same as in the position packet
unsigned int Pressure :14; // [0.08hPa] // barometric pressure
unsigned int Satellites: 4; // [ ]
unsigned int Pulse : 8; // [bpm] // pilot: heart pulse rate
unsigned int Oxygen : 7; // [%] // pilot: oxygen level in the blood
// unsigned int FEScurr : 5; // [A] //
unsigned int SatSNR : 5; // [dB] // average SNR of GPS signals
unsigned int RxRate : 4; // [/min] // log2 of received packet rate
unsigned int Time : 6; // [sec] // same as in the position packet
unsigned int FixQuality: 2;
unsigned int AudioNoise: 8; // [dB] //
unsigned int RadioNoise: 8; // [dBm] // noise seen by the RF chip
unsigned int Temperature:8; // [0.1degC] VR // temperature by the baro or RF chip
unsigned int Humidity : 8; // [%] // humidity
} Status ;
union
{ uint8_t Byte[16];
struct
{ uint8_t ReportType: 4; // [1] // 1 for the Info packets
uint8_t DataChars: 4; // [int] number of characters in the packed string
uint8_t Data[14]; // [16x7bit]packed string of 16-char: 7bit/char
uint8_t Check; // CRC check
} ;
} Info;
} ;
uint8_t *Byte(void) const { return (uint8_t *)&HeaderWord; } // packet as bytes
uint32_t *Word(void) const { return (uint32_t *)&HeaderWord; } // packet as words
static const uint8_t InfoParmNum = 14; // [int] number of info-parameters and their names
static const char *InfoParmName(uint8_t Idx) { static const char *Name[InfoParmNum] =
{ "Pilot", "Manuf", "Model", "Type", "SN", "Reg", "ID", "Class",
"Task" , "Base" , "ICE" , "PilotID", "Hard", "Soft" } ;
return Idx<InfoParmNum ? Name[Idx]:0; }
void Dump(void) const
{ printf("%08lX: %08lX %08lX %08lX %08lX\n",
(long int)HeaderWord, (long int)Data[0], (long int)Data[1],
(long int)Data[2], (long int)Data[3] ); }
void DumpBytes(void) const
{ for(uint8_t Idx=0; Idx<Bytes; Idx++)
{ printf(" %02X", Byte()[Idx]); }
printf("\n"); }
int WriteStatus(char *Out)
{ uint8_t Len=0;
Out[Len++]=' '; Out[Len++]='h'; Len+=Format_Hex(Out+Len, (uint8_t)Status.Hardware);
Out[Len++]=' '; Out[Len++]='v'; Len+=Format_Hex(Out+Len, (uint8_t)Status.Firmware);
Out[Len++]=' '; Len+=Format_UnsDec(Out+Len, Status.Satellites); Out[Len++]='s'; Out[Len++]='a'; Out[Len++]='t';
Out[Len++]='/'; Out[Len++]='0'+Status.FixQuality;
Out[Len++]='/'; Len+=Format_UnsDec(Out+Len, Status.SatSNR+8); Out[Len++]='d'; Out[Len++]='B';
Out[Len++]=' '; Len+=Format_SignDec(Out+Len, DecodeAltitude(), 1, 0, 1); Out[Len++]='m';
if(Status.Pressure>0)
{ Out[Len++]=' '; Len+=Format_UnsDec(Out+Len, (((uint32_t)Status.Pressure<<3)+5)/10, 2, 1); Out[Len++]='h'; Out[Len++]='P'; Out[Len++]='a'; }
if(hasTemperature())
{ Out[Len++]=' '; Len+=Format_SignDec(Out+Len, DecodeTemperature(), 2, 1); Out[Len++]='d'; Out[Len++]='e'; Out[Len++]='g'; Out[Len++]='C'; }
if(hasHumidity())
{ Out[Len++]=' '; Len+=Format_SignDec(Out+Len, DecodeHumidity(), 2, 1); Out[Len++]='%'; }
Out[Len++]=' '; Len+=Format_SignDec(Out+Len, ((uint32_t)DecodeVoltage()*100+32)>>6, 3, 2); Out[Len++]='V';
Out[Len++]=' '; Len+=Format_UnsDec(Out+Len, Status.TxPower+4);
Out[Len++]='/'; Out[Len++]='-'; Len+=Format_UnsDec(Out+Len, 5*Status.RadioNoise, 2, 1); Out[Len++]='d'; Out[Len++]='B'; Out[Len++]='m';
Out[Len++]=' '; Len+=Format_UnsDec(Out+Len, (1<<Status.RxRate)-1); Out[Len++]='/'; Out[Len++]='m'; Out[Len++]='i'; Out[Len++]='n';
Out[Len]=0; return Len; }
int WriteDeviceStatus(char *Out)
{ return sprintf(Out, " h%02X v%02X %dsat/%d/%ddB %ldm %3.1fhPa %+4.1fdegC %3.1f%% %4.2fV %d/%+4.1fdBm %d/min",
Status.Hardware, Status.Firmware, Status.Satellites, Status.FixQuality, 8+Status.SatSNR, (long int)DecodeAltitude(),
0.08*Status.Pressure, 0.1*DecodeTemperature(), 0.1*DecodeHumidity(),
(1.0/64)*DecodeVoltage(), Status.TxPower+4, -0.5*Status.RadioNoise, (1<<Status.RxRate)-1 );
}
int WriteDeviceInfo(char *Out)
{ int Len=0;
char Value[16];
uint8_t InfoType;
uint8_t Idx=0;
for( ; ; )
{ uint8_t Chars = readInfo(Value, InfoType, Idx);
if(Chars==0) break;
if(InfoType<InfoParmNum)
{ Len += sprintf(Out+Len, " %s=%s", InfoParmName(InfoType), Value); }
else
{ Len += sprintf(Out+Len, " #%d=%s", InfoType, Value); }
Idx+=Chars; }
Out[Len]=0; return Len; }
void Print(void) const
{ if(!Header.NonPos) { PrintPosition(); return; }
if(Status.ReportType==0) { PrintDeviceStatus(); return; }
}
void PrintDeviceStatus(void) const
{ printf("%c:%06lX R%c%c %02ds:",
'0'+Header.AddrType, (long int)Header.Address, '0'+Header.Relay, Header.Emergency?'E':' ', Status.Time);
printf(" h%02X v%02X %dsat/%d/%ddB %ldm %3.1fhPa %+4.1fdegC %3.1f%% %4.2fV Tx:%ddBm Rx:%+4.1fdBm %d/min",
Status.Hardware, Status.Firmware, Status.Satellites, Status.FixQuality, 8+Status.SatSNR, (long int)DecodeAltitude(),
0.08*Status.Pressure, 0.1*DecodeTemperature(), 0.1*DecodeHumidity(),
(1.0/64)*DecodeVoltage(), Status.TxPower+4, -0.5*Status.RadioNoise, (1<<Status.RxRate)-1 );
printf("\n");
}
void PrintPosition(void) const
{ printf(" %X:%c:%06lX R%c%c",
(int)Position.AcftType, '0'+Header.AddrType, (long int)Header.Address, '0'+Header.Relay,
Header.Emergency?'E':' ');
printf(" %d/%dD/%4.1f", (int)Position.FixQuality, (int)Position.FixMode+2, 0.1*(10+DecodeDOP()) );
if(Position.Time<60) printf(" %02ds:", (int)Position.Time);
else printf(" ---:");
printf(" [%+10.6f, %+11.6f]deg %ldm",
0.0001/60*DecodeLatitude(), 0.0001/60*DecodeLongitude(), (long int)DecodeAltitude() );
if(hasBaro())
{ printf("[%+dm]", (int)getBaroAltDiff() ); }
printf(" %3.1fm/s %05.1fdeg %+4.1fm/s %+4.1fdeg/s",
0.1*DecodeSpeed(), 0.1*DecodeHeading(), 0.1*DecodeClimbRate(), 0.1*DecodeTurnRate() );
printf("\n");
}
// calculate distance vector [LatDist, LonDist] from a given reference [RefLat, Reflon]
int calcDistanceVector(int32_t &LatDist, int32_t &LonDist, int32_t RefLat, int32_t RefLon, uint16_t LatCos=3000, int32_t MaxDist=0x7FFF)
{ LatDist = ((DecodeLatitude()-RefLat)*1517+0x1000)>>13; // convert from 1/600000deg to meters (40000000m = 360deg) => x 5/27 = 151$
if(abs(LatDist)>MaxDist) return -1;
LonDist = ((DecodeLongitude()-RefLon)*1517+0x1000)>>13;
if(abs(LonDist)>(4*MaxDist)) return -1;
LonDist = (LonDist*LatCos+0x800)>>12;
if(abs(LonDist)>MaxDist) return -1;
return 1; }
// sets position [Lat, Lon] according to given distance vector [LatDist, LonDist] from a reference point [RefLat, RefLon]
void setDistanceVector(int32_t LatDist, int32_t LonDist, int32_t RefLat, int32_t RefLon, uint16_t LatCos=3000)
{ EncodeLatitude(RefLat+(LatDist*27)/5);
LonDist = (LonDist<<12)/LatCos; // LonDist/=cosine(Latitude)
EncodeLongitude(RefLon+(LonDist*27)/5); }
// Centripetal acceleration
static int16_t calcCPaccel(int16_t Speed, int16_t TurnRate) { return ((int32_t)TurnRate*Speed*229+0x10000)>>17; } // [0.1m/s^2]
int16_t calcCPaccel(void) { return calcCPaccel(DecodeSpeed(), DecodeTurnRate()); }
// Turn radius
static int16_t calcTurnRadius(int16_t Speed, int16_t TurnRate, int16_t MaxRadius=0x7FFF) // [m] ([0.1m/s], [], [m])
{ if(TurnRate==0) return 0;
int32_t Radius = 14675*Speed;
Radius /= TurnRate; Radius = (Radius+128)>>8;
if(abs(Radius)>MaxRadius) return 0;
return Radius; }
int16_t calcTurnRadius(int16_t MaxRadius=0x7FFF) { return calcTurnRadius(DecodeSpeed(), DecodeTurnRate(), MaxRadius); }
void Clear(void) { HeaderWord=0; Data[0]=0; Data[1]=0; Data[2]=0; Data[3]=0; }
uint32_t getAddressAndType(void) const { return HeaderWord&0x03FFFFFF; } // Address with address-type: 26-bit
void setAddressAndType(uint32_t AddrAndType) { HeaderWord = (HeaderWord&0xFC000000) | (AddrAndType&0x03FFFFFF); }
bool goodAddrParity(void) const { return ((Count1s(HeaderWord&0x0FFFFFFF)&1)==0); } // Address parity should be EVEN
void calcAddrParity(void) { if(!goodAddrParity()) HeaderWord ^= 0x08000000; } // if not correct parity, flip the parity bit
void clrBaro(void) { Position.BaroAltDiff=EncodeGray((uint16_t)0x100); }
bool hasBaro(void) const { return DecodeGray((uint16_t)Position.BaroAltDiff)!=0x100; }
void setBaroAltDiff(int32_t AltDiff)
{ if(AltDiff<(-255)) AltDiff=(-255);
else if(AltDiff>255) AltDiff=255;
Position.BaroAltDiff=EncodeGray((uint16_t)(AltDiff&0x1FF)); }
int16_t getBaroAltDiff(void) const
{ int16_t AltDiff=DecodeGray((uint16_t)Position.BaroAltDiff);
if(AltDiff & 0x100) AltDiff |= 0xFE00;
return AltDiff; }
void EncodeStdAltitude(int32_t StdAlt) { setBaroAltDiff((StdAlt-DecodeAltitude())); }
int32_t DecodeStdAltitude(void) const { return (DecodeAltitude()+getBaroAltDiff()); }
void EncodeAltitude(int32_t Altitude) // encode altitude in meters
{ Altitude += 1024;
if(Altitude<0) Altitude=0;
Position.Altitude = EncodeGray((uint16_t)UnsVRencode<uint16_t, 12>((uint16_t)Altitude)); }
int32_t DecodeAltitude(void) const // return Altitude in meters
{ return (int32_t)UnsVRdecode<uint16_t, 12>(DecodeGray((uint16_t)Position.Altitude))-1024; }
static const int32_t FullAngle = 360*600000; // Latitude and Longitude are encoded in units of 1/10000 arcmin
static const int32_t HalfAngle = FullAngle/2;
// void EncodeLatitude(int32_t Latitude)
// { Position.Latitude = ((int64_t)Latitude*83399993+(1<<28))>>29; }
// int32_t DecodeLatitude(void) const
// { int32_t Latitude = Position.Latitude;
// return ((int64_t)Latitude*HalfAngle+(1<<23))>>24; }
void EncodeLatitude(int32_t Latitude)
{ Latitude = ((int64_t)Latitude*83399993+(1<<28))>>29; // convert to cordic units
Latitude &= 0x00FFFFFF;
Position.Latitude = EncodeGray((uint32_t)Latitude); }
int32_t DecodeLatitude(void) const
{ int32_t Latitude = DecodeGray((uint32_t)Position.Latitude);
if(Latitude&0x00800000) Latitude |= 0xFF000000;
return ((int64_t)Latitude*HalfAngle+(1<<23))>>24; } // convert from cordic units to 1/10000 arcmin
void EncodeLongitude(int32_t Longitude)
{ Longitude = ((int64_t)Longitude*83399993+(1<<28))>>29;
Longitude &= 0x01FFFFFF;
Position.Longitude = EncodeGray((uint32_t)Longitude); }
int32_t DecodeLongitude(void) const
{ int32_t Longitude = DecodeGray((uint32_t)Position.Longitude);
if(Longitude&0x01000000) Longitude |= 0xFE000000;
Longitude = ((int64_t)Longitude*HalfAngle+(1<<23))>>24;
return Longitude; }
void EncodeDOP(uint8_t DOP)
{ Position.DOP = EncodeGray(UnsVRencode<uint8_t, 4>(DOP)); }
uint8_t DecodeDOP(void) const
{ return UnsVRdecode<uint8_t, 4>(DecodeGray((uint8_t)Position.DOP)); }
void EncodeSpeed(int16_t Speed) // speed in 0.2 knots (or 0.1m/s)
{ if(Speed<0) Speed=0;
else Speed=UnsVRencode<uint16_t, 8>(Speed);
Position.Speed = EncodeGray((uint16_t)Speed); }
int16_t DecodeSpeed(void) const // return speed in 0.2 knots or 0.1m/s units
{ return UnsVRdecode<uint16_t, 8>(DecodeGray((uint16_t)Position.Speed)); } // => max. speed: 3832*0.2 = 766 knots
void EncodeHeading(int16_t Heading)
{ if(Heading<0) Heading+=3600;
Heading = ((int32_t)Heading*1165+(1<<11))>>12;
Position.Heading = EncodeGray((uint16_t)Heading); }
int16_t DecodeHeading(void) const // return Heading in 0.1 degree units 0..359.9 deg
{ int32_t Heading = DecodeGray((uint16_t)Position.Heading);
return (Heading*3600+512)>>10; }
void setHeadingAngle(uint16_t HeadingAngle)
{ Position.Heading = EncodeGray((uint16_t)((HeadingAngle+32)>>6)); }
uint16_t getHeadingAngle(void) const
{ return (uint16_t)DecodeGray((uint16_t)Position.Heading)<<6; }
void clrTurnRate(void) { Position.TurnRate=0x80; }
bool hasTurnRate(void) const { return Position.TurnRate==0x80; }
void EncodeTurnRate(int16_t Turn) // [0.1 deg/sec]
{ Position.TurnRate = SignVRencode<int16_t, 5>(Turn); }
int16_t DecodeTurnRate(void) const
{ return SignVRdecode<int16_t, 5>(Position.TurnRate); }
void clrClimbRate(void) { Position.ClimbRate=0x100; }
bool hasClimbRate(void) const { return Position.ClimbRate==0x100; }
void EncodeClimbRate(int16_t Climb) // [0.1m/s]
{ Position.ClimbRate = SignVRencode<int16_t, 6>(Climb); }
int16_t DecodeClimbRate(void) const
{ return SignVRdecode<int16_t, 6>(Position.ClimbRate); }
// --------------------------------------------------------------------------------------------------------------
// Status fields
void clrTemperature(void) { Status.Temperature=EncodeGray((uint8_t)0x80); }
bool hasTemperature(void) const { return DecodeGray((uint8_t)Status.Temperature)!=0x80; }
void EncodeTemperature(int16_t Temp) { Status.Temperature=EncodeGray(EncodeSR2V5(Temp-200)); } // [0.1degC]
int16_t DecodeTemperature(void) const { return 200+DecodeSR2V5(DecodeGray((uint8_t)Status.Temperature)); }
void clrHumidity(void) { Status.Humidity=EncodeGray((uint8_t)0x80); }
bool hasHumidity(void) const { return DecodeGray((uint8_t)Status.Humidity)!=0x80; }
void EncodeHumidity(uint16_t Hum) { Status.Humidity=EncodeGray(EncodeSR2V5((int16_t)(Hum-525))); } // [0.1%]
uint16_t DecodeHumidity(void) const { return 525+DecodeSR2V5(DecodeGray((uint8_t)Status.Humidity)); }
void EncodeVoltage_mV(uint16_t Voltage) { EncodeVoltage((Voltage*8+63)/125); }
void EncodeVoltage(uint16_t Voltage) // [1/64V]
{ if(Voltage<80) Voltage = 0;
else Voltage-=80;
Status.Voltage=EncodeGray(EncodeUR2V6(Voltage)); }
uint16_t DecodeVoltage(void) const { return 80+DecodeUR2V6(DecodeGray((uint8_t)Status.Voltage)); }
// --------------------------------------------------------------------------------------------------------------
// Info fields: pack and unpack 7-bit char into the Info packets
void setInfoChar(uint8_t Char, uint8_t Idx) // put 7-bit Char onto give position
{ if(Idx>=16) return; // Idx = 0..15
Char&=0x7F;
uint8_t BitIdx = Idx*7; // [bits] bit index to the target field
Idx = BitIdx>>3; // [bytes] index of the first byte to change
uint8_t Ofs = BitIdx&0x07;
if(Ofs==0) { Info.Data[Idx] = (Info.Data[Idx]&0x80) | Char ; return; }
if(Ofs==1) { Info.Data[Idx] = (Info.Data[Idx]&0x01) | (Char<<1) ; return; }
uint8_t Len1 = 8-Ofs;
uint8_t Len2 = Ofs-1;
uint8_t Msk1 = 0xFF; Msk1<<=Ofs;
uint8_t Msk2 = 0x01; Msk2 = (Msk2<<Len2)-1;
Info.Data[Idx ] = (Info.Data[Idx ]&(~Msk1)) | (Char<<Ofs);
Info.Data[Idx+1] = (Info.Data[Idx+1]&(~Msk2)) | (Char>>Len1); }
uint8_t getInfoChar(uint8_t Idx) const // get 7-bit Char from given position
{ if(Idx>=16) return 0; // Idx = 0..15
uint8_t BitIdx = Idx*7; // [bits] bit index to the target field
Idx = BitIdx>>3; // [bytes] index of the first byte to change
uint8_t Ofs = BitIdx&0x07;
if(Ofs==0) return Info.Data[Idx]&0x7F;
if(Ofs==1) return Info.Data[Idx]>>1;
uint8_t Len = 8-Ofs;
return (Info.Data[Idx]>>Ofs) | ((Info.Data[Idx+1]<<Len)&0x7F); }
void clrInfo(void) // clear the info packet
{ Info.DataChars=0; // clear number of characters
Info.ReportType=1; } // just in case: set the report-type
uint8_t addInfo(const char *Value, uint8_t InfoType) // add an info field
{ uint8_t Idx=Info.DataChars; // number of characters already in the info packet
if(Idx) Idx++; // if at least one already, then skip over the terminator
if(Idx>=15) return 0;
uint8_t Len=0;
for( ; ; )
{ uint8_t Char = Value[Len]; if(Char==0) break;
if(Idx>=15) return 0;
setInfoChar(Char, Idx++);
Len++; }
setInfoChar(InfoType, Idx); // terminating character
Info.DataChars=Idx; // update number of characters
return Len+1; } // return number of added Value characters
uint8_t readInfo(char *Value, uint8_t &InfoType, uint8_t ValueIdx=0) const
{ uint8_t Len=0; // count characters in the info-string
uint8_t Chars = Info.DataChars; // total number of characters in the record
char Char=0;
for( ; ; ) // loop over characters
{ if((ValueIdx+Len)>Chars) return 0; // return failure if overrun the data
Char = getInfoChar(ValueIdx+Len); // get the character
if(Char<0x20) break; // if less than 0x20 (space) then this is the terminator
Value[Len++]=Char; }
Value[Len]=0; // null-terminate the infor string
InfoType=Char; // get the info-type: Pilot, Type, etc.
return Len+1; } // return number of character taken thus info length + terminator
uint8_t InfoCheck(void) const
{ uint8_t Check=0;
for( uint8_t Idx=0; Idx<15; Idx++)
{ Check ^= Info.Byte[Idx]; }
// printf("Check = %02X\n", Check);
return Check; }
void setInfoCheck(void)
{ Info.Check = InfoCheck();
// printf("Check = %02X\n", Info.Check);
}
uint8_t goodInfoCheck(void) const
{ return Info.Check == InfoCheck(); }
// --------------------------------------------------------------------------------------------------------------
void Whiten (uint8_t Prefix=0x05)
{ uint32_t Mask = ((uint32_t)Prefix<<26) | (HeaderWord & 0x03FFFFFF);
for( uint8_t Idx=0; Idx<4; Idx++)
{ XorShift32(Mask); Mask += 0x01234567;
Data[Idx] ^= Mask; }
}
void Dewhiten(void) { Whiten(); }
} ;
#endif // __OGN2_H__