Bürokraten, mitglieder, Administratoren
1.042
Bearbeitungen
(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]] | ||
==Quellen== | ==Quellen== |