Projekt:Deacon: Unterschied zwischen den Versionen

Aus Schaffenburg
Zur Navigation springen Zur Suche springen
(bilder grob rein)
(→‎Umsetzung: code dazu)
 
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 25: Zeile 25:
3.1 Die Modulation.
3.1 Die Modulation.
  The modulation is Gaussian Frequency Shift Keying (GFSK) with a bandwidth-bit period product BT=0.5. The modulation index shall be between 0.45 and 0.55. A binary one shall be represented by a positive frequency deviation, and a binary zero shall be represented by a negative frequency deviation.
  The modulation is Gaussian Frequency Shift Keying (GFSK) with a bandwidth-bit period product BT=0.5. The modulation index shall be between 0.45 and 0.55. A binary one shall be represented by a positive frequency deviation, and a binary zero shall be represented by a negative frequency deviation.
dazu gibt's folgende nette grafik zu anschauung
 
Die BT bezieht sich auf die Form des Gauß-Filters, den wir vorläufig ignorieren. Der Modulationsindex gibt den Abstand zwischen negativ und positiv gehobener Frequenz bezogen auf die Symbolrate an. Mit der Symbolrate von 1MSymbol/s ergibt sich damit ein Abstand zwischen 450 kHz und 550 kHz, bzw. ein '''Frequenzhub zwischen 225 kHz und 275 kHz'''.
 
Dazu gibt's folgende nette Grafik zu Anschauung (sog. "Augendiagramm")


[[File:gfskparam_corespec.JPG|400px]]
[[File:gfskparam_corespec.JPG|400px]]
Zeile 113: Zeile 116:
zum Thema Access Adress gibt es den folgenden Punkt für uns:
zum Thema Access Adress gibt es den folgenden Punkt für uns:
   
   
  2.1.2  Access Address
  '''2.1.2  Access Address'''
  ...
  ...
  The Access Address for all other advertising channel packets shall be  
  The Access Address for all other advertising channel packets shall be  
Zeile 120: Zeile 123:


Wir haben also damit unsere Broadcastadresse. :)<br>
Wir haben also damit unsere Broadcastadresse. :)<br>
..die Absätze zu PDU (Protocol Data Unit) und CRC verweisen uns auf spätere Abschnitte (2.3 und 3.1.1). Da uns die folgenden erläuterungen des Coded PHY (long range modus von BT 5.0) nicht belangen springen wir also direkt weiter zu 2.3 ADVERTISING CHANNEL PDU (Seite 2567)
..die Absätze zu PDU (Protocol Data Unit) und CRC verweisen uns auf spätere Abschnitte (2.3 und 3.1.1).  
 
'''2.1.4 CRC'''
The PDU is followed by a 24-bit CRC. It shall be calculated over the PDU. The CRC polynomial is defined in Section 3.1.1.
 
Da uns die folgenden Erläuterungen des Coded PHY (long range modus von BT 5.0) nicht belangen springen wir also direkt weiter zu 2.3 ADVERTISING CHANNEL PDU (Seite 2567)
Dort finden wir sowohl den allgemeinen Aufbau als auch den des Headers  
Dort finden wir sowohl den allgemeinen Aufbau als auch den des Headers  


Zeile 132: Zeile 140:
[[File:advpduheaderfields2_corespec.JPG|400px]]
[[File:advpduheaderfields2_corespec.JPG|400px]]


ergänzender Text von relevanz:
Ergänzender Text von relevanz:
  The ChSel, TxAdd and RxAdd fields of the advertising channel PDU that are contained in the header contain information specific to the PDU type  defined for each advertising channel PDU separately. If the ChSel, TxAdd or RxAdd fields are not defined as used in a given PDU then they shall be considered Reserved for Future Use.
  The ChSel, TxAdd and RxAdd fields of the advertising channel PDU that are contained in the header contain information specific to the PDU type  defined for each advertising channel PDU separately. If the ChSel, TxAdd or RxAdd fields are not defined as used in a given PDU then they shall be considered Reserved for Future Use.
  The Length field of the advertising channel PDU header indicates the payload field length in octets. The valid range of the Length field shall be 1 to 255 octets.
  The Length field of the advertising channel PDU header indicates the payload field length in octets. The valid range of the Length field shall be 1 to 255 octets.
Zeile 142: Zeile 150:
Wir müssen uns also erstmal schlau machen welchen PDU Typ wir verwenden wollen um dann herauszufinden, wie das exakte Format aussieht. Außerdem müssen wir einen Abstecher "zurück" zu Volume 3 (zur erinnerung: wir befinden uns in Volume 6) machen, um das Datenformat für die "Nutzdaten" zu erfahren.<br>
Wir müssen uns also erstmal schlau machen welchen PDU Typ wir verwenden wollen um dann herauszufinden, wie das exakte Format aussieht. Außerdem müssen wir einen Abstecher "zurück" zu Volume 3 (zur erinnerung: wir befinden uns in Volume 6) machen, um das Datenformat für die "Nutzdaten" zu erfahren.<br>


