Bluetooth LE是蓝牙4.0规范中引入的一项新技术。 除了名称外,它与蓝牙完全无关。它是为低功耗而设计的,设计表明了这一点。它不像真正的蓝牙那样,会按照精确的时间表进行跳频,而不管任何事情,LE都会在发送一定数量的数据包后进行跳频,因此无需唤醒即可保持运行时钟来知道下一跳的位置。实际上,LE允许设备在保持连接的同时,长时间完全关闭其无线电。这对于键盘和鼠标以及所有其他类似的东西来说非常棒。LE的另一个很酷的特性是,设备可以主动发送小块数据的广播。与真正的蓝牙不同,在现实世界中扫描设备可能是被动的——你只需在正确的频道上收听广播包,就能听到所有的广播。
LE的简单信道跳变行为意味着我们可能假装成没有正常蓝牙所需的复杂无线电的LE设备。 频率为2.4GHz,信道间隔为1MHz,调制为GFSK,数据速率为1MBps,前导码为10101010或01010101(基于第一个数据字节),并使用32位地址进行寻址。 哎呀,我们不知道可以做到所有这些的设备吗? 当然可以! 广受欢迎的北欧nRF24L01 +。 因此,让我们看一下LE需求与我们拥有的需求之间的区别:
LE的信道跳频行为意味着我们可以假装是一个LE设备,而不需要普通bluetoth所需要的复杂无线电。频率为2.4GHz,信道间隔为1MHz,调制为GFSK,数据速率为1MBps,前导码为10101010或01010101(基于第一个数据字节),使用32位地址进行寻址。
本文使用芯片nRF24L01+来实现。所以让我们来看看LE的需求和nRF24L01+之间的区别:
所以...我们不能接收连接,但不会丢失所有... BTLE允许未经请求的广播,因此我们仍然可以通过使它们向将收听的任何人广播数据来做一些很酷的事情。
让我们计算出我们能发送多少数据...在我们32字节的预算中:3字节:CRC,2字节:ADV_NONCONN_IND数据包头,6字节:MAC地址,剩下21字节的有效负载。 但是,此有效负载必须具有结构。 假设所需的头最少,如果我们不需要广播设备名称,则可以发送19个字节的数据。 如果需要广播设备名称,我们有17个字节可以在名称(在UTF-8中)和数据之间分割。如果我们想更好地遵守规范和广播设备属性,我们将有14个字节的名称和数据或16个字节的纯数据。
然后让我们整理细节……首先,BTLE和nRF24以相反的顺序在空中发送数据位,因此我们必须将所有位都反转。 其次,BTLE使用数据白化,而nRF24不使用数据白化,因此我们也需要手动进行。 最后,前面提到的24位CRC。
所有LE广播都发送到相同的“地址”:0x8E89BED6,也称为“bed6”。 当然,对我们来说,它将是位反转。 BTLE将CRC应用于整个有效负载,而不是地址。 白化应用于有效载荷和CRC。 因此,知道了这一点,就为我们提供了组装完整工作包所需的事件顺序。 广播包在3个通道上发送:37、38和39,分别为2.402HGz,2.426GHz和2.480GHz。 我们将在他们之间轮换,有条不紊地广播。
BTLE CRC在C语言中不太难实现,使用初始值0x555555,看起来像这样:
void btLeCrc(const uint8_t* data, uint8_t len, uint8_t* dst){
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;
if(t != (d & 1)){
dst[2] ^= 0x5B;
dst[1] ^= 0x06;
}
}
}
}
数据白化功能也不太复杂。 它是一种7位线性移位反馈样式,并通过等于(channelNum << 1)+ 1的值进行初始化。代码如下所示:
void btLeWhiten(uint8_t* data, uint8_t len, uint8_t whitenCoeff){
uint8_t m;
while(len--){
for(m = 1; m; m <<= 1){
if(whitenCoeff & 0x80){
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
}
因此,如果将所有这些都放在一起,最终将得到带有上述标头的数据包。 然后,我们使用上述CRC函数对其进行CRC处理。 然后,使用上面的btLeWhiten函数将其白化。 之后,我们将其发送。 让我们看看...是的,它有效。 运行BTLExplorer的iPad3显示我们的设备可见并且是BTLE设备。 凉! 一个警告:如果您不广播设备名称,则BTLExplorer将崩溃-这是它们的错误,因此请不要担心。
有效载荷数据格式是什么? 你可能会问。 好吧,数据是由块组成的,每个块由3部分组成,长度、类型、数据。
长度:包括类型+数据的长度
类型:用来表示数据的作用,比如为08或者09时代表数据部分属于设备名称
数据:不同类型代表不同意义的数据
我们一般只使用以下几个类型。
参考:https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/
完整的示例代码大致如下所示:
#include
#include
#include
#include
#define F_CPU 8000000
#include
#define PIN_CE 1 //Output
#define PIN_nCS 2 //Output
#define MY_MAC_0 0xEF
#define MY_MAC_1 0xFF
#define MY_MAC_2 0xC0
#define MY_MAC_3 0xAA
#define MY_MAC_4 0x18
#define MY_MAC_5 0x00
ISR(PCINT0_vect)
{
//useless
}
void btLeCrc(const uint8_t* data, uint8_t len, uint8_t* dst){
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;
if(t != (d & 1)){
dst[2] ^= 0x5B;
dst[1] ^= 0x06;
}
}
}
}
uint8_t swapbits(uint8_t a){
uint8 v = 0;
if(a & 0x80) v |= 0x01;
if(a & 0x40) v |= 0x02;
if(a & 0x20) v |= 0x04;
if(a & 0x10) v |= 0x08;
if(a & 0x08) v |= 0x10;
if(a & 0x04) v |= 0x20;
if(a & 0x02) v |= 0x40;
if(a & 0x01) v |= 0x80;
return v;
}
void btLeWhiten(uint8_t* data, uint8_t len, uint8_t whitenCoeff){
uint8_t m;
while(len--){
for(m = 1; m; m <<= 1){
if(whitenCoeff & 0x80){
whitenCoeff ^= 0x11;
(*data) ^= m;
}
whitenCoeff <<= 1;
}
data++;
}
}
static inline uint8_t btLeWhitenStart(uint8_t chan){
//the value we actually use is what BT'd use left shifted one...makes our life easier
return swapbits(chan) | 2;
}
void btLePacketEncode(uint8_t* packet, uint8_t len, uint8_t chan){
//length is of packet, including crc. pre-populate crc in packet with initial crc value!
uint8_t i, dataLen = len - 3;
btLeCrc(packet, dataLen, packet + dataLen);
for(i = 0; i < 3; i++, dataLen++) packet[dataLen] = swapbits(packet[dataLen]);
btLeWhiten(packet, len, btLeWhitenStart(chan));
for(i = 0; i < len; i++) packet[i] = swapbits(packet[i]);
}
uint8_t spi_byte(uint8_t byte){
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;
}
void nrf_cmd(uint8_t cmd, uint8_t data)
{
cbi(PORTB, PIN_nCS);
spi_byte(cmd);
spi_byte(data);
sbi(PORTB, PIN_nCS); //Deselect chip
}
void nrf_simplebyte(uint8_t cmd)
{
cbi(PORTB, PIN_nCS);
spi_byte(cmd);
sbi(PORTB, PIN_nCS);
}
void nrf_manybytes(uint8_t* data, uint8_t len){
cbi(PORTB, PIN_nCS);
do{
spi_byte(*data++);
}while(--len);
sbi(PORTB, PIN_nCS);
}
void fob_init (void)
{
DDRA = (uint8_t)~(1<<5);
DDRB = 0b00000110;
PORTA = 0b10001111;
cbi(PORTB, PIN_CE);
TCCR0B = (1<
另外上传了类似功能的demo
"嗅探和解码 NRF24L01+和低功耗蓝牙"https://download.csdn.net/download/dear_Wally/12529139
"国产2.4G芯片 XN297做蓝牙广播"https://download.csdn.net/download/dear_Wally/12529108