目前大部分BLE芯片都支持BLE 4.2 协议,默认情况下,不论是read/write 还是notify等操作每次传输最长不超过23个字节,其中有三个字节是头,所以有效的容量是20个字节。
对于Dialog14585而言,通过修改max_mtu可以使最大长度达到247字节(蓝牙4.2 为247,蓝牙5.0为 512),但是传输的数据多了,相应的传输每个包所需要的时间(max_txtime)也变多了 ,具体的计算公式为 (empty packet overhead) + (TxBytes * 8),若忘记修改,会造成大量丢包。
我在最开始接触固件的时候开发的几个项目中,传输notification都是使用的一个timer,每隔20~30ms定时传输的。我这边后来因为发现内核timer使用上的bug(参考:蓝牙MCU开发之旅:Dialog SDK中app_easy_timer的大坑),改造的时候,是使用了一个回调消息来代替timer的。Dialog的sdk中每个notification package 成功发送后都会有一个回调,如果服务定义在custs1,其回调消息是CUSTS1_VAL_NTF_CFM,这个消息附带的回调参数中包含一个handler会告诉你是哪个uuid notification包发送出去了。如果是连续传输数据的话,可以直接在这个回调中发起下一包的传输,经过我们的测试,只要信号不太差,android机和苹果机都不会丢包,相反使用定时器如果间隔太短还会导致丢包。
然后不论是使用timer还是使用这个回调的方式来发送下一个数据包,都需要注意一个问题,就是相同的handler,必须在上一个包发送完了之后才能发送下一个包,不然就有可能导致内核溢出从而直接进入HardFault。
正常情况下只要timer interval不要定义的太短,或者在等到 tx 的回调后再发下一包数据是不会发生这样的问题的。但是有一种情况会导致死机,就是client端也就是app重复开启notification。一个合格的稳定的硬件不应该被自己暴露的蓝牙接口搞死,也无法防止app不这样做,我司的小哥可是反复开启的...然后,我使用自己写的android测试程序验证,这样做确实会导致硬件死机。
处理这个问题有两个思路,一个是在notification的入口的地方检查本次连接这个handler是否已经开启传输了,如果开启了就不再处理(不能先停下再重新开启,因为可能有正在传输的包,这样操作仍然会导致同一时间同一个handler有多个传输动作);另一个方式是,使用一个flag来标记特定的handler的传输状态,开始传输一包数据的时候标记为false,收到回调后标记为true,开始下一包传输的时候,检查这个flag,如果是false说明有正在传输的包,就不处理本次传输了。我们的应用需求,app每次传输之前需要重置一些参数,所以采用的第二个方案。
对于Nordic的sdk而言,不存在上述的问题,只要配置的BLE_GAP_EVENT_LENGTH 参数在合理的范围内,sdk会处理这样的问题。这个参数配置合理的话,会让sdk充分利用每一个连接间隔,在连接间隔内,发送尽可能多的package又保证不丢包。比较坑爹的是,他们的sdk的回调中,会告诉你,notification发送是否成功了,还会告诉你client端是否有接收到,就是不告诉你:你刚才发送的是哪个handler的包!一旦应用的蓝牙协议中定义了多个notification,就会有诸多的不便。