3127|2

1372

帖子

2

TA的资源

五彩晶圆(初级)

楼主
 

BLE入门谈:从空中数据收发理解BLE(下) [复制链接]

  在使用带BLE功能的MCU进行应用开发的时候,需要先熟悉BLEAPI. 然而,各厂家的BLE API风格差异很大,要比不同器件平台硬件驱动库HAL之间的差别更大。底层无线电部分的硬件,各家自有独立的设计(硬件寄存器也不一定开放),况且BLE协议栈有很大一部分是软件实现,它不光涉及无线电部分,还需要定时器和中断管理、电源管理,甚至用到动态内存分配。于是要用BLE通信,协议栈部分几十上百kB的代码占用是很常见的(有的平台把API实现放到ROM里能省部分),但难处在于不容易预测它的软件行为,如一个API调用的执行时间、什么时候会用回调函数、什么时候需要切换低功耗模式等等。每当接触一个新的BLE MCU平台时,对BLE API的学习时间要远多于GPIOUART这些基础硬件。如果对BLE技术缺乏认识,学习这些API更容易一头雾水。

  BLE协议栈包含的内容太多了,一下弄明白太难。作为MCU应用开发,又不一定需要了解那么多,只要能实现需要的数据通信就够了。跟手机用BLE通信会麻烦一点,但如果是MCUMCU之间通信呢?用过NRF24L01吗?它的空中数据包和BLE的数据包很相似,因为协议简单了,没有BLEProfile, Service那些概念,MCU工程师友好很多。

 

  BLE应用如果只做一个beacon的话,就是只管定期发出数据,不需要建立连接的那种,其实是用不着协议栈的,甚至可能BLE API都不用到——这么说是不是一下子简单了?比如,我只需要定时广播一个温度信息,真没必要那么复杂啊。理解了BLE的数据包,就可以用不复杂的办法来做。

  这还有一个条件,就是能直接访问MCU上的无线电部分硬件:得有一个开放的硬件环境,有手册。本帖将用nRF51822来演示怎么直接操作硬件进行数据包的收发。nRF51822是比较老的BLE MCU,很容易从拆机的手环类电路板上找到,它后一代的nRF52xxx系列性能更好,无线部分硬件变化不大。除了nRF51xxx之外,看手里的板子能不能直接操作无线电部分硬件,就查参考手册看对应有没有详细的寄存器描述。刚结束的RSL10大赛用的板子也是可以玩的。

 

  下面是nRF51xxx手册中RADIO部分的硬件结构框图:

  接收和发送部分大致是独立的,但不能同时工作,就是半双工的意思。要发送的数据包存放在RAM中,硬件通过DMA自动读取,然后会加上地址、CRC、同步头等,并经过whitening步骤,然后用GFSK调制发送出去。接收过程是类似的,硬件通过包头检测、地址匹配、CRC校验过程筛选合法的数据包,由DMA写到RAM中指定的地址。

  nRF51822支持的数据包格式是这样的:

  这和BLE spec中基础数据包格式是兼容的(不然怎么支持BLE),所以我们将它配置成BLE的格式,就可以直接收发数据了。

  Preamble部分是0/1交错的同步码,0xAA或者0x55,取决于地址部分的LSB(最先发送的那一bit),硬件负责。

  地址部分,nRF51822的地址长度可以是3~5字节,分被BASEPREFIX两部分。BLEAccess Address4字节,因此设置BASE长度为3字节。

  接下来的S0LENGTHS1字段是可选的(长度可以设成0),如果用了,则需要看成BLE数据包的PDU的一部分。和后面的PAYLOAD部分一起组成PDU.

  最后CRC部分由硬件负责,需要设置为24-bit, 要按照BLE要求设置。

 

  先试验能否从空中捕捉到BLE的数据包。需要提供给RADIO硬件的参数还有:(1)信道,(2)地址,(3)包长度。关于信道,为了捕捉advertising类型的包,可以设置成373839信道当中的一个。设成其它信道捕捉连接数据包,除了要根据跳频算法不断更改信道外,还需要知道Access Address才可以。BLE 373839信道使用固定的Access Address: 0x8E89BED6, 但建立连接后用的Access Address是主设备随机生成的,在CONNECT_REQ包中提供给从设备。包长度在BLEPDU的第2个字节,也就是把上面的S0字段长度设置为1字节后,LENGTH字段就可以对应BLE PDU长度。nRF51822RADIO使用LENGTH字段的信息(接收时来自空中数据,发送时来自RAM数据)来决定收发数据长度,不然就只能采用固定长度了。

  为了接收37信道(中心频率2402MHz)的advertising类型数据包,用这样的配置:

