前言
经过分析,发现nrf24l01的无线频段和调制方式和蓝牙是相同的,都是2.4Ghz和高斯键控频移,由此产生了是否可以使用nrf24l01发送蓝牙数据的想法,在网络上搜索发现有人在Arduino上实现了发送蓝牙广播,由此确信使用nrf24l01发送蓝牙数据是可行的。
本文章参考了:http://www.github.com/floe/BTLE
一、硬件平台
为了方便,减少不必要的工作,本文使用正点原子的探索者开发板,并且在《nrf24l01无线通信实验》工程下做修改。
二、编写蓝牙兼容代码
修改发送数据函数为蓝牙兼容的形式:
其中主要的修改点是,修改同步地址为蓝牙广播的同步地址,由于nrf24l01的数据存储方式和蓝牙是相反的,所以要颠倒位序;然后不使能crc
void NRF24L01_TX_Mode(void)
{
NRF24L01_CE=0;
//发送节点地址4
u8 addr[4]={0x6B,0x7D,0x91,0x71};
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)addr,4);
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)addr,4);
//禁止通道自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x00);
//使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);
//禁止自动重发
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x00);
//设置RF通道为40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,2);
//设置TX发射参数,0db增益,1Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x07);
//配置基本工作模式的参数;PWR_UP,不启用CRC,接收模式,开启所有中断
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x06);
NRF24L01_CE=1;//CE为高,10us后启动发送
}
接下来仿照 http://www.github.com/floe/BTLE 中的ble.cpp文件编写数据帧构造的相关代码。
新建ble.c和ble.h文件,在ble.h中添加如下代码:
#include "sys.h"
// advertisement PDU
__packed struct btle_adv_pdu {
// packet header
uint8_t pdu_type; // PDU type
uint8_t pl_size; // payload size 负载大小,包括6字节的mac
// MAC address
uint8_t mac[6];
// payload (including 3 bytes for CRC)
uint8_t payload[24];
};
// payload chunk in advertisement PDU payload
__packed struct btle_pdu_chunk {
uint8_t size;
uint8_t type;
uint8_t data[];
};
typedef struct
{
struct btle_adv_pdu buffer;
char *name;
uint8_t current; // current channel index
}ble_struct;
//把无线模块初始化为蓝牙兼容的形式
void ble_begin( ble_struct *ble,char* _name ) ;
//发送广播数据
int ble_advertise( ble_struct *ble,uint8_t data_type, void* buf, uint8_t buflen ) ;
//改变信道
void ble_hopChannel(ble_struct *ble) ;
#endif
在ble.c中添加如下代码:
主要需要做的是构造广播数据帧,其中包括蓝牙的mac地址,蓝牙名称等,用户还可以添加其他数据比如传感器的温湿度等,最后还要交换所有数据的位序和“白化”操作
#include "ble.h"
#include "string.h"
#include "24l01.h"
// This is a rather convoluted hack to extract the month number from the build date in
// the __DATE__ macro using a small hash function + lookup table. Since all inputs are
// const, this can be fully resolved by the compiler and saves over 200 bytes of code.
#define month(m) month_lookup[ (( ((( (m[0] % 24) * 13) + m[1]) % 24) * 13) + m[2]) % 24 ]
const uint8_t month_lookup[24] = { 0,6,0,4,0,1,0,17,0,8,0,0,3,0,0,0,18,2,16,5,9,0,1,7 };
const uint8_t channel[3] = {37,38,39}; // logical BTLE channel number (37-39)
const uint8_t frequency[3] = { 2,26,80}; // physical frequency (2400+x MHz)
void ble_preparePacket(ble_struct *ble) ;
void ble_transmitPacket(ble_struct *ble) ;
void ble_whiten( ble_struct *ble,uint8_t len ) ;
void ble_swapbuf( ble_struct *ble,uint8_t len ) ;
void ble_crc( ble_struct *ble,uint8_t len, uint8_t* dst ) ;
//添加数据段,返回0,成功
int ble_addChunk(ble_struct *ble,uint8_t chunk_type, uint8_t buflen, const void* buf)
{
if (ble->buffer.pl_size + buflen + 2 > 21 + 6) // (buflen+2) is how much this chunk will take, 21 is payload size without crc and 6 is MAC size
return -1;
struct btle_pdu_chunk* chunk = (struct btle_pdu_chunk*) (ble->buffer.payload+ble->buffer.pl_size-6);
chunk->type = chunk_type;
for (uint8_t i = 0; i < buflen; i++)
chunk->data[i] = ((uint8_t*)buf)[i];
chunk->size = buflen + 1;
ble->buffer.pl_size += buflen + 2;
return 0;
}
void ble_hopChannel(ble_struct *ble) {
ble->current++;
if (ble->current >= sizeof(channel)) ble->current = 0;
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH, frequency[ble->current] );
}
//发送一个广播包
int ble_advertise( ble_struct *ble,uint8_t data_type, void* buf, uint8_t buflen ) {
ble_preparePacket(ble);
// add custom data, if applicable
if (buflen > 0) {
int success = ble_addChunk(ble,data_type, buflen, buf);
if (0!=success) {
return -1;
}
}
ble_transmitPacket(ble );
return 0;
}
void ble_preparePacket(ble_struct *ble)
{
// insert pseudo-random MAC address
ble->buffer.mac[0] = ((__TIME__[6]-0x30) << 4) | (__TIME__[7]-0x30);
ble->buffer.mac[1] = ((__TIME__[3]-0x30) << 4) | (__TIME__[4]-0x30);
ble->buffer.mac[2] = ((__TIME__[0]-0x30) << 4) | (__TIME__[1]-0x30);
ble->buffer.mac[3] = ((__DATE__[4]-0x30) << 4) | (__DATE__[5]-0x30);
ble->buffer.mac[4] = month(__DATE__);
ble->buffer.mac[5] = ((__DATE__[9]-0x30) << 4) | (__DATE__[10]-0x30) | 0xC0; // static random address should have two topmost bits set
//ble->buffer.pdu_type = 0x42; // PDU type: ADV_NONCONN_IND, TX address is random
ble->buffer.pdu_type = 0x02;
ble->buffer.pl_size = 6; //including MAC
// add device descriptor chunk
uint8_t flags = 0x05;
ble_addChunk(ble,0x01, 1, &flags);
// add "complete name" chunk
if (strlen(ble->name) > 0) {
ble_addChunk(ble,0x09, strlen(ble->name), ble->name);
}
}
void ble_transmitPacket(ble_struct *ble)
{
uint8_t pls = ble->buffer.pl_size - 6;
// calculate CRC over header+MAC+payload, append after payload
uint8_t* outbuf = (uint8_t*)&ble->buffer;
ble_crc( ble,pls+8, outbuf+pls+8);
// whiten header+MAC+payload+CRC, swap bit order
ble_whiten(ble, pls+11 );
ble_swapbuf( ble,pls+11 );
// flush buffers and send
//radio->stopListening();
//radio->write( outbuf, pls+11 );
NRF24L01_TxPacket(outbuf,32);
}
// change buffer contents to "wire bit order"
void ble_swapbuf( ble_struct *ble,uint8_t len ) {
uint8_t* buf = (uint8_t*)&ble->buffer;
while (len--) {
uint8_t a = *buf;
uint8_t 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;
*(buf++) = v;
}
}
// see BT Core Spec 4.0, Section 6.B.3.2
void ble_whiten( ble_struct *ble,uint8_t len ) {
uint8_t* buf = (uint8_t*)&ble->buffer;
// initialize LFSR with current channel, set bit 6
uint8_t lfsr = channel[ble->current] | 0x40;
while (len--) {
uint8_t res = 0;
// LFSR in "wire bit order"
for (uint8_t i = 1; i; i <<= 1) {
if (lfsr & 0x01) {
lfsr ^= 0x88;
res |= i;
}
lfsr >>= 1;
}
*(buf++) ^= res;
}
}
void ble_crc( ble_struct *ble,uint8_t len, uint8_t* dst ) {
uint8_t* buf = (uint8_t*)&ble->buffer;
// initialize 24-bit shift register in "wire bit order"
// dst[0] = bits 23-16, dst[1] = bits 15-8, dst[2] = bits 7-0
dst[0] = 0xAA;
dst[1] = 0xAA;
dst[2] = 0xAA;
while (len--) {
uint8_t d = *(buf++);
for (uint8_t i = 1; i; i <<= 1, d >>= 1) {
// save bit 23 (highest-value), left-shift the entire register by one
uint8_t t = dst[0] & 0x01; dst[0] >>= 1;
if (dst[1] & 0x01) dst[0] |= 0x80; dst[1] >>= 1;
if (dst[2] & 0x01) dst[1] |= 0x80; dst[2] >>= 1;
// if the bit just shifted out (former bit 23) and the incoming data
// bit are not equal (i.e. bit_out ^ bit_in == 1) => toggle tap bits
if (t != (d & 1)) {
// toggle register tap bits (=XOR with 1) according to CRC polynom
dst[2] ^= 0xDA; // 0b11011010 inv. = 0b01011011 ^= x^6+x^4+x^3+x+1
dst[1] ^= 0x60; // 0b01100000 inv. = 0b00000110 ^= x^10+x^9
}
}
}
}
void ble_begin( ble_struct *ble,char* _name )
{
ble->name = _name;
NRF24L01_CE=0;
//发送节点地址4
// u8 addr[4]={0x6B,0x7D,0x91,0x71};
u8 addr[4]={0x71,0x91,0x7D,0x6B};
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)addr,4);
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)addr,4);
//禁止通道自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x00);
//使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);
//禁止自动重发
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x00);
//设置RF通道为40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,frequency[ble->current]);
//设置TX发射参数,0db增益,1Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x07);
//配置基本工作模式的参数;PWR_UP,不启用CRC,接收模式,开启所有中断
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x06);
NRF24L01_CE=1;//CE为高,10us后启动发送
}
三、调用
在主函数中添加如下代码:
int index=0;
//初始化ble对象
ble_begin(&ble,"ble_chuan");
while(1)
{
//在屏幕上显示发送次数
sprintf (TEXT_Buffer,"Tx runed:%d",index);
LCD_ShowString(30,150,200,16,16,(u8 *)TEXT_Buffer);
//发送广播
ble_advertise(&ble,0xff,0,0);
//蓝牙广播信道有3个,这里切换广播信道
ble_hopChannel(&ble);
index++;
LED0=!LED0;
delay_ms(300);
};
四、现象
手机端搜索到如下设备:
其中“ble_chuan”和程序中设置的名称相同,实验成功。
五、总结
由于nrf24l01的数据fifo只有32字节,每次广播的数据非常有限,并且由于其没有同步地址匹配中断等细化的功能,并不能用来做蓝牙的无线收发器,因此实现蓝牙数据广播并不具有太大的使用意义,实现本程序更大的意义在于学习蓝牙协议。