http://blog.chinaunix.net/uid-28852942-id-5745469.html
理论部分主要介绍了BLE广播包的 数据包格式,知道了数据包格式只需要按照格式要求设置数据包然后发送,那么BLE扫描者就能将这个数据包解析成ble广播包了。
这里我们用51822的radio来实现ble的广播包。
下图是51822空中包的格式。
Preamble: 该部分会根据接入地址而自动设置,不需要我们去设置
ADDRESS:由BASE和PREFIX组成,就是前面理论部分说的接入地址,对于广播 信道的数据包来说,接入地址总是0x8E89BED6。注意这个不是ble的广播地 址,广播地址是在广播数据负载中的,而且是6字节,这里的接入地址是4 字节
S0,LENGTH,S1,PAYLOAD:这四个部分组成了理论部分介绍的 PDU。S0,LENGTH,S1这三个部分是可选的。理论部分介绍过PDU有2部分组成,2字节的header和payload所以我们可以使用S0,LENGTH来作为header,不使用s1,然后payload刚好就作为payload。 当然你也可以S0,LENGTH,S1一个都不使用,是使用payload,那么就将payload的前两字节按照理论部分中的2字节header设置,剩下的当做应用负载payload。
CRC:我们需要设置一下 CRC的字节数,以及生成式,并且使之值计算的部分不包括前导和ADDRESS部分。
首先设置接入地址ADDRESS,因为广播数据是在广播信道中发送的,所以使用的是固定的接入地址0x8E89BED6. 51822有8个逻辑地址0-7,并且8个逻辑地址对应的实际地址可以设置,对应关系如下。也就是通过设置BASE0,BASE1,和PREFIX0,PREFIX1,四个寄存器,我们能分别设置8个逻辑地址的实际地址。
因为这里用的是广播信道的固定地址,所以我们将 8个逻辑地址的实际地址全部都设置成0x8E89BED6,然后设置发送地址为 逻辑地址0就行了。因为数据发送是LSByte先发送,而51822发送ADDRESS是先发送BASE再发送PREFIX,所以我们需要将PREFIX设置成高位字节0x8E,低位3字节设置到BASE中。
NRF_RADIO->BASE0 = (0x89BED600);
NRF_RADIO->BASE1 = (0x89BED600);
NRF_RADIO->PREFIX0 = 0x8E8E8E8E;
NRF_RADIO->PREFIX1 = 0x8E8E8E8E;
NRF_RADIO->TXADDRESS = 0; //使用逻辑地址0
然后是理论中介绍的PDU部分的设置,即51822中的S0,LENGTH,S1,PAYLOAD,我们使用S0,LENGTH来当做header,PAYLOAD就是PDU中的payload.并且设置S0,LENGTH都为1字节, 然后设置BLE可以发送的最大应用数据 (51822发送的包组成中的payload的长度)为37字节,因为理论部分说过PDU为2-39字节,2为2字节头,所以payload最长为37字节。并且设置接入地址的长度,上面已经设置了为4字节。设置发送顺序为LSB(规范要求)。
然后使能白化功能,白化是为了将原始信息中转换为高度随机的Bit序列,避免出现太长连续的bit0或bit 1从而导致接收出错。是规范要求
设置如下:
//8bit长度的LENGTH 1字节长度的S0,不需要s1,因为广播格式里面负载数 //据前面只需要2字节头,一个是报头,一个是长度。
NRF_RADIO->PCNF0 = (8<<0) | (1<<8);
//最大长度37,没有静态长度,基础地址为3字节,所以加一字节头后为四 //字节,就是蓝牙规范中接入地址.字节序为小端(CRC不在这里设置)
//使能数据白化
NRF_RADIO->PCNF1 = (37<<0) | (3<<16) | (1<<25) ;
//白化0x25; 初始值由报文所在链路层信道号决定,我们在37好广播信道上广播
NRF_RADIO->DATAWHITEIV = 0x25;
对于应用负载payload,理论部分说过我们使用 不可连接广播,
定义一个数组用来存放将要广播的数据,然后将数据指针指设置为改buff地址
static uint8_t adv_array[31] = {0};
NRF_RADIO->PACKETPTR = (uint32_t)&adv_array[0];
然后设置广播应用数据,如下函数所示
//广播地址
uint8_t device_add[6]={0xFF,0x01,0x02,0x03,0x04,0xff};
//广播数据,第一个0x01为flag设置成只支持BLE,
//第二个0x09为设备名,名字随便写的
uint8_t adv_data[10] = {0x02,0x01,0x04, 0x06,0x09,0x4e,0x6f,0x48,0x52,0x3d};
void set_advdata(void){
//adv_array的前两字节为 header 即S0和LENGTH
//PDU Type设置为ADV_NONCONN_IND,如果设置成普通广播的话,手机可能 //会发扫描包,因为这里没有做扫描回应,手机就会过滤该设备,导致手机 //搜不到设备。
adv_array[0] = 2;
adv_array[1] = 0;//最后再计算长度
memcpy(adv_array+2, device_add, 6);
memcpy(adv_array+2+6, adv_data, sizeof(adv_data));
adv_array[1] = 6+sizeof(adv_data);
}
然后需要将 radio的发送指针寄存器赋值为 adv_array.那么发送的时候就会自动将这个数组里的数据发送出去了
NRF_RADIO->PACKETPTR = (uint32_t)&adv_array[0];
对于CRC,需要设置其字节数,生成式,和初始值。设置如下
//3字节crc,计算不包括接入地址部分和前导部分
NRF_RADIO->CRCCNF = (3<<0) | (1<<8);
//crc多项式为 x^24+x^10+x^9+x^6+x^4+x^3+x^1+x^0
NRF_RADIO->CRCPOLY = 0x100065b
//广播信道的数据包中crc初始值为0x555555
NRF_RADIO->CRCINIT = 0x555555;
这个里 BLE广播相关的规范设置都设置完了。
我们还需要设置一下,广播的信道。我们在37号广播信道上广播。
设置一下发射功率,以及模式选择为Ble_1Mbit
//链路层信道编号 37:2402MHz, 38:2426MHz, 39:2480MHz
NRF_RADIO->FREQUENCY = 2;
NRF_RADIO->TXPOWER = 0x04;
NRF_RADIO->MODE = 0x03; //ble_1Mbit
另外根据手册说明 还有一个校准值的设置,如下图手册中的描述
NRF_RADIO->OVERRIDE4 = 1<<31;
NRF_RADIO->OVERRIDE0 = NRF_FICR->BLE_1MBIT[0];
NRF_RADIO->OVERRIDE1 = NRF_FICR->BLE_1MBIT[1];
NRF_RADIO->OVERRIDE2 = NRF_FICR->BLE_1MBIT[2];
NRF_RADIO->OVERRIDE3 = NRF_FICR->BLE_1MBIT[3];
NRF_RADIO->OVERRIDE4 = NRF_FICR->BLE_1MBIT[4];
实际使用中,我查看了FICR中的OVERRIDEEN 的值,两个指示位都为0,应该是要用FICR中的校准值覆盖RADIO中的校准值,不过代码实现中我屏蔽了设置也能收到广播。
最后就是发送数据的实现了
函数实现很简单,直接启动就可以了,radio会自动将上面设置的NRF_RADIO->PACKETPTR指向的数组数据发送出去
void send_data(void){
NRF_RADIO->EVENTS_READY = 0;
NRF_RADIO->TASKS_TXEN = 1; //启动发送使能
while(NRF_RADIO->EVENTS_READY == 0){} //等待准备好
NRF_RADIO->EVENTS_END = 0;
NRF_RADIO->TASKS_START = 1; //开始发送
while(NRF_RADIO->EVENTS_END == 0)//等待发送完成
NRF_RADIO->EVENTS_DISABLED = 0;
NRF_RADIO->TASKS_DISABLE = 1;
while(NRF_RADIO->EVENTS_DISABLED == 0){}//等待停止完成
}
下面贴出整体代码
#include "nrf51.h"
#include "nrf_gpio.h"
#include <stdio.h>
#include <string.h>
#include "nrf_delay.h"
uint8_t adv_data[10] = {0x02,0x01,0x04, 0x06,0x09,0x4e,0x6f,0x48,0x52,0x3d};
uint8_t device_add[6]={0xFF,0x01,0x02,0x03,0x05,0xff};
static uint8_t adv_array[37] = {0};
void init_clock(void){
NRF_CLOCK->XTALFREQ = 0xff; //16M
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0){
} //等待启振完成
}
void radio_init(void){
//这里设置8个逻辑地址的实际地址,因为我们只是做广播,所以把全部地址都设置成0x8E89BED6,
NRF_RADIO->BASE0 = (0x89BED600);
NRF_RADIO->BASE1 = (0x89BED600);
NRF_RADIO->PREFIX0 = 0x8E8E8E8E;
NRF_RADIO->PREFIX1 = 0x8E8E8E8E;
NRF_RADIO->TXADDRESS = 0; //使用逻辑地址0
NRF_RADIO->CRCCNF = (3<<0) | (1<<8); //3字节crc,计算不包括接入地址部分
NRF_RADIO->CRCPOLY = 0x100065b;//crc多项式为 x^24+x^10+x^9+x^6+x^4+x^3+x^1+x^0
NRF_RADIO->CRCINIT = 0x555555; //广播信道的数据包中crc初始值为0x555555
NRF_RADIO->PACKETPTR = (uint32_t)&adv_array[0];
NRF_RADIO->FREQUENCY = 2; //链路层信道编号 37:2402MHz, 38:2426MHz, 39:2480MHz
NRF_RADIO->TXPOWER = 0x04;
NRF_RADIO->MODE = 0x03; //ble_1Mbit
//8bit长度的LENGTH 1字节长度的S0,不需要s1,因为广播格式里面负载数 据前面只需要2字节头,一个是报头,一个是长度。
NRF_RADIO->PCNF0 = (8<<0) | (1<<8);
//payload最大长度37,没有静态长度,基础地址为3字节,所以加一字节头后为四字节,就是蓝牙规范中接入地址.字节序为小端(CRC不在这里设置)
//使能数据白化
NRF_RADIO->PCNF1 = (31<<0) | (3<<16) | (1<<25) ;
NRF_RADIO->DATAWHITEIV = 0x25;//0x25; //初始值由报文所在链路层信道号决定,这里为37
NRF_RADIO->OVERRIDE4 = 1<<31;
NRF_RADIO->OVERRIDE0 = NRF_FICR->BLE_1MBIT[0];
NRF_RADIO->OVERRIDE1 = NRF_FICR->BLE_1MBIT[1];
NRF_RADIO->OVERRIDE2 = NRF_FICR->BLE_1MBIT[2];
NRF_RADIO->OVERRIDE3 = NRF_FICR->BLE_1MBIT[3];
NRF_RADIO->OVERRIDE4 = NRF_FICR->BLE_1MBIT[4];
}
void set_advdata(void){
adv_array[0] = 2; //PDU Type为ADV_NONCONN_IND,如果设置成普通广播的话,手机可能会发扫描包,因为这里没有做扫描回应,手机就会过滤该设备,导致手机搜不到设备。
adv_array[1] = 0;//最后再计算长度
memcpy(adv_array+2, device_add, 6);
memcpy(adv_array+2+6, adv_data, sizeof(adv_data));
adv_array[1] = 6+sizeof(adv_data);
}
void send_data(void){
NRF_RADIO->EVENTS_READY = 0;
NRF_RADIO->TASKS_TXEN = 1;
while(NRF_RADIO->EVENTS_READY == 0){} //等待准备好
NRF_RADIO->EVENTS_END = 0;
NRF_RADIO->TASKS_START = 1;
while(NRF_RADIO->EVENTS_END == 0)//等待发送完成
NRF_RADIO->EVENTS_DISABLED = 0;
NRF_RADIO->TASKS_DISABLE = 1;
while(NRF_RADIO->EVENTS_DISABLED == 0){}//等待停止完成
}
int main(void){
uint32_t data;
init_clock();
radio_init();
set_advdata();
nrf_gpio_cfg_output(22);
nrf_gpio_pin_clear(22);
while(1){
nrf_delay_ms(50);
send_data();
}
return 0;
}