Der Folgende PDU-Typ sieht vielversprechend aus:
'''2.3.1.3 ADV_NONCONN_IND'''
The ADV_NONCONN_IND PDU has the Payload as shown in Figure 2.8. The PDU '''shall be used in non-connectable and non-scannable undirected advertising events'''. The TxAdd in the advertising channel PDU header indicates whether the advertiser’s address in the AdvA field is public (TxAdd = 0) or random (TxAdd = 1).
The Payload field consists of AdvA and AdvData fields. The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd. The AdvData field may contain Advertising Data from the advertiser’s Host.
[[File:ADV_NONCONN_IND_corespec.JPG|250px]]
Der Informationsgewinn aus dem Supplement hält sich in Grenzen:
'''1.2 LOCAL NAME'''
1.2.1 Description
The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the device. The Local Name data type value indicates if the name is complete or shortened. If the name is shortened, the complete name can be read using the remote name request procedure over BR/EDR or by reading the device name characteristic after the connection has been established using GATT.
A shortened name shall only contain contiguous characters from the beginning of the full name. For example, if the device name is ‘BT_Device_Name’ then the shortened name could be ‘BT_Device’ or ‘BT_Dev’.
=> Local Name is der Name; shortened Name ist der gekürzte Name...you don't say
..Auf jeden Fall werden wir den Datentyp verwenden, da dieser praktsich von allen Scanapps angezeigt wird.
Unpraktsicher Weise hat man sich aus irgendeinem Grund dagegen entschieden, die zugehörigen Zahlenwerte nicht in das Dokument selbst aufzunehmen, sondern auf eine Website zu packen:
The values for the data types are listed in Assigned Numbers.
..Dabei bleibt es einem selbst überlassen herauszufinden, welche der etlichen Assigned-Numbers-Listen die richtige ist (die Lösung: "Assigned numbers and GAP"). Hier ist ein Auszug der Liste:
[[File:bleassignednumbers_web3.JPG|800px]]
Für uns sinnvoll wären also die Werte 0x08 oder 0x09.
Damit haben wir also alles für den Inhalt zusammen. Zurück zu den oben erwähnten CRC und Whitening. Das führt uns zu Sektion 3 "Bitstream Processing"
[[File:bitstream_corespec.JPG|600px]]
Da wir Advertising betreiben und entsprechend natürlich keine Verschlüsselung angwenden ergeben sich für uns also nach dem "Zusammenpacken" der Daten 2 Arbeitsschritte, bevor wir diese endlich rausfunken dürfen:
Es muss eine CRC-Prüfsumme über die PDU berechnet und anschließend ein Whiteningverfahren durchgeführt werden.
'''3.1.1 CRC Generation'''
The CRC shall be calculated on the PDU field in all Link Layer packets. If the PDU is encrypted, then the CRC shall be calculated after encryption of the PDU has been performed.
The CRC polynomial is a 24-bit CRC and all bits in the PDU shall be processed in transmitted order starting from the least significant bit. '''The polynomial has the form of x24 + x10 + x9 + x6 + x4 + x3 + x + 1'''. For every Data Channel PDU, the shift register shall be preset with the CRC initialization value set for the Link Layer connection and communicated in the CONNECT_IND PDU. For the AUX_SYNC_IND PDU and its subordinate set, the shift register shall be preset with the CRCInit value set in the SyncInfo field (see Section 2.3.4.6) contained in the AUX_ADV_IND PDU that describes the periodic advertising. '''For all other Advertising Channel PDUs, the shift register shall be preset with 0x555555'''.
Position 0 shall be set as the least significant bit and position 23 shall be set as the most significant bit of the initialization value. '''The CRC is transmitted most significant bit first''', i.e. from position 23 to position 0 (see Section 1.2).
Figure 3.3 shows an example linear feedback shift register (LFSR) to generate the CRC.
[[File:crc_corespec.JPG|600px]]
Das zu verwendende Polynom ist also x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1 und der Initialwert ist in unserem Fall 0x555555.
'''3.2 DATA WHITENING'''
Data whitening is used to avoid long sequences of zeros or ones, e.g. 0000000b or 1111111b, in the data bit stream. '''Whitening shall be applied on the PDU and CRC fields of all Link Layer packets''' and is performed after the CRC in the transmitter. De-whitening is performed before the CRC in the receiver (see Figure 3.1).
The whitener and de-whitener are defined the same way, using a 7-bit linear feedback shift register with the '''polynomial x7 + x4 + 1'''. '''Before whitening or dewhitening, the shift register is initialized with a sequence that is derived from the channel index''' (data channel index or advertising channel index) in which the packet is transmitted in the following manner:
• Position 0 is set to one.
• Positions 1 to 6 are set to the channel index of the channel used when
transmitting or receiving, from the most significant bit in position 1 to the least significant bit in position 6.
For example, if the channel index = 23 (0x17), the positions would be set as follows:
Position 0 = 1
Position 1 = 0
Position 2 = 1
Position 3 = 0
Position 4 = 1
Position 5 = 1
Position 6 = 1
[[File:whitening_corespec.JPG|400px]]
Das Whitening ist also mit der PDU sowie auch den zuvor generierten CRC Daten durchzuführen. Das Polynom ist x^7+x^4+1. Der Initialswert hängt von gerade verwendeten Kanal ab. Dabei ist zu beachten, dass der "Channel Index" nicht das gleiche wie die Channel No. ist! Der "erste" Kanal bei 2402 MHz ist nicht etwa Channel Index 0, sondern 37! (Vergleiche Tabelle weiter oben)
Warum das Whitining? Das Verfahren dient dazu, sicherzustellen, dass keine zu lange Aneinanderreihung von Nullen oder Einsen zu übertragen sind. Das macht die Demodulation vor allem für simple Empfängertypen deutlich einfacher.<br>
Beispielsweise kann man FSK sehr bequem mit einem sog. Flankendiskriminator Demodulieren: Das Signal wird wie der Name bereits andeutet so in einen Filter geführt, dass dieses frequenzabhängig unterschiedlich stark gedämpft wird, da es sich eben in der "Flanke" und nicht im normalen Durchlassbereich befindet. Dadurch wird aus der Frequenzmodulation eine Amplitudenmodulation. Diese kann dann sehr einfach demoduliert werden, indem man den durchschnittlichen Pegel mit dem aktuellen vergleicht. Das funktioniert aber nur, solange der Pegel nicht zu lange konstant bleibt (nur 1en oder 0en übertragen werden) und der Durchschnitt daher zu weit abdriftet.
=== Zusammenfassung ===
Es gilt also folgendes zu tun: <br>
Wir "packen" ein Datenpaket, bestehend aus 1 Byte Praeamble (0b10101010) gefolgt von 4 Byte Broadcast-Adresse (0x8E89BED6). Dann kommt die PDU, die ihrerseits aus 2 Byte Header (1 Byte PDU-Typ = 0b0010, txaddr random =1; 1 Byte Länge der PDU), und der Payload, die wiederum aus der (zufällig gem. obiger Vorgaben) generierten Hardwareadresse unseres Beacons (6 Byte) und den Advertisingdaten besteht. In unserem Fall ist das nur der Devicename den wir zur Textübermittlung einsetzen. Diese bestehen somit aus einem Byte für die Länge (für Typ+Daten), einem Byte mit dem Typ (0x09) und anschließend dem Text im ASCII-Format.<br>
Anschließend wird die CRC für die PDU berechnet (Initalwert 0x555555), womit das Paket vollständig ist. Vor dem Versenden muss für PDU +  CRC noch das Whiteningverfahren durchgeführt werden (Initialwert abhängig von Kanal). Die dabei resultierenden Bits können direkt an den Modulator / den Sender geführt werden.<br>
Dieser arbeitet (mindestens) auf einem der Adverstingknaäle (z.B. Kanal 37 auf 2402 MHz) mit einem Frequenzhub on 250 kHz. Der im Standard Vorgegebenen Mindestabstand für Advertisingpakete beträgt dabei 20ms + 0-10ms Zufallsintervall.
== Umsetzung ==
=== (absolutely) notDeacon ===
Ich habe mich entschieden als ersten Schritt genau das Gegenteil des eignetlichen Plans zu machen: Den Beacon praktisch vollständig in Software umsetzen.<br>
Ziel dabei ist zum Einen zu checken, ob meine Interpretation des Standards soweit korrekt ist und zum Anderen damit eine Referenzimplementierung zu schaffen, gegen die ich Teilschritte der eigentlichen Implementierung testen kann.<br>
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.
==== 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]]