NRF_RADIO->RXADDRESSES = 1; // enable address 0

NRF_RADIO->FREQUENCY = 2; // 2402MHz, CH37
NRF_RADIO->DATAWHITEIV = 37;

NRF_RADIO->MODE = (RADIO_MODE_MODE_Ble_1Mbit << RADIO_MODE_MODE_Pos);

NRF_RADIO->PREFIX0 = 0x8E;
NRF_RADIO->BASE0 = 0x89BED600;

// LFLEN=6 bits, S0LEN=1Byte, S1LEN=2bit
NRF_RADIO->PCNF0 = 0x00020106;
// STATLEN=6, MAXLEN=37, BALEN=3, ENDIAN=0 (little), WHITEEN=1
NRF_RADIO->PCNF1 = 0x02030025;

NRF_RADIO->CRCCNF = 0x103; // only PDU, 3 octets
NRF_RADIO->CRCINIT = 0x555555; // for advertising packet
NRF_RADIO->CRCPOLY = 0x100065b;

// set receive buffer
NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf;

  启动接收过程或发送过程要通过nRF51task型寄存器。先看下RADIO部分的状态转移图:

  在DISABLED状态通过TXENRXEN task启动硬件,到TXIDLERXDILE的准备状态,然后用START来进行一次传输。从接收切换到发送,以及从发送切换到接收,必须先转回DISABLED状态。

  在接收状态下,硬件会监听指定地址的数据包,接收完成后转到RXIDLE状态,并产生END event.

  当收到END event时,表示收到了一个数据包(地址匹配有效),然后可以访问CRCSTATUS寄存器判断CRC校验是否正确。若CRC有错,可能是数据包被干扰破坏,或者格式不正确。接收数据包的S0LENGTHS1PAYLOAD字段存放到RAM中,稍有变化的是LENGTHS1字段都被扩展成了字节存储。

 

  我写了一个循环来持续接收数据包,进行37信道的监听。使用双缓冲区轮流存放收到的数据包,以便一边解析数据一边接收。

	for(;;)
	{
	    NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf1;
	    NRF_RADIO->EVENTS_END = 0;
	    NRF_RADIO->TASKS_START = 1;
		if(crcok2)
			show_pkt(pkt_buf2);
		else
			uart_wstr(".");
		if(NRF_RADIO->EVENTS_END)
			uart_wstr("!");
		while(! NRF_RADIO->EVENTS_END)
		{}
		crcok1=NRF_RADIO->CRCSTATUS;
	    NRF_RADIO->CRCINIT = 0x555555;	// for advertising packet
	    NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf2;
	    NRF_RADIO->EVENTS_END = 0;
	    NRF_RADIO->TASKS_START = 1;
		if(crcok1)
			show_pkt(pkt_buf1);
		else
			uart_wstr(".");
		if(NRF_RADIO->EVENTS_END)
			uart_wstr("!");
		while(! NRF_RADIO->EVENTS_END)
		{}
		crcok2=NRF_RADIO->CRCSTATUS;
  }

 

  通过对PDU第一个字节的低4位,可以判断数据包类型,然后识别余下数据。

static inline void show_pkt(volatile uint8_t *buf)
{
	switch(buf[0]&0xF)
	{
		case 6: // ADV_SCAN_IND
			uart_wstr("s");
			add_log(buf);
			break;
		case 0: // ADV_IND
			uart_wstr("A");
			add_log(buf);
			break;
		case 2: // ADV_NONCONN_IND
			uart_wstr("n");
			add_log(buf);
			break;
		case 4: // SCAN_RESP
			uart_wstr("R");
			break;
		case 1: // ADV_DIRECT_IND
			uart_wstr("i");
			break;
		case 3: // SCAN_REQ
			uart_wstr("+");
			break;
		case 5: // CONN_REQ
			uart_wstr("C");
			break;
		default:
			uart_wstr("?");
			break;
	}
}

  如果是包含advertising数据的包,可以将地址、数据记录下来,待收集一段时间后进行统计。

