深入剖析BLE蓝牙数据收发处理

简介

我将通过一个BLE蓝牙广播的例子,深入的解释蓝牙数据从controller到rf天线发送到空中这个路径的数据是如何处理,整个过程涉及蓝牙协议5.0,常见嵌入式芯片的架构,数字电路设计,通信原理,信号处理等相关的知识,最需要了解的是蓝牙核心规范5.0,如下图展示的内容,这里推荐学习的博客:参考博文链接 1 2 3 4 5 9章节的所有内容,通过这里的学习将掌握蓝牙广播数据包的格式和蓝牙广播事件的行为,对后续的数据的收发发送才能更加容易的理解。
蓝牙核心规范5.0

蓝牙基本架构

蓝牙协议架构
原图引用:原图链接
本文主要介绍主机接口HCI往下的内容,统称为蓝牙的controller,为了方便理解并没有涉及蓝牙音频相关的。

1 开启BLE的广播

(1)用打开enable adv进行广播数据为例
(2)在host端开启adv enable即是发送hci指令在协议规范中的描述
打开广播的HCI指令
在此之前还会设置广播数据和广播参数
设置广播数据的HCI指令
设置广播参数的HCI指令
在进行广播参数和广播数据设置的时候controller将这些数据通过全局变量存储起来,在收到adv enable的指令时,把这些数据按照协议规范定义好的pdu格式拼接起来。如下图所示Payload部分根据adv的类型不同又存在不同的格式,比如常见的ADV_IND,ADV_NONCONN_IND,等同时由于5.0规范的扩充还存在extend adv的格式更加的复杂,所以在contrller看似按照固定的格式进行简单的拼接其实需要考虑的东西还是很复杂的。
广播数据包的格式
蓝牙的广播是一个周期性的事件,细心的朋友应该在set adv parameter的参数中看到一个interval_min和interval_max,这就是蓝牙广播的周期,controller会在这个最大值和最小值权衡一个合适的interval,也就是每隔这么长的时间就发送一次adv_packt,至于这个是如何实现的我们在后面数字基带部分详细的介绍。在这里我想说的是controller在封包结束后也是会考虑当前的时间,和下一次发送adv的时间。还有需要考虑的adv的channel的映射,对于规范5.0之前的蓝牙广播只使用了37 38 39三个信道称为primary channel,这里我们只考虑5.0之前的channel map,adv 的参数设定channel map是通过设置一个变量的三个位来表示开启的广播通道,比如UINT8 channel_map |= 0x01<<0;表示只使用37通道UINT8 channel_map |= 0x03<<0;表示使用37 38,依次类推,当然在controller这边也只是将这个参数拿到,具体通道的切换是由数字基带实现的。到此controller在adv enable相关的事情就完成了,接下来将封装好的adv pdu,以及时间值的设定,通道映射等写入数字基带的寄存器。

2 常见芯片的基本架构

在上述中我不断提到数字基带,那么到底什么是基带呢?如何将数据写入到基带的呢?基带又是如何处理controller写下来的数据呢?比如提到的对adv packt interval的控制。在这里我将为大家详细的介绍。首先需要了解芯片的结构,我们以最常见的stm32芯片为例。图1中芯片由m3的内核和总线以及外设组成,m3就是我们常说的核心处理器,处理我们在软件端编写的程序,当然这个程序是经过预处理->编译->连接->汇编的二进制机器码。外设的话我们常见的uart,spi等相信大家都已经了解过或者使用过,在芯片设计阶段会为每个外设分配一个地址,再将这个外设挂在合适的总线上面,比如这里所有外设基本都挂在apb2上面,在图中看到的总线矩阵AHB、APB1、APB2等都是AMBA协议中的内容,大家感兴趣的可以详细去了解,这里简单介绍,总线的设备分为master和slave,只有master才能够发起操作slave响应,master首先访问总裁器获取对总线的控制,再在总线广播要操作slave的地址,经过地址译码器选中操作的slave,master就可以进行读写slave的操作了,这里的master主要就是cm3核心处理器了,slave就是片上的外设,这里我们又想到操作都是由处理器发起的,万一外设还没等到处理器来读取,但是有数据需要处理该怎么办呢?这个时候就是触发中断,在内核中也叫触发异常来处理特殊情况。到这儿芯片基本的结构相信大家也基本清除了,再说数字基带其实也就是一个片上的外设,挂在AHB总线上的一个从设备。假定基带分配的起始地址为BB_Base = 0x0050000,其中adv_pack寄存器的地址的相对基地址偏移为ADV_Pack_Reg = 0x10;那么我们把数据写进去的操作就是:
(uint_32 *)(BB_Base+ADV_Pack_Reg) = adv_pack;是不是感觉说了半天结果就这么一句简单的赋值操作,把人给看懵了,上述的总线寻址是通过数子电路已经完成了的硬件,在软件端我们其实只要知道地址就可以直接操作了这些复杂的事情都交给硬件去完成。同样基带有需要处理的事情都是通过触发中断交给软件端。
图1 STM32
图2 芯片内部结构

3 基带的功能

到此我们已经了解了基带在芯片的结构,然后我们需要进一步了解基带的功能。我认为基带在蓝牙通信最核心的是两个功能,一个是空口数据包的发送封装和接收解析,还有就是蓝牙通讯的时序控制,特别是在时序控制,BLE协议规范规定每两个蓝牙包的间隔是T_IFS 150(±2)us,特别是在创建连接的过程以及保持连接是需要非常准确的时间控制。

