一个蓝牙收发器的开发经验
发布时间: 2022-12-03 19:43:59
前不久一个在深圳的大学好友联系到我,他们公司需要做一个USB蓝牙接收器,功能大体如下:
配一张使用场景的图片:

他这个需求多少有点非主流,看着像是蓝牙键盘,但是物理上却是USB接口的HID设备,并不是BLE的HID,BLE在这里只是用来接收手机发送的数据。
起初我也没太认真想如何实现,就随手发到我们的嵌入式交流群里,各路高手们纷纷提出了自己的方案:
-
-
群友heibus的方案是:串口转USB HID芯片+蓝牙串口透传,可以用CH9328+KT6368A。
-
群友oxlm、Pengfei的方案是:使用单颗蓝牙SOC,可以用Nordic的NRF52840、NXP的QN9080等,蓝牙芯片自带USB接口,一颗芯片搞定。
-
群友baolei的方案是:CH340+KT6368A,通过Device Simulation Framework在PC端写个上位机软件,将串口收到的数据转换成虚拟HID。
这4种方案从原理上来说都可以实现我这个同学的需求,说到我这个大学同学,请允许我临时跑个题,当年上学时,他住我宿舍正对面,是个不折不扣的单片机迷,最初玩51单片机,后来捣鼓AVR单片机、然后自学uCosII操作系统,后来不知道怎么又自学了Java,技术上特别爱专研。大学毕业后,我们就一南一北各自闯天涯了,他南下深圳直接工作了,从事安卓相关研发工作,这么多年一直在这个领域,在深圳也是纯凭借个人能力攒钱买了房子。不得不感慨,干移动互联网的就是比干嵌入式的更容易搞钱啊。
那他为什么要整这个USB蓝牙接收器呢?因为他们新开发的这款APP用在国外,而这个蓝牙接收器是用来控制彩票机的,大概意思就是在手机点一点,实现在彩票机购买彩票的功能。至于为什么不直接在彩票机上购买,他给我解释了所谓智能的概念,听的我一头懵逼。
神奇了,最近老和BLE打交道,前段时间才研究了那个超便宜的BLE芯片KT6368A,这又来了一个BLE的相关需求,索性就考虑用群友heibus提出的CH9328+KT6368A方案来实现看看。
不过随着后来进一步的需求沟通,发现用CH9328+KT6368A还不行,原因是它手机端发送的指令并不是原封不动的透传过去就行了,实际上需要做转换,比如说手机端发送十进制1,对应到USB HID 的是两组8字节的16进制数据:00 00 08 00 00 00 00 00和00 00 00 00 00 00 00 00,这样单纯靠硬件就完成不了了,需要涉及到软件开发。
关于00 00 08 00 00 00 00 00和00 00 00 00 00 00 00 00这两组数据的含义,那就得简单补习点USB HID的基础知识了。
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6 BYTE7 BYTE8
|--bit0: Left Control是否按下,按下为1
|--bit1: Left Shift 是否按下,按下为1 |--bit2: Left Alt 是否按下,按下为1
|--bit3: Left GUI 是否按下,按下为1
|--bit4: Right Control是否按下,按下为1
|--bit5: Right Shift 是否按下,按下为1 |--bit6: Right Alt 是否按下,按下为1
|--bit7: Right GUI 是否按下,按下为1
BYTE3-BYTE8 :这六个为普通按键,键值可以参考USB HID to PS/2 Scan Code Translation Table.
举个例子,比如按键a对应的一帧数据是:00 00 0x04 0x00 00 00 00 00,第3字节04就是由下面这个表定义的:
这么说还是有点抽象,来点更直观的,电脑端我们可以用Bushound等USB分析软件,我这里用的是Free USB Analyzer :
-
-
在软件左侧找到USB键盘对应的设备,开始监控,这里只选择Raw Data View
-
按一下按键a并松开,这时软件界面就会显示收到了一串数据,它其实是对应了两组8字节数据,可以看到a确实对应04,另外00 00 00 00 00 00 00 00表示的是按键弹起
只有当你弹起按键a时才会显示00 00 00 00 00 00 00 00
如果你要同时按下SHIFT+a组合按键再同时松开,那么对应的数据就如下:
当然如果是你先按下Shift键,再按下a键,再松开a键,最后松开Shift键,那么就对应4组数据,分别为:
为了搞清楚这个,我就花了好久的时间,毕竟以前也没有怎么实际用过USB。再次回到他的蓝牙接收器需求,手机端输入的范围是数字1-83,有的数字是对应2个8字节数据,表示的是一个按键的按下和松开,有的数字是对应4个字节,表示的是Shift+按键的组合按下与松开,并且每8个字节数据之间的时间间隔是200ms。
既然KT6368A不行,那就换一个可以编程的蓝牙模块,比如TI的CC2541模块、Nordic NRF51822模块都可以,因为我原来支持过NXP的QN9021芯片,对它相对熟一点,所以就用QN9021来实现了。
用QN9021来实现上述软件功能(蓝牙接收手机发送过来的一串数据,然后转码输出)我本来以为分分钟就搞定了,结果实际调试起来并不是想象的那么简单。因为常规的蓝牙透传使用方式是串口接收数据然后蓝牙发送,这个需求正好是一个反向的操作。其中涉及到几个关键的问题:
-
手机端发送过来的是一串长度可能长、可能短的数据。因为QN9021是BLE 4.0芯片,一次发送字节最多是20个字节,所以要考虑超过20字节的情况。
-
蓝牙芯片一边蓝牙接收数据,一边串口发送数据,要考虑串口没有发送完,蓝牙又来数据的的情况。
-
手机发送的不同键值,程序里要实现转码(有的是对应发送2个8字节数据,有的是对应4个8字节数据,每个8字节数据中间都是200ms)的代码实现问题。
有经验的程序高手可能不觉得是什么问题,但是对我这样好久没实际写代码的人,还是折腾了不少时间。
上述问题1可以通过手机端分包来解决,问题2解决办法是加一个队列,把蓝牙接收的数据放到队列里缓存起来,另外一个地方从队列取数串口发送。队列如何用C语言实现,让我直接写我肯定写不出来,我用了github上的一个开源代码:https://github.com/kuaileguyue/Ring-Buffer。问题3我是在200ms定时器函数里做了一个小状态机来解决的,状态机通过switch/case和标志位就可以实现。
最后我们再来总结下这几种方案:
这几种方案从硬件角度来看,都具备BLE和USB功能,只不过软件部分跑的地方不同,最终都可以实现所需要的功能。
至于在实际项目或产品中,到底选取哪一种方案,实际上是需要综合考虑多方面的因素的,比如开发周期、成本、软件开发难易、甚至芯片是否好买等。
下一步我会再研究第一个方案的实现,即CH551+KT6368A,后面大概率用这个方案,原因大家应该都明白。