void add_log(uint8_t *buf)
{
	int i;
	for(i=0;i<32;i++)
	{
		if(adv_log[i].count)	// not blank
		{
			if(memcmp(adv_log[i].addr, buf+3, 6)==0 && adv_log[i].type==buf[0])	// match
			{
				adv_log[i].count++;
				return;
			}
		}
		else	// add entry
		{
			memcpy(adv_log[i].addr, buf+3, 6);
			adv_log[i].type = buf[0];
			adv_log[i].len = buf[1]-6;
			memcpy(adv_log[i].payload, buf+9, adv_log[i].len);
			adv_log[i].count=1;
			return;
		}
	}
}

 

  这样就可以发现周围的一部分BLE设备了。现在我的程序只是接收,没有主动发起“扫描”。但是我的程序收到了许多主动扫描的包,表明附近有设备在持续进行疯狂扫描……

 

  以上演示的是单向接收。单向发送也是容易实现的,只要填充一个advertising包,把要发送的数据包含在内,用TX模式发送出去就是了。发送的设置和接收基本一样。

void radio_adv_tx(uint8_t *pdu, uint8_t len)
{
	uint8_t txpkt[40];

	NRF_RADIO->EVENTS_READY = 0;
    NRF_RADIO->TASKS_TXEN = 1;
    while (NRF_RADIO->EVENTS_READY == 0);
	// now in TXIDLE state

	txpkt[0]=0x42;	// private TX address, non-connectable
	if(len>31)
		len=31;
	txpkt[1]=len+6;
	txpkt[2]=0;
	txpkt[3]=0x37; txpkt[4]=0x5A; txpkt[5]=0x29;
	txpkt[6]=0xC6; txpkt[7]=0x8B; txpkt[8]=0x04;
	memcpy(txpkt+9, pdu, len);
    NRF_RADIO->PACKETPTR = (uint32_t)txpkt;
    NRF_RADIO->EVENTS_END = 0;
	NRF_RADIO->TASKS_START = 1;
	while(! NRF_RADIO->EVENTS_END)
	{}
}

  使用一个包含名称的advertising数据,调用上面的函数。设备地址是04:8B:C6:29:5A:37, 写在发送函数中了。

	const uint8_t dummy_adv[]={0x02,0x01,0x06,	// flags
					15,0x09,'A','D','V','_','D','e','m','o',' ','5','1','8','2','2'};
	radio_adv_tx(dummy_adv,sizeof(dummy_adv));

  定期(比如1秒)发送一次,在手机上用BLE扫描工具可以发现这个设备。当然现在仅仅广播了一个名称而已,要添加自定的传感器数据也很简单,不过要注意31个字节长度限制。

 

  以上只是最初级的直接操作硬件进行BLE数据包收发的演示,只用了单向数据,因此简单了。如果要两个设备有应答地交互,就需要发送方在数据包发送之后切换到接收状态,等待一小段时间看是否有应答。BLE的连接建立起来后,主从双方的收发方向就是在不断地切换,如果要自己编程操作硬件实现这些,而不使用协议栈的API, 理论上是可以做的,问题在于有没有必要了。

  利用BLE MCU的无线电硬件部分,做一些调试工具是可行而且有用的。还可以做自己的私有协议通讯,那样就不能再叫做BLE了。

此帖出自RF/无线论坛

最新回复

nmg
“但是我的程序收到了许多主动扫描的包,表明附近有设备在持续进行疯狂扫描……” 定位,把它扒出来,哈哈   详情 回复 发表于 2021-8-9 08:34
点赞 关注
 

回复
举报

6593

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

还做了总结:由于BLE的连接建立起来后,主从双方的收发方向就是在不断地切换,如果要自己编程操作硬件实现这些,而不使用协议栈的API, 理论上是可以做的,问题在于有没有必要了。

记住了,谢谢

此帖出自RF/无线论坛
 
 

回复

5219

帖子

236

TA的资源

管理员

板凳
 

“但是我的程序收到了许多主动扫描的包,表明附近有设备在持续进行疯狂扫描……”

定位,把它扒出来,哈哈

此帖出自RF/无线论坛
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表