4 基带封装广播的空口数据包

未编码的空口包
编码的空口包
协议中规定的空口包主要是编码和非编码这两种数据包格式,PDU就是controller处理完后写入基带的数据,基带的实现主要是通过状态机fsm来处理的,如果大家了解数字前端相关的知识应该知道数字电路都是通过写硬件描述语言verilog或者VHDL来实现的,当然这个相比软件写的代码,可就复杂谨慎的多了,软件端基本上只要逻辑能够实现,随便你怎么写,出bug了大不了就改,而硬件描述写的代码,最终是生成数字电路流片生产芯片,如果有bug那这批芯片全费,直接造成巨大损失,如果是初创公司,基本也就宣告凉凉,所以前期数字电路是要经过无数次的验证和改版才能投入流片,继续说回基带的数据处理,这里简单的写个verilog和画个流程图帮助理解,实际情况肯定比这个复杂很多。

//状态机的主状态只列举发送
localparame  IDLE       =  8’h11;
localparame  STR_TX    =  8’h22;
localparame  TX        =  8’h33;
localparame  TX_DONE  =  8’h44;

//发送包的格式这里只举例ADV_IND
localparame  ADV_IND              =  8’h0;
localparame  ADV_DIRECT_IND       =  8’h1;
localparame  ADV_NONCONN_IND    =  8’h2;
localparame  SCAN_REQ             =  8’h3;
..........................
always@(posedge clk or negedge rst) begin
	if(!rst)
		//复位代码
	else begin
		case(state)
		IDLE:begin
			if(s_en) begin //需要处理时把这个信号拉起来
				case(pdu_mode)
					ADV_IND:begin
						//做打开tx的准备工作
					state <= STR_TX;
					end
					ADV_DIRECT_IND:begin
					//	处理这个包需要的任务
					end
					ADV_NONCONN_IND:begin
					//	处理这个包需要的任务
					end
					SCAN_REQ:begin
					//	处理这个包需要的任务
					end
					default:begin
					//数字电路的严谨性不完整的case需要加上
					//default以免生成锁存器
					end
			end
			STR_TX:begin
				//等待tx的所有事物完成
				if(complete)
				state <= TX;
			end
			TX:begin
				//打开TX,当这个信号拉起来的同时
				//数据封装模块检测到这个信号将启动起来
				tx_enbale <= 1;
				if(tx_packt_process)//如果数据发送完
					state <=TX_DONE;
			end
			TX_DONE:begin
			//关闭tx
				tx_enbale<=0;
				state <=IDLE;
			end
		else
			state <= IDLE;
		end 
	end
end

数据处理的TX也是状态机控制的,当检测到tx_enable信号状态机就工作起来,空口数据包发送完后拉起一个完成信号通知主状态机,关闭TX状态。流程图如下。
BB的数据封装
以上介绍了BB发送数据的封装,对于接收其实就是一个逆过程,关键在于接收的时机也就是BB打开RX的时机,如果是可扫描的广播态 BB在一个pramiry channel发送广播数据包后经过一个I_TFS会打开RX接收数据如果有数据就接收处理,没有数据就继续在下一个通道重复操作,关于时序控制会在下一章详细的介绍。
由于编者经验水平有限,如有错误请更正!有感兴趣朋友也欢迎讨论,谢谢!

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是在 Android 平台上使用 Bluetooth Low Energy(BLE)API 进行收发数据的示例代码: 首先,您需要在应用程序中获取 BluetoothAdapter 和 BluetoothGatt 对象。然后,您需要扫描并连接到 BLE 设备,获取其 BluetoothGatt 对象。一旦连接成功,您可以使用 BluetoothGatt 对象进行数据传输。 发送数据: ```java // 获取要发送的数据 byte[] data = "hello world".getBytes(); // 获取要发送数据的特征值对象 BluetoothGattCharacteristic characteristic = bluetoothGatt.getService(serviceUuid) .getCharacteristic(characteristicUuid); // 设置数据到特征值中 characteristic.setValue(data); // 发送数据 bluetoothGatt.writeCharacteristic(characteristic); ``` 在上面的示例代码中,`serviceUuid` 和 `characteristicUuid` 是您要发送数据的特征值的 UUID。 一旦您调用 `writeCharacteristic` 方法,BLE 设备将接收到发送的数据。如果发送成功,将会触发 `onCharacteristicWrite` 回调。 接收数据: 要接收 BLE 设备发送的数据,您需要在 BluetoothGatt 对象上注册接收数据的回调。 ```java // 注册接收数据的回调 bluetoothGatt.setCharacteristicNotification(characteristic, true); // 获取数据的描述符 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUuid); // 启用数据的通知 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); bluetoothGatt.writeDescriptor(descriptor); ``` 在上面的示例代码中,`characteristic` 是您要接收数据的特征值对象,`descriptorUuid` 是通知数据的描述符的 UUID。 一旦您调用 `setCharacteristicNotification` 和 `writeDescriptor` 方法,BLE 设备将开始将数据发送到 Android 设备。当数据到达时,将触发 `onCharacteristicChanged` 回调。在此回调中,您可以从特征值对象中获取数据并进行处理。 ```java @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // 处理收到数据 byte[] data = characteristic.getValue(); // ... } ``` 请注意,BLE 通信是异步的,因此您需要在适当的回调中处理数据。如果您需要连续接收多个特征值的数据,请考虑使用 BluetoothGatt 的 `setCharacteristicNotification` 方法注册通知,以便连续接收数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值