==Quellen==
* [https://www.bluetooth.com/specifications/specs/core-specification-5/ Bluetooth Core Specification v5.0]
* [https://www.bluetooth.com/specifications/specs/?status=all&keyword=supplement&filter= Supplement to the Bluetooth Core Specification v8]
* [https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ Assigned Numbers and GAP (Bluetooth SIG)]


==Verantwortlicher/Ansprechpartner==
==Verantwortlicher/Ansprechpartner==
Karsten
Karsten

Aktuelle Version vom 10. Juni 2021, 17:58 Uhr

Crystal Clear app error.png
Deacon

Status: unstable

Deacon Palmer.jpg
Beschreibung Diskret aufgebautes Radio für BLE-Beacon
Ansprechpartner Karsten


Diskret aufgebaute Sender und Emfänger für BLE-Beacons

Übersicht

Da ich mich in letzter Zeit aus diversen Gründen recht viel mit dem BLE-Stack und der Core Spec auseinandergesetzt hab, viel mir auf, dass der Physical Layer, also das Radio und auch der Teil knapp darüber an und für sich ziemlich simpel gestrickt sind (was zugegeben an sich wegen der Anforderungen "Low Energy" und "Low Cost" natürlich eigentlich auf der Hand lag).
Mit binärem (G)FSK könnte die Modulation eigentlich kaum einfacher sein. Das Paketformat für die von Beacons genutze Advertising genutzte Pakete ist recht kompakt und die einzig nötigen Verarbeitungsschritte des Link Layers (CRC-Kalkulation und Whitening) ziemlich überschaubar. Es sollte kein Problem sein mit einem Arduino Uno oder ähnlichem Pakete zurechtzumachen und z.b. per spi-bus mit der benötigen Rate von 1MHz, bzw 1MSymbol/s rauszuhauen.
Obwohl Advertisingpakete normalerweise sequenziell über die 3 dedizierten Kanäle versendet werden, ist es möglich (und sogar von der Spec erlaubt) auf nur einem Kanal zu senden. Da Scanner normalerweise natürlich auch auf allen Kanälen suchen werden sie den Beacon trotzdem finden. Umgekehrt wird auch ein Scanner, der nur einen Kanal absucht normale BLE-Geräte finden, da sie ja auf allen Kanälen unterwegs sind.Das "richtige" Frequency-Hopping findet nur auf den Datenkanälen statt, die erst von verbundene BLE-Devices genutzt werden
Sender und Empfänger können also für einen festen Kanal gebaut werden, was die Geschichte nicht unerheblich vereinfacht. Das einzige wirkliche Problem dürfte der 2,4 Ghz Teil sein.

Das ganze dient vor allem dem Spaß am Experiment und im Erfolgsfall als Teil des Amateurfunk Workshops...und als Ausgangsbasis für ein wesentlich verrückteres Projekt

Was ist zu tun?

Im folgenden arbeiten wir uns durch die Bluetooth Spezifiaktion 5.0 bis wir alles zusammen haben, das wir für den Beacon brauchen. Glücklicher Weise sind nicht all der fast 3000 Seiten relevant: Richtig los gehts mit BLE erst mit Vol.6 auf Seite 2528. Der Teil davor bezieht sich hauptsächlich auf die klassischen Varianten und enthält nur ein paar gemeinsam genutzte Definitionen, wie z.B. das Datenformat fürs Advertising.

Der analoge Teil

Volume 6 beginnt direkt mit dem wichtigsten Teil für dieses Projekt - dem genutzten Frequenzband und der Kanaleinteilung:

The LE system operates in the 2.4 GHz ISM band at 2400-2483.5 MHz. The LE system uses 40 RF channels. These RF channels have center frequencies 2402 + k * 2 MHz, where k = 0, ..., 39. 

So weit so gut..der Frequenzbereich ist wenig überraschen. Interessant ist evtl dass BLE den Bereich in 40 Kanäle einteilt, während das klassische Bluetooth noch 79 Kanäle verwendet hat. Weiter gehts mit der Klasseneinteilung der möglichen Sendeleistung von Geräten, was uns eher weniger interessiert. Als nächstes kommt ab Seite 2537 dann der wichtigste Teil: Teil A - Physical Layer 3.1 Die Modulation.

The modulation is Gaussian Frequency Shift Keying (GFSK) with a bandwidth-bit period product BT=0.5. The modulation index shall be between 0.45 and 0.55. A binary one shall be represented by a positive frequency deviation, and a binary zero shall be represented by a negative frequency deviation.

Die BT bezieht sich auf die Form des Gauß-Filters, den wir vorläufig ignorieren. Der Modulationsindex gibt den Abstand zwischen negativ und positiv gehobener Frequenz bezogen auf die Symbolrate an. Mit der Symbolrate von 1MSymbol/s ergibt sich damit ein Abstand zwischen 450 kHz und 550 kHz, bzw. ein Frequenzhub zwischen 225 kHz und 275 kHz.

Dazu gibt's folgende nette Grafik zu Anschauung (sog. "Augendiagramm")

Gfskparam corespec.JPG

und weiter

For each transmission the minimum frequency deviation,
Fmin = min { |Fmin+ | , Fmin- }
which corresponds to a 1010 sequence, shall be no smaller than ±80% of the frequency deviation with respect to the transmit frequency, which corresponds to a 00001111 sequence.
The minimum frequency deviation shall never be less than 185 kHz when transmitting at 1 megasymbol per second (Msym/s) symbol rate and never be less than 370 kHz when transmitting at 2 Msym/s symbol rate. The symbol timing accuracy shall be better than ±50 ppm. The zero crossing error is the time difference between the ideal symbol period and the measured crossing time. This shall be less than ±1/8 of a symbol period.

3.3 geht nochmal ein wenige genauer auf Toleranzen ein

The deviation of the center frequency during the packet shall not exceed ±150 kHz, including both the initial frequency offset and drift. The frequency drift during any packet shall be less than 50 kHz. The drift rate shall be less than 400 Hz/µs.

Damit haben wir die wichtigsten Anforderungen für unseren Sender zusammen...Sendefrequenzbereich, Modulationsart (binäre FM), Frequenzhub , Toleranzen, Symbolrate (1 MSymbol) Nur das mit den Kanälen sollte noch ein wenig genauer festgelegt werden. Die folgenden Angaben zu Störabständen etc ignorieren wir mal vorläufig und auch die Anforderungen für Sende- und Empfangsleistung können uns erstmal egal sein. ..und schon sind wir bei Teil B - Link Layer Die Dokumentation der untersten logischen Schicht beginnt zunächst mit den verschiedenen Zuständen für BLE-Devices, was für uns eher weniger von belang ist, da wir uns nur für den Advertising zustand interessieren. Interessant ist vllt allenfalls der Abschnitt

1.1.2  Devices supporting only some states
Devices supporting only some link layer states or only one of the two roles within the Connection State are not required to support features (including supporting particular PDUs, procedures, data lengths, or HCI commands or particular features of an HCI command) that are only used by a state or mode that the device does not support.

Wenn auch nur von eher theoretischer Art..es ist zumindest nett dass wir vermutlich theoretisch den Standard zumindest in der Hinsicht erfüllen könnten.

Abschnitt 1.4 kommt nochmals auf die eingeteilten Kanäle zurück

1.4   PHYSICAL CHANNEL
As specified in [Vol 6] Part A, Section 2, 40 RF channels are defined in the 2.4GHz ISM band. These RF channels are allocated into three LE physical channels: advertising, periodic, and data. The advertising physical channel uses all 40 RF channels for discovering devices, initiating a connection and broadcasting data. These RF channels are divided into 3 RF channels, known as the "primary advertising channel", used for initial advertising and all legacy advertising activities, and 37 RF channels, known as the "secondary advertising channel", used for the majority of the communications involved. 
The data physical channel uses up to 37 (see Section 4.5.8) RF channels for communication between connected devices. Each of these RF channels is allocated a unique channel index (see Section 1.4.1). The periodic physical channel uses the same RF channels as the secondary advertising channel over the advertising physical channel.
...

Wir erfahren dass 3 der Kanäle für das "primäre" Advertising reserviert sind, während die restlichen 37 Kanäle für Daten und (seit 5.0) für "sekundäres Advertising" genutzt werden. Vor 5.0 war das zugegeben etwas deutlich formuliert. Die Tabelle auf Seite 2561 teilt uns mit, welche der 3 Kanäle für Advertising vorgesehen sind:

Blechannels corespec.JPG

Also die Kanäle 0; 12 und 39 - bzw. die Frequenzen 2402 MHz, 2426 MHz und 2480 Mhz. Da im nicht Verbundenen Zustand noch kein fancy Frequenzgehoppel möglich ist wurden die Kanäle so gelegt, dass sie zumindest in den USA in den am geringsten von WLAN belegten Bereichen liegen.

Zusammenfassung: tbd

Der digitale Teil

Jetzt gehts erstmal wieder zurück auf Seite 2555. Hier beschäftigt sich die Spec mit der Bit-Reihenfolge. Dabei gibt es folgende Besonderheit zu beachten: die CRC-Prüfsumme wird in einer anderen Reihenfolge übertragen als der Rest!

1.2   BIT ORDERING
The bit ordering when defining fields within the packet or Protocol Data Unit (PDU) in the Link Layer specification follows the Little Endian format. The 
following rules apply:
• The Least Significant Bit (LSB) corresponds to b0
• The LSB is the first bit sent over the air
• In illustrations, the LSB is shown on the left side
Furthermore, data fields defined in the Link Layer, such as the PDU header fields, shall be transmitted with the LSB first. For instance, a 3-bit parameter X=3 is sent as: 
b0b1b2 = 110

...

Multi-octet fields, with the exception of the Cyclic Redundancy Check (CRC) and the Message Integrity Check (MIC), shall be transmitted with the least significant octet first. Each octet within multi-octet fields, with the exception of the CRC (see Section 3.1.1), shall be transmitted in LSB first order. For example, the 48-bit addresses in the advertising channel PDUs shall be transmitted with the least significant octet first, followed by the remainder of the five octets in increasing order.
Multi-octet field values specified in this specification (e.g. the CRC initial value in Section 2.3.3.1) are written with the most significant octet to the left; for example in 0x112233445566, the octet 0x11 is the most significant octet.

Danach folgen die Device-Adressen. Es existieren verschiedene Typen: öffentliche (kostenpflichtige registrierung bei der BT SIG erforderlich) und zufällige, die wiederum in statisch und privat unterteilt werden. Private Adressen verwenden ein spezielles System durch die sich gekoppelte Geräte trotz regelmäßig von außen betrachtet scheinbar zufällig ändernder Adresse erkennen können, um z.B. Tracking durch Dritte zu vermeiden. Was uns interessiert ist die statische variante - eine einmalig zufällig erzeuge Adresse.

1.3.2.1  Static Device Address
 A static address is a 48-bit randomly generated address and shall meet the following requirements:
• The two most significant bits of the address shall be equal to 1
• At least one bit of the random part of the address shall be 0
• At least one bit of the random part of the address shall be 1
A device may choose to initialize its static address to a new value after each power cycle. A device shall not change its static address value once initialized until the device is power cycled.
Note: If the static address of a device is changed, then the address stored in peer devices will not be valid and the ability to reconnect using the old address will be lost.

Randomstaticaddr corespec.JPG

Wie wir sehen haben wir im großen und ganzen 46 für unsere Zufallszahl, da die oberen beiden als 1 festgelegt wurden. Die vielleich etwas willkürlich wirkende Anforderung dass 0 und 1 je mindestens ein mal vorkommen sollen hat durchaus einen Hintergrund, auf den ich später noch eingehen werde, wenn wir zu dem sog. Whitening-Verfahren kommen.

Auf Seite 2652 gehts mit Abschnitt 2 ans Eingemachte..das Paketformat

Llpaketformat corespec.JPG

Was das mit den Klammern soll?

The preamble is 1 octet when transmitting or receiving on the LE 1M PHY and 2 octets when transmitting or receiving on the LE 2M PHY. The Access Address is 4 octets. The PDU range is from 2 to 257 octets. The CRC is 3 octets.

Wir verwenden natülich die bewährte 1M-Variante und nicht den neumodischen (seit 5.0 optional verfügbaren) 2M Kram! Die Maximale PDU Größe ist übrigens erst seit 4.2 so hoch, vorher betrug sie ca 30 Byte. ...Und zur Reihenfolge:

The Preamble is transmitted first, followed by the Access Address, followed by the PDU followed by the CRC. The entire packet is transmitted at the same symbol rate (either 1 Msym/s or 2 Msym/s modulation).

Die Spec ist auch so nett, uns den Sinn der Präambel zu erklären

2.1.1  Preamble
All Link Layer packets have a preamble which is used in the receiver to perform frequency synchronization, symbol timing estimation, and Automatic Gain Control (AGC) training. 

dann gibt es Details zur Umsetzung:

The preamble is a fixed sequence of alternating 0 and 1 bits. For packets transmitted on the LE 1M PHY, the preamble is 8 bits; for packets transmitted on the LE 2M PHY, the preamble is 16 bits. The first bit of the preamble (in transmission order) shall be the same as the LSB of the Access Address. The preamble is shown in Figure 2.2.

..oder anders formuliert muss die Präambel immer so Enden dass ein Bitwechsel zwischen Präambel und der darauf folgenden Adresse stattfindet (siehe Bild)!

Preamble corespec.JPG

zum Thema Access Adress gibt es den folgenden Punkt für uns:

2.1.2  Access Address
...
The Access Address for all other advertising channel packets shall be 
10001110100010011011111011010110b (0x8E89BED6). 
....

Wir haben also damit unsere Broadcastadresse. :)
..die Absätze zu PDU (Protocol Data Unit) und CRC verweisen uns auf spätere Abschnitte (2.3 und 3.1.1).

2.1.4 CRC
The PDU is followed by a 24-bit CRC. It shall be calculated over the PDU. The CRC polynomial is defined in Section 3.1.1.

Da uns die folgenden Erläuterungen des Coded PHY (long range modus von BT 5.0) nicht belangen springen wir also direkt weiter zu 2.3 ADVERTISING CHANNEL PDU (Seite 2567) Dort finden wir sowohl den allgemeinen Aufbau als auch den des Headers

Advertising Data Format Advertising PDU Advertising PDU-Header

..und eine Tabelle mit den Verfügbaren Typen (eigentlich zwei, aber die zweite enthält nur BT 5 Typen für "erweitertes" advertising)

Advpduheaderfields corespec.JPG Advpduheaderfields2 corespec.JPG

Ergänzender Text von relevanz:

The ChSel, TxAdd and RxAdd fields of the advertising channel PDU that are contained in the header contain information specific to the PDU type   defined for each advertising channel PDU separately. If the ChSel, TxAdd or RxAdd fields are not defined as used in a given PDU then they shall be considered Reserved for Future Use.
The Length field of the advertising channel PDU header indicates the payload field length in octets. The valid range of the Length field shall be 1 to 255 octets.

The Payload fields in the advertising channel PDUs are specific to the PDU Type and are defined in Section 2.3.1 through Section 2.3.4. The PDU Types marked as Reserved for future use shall not be sent and shall be ignored upon receipt.

Within advertising channel PDUs, advertising data or scan response data from the Host may be included in the Payload in some PDU Types. The format of this data is defined in [Vol 3] Part C, Section 11.

Wir müssen uns also erstmal schlau machen welchen PDU Typ wir verwenden wollen um dann herauszufinden, wie das exakte Format aussieht. Außerdem müssen wir einen Abstecher "zurück" zu Volume 3 (zur erinnerung: wir befinden uns in Volume 6) machen, um das Datenformat für die "Nutzdaten" zu erfahren.

Der Folgende PDU-Typ sieht vielversprechend aus:

2.3.1.3 ADV_NONCONN_IND
The ADV_NONCONN_IND PDU has the Payload as shown in Figure 2.8. The PDU shall be used in non-connectable and non-scannable undirected advertising events. The TxAdd in the advertising channel PDU header indicates whether the advertiser’s address in the AdvA field is public (TxAdd = 0) or random (TxAdd = 1).
The Payload field consists of AdvA and AdvData fields. The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd. The AdvData field may contain Advertising Data from the advertiser’s Host. 

ADV NONCONN IND corespec.JPG

Der Informationsgewinn aus dem Supplement hält sich in Grenzen:

1.2 LOCAL NAME
1.2.1 Description
The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the device. The Local Name data type value indicates if the name is complete or shortened. If the name is shortened, the complete name can be read using the remote name request procedure over BR/EDR or by reading the device name characteristic after the connection has been established using GATT.
A shortened name shall only contain contiguous characters from the beginning of the full name. For example, if the device name is ‘BT_Device_Name’ then the shortened name could be ‘BT_Device’ or ‘BT_Dev’.

=> Local Name is der Name; shortened Name ist der gekürzte Name...you don't say ..Auf jeden Fall werden wir den Datentyp verwenden, da dieser praktsich von allen Scanapps angezeigt wird.

Unpraktsicher Weise hat man sich aus irgendeinem Grund dagegen entschieden, die zugehörigen Zahlenwerte nicht in das Dokument selbst aufzunehmen, sondern auf eine Website zu packen:

The values for the data types are listed in Assigned Numbers. 

..Dabei bleibt es einem selbst überlassen herauszufinden, welche der etlichen Assigned-Numbers-Listen die richtige ist (die Lösung: "Assigned numbers and GAP"). Hier ist ein Auszug der Liste:

Bleassignednumbers web3.JPG

Für uns sinnvoll wären also die Werte 0x08 oder 0x09.

Damit haben wir also alles für den Inhalt zusammen. Zurück zu den oben erwähnten CRC und Whitening. Das führt uns zu Sektion 3 "Bitstream Processing"

Bitstream corespec.JPG

Da wir Advertising betreiben und entsprechend natürlich keine Verschlüsselung angwenden ergeben sich für uns also nach dem "Zusammenpacken" der Daten 2 Arbeitsschritte, bevor wir diese endlich rausfunken dürfen: Es muss eine CRC-Prüfsumme über die PDU berechnet und anschließend ein Whiteningverfahren durchgeführt werden.

3.1.1 CRC Generation
The CRC shall be calculated on the PDU field in all Link Layer packets. If the PDU is encrypted, then the CRC shall be calculated after encryption of the PDU has been performed.
The CRC polynomial is a 24-bit CRC and all bits in the PDU shall be processed in transmitted order starting from the least significant bit. The polynomial has the form of x24 + x10 + x9 + x6 + x4 + x3 + x + 1. For every Data Channel PDU, the shift register shall be preset with the CRC initialization value set for the Link Layer connection and communicated in the CONNECT_IND PDU. For the AUX_SYNC_IND PDU and its subordinate set, the shift register shall be preset with the CRCInit value set in the SyncInfo field (see Section 2.3.4.6) contained in the AUX_ADV_IND PDU that describes the periodic advertising. For all other Advertising Channel PDUs, the shift register shall be preset with 0x555555.
Position 0 shall be set as the least significant bit and position 23 shall be set as the most significant bit of the initialization value. The CRC is transmitted most significant bit first, i.e. from position 23 to position 0 (see Section 1.2).
Figure 3.3 shows an example linear feedback shift register (LFSR) to generate the CRC.

Crc corespec.JPG

Das zu verwendende Polynom ist also x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1 und der Initialwert ist in unserem Fall 0x555555.

3.2 DATA WHITENING
Data whitening is used to avoid long sequences of zeros or ones, e.g. 0000000b or 1111111b, in the data bit stream. Whitening shall be applied on the PDU and CRC fields of all Link Layer packets and is performed after the CRC in the transmitter. De-whitening is performed before the CRC in the receiver (see Figure 3.1).
The whitener and de-whitener are defined the same way, using a 7-bit linear feedback shift register with the polynomial x7 + x4 + 1. Before whitening or dewhitening, the shift register is initialized with a sequence that is derived from the channel index (data channel index or advertising channel index) in which the packet is transmitted in the following manner:
• Position 0 is set to one.
• Positions 1 to 6 are set to the channel index of the channel used when
transmitting or receiving, from the most significant bit in position 1 to the least significant bit in position 6.
For example, if the channel index = 23 (0x17), the positions would be set as follows:
Position 0 = 1
Position 1 = 0
Position 2 = 1
Position 3 = 0
Position 4 = 1
Position 5 = 1
Position 6 = 1

Whitening corespec.JPG

Das Whitening ist also mit der PDU sowie auch den zuvor generierten CRC Daten durchzuführen. Das Polynom ist x^7+x^4+1. Der Initialswert hängt von gerade verwendeten Kanal ab. Dabei ist zu beachten, dass der "Channel Index" nicht das gleiche wie die Channel No. ist! Der "erste" Kanal bei 2402 MHz ist nicht etwa Channel Index 0, sondern 37! (Vergleiche Tabelle weiter oben)

Warum das Whitining? Das Verfahren dient dazu, sicherzustellen, dass keine zu lange Aneinanderreihung von Nullen oder Einsen zu übertragen sind. Das macht die Demodulation vor allem für simple Empfängertypen deutlich einfacher.
Beispielsweise kann man FSK sehr bequem mit einem sog. Flankendiskriminator Demodulieren: Das Signal wird wie der Name bereits andeutet so in einen Filter geführt, dass dieses frequenzabhängig unterschiedlich stark gedämpft wird, da es sich eben in der "Flanke" und nicht im normalen Durchlassbereich befindet. Dadurch wird aus der Frequenzmodulation eine Amplitudenmodulation. Diese kann dann sehr einfach demoduliert werden, indem man den durchschnittlichen Pegel mit dem aktuellen vergleicht. Das funktioniert aber nur, solange der Pegel nicht zu lange konstant bleibt (nur 1en oder 0en übertragen werden) und der Durchschnitt daher zu weit abdriftet.

Zusammenfassung

Es gilt also folgendes zu tun:
Wir "packen" ein Datenpaket, bestehend aus 1 Byte Praeamble (0b10101010) gefolgt von 4 Byte Broadcast-Adresse (0x8E89BED6). Dann kommt die PDU, die ihrerseits aus 2 Byte Header (1 Byte PDU-Typ = 0b0010, txaddr random =1; 1 Byte Länge der PDU), und der Payload, die wiederum aus der (zufällig gem. obiger Vorgaben) generierten Hardwareadresse unseres Beacons (6 Byte) und den Advertisingdaten besteht. In unserem Fall ist das nur der Devicename den wir zur Textübermittlung einsetzen. Diese bestehen somit aus einem Byte für die Länge (für Typ+Daten), einem Byte mit dem Typ (0x09) und anschließend dem Text im ASCII-Format.
Anschließend wird die CRC für die PDU berechnet (Initalwert 0x555555), womit das Paket vollständig ist. Vor dem Versenden muss für PDU + CRC noch das Whiteningverfahren durchgeführt werden (Initialwert abhängig von Kanal). Die dabei resultierenden Bits können direkt an den Modulator / den Sender geführt werden.
Dieser arbeitet (mindestens) auf einem der Adverstingknaäle (z.B. Kanal 37 auf 2402 MHz) mit einem Frequenzhub on 250 kHz. Der im Standard Vorgegebenen Mindestabstand für Advertisingpakete beträgt dabei 20ms + 0-10ms Zufallsintervall.

Umsetzung

(absolutely) notDeacon

Ich habe mich entschieden als ersten Schritt genau das Gegenteil des eignetlichen Plans zu machen: Den Beacon praktisch vollständig in Software umsetzen.
Ziel dabei ist zum Einen zu checken, ob meine Interpretation des Standards soweit korrekt ist und zum Anderen damit eine Referenzimplementierung zu schaffen, gegen die ich Teilschritte der eigentlichen Implementierung testen kann.
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.

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^^
Den vollständigen Code gibt es in meinem GitHub Repository.

Das Datenformat

#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));

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.
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

	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);

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.

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));
						
		}		
	}	
}

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:

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";
		
	}
}

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.
"Mischen" bedeutet hier anders als z.B. im HiFi-Bereich nicht das addieren, sondern das Multiplizieren der Signale (im Zeitbereich).
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

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);	
}

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 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.

Notdeacon crcerr.PNGNotdeacon crcok.PNGNotdeacon handy.png

Quellen

Verantwortlicher/Ansprechpartner

Karsten