Projekt:Deacon: Unterschied zwischen den Versionen

8.137 Bytes hinzugefügt ,  10. Juni 2021
→‎Umsetzung: code dazu
(notdeacon implementierung; theorie soweit vollständig)
(→‎Umsetzung: code dazu)
 
Zeile 225: Zeile 225:
Ursprünglich war mein Plan den Nordic Chip auf meinem von der Bachelorarbeit verbliebenen Evaluationboard als Sender zu verwenden, da dessen Transceiver auch direkt angesteuert werden kann. Da ich ja aber inzwischen meine LimeSDR Hardwarer herumliegen habe (mit der ich onehin spielen wollte), hab ich diese verwendet. Das ermöglicht mir ein bisschen mehr mit der Modulation zu spielen und evtl später Hardwareansätze damit näherungsweise zu Simulieren und auf Brauchbarkeit zu testen. Außerdem konnte ich daher Evaluationboard mit der entsprechenden Firmware von Nordic als BLE-Sniffer mit Wireshark zur Fehlerdiagnose verwenden :-).
Ursprünglich war mein Plan den Nordic Chip auf meinem von der Bachelorarbeit verbliebenen Evaluationboard als Sender zu verwenden, da dessen Transceiver auch direkt angesteuert werden kann. Da ich ja aber inzwischen meine LimeSDR Hardwarer herumliegen habe (mit der ich onehin spielen wollte), hab ich diese verwendet. Das ermöglicht mir ein bisschen mehr mit der Modulation zu spielen und evtl später Hardwareansätze damit näherungsweise zu Simulieren und auf Brauchbarkeit zu testen. Außerdem konnte ich daher Evaluationboard mit der entsprechenden Firmware von Nordic als BLE-Sniffer mit Wireshark zur Fehlerdiagnose verwenden :-).


Nach anfänglicher Problemchen aufgrund von Verwirrung wegen der Bitorderproblematik und Huddelei meinerseits hab ich den Code inzwischen zum laufen bekommen. Hier zu sehen Wireshark aufnahmen mit dem BLE-Sniffer mit Fehlerhafter wie korrekter CRC sowie das Suchergebnis einer BLE-Scan-App auf meinem Handy.
Nach anfänglicher Problemchen aufgrund von Verwirrung wegen der Bitorderproblematik und Huddelei meinerseits hab ich den Code inzwischen zum laufen bekommen.  
 
