Professional Documents
Culture Documents
%20Bluetooth%20LE%20fakery
Faking Bluetooth LE
Bluetooth LE is a new technology, introduced in the Bluetooth 4.0 spec. It has absolutely
nothing to do with bluetooth besides the name. Now that we have that out of the way, why is
it cool? Well, it was made for low power, and the design shows. Unlike real bluetooth that does
frequency hoppping on a precise schedule, regardless of anything, LE hops after some number
of packets are sent, and thus one does not need to be awake to keep a running clock to know
where to hop to next. In fact, LE allows a device to completely shut down its radio for large
periods of time while maintaining a connection. This makes it awesome for keyboards and
mice and all kinds of other such things. Another cool feature in LE is that devices can send
unsolicited broadcasts of small chunks of data. Unlike real bluetooth, scanning for devices in
the LE world can be passive - you just listen for advertisement packets on the right channels
and you hear all the advertisements.
The simple channel hopping behaviour of LE means that we can probably pretend to be
an LE device without the complex radio that normal bluetoth requires. The frequency is
2.4GHz, chanels are 1MHz apart, modulation is GFSK, datarate is 1MBps, preamble is
10101010 or 01010101 based on first data byte, and addressing is done using a 32-bit address.
Gee, don't we know a device that can do all that? Of course we do! The ever-popular Nordic
nRF24L01+. So let's look at the differences between what LE needs and what we have:
CRC: BTLE uses a 24-bit CRC that is not something nRF24 can do. Luckily for us,
nRF24 allows us to disable the CRC, and then we can send our own and check
received ones manually. Unpleasant? Sure, but doable.
Frequency Hopping: BTLE needs ut to be able to hop quickly - 150us or faster.
Sadly here nRF24 fails us. After every transmission it shuts down its PLL, and then
restarts it on every transmit or receive command. This takes 130us. We are left with
too little time to do anything here, sadly. Luckily this is not the major issue.
Packet Length: BTLE packets are of various sizes, up to 39 bytes of payload in fact.
But the one packet we would really want to support is the CONNECT_REQ packet,
which is in fact 34 bytes of payload + 3 of CRC, meaning we'd need to receive 37
bytes if we want to check CRCs, and even if we were willing to throw caution to the
wind and ignore them, we need to receive 34. nRF24 will not handle packets over 32
bytes. Sadly the last two bytes are kind of important (hop size and a part of the
channel map). This is where we lose.
So... we cannot accept connections, but not all is lost... BTLE allows unsolicited
advertisements, so we can still do some cool things by making them broadcast data to anyone
who'll listen. Let's work out just how much data we can send... Out of our 32-byte budget: 3 go
into CRC, 2 go into the ADV_NONCONN_IND packet header, and 6 go into the MAC
address, leaving us with 21 bytes of payload. This payload, however, must have structure.
Assuming minimum required headers, we can get away with sending 19 bytes of data if we do
not want our device to broadcast a name. If we do want a name, we have 17 bytes to split
between the name (in UTF-8) and our data. And if we want to comply with the spec better and
broadcast device attributes, we'll have 14 bytes to split between name and data or 16 bytes of
pure data - not too bad.
Let's sort out the details then... first of all, BTLE and nRF24 send data bits in the air in
opposite order, so we'll have to reverse all our bits. Second, BTLE uses data whitening, and
nRF24 does not, so we'll need to do that by hand too. Lastly, there is the previously-mentioned
24-bit CRC. All LE broadcasts get sent to the same "address": 0x8E89BED6, also known as
"bed six." Of course, for us it'll be bit-reversed. BTLE applies CRC to the whole payload but
not the address. Whitening is applied to the payload and the CRC. Knowing this, thus, gives us
the ordering of events needed to assemble a complete working packet. Advertisement packets
are sent on 3 channels: 37, 38, and 39, which are 2.402HGz, 2.426GHz, and 2.480GHz
respectively. We'll alternate between them, spewing our broadcast everywhere methodically.
BTLE CRC is not too hard to implement in C, uses the initial value of 0x555555, and
looks something like this:
uint8_t v, t, d;
while(len--){
d = *data++;
for(v = 0; v < 8; v++, d >>= 1){
t = dst[0] >> 7;
dst[0] <<= 1;
if(dst[1] & 0x80) dst[0] |= 1;
dst[1] <<= 1;
if(dst[2] & 0x80) dst[1] |= 1;
dst[2] <<= 1;
dst[2] ^= 0x5B;
dst[1] ^= 0x06;
}
}
}
}
The data whitening function is also not too complicated. It is a 7-bit linear-shift feedback
style and is initialized by the value that is equal to (channelNum << 1) + 1. The code goes
something like this:
while(len--){
whitenCoeff ^= 0x11;
(*data) ^= m;
}
whitenCoeff <<= 1;
}
data++;
}
}
struct adv_hdr{
uint8_t header; //we use 0x40 to say it is a non-connectable
undirected
//advertisement and address we're sending is random
(not assigned)
uint8_t dataLen; //length of following data (including MAC addr)
uint8_t MAC[6]; //the mac address
}
So if we lump all this together, we'll end up with a packet with the above header. We then
CRC it using the above CRC function. We then whiten it using the above whitening function.
After this we send it. Let's see... yup, it works. An iPad3 running BTLExplorer shows that our
device is visible and is a BTLE device. Cool! One caveat: if you do not broadcast a device
name, BTLExplorer will crash - this is their bug, so do not worry.
What is the payload data format? you may ask. Well, data is made of chunks, each of
which has a 2-byte header: length and type (in that order, length includes the length of the type
byte). Types you care about are:
2: Flags - length will be 2 and the lone byte's value you want is 5 - this means a
single-mode (not BTLE/BT combo) device in limited discovery mode.
8 or 9: Name - length is the length of the name in UTF-8 without NULL-termination.
8 is for "shortened name" and 9 is for "complete name."
255: Custom data. This is where you can shove your custom data. On iPhone you'll
have complete access to this.
Why all this? Well, if you know a simpler way for an embedded project to communicate
data to an iPhone, let me know. WiFi is power hungry and messy. Real bluetooth is locked
down in iPhone, as are serial ports. This method works. If and when Android gets a BTLE API,
I am sure broadcast data will be available to you too - it just makes sense.
Future work: I just got some non-nordic 2.4GHz parts that support packets up to 64 bytes
and can keep their PPLs on, meaning that a full BTLE stack may be possible on them. I am
working on this as you read this.
All the code as well as the research that went into this and is published here is under this
license: you may use it in any way you please if and only if it is for non-commercial purposes,
you must provide a link to this page as well. Any commercial use must be discussed with me.
Full sample code, that will run on the nordic-fob from Sparkfun looks approximately like
this:
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#define F_CPU 8000000
#include <avr/delay.h>
#define PIN_CE 1 //Output
#define PIN_nCS 2 //Output
ISR(PCINT0_vect)
{
//useless
}
uint8_t v, t, d;
while(len--){
d = *data++;
for(v = 0; v < 8; v++, d >>= 1){
t = dst[0] >> 7;
dst[0] <<= 1;
if(dst[1] & 0x80) dst[0] |= 1;
dst[1] <<= 1;
if(dst[2] & 0x80) dst[1] |= 1;
dst[2] <<= 1;
dst[2] ^= 0x5B;
dst[1] ^= 0x06;
}
}
}
}
uint8 v = 0;
return v;
}
void btLeWhiten(uint8_t* data, uint8_t len, uint8_t whitenCoeff){
uint8_t m;
while(len--){
whitenCoeff ^= 0x11;
(*data) ^= m;
}
whitenCoeff <<= 1;
}
data++;
}
}
return swapbits(chan) | 2;
}
uint8_t i = 0;
do{
PORTB &=~ (uint8_t)(1 << 6);
if(byte & 0x80) PORTB |= (uint8_t)(1 << 6);
CLK |= (uint8_t)(1 << 4);
byte <<= 1;
if(PINA & (uint8_t)32) byte++;
CLK &=~ (uint8_t)(1 << 4);
}while(--i);
return byte;
}
cbi(PORTB, PIN_nCS);
do{
spi_byte(*data++);
}while(--len);
sbi(PORTB, PIN_nCS);
}
fob_init();
DDRA |= 4;
PORTA |= 4;
while(1){
L = 0;
buf[L++] = MY_MAC_0;
buf[L++] = MY_MAC_1;
buf[L++] = MY_MAC_2;
buf[L++] = MY_MAC_3;
buf[L++] = MY_MAC_4;
buf[L++] = MY_MAC_5;
buf[L++] = 7;
buf[L++] = 0x08;
buf[L++] = 'n';
buf[L++] = 'R';
buf[L++] = 'F';
buf[L++] = ' ';
buf[L++] = 'L';
buf[L++] = 'E';
if(++ch == sizeof(chRf)) ch = 0;
nrf_cmd(0x25, chRf[ch]);
nrf_cmd(0x27, 0x6E); //clear flags
btLePacketEncode(buf, L, chLe[ch]);
cbi(PORTB, PIN_nCS);
spi_byte(0xA0);
for(i = 0 ; i < L ; i++) spi_byte(buf[i]);
sbi(PORTB, PIN_nCS);
My favorite spec in the BLE specifications was the free space in the advertising data but all the
existing implementations use a very heavy setup function to set advertising data presumably
once.
I want to change advertising data based on sensor input, etc, and this 'bit banging' method seems
really powerful for that.
Thanks,
Jacob
Dmitry Grinberg
Mon, 25 Feb 2013 19:02:43 +0000
@Jacob: Glad you like it:)
This looks really interesting. I really want to see how this pans out with the final hardware. Are
you close to getting this up and running?
You say the nRF24 will not handle packets over 32 bytes, but how is that device normally used
if it cannot accept connections?
Cheers,
Baza.
i have another device that can do better packet length that i am playing with (but i am busy so
it is not moving quickly)
Hugh O'Brien
Sat, 20 Apr 2013 23:54:04 +0000
This is excellent, and I thought bit-banging the HCI was tricky.
Vladimir
Sat, 21 Sep 2013 12:18:38 +0000
Hi! This looks really good.
Can we simultaneously receive the data of the same type? What's the best way of doing that?
phonytony
Sat, 21 Sep 2013 14:19:37 +0000
Any progress on the alternative transceiver?
Dmitry Grinberg
Sat, 21 Sep 2013 15:13:59 +0000
@pgonytony: the chip was A7125:
http://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_easy_radio_A7125
Theo
Sat, 21 Sep 2013 18:30:01 +0000
You say there isnt an api for android, but isnt this it
http://developer.android.com/guide/topics/connectivity/bluetooth-le.html and there seems to
be a similar app on the play store: ble explorer. Ive had a go trying your code out but im not
getting anything with that app, however I did have to rewrite a fair piece of it to run it on a
different microcontroller so im not sure if its my modifications or android.
Nik G
Sun, 22 Sep 2013 00:01:14 +0000
Mind if I wrap your code up in an Arduino library and post it? I'll make sure and give you full
credit.
Gr0b
Mon, 23 Sep 2013 03:58:59 +0000
This is awesome, I have been using these little Radios for years now and was looking to start
using BLE radios as well but I can add BLE without changing my hardware.
Dmitry Grinberg
Mon, 23 Sep 2013 16:34:01 +0000
@Theo:
at time of writing there was no public LE API for android
@Nik G:
have at it
Ralph Doncaster
Mon, 23 Sep 2013 19:14:30 +0000
I was confused by the comment that 5-byte addresses are used, when it is actually 4.
nrf_cmd(0x23, 0x02); //5-byte address
0x02 sets 4-byte addresses, and 0x03 would set a 5-byte address.
James
Thu, 26 Sep 2013 03:11:20 +0000
i just wanted to confirm which way the data goes? From the module to phone, or from phone
to module? or both?
Paul
Sun, 29 Sep 2013 21:17:45 +0000
The A7125 doesn't seem to support GFSK.
There are some other transceivers from that manufacturer that could work, such as the A7105
Mark Schwab
Sun, 06 Oct 2013 15:58:48 +0000
Thank You
I'm developing on the CC2541 and was struggling a little with the BLE packet structure. Your
code helped a lot. The spec had me mixed up with endiannesss
floe
Wed, 09 Oct 2013 10:54:10 +0000
Very awesome. Looking forward to trying this on my Arduino. BTW, what license is your code
published under?
Guan Yang
Sat, 12 Oct 2013 14:51:23 +0000
Do you think the Analog Devices ADF7242 could do this too?
Moustafa Berg
Sat, 12 Oct 2013 17:44:07 +0000
Great work !, but i had one comment on your website's styling, the background makes the text
unreadable !, so if you can try to find a workaround for that, it would be much clearer for people
to read, my eyes literally hurt trying to read through the whole post !
Did you have any luck using a transceiver with a 64 byte TX buffer? I was thinking of creating
a iBeacon using your technique, but 5 more bytes are needed. Here is the format:
https://github.com/sandeepmistry/node-bleacon#ibeacon-advertisement-format
Thanks.
floe
Tue, 05 Nov 2013 12:42:27 +0000
I've wrapped/rewritten Dmitry's code into an Arduino library: https://github.com/floe/BTLE
Feedback welcome!
wiktor
Sat, 09 Nov 2013 17:04:27 +0000
nice work Dimitry!
floe, i was about to do same thing before i saw your comment. thanks for the library, it saved
me a lot of work, however i cant manage to get the data on android - it only sees the name.
Can a Bluetooth 2.1+EDR module wake a Bluetooth 4.0 Low Energy+Dual Mode (BLE+EDR)
module up, if that is in sleeping mode, if it tries to connect to the BLE+EDR? And can the
2.1+EDR module see the name of BLE+EDR in sleep mode at all?
BLE works together with an other BLE device only, but a BLE+EDR Dual Mode works with
Classic BT (2.1+EDR) too. My question would be: Could a Classic BT wake up a BLE+EDR
Dual Mode device if it is in sleeping mode by trying to connect to it?
CyberExplorer
Tue, 21 Jan 2014 20:17:19 +0000
Thanks for the information. It helped me with my NRF / BTLE sniffing project
http://blog.cyberexplorer.me/2014/01/sniffing-and-decoding-nrf24l01-and.html
floe
Thu, 30 Jan 2014 07:44:39 +0000
Just a quick note: I've found that Android can be very, very picky about the packet timing. E.g.
when running identical code from my library on an ATmega328 and a 32u4, only the older one
(328) is picked up by Android devices. An iPhone happily shows both devices... not yet sure
what the root cause is.
I ran the code on an Arduino Leonardo and my Android device picks packages without any
problems.
Anyway, I had to change pins when connecting my NRF24 to Leonardo as stated below:
nrf commands
0x20,0x21,0x22 ...
etc dont exists replaced with
0x00, 0x01,0x02
uint8_t v, t, d;
while(len--){
d = *data++;
for(v = 0; v < 8; v++, d >>= 1){
t = dst[0] >> 7;
dst[0] <<= 1;
if(dst[1] & 0x80) dst[0] |= 1;
dst[1] <<= 1;
if(dst[2] & 0x80) dst[1] |= 1;
dst[2] <<= 1;
dst[2] ^= 0x5B;
dst[1] ^= 0x06;
}
}
}
}
return v;
}
uint8_t m;
while(len--){
whitenCoeff ^= 0x11;
(*data) ^= m;
}
whitenCoeff <<= 1;
}
data++;
}
}
return swapbits(chan) | 2;
}
// uint8_t i = 0;
//
// do{
// digitalWrite(11, LOW); //PORTB &=~ (uint8_t)(1 << 6);
// if(byte & 0x80) digitalWrite(11, HIGH); //PORTB |= (uint8_t)(1 << 6);
// digitalWrite(13, HIGH); //CLK |= (uint8_t)(1 << 4);
// byte <<= 1;
//// if(PINA & (uint8_t)32) byte++;
//// delay(10);
// digitalWrite(13, LOW);//CLK &=~ (uint8_t)(1 << 4);
//
// }while(--i);
// Serial.println("SPI BYTE");
shiftOut(11, 13, MSBFIRST, byte);
return byte;
}
spi_byte(*data++);
}while(--len);
digitalWrite(PIN_nCS, HIGH); // sbi(PORTB, PIN_nCS);
}
//void fob_init (void)
//{
// DDRA = (uint8_t)~(1<<5);
// DDRB = 0b00000110;
// PORTA = 0b10001111;
// cbi(PORTB, PIN_CE);
// TCCR0B = (1<<CS00);
// MCUCR = (1<<SM1)|(1<<SE);
// sei();
//}
void setup() {
// set the digital pin as output:
pinMode(PIN_nCS, OUTPUT);
pinMode(PIN_CE, OUTPUT);
pinMode(11, OUTPUT);
pinMode(13, OUTPUT);
Serial.begin(9600);
Serial.println("HELLO");
// fob_init();
// DDRA |= 4;
// PORTA |= 4;
while(1){
float h = random() ;
float t = random();
L = 0;
buf[L++] = MY_MAC_0;
buf[L++] = MY_MAC_1;
buf[L++] = MY_MAC_2;
buf[L++] = MY_MAC_3;
buf[L++] = MY_MAC_4;
buf[L++] = MY_MAC_5;
buf[L++] = 7;// + 8;
buf[L++] = 0x08;
buf[L++] = 'n';
buf[L++] = 'R';
buf[L++] = 'F';
buf[L++] = ' ';
buf[L++] = 'L';
buf[L++] = 'E';
buf[L++] = 9;
buf[L++] = 0xff;
memcpy(&buf[L], &h, 4);
L += 4;
memcpy(&buf[L], &t, 4);
L += 4;
//if(++ch == sizeof(chRf)) ch = 0;
nrf_cmd(0x05, chRf[ch]);
nrf_cmd(0x07, 0x7E); //clear flags
btLePacketEncode(buf, L, chLe[ch]);
// return 0;
}
Nataraja
Thu, 27 Feb 2014 16:37:36 +0000
Can anyone tell me where I'm going wrong ?
and how is everyone testing it ?
I mean which App are you using on Android OS
Thanks :)
Nataraja G
Thu, 27 Feb 2014 16:39:34 +0000
inline with my comments
even this App is not able to read the broadcast messages from Arduino
eric
Fri, 28 Mar 2014 23:05:38 +0000
Hi this is great! Can you email me about a semi commercial use of this? I'd like to transmit
sensor data in the payload as well. Thanks!
Andre Luiz
Fri, 02 May 2014 04:52:00 +0000
Hey Dima,
Nice job there I wanted to have a skype talk with you, I am a CTO of a Ecosystem mobile
company I have an interesting project to discuss with you
Regards,
Javier
Wed, 07 May 2014 02:15:55 +0000
Hello,
and yours and I was wondering if it's possible to send and receive BLE between an Arduino
and an Attiny85. Which library should I use? Floe's BLTE library with the arduino-nrf24l01
(https://github.com/stanleyseow/arduino-nrf24l01) or just one should work?
Thanks in advance
jamesk
Wed, 04 Mar 2015 21:44:48 +0000
anyone know if this still works? I can't see my NRF24L01 on an android 4.4 phone. Tried all
variations of code mentioned here in comments, can't tell if its an android limitation.
John S.
Mon, 30 Mar 2015 21:14:23 +0000
It's awesome to be able to do this with any radio, but isn't it a bit too much work? Can you hack
BLE like this to do iBeacons? That's what I'm trying to do with the information <a
href="http://www.argenox.com/bluetooth-low-energy-ble-v4-0-development/library/">here
</a>
saleem
Sun, 19 Apr 2015 00:46:20 +0000
may be...cc2550 or cc3000 work good for integrating full BT stack
A. Misbah
Fri, 28 Aug 2015 19:35:57 +0000
Hello Dmitry,
Thanks.
John Doe
Sat, 27 Feb 2016 15:10:25 +0000
I know this is years later,but i live in a remote part of the world and still can't get access to a
beacon.This was exactly what i needed.You're brilliant to have thought of it so long ago:)
Thanks to you,i now have a solution.
Divyansh Lohia
Wed, 15 Jun 2016 08:10:05 +0000
with reference to blog. i had a query where is the actual data that we want to transmit.
Tulio
Thu, 23 Jun 2016 13:31:21 +0000
Excellent postage!!! How can I receive Bluetooth Packets with the NRF24L01?
Matteo
Wed, 27 Jul 2016 13:02:31 +0000
Excellent work!! :)
But Can I ask a question, How can nrf24l01 send data packages to my smartphone (My goal is
that my mobile phone can detect this beacon and there should be on the display "This is a
Ibeacon device",
Dan
Dedy Yasriady
Mon, 29 Aug 2016 03:46:03 +0000
Hi Dmitry,
Rgrds/Ddy