==== Code ====
Vorab: Das folgende ist (natürlich) kein Musterbeispiel für vorbildlichen Code. Und vielleich noch schlimmer ist es fast frevelhaft dass ich zwar wegen des LimeSDK C++ verwendet, aber trotzdem praktisch fast nur C programmiert hab^^<br>
Den vollständigen Code gibt es [https://github.com/MaesterK/Deacon/blob/main/notDeacon/ in meinem GitHub Repository].
 
Das Datenformat
<syntaxhighlight lang=cpp>
#define ADV_TEXT_MAX_SIZE 20
 
struct adPayload
{
uint8_t ad_address[6];
uint8_t ad_data[ADV_TEXT_MAX_SIZE+2];/*enthaelt neben text je byte fuer laenge + datentyp*/
}__attribute__((packed));
 
struct adPDU
{
uint8_t header[2];
adPayload payload;
 
}__attribute__((packed));
 
struct Paket
{
uint8_t preamble;
uint8_t access_address[4];
 
adPDU pdu;
uint8_t crc[3];
}__attribute__((packed));
</syntaxhighlight>
Da ich wegen des LimeSDK eigentlikch c++ verwendet hab schreit das ganze danach mit Vererbung zu arbeiten, aber meh....sollte ich da je was auwändigeres draus machen wollen wäre das wohl einer der ersten Schritte.<br>
Das "__attribute__((packed))" am Ende der Strukturen ist da, um zu verhindern, dass der Compiler die Daten an einem 32bit Raster ausrichtet, was zu ungewollten "Lücken" ohne zuorndung im Speicher führen würde.
 
Die Daten
<syntaxhighlight lang=cpp>
Paket paket={0};
 
//preamble muss gem vol6 2.1.1 mit bitwechsel zu adresse uebergehen
paket.preamble = 0b01010101;
//adresse fuer advertising broadcast gem vol6 2.1.2 in bt core spec 5.0
//adv broadcast adresse: 0x8E89BED6 !! bytorder lsb first!
paket.access_address[3]=0x8E;
paket.access_address[2]=0x89;
paket.access_address[1]=0xBE;
paket.access_address[0]=0xD6;
//pdu header v6 2.3
        //pdutype adv_nonconn_ind 0b0010 (adv allgemein waere 0b0000)
        //tx add = 1 (pos 6) (random) gem corespec 2.3.1.3
      //keine angabe zu chesel rx add -> reserved -> 0 lassen
      //rfu = reserved for future use
paket.pdu.header[0] = (0b0010 ) | (1 << 6);
 
//payloadlaenge im zweiten teil des headers
paket.pdu.header[1]=sizeof(paket.pdu.payload);
 
//devaddress (random)
//vol6 1.3.2.1: die beiden most signifcant muessen 1 sein, im rest min 1 mal 0 u 1 mal 1
paket.pdu.payload.ad_address[0]=0b11000011;
paket.pdu.payload.ad_address[1]=0b11111111;
paket.pdu.payload.ad_address[2]=0b11101110;
paket.pdu.payload.ad_address[3]=0b11101110;
paket.pdu.payload.ad_address[4]=0b01010101;
paket.pdu.payload.ad_address[5]=0b11000011;
 
paket.pdu.payload.ad_data[0] = ADV_TEXT_MAX_SIZE + 1;//laenge daten inkl adtyp
paket.pdu.payload.ad_data[1] = 0x09;//addata typ = local name
//text ab index 2
strcpy((char*) &paket.pdu.payload.ad_data[2],"test!");//!!stringlaenge vs speicherbereich beachten
 
//crc preset fuer advertising: 0x555555 (spec v6 3.1.1)
paket.crc[0] = 0x55;
paket.crc[1] = 0x55;
paket.crc[2] = 0x55;
 
        deacon_crc((uint8_t*) &paket.pdu,sizeof(paket.pdu),(uint8_t*) paket.crc);
//!!crc wird msb first uebertragen! (spec v6 1.2) deswegen reihenfolge vor whitening und uebertragung umkehren
swapbits(paket.crc,sizeof(paket.crc));
 
//whitening (initialwert abhaengig von channel)
deacon_dowhitening((uint8_t*) &paket.pdu,(sizeof(paket.pdu)+3), 37);
</syntaxhighlight>
Für Shiftregisterimplementierungen kann man eine ganze Menge an Optimierungstechniken anwenden. Aber erstens war hier Priorität auf les- und nachvollziebarkeit (und später Vergleichbarkeit mit "echten") und zweitens hat mein i7 Laptop nun wirklich keine Performanceprobleme damit - zumal das Ergebnis eh gecacht wird. Wenn man das ganze auf nem kleinen Mikrocontroller implementieren wollte, sähe das anders aus.
<syntaxhighlight lang=cpp>
void deacon_crc(const uint8_t* data,uint16_t length,uint8_t* crc){
//crc polynom: x24 + x10 + x9 + x6 + x4 + x3 + x + 1
const uint8_t poly[] = {0,0b00000110,0b01011011};
uint8_t carry = 0;
uint8_t din = 0;
uint8_t x24 = 0;
//eingangsbytes iterieren
for (int byteindex=0; byteindex < length; byteindex++)
{
//eingangsbits iterieren
for(int bitindex=0;bitindex < 8; bitindex++)
{
//carry zwischenspeichern
carry = (crc[0] & 0b10000000) >> 7;//bitmaske fuer fkt ueberflussig
//naechtes bit einlesen
din = ((data[byteindex] & (1 << (bitindex) )) >> (bitindex));
//xor fuer eingang durchfuehren
x24 = carry^din;
//register weitershiften und dabei daten aus vorangehenden bytes uebernehmen
crc[0] = crc[0] << 1;
crc[0] = crc[0] | ((crc[1] & 0b10000000) >> 7);
crc[1] = crc[1] << 1;
crc[1] = crc[1] | ((crc[2] & 0b10000000) >> 7);
crc[2] = crc[2] << 1;
//das letzte bit geht ueber x24 mit bitmaske ein
crc[2] = crc[2] ^ (x24 * poly[2]);
crc[1] = crc[1] ^ (x24 * poly[1]);
}
}
std::cout<< "crc " << std::hex << (int)crc[0] << std::hex << (int)crc[1]
<<std::hex<<(int)crc[2]<< std::endl;
 
}
 
 
void deacon_dowhitening( uint8_t* data,uint16_t length,uint8_t channel_index){
//whitening polynom: x7+x4+1
const uint8_t  poly = 0b00010000;
uint8_t reg = 1; // pos 0 immer 1
swapbits(&channel_index,1);
reg = reg | (channel_index >> 1);
//eingangsbytes iterieren
for (int byteindex = 0 ; byteindex < length; byteindex++)
{
//eingangsbits iterieren
for (int bitindex = 0 ; bitindex < 8 ; bitindex++)
{
//register nach bit 7 rotieren
reg = reg << 1;
reg = reg | ( reg >> 7);
//dabei ueberhang abschneiden
reg = reg & 0b01111111;
//polygon anwenden
reg = reg ^ (poly *(reg & 1));
//datenbit verarbeiten
//relevantes bit befindet sich nach rotation an pos 1
data[byteindex] = data[byteindex] ^ ((reg & 1) << (bitindex));
}
}
}
</syntaxhighlight>
Nachdem die Daten nun vollständig vorbereitet sind, iteriere ich durch die Bits (natürlich LSB first!), um daraus Samples zum Mischen in der SDR-Hardware zu erzeugen:
<syntaxhighlight lang=cpp>
void deacon_geniqsamples(uint8_t* pkg_data, int pkg_len, float* tx_buffer,int sample_rate)
{
const double basemod_freq = 500e3;
const double pi = M_PI;
double w = 2 * pi * basemod_freq ;
int samples_per_symbol = sample_rate / 1e6;//ble uebertragungsrate 1Mbit/s
int sampleindex = 0;
double t = 0;
int factor = 2;
for (int byteindex = 0; byteindex < pkg_len; byteindex++)
{
for (int bitindex =0; bitindex < 8; bitindex++)
{
if((pkg_data[byteindex] & (1<<(bitindex))) != 0)
{
factor = 2;
std::cout << "1";
}else{
factor = 1;
std::cout << "0";
}
//iqsamples fuer jeweiliges bit generieren
for (int rsampleindex=0; rsampleindex < samples_per_symbol; rsampleindex++)
{
t += w * factor / sample_rate;
 
tx_buffer[2*sampleindex] = cos(t);// "*-1" um runter statt hochzumischen
tx_buffer[2*sampleindex+1] =sin(t);
sampleindex++;
}
}
std::cout << "\n";
}
}
</syntaxhighlight>
Der Bequemlichkeit halber verwende ich eine LF-Frequenz von 2401,25 MHz, die ich je nach Bitstate mit 500 oder 1000 kHz mische. um damit 2401,75 ("0") oder 2402,25 ("1") zu erreichen.<br>
"Mischen" bedeutet hier anders als z.B. im HiFi-Bereich nicht das addieren, sondern das Multiplizieren der Signale (im Zeitbereich).<br>
Das Ergebnis sind dann zwei sog Seitenbänder, deren Frequenz dann einmal der Differenz und einmal der Summe der Ursprungssignale entsricht.
Da ich IQ-Samples verwende, hab ich den Vorteil, dass ich damit eines der beiden enstehenden Seitenbänder unterdrücken kann und das somit nicht extra Filtern muss.
 
Neben dem ganzen Initialisierungskram für die SDR-Hardware gibt es im Grunde sonst nurnoch eines zu tun: Die Daten regelmäßig Broadcasten
<syntaxhighlight lang=cpp>
while (true)
{
int ret = LMS_SendStream(&tx_stream,tx_buffer, send_cnt, nullptr, 1000);
 
...
 
//adv-interval = 20ms-10s + 0-10ms rnd gegen kollision
usleep(100000);
}
</syntaxhighlight>
Das mit der zufälligen Pause hab ich mir erstmal geschenkt, aber natürlich hat das keinen Einfluss auf die Funktion, vor allem weil sich (hoffentlich) alle anderen Geräte daran halten^^
 
Zur Erinnerung: Den vollständigen Code gibt es [https://github.com/MaesterK/Deacon/blob/main/notDeacon/notDeacon.cpp in meinem GitHub Repository].
 
Hier zu sehen Wireshark aufnahmen mit dem BLE-Sniffer mit Fehlerhafter wie korrekter CRC sowie das Suchergebnis einer BLE-Scan-App auf meinem Handy.


[[File:notdeacon_crcerr.PNG|600px]][[File:notdeacon_crcok.PNG|600px]][[File:notdeacon_handy.png|200px]]
[[File:notdeacon_crcerr.PNG|600px]][[File:notdeacon_crcok.PNG|600px]][[File:notdeacon_handy.png|200px]]
Code folgt


==Quellen==
==Quellen==
1.042

Bearbeitungen