TopSemic · 2021年10月13日

一个蓝牙实战项目的掏肺总结

前不久一个在深圳的大学好友联系到我,他们公司需要做一个USB蓝牙接收器,功能大体如下:

  • USB蓝牙接收器插在电脑上使用,被识别为键盘
  • 手机程序连接该USB蓝牙接收器
  • 手机程序向电脑发送键盘输入指令

配一张使用场景的图片:

1e44a02951e0522c061074401286aef2.png

他这个需求多少有点非主流,看着像是蓝牙键盘,但是物理上却是USB接口的HID设备,并不是BLE的HID,BLE在这里只是用来接收手机发送的数据。

起初我也没太认真想如何实现,就随手发到我们的嵌入式交流群里,各路高手们纷纷提出了自己的方案:

1. 群友喵了个咪的方案是:单片机模拟USB键盘+蓝牙串口透传,可以用CH551+KT6368A,KT6368A可以参考之前写的文章:尝鲜1.6元的蓝牙芯片KT6368A
2. 群友heibus的方案是:串口转USB HID芯片+蓝牙串口透传,可以用CH9328+KT6368A。
3. 群友oxlm、Pengfei的方案是:使用单颗蓝牙SOC,可以用Nordic的NRF52840、NXP的QN9080等,蓝牙芯片自带USB接口,一颗芯片搞定。
4. 群友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的基础知识了。

键盘发送给PC的数据每次是8个字节:
BYTE1  BYTE2  BYTE3  BYTE4 BYTE5  BYTE6 BYTE7 BYTE8 

定义分别是:
BYTE1 :特殊按键,具体各位含义如下:
    |--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 

BYTE2:保留

BYTE3-BYTE8 :这六个为普通按键,键值可以参考USB HID to PS/2 Scan Code Translation Table.

举个例子,比如按键a对应的一帧数据是:00 00 0x04 0x00 00 00 00 00,第3字节04就是由下面这个表定义的:

Image

这么说还是有点抽象,来点更直观的,电脑端我们可以用Bushound等USB分析软件,我这里用的是Free USB Analyzer :

  1. 我用的是笔记本电脑,先外接一个USB键盘
  2. 在软件左侧找到USB键盘对应的设备,开始监控,这里只选择Raw Data View

c7bf1781e88e9b2c2fd13ab51d1fcebf.png

  1. 按一下按键a并松开,这时软件界面就会显示收到了一串数据,它其实是对应了两组8字节数据,可以看到a确实对应04,另外00 00 00 00 00 00 00 00表示的是按键弹起

f8d909fea1597e2a9dff765b6ffd5361.png

如果一直按住a不松手,那么显示的就会是如下信息:

86cbe3bbfd8bf7a02926036c760bdad0.png

只有当你弹起按键a时才会显示00 00 00 00 00 00 00 00

如果你要同时按下SHIFT+a组合按键再同时松开,那么对应的数据就如下:

8a04ebdf9e7f1ba7da6546d5ae01980f.png

第一个字节就表示左侧的Shift键。

当然如果是你先按下Shift键,再按下a键,再松开a键,最后松开Shift键,那么就对应4组数据,分别为:

27e8ba0ad3a184b66680973d8cbefca5.png

为了搞清楚这个,我就花了好久的时间,毕竟以前也没有怎么实际用过USB。再次回到他的蓝牙接收器需求,手机端输入的范围是数字1-83,有的数字是对应2个8字节数据,表示的是一个按键的按下和松开,有的数字是对应4个字节,表示的是Shift+按键的组合按下与松开,并且每8个字节数据之间的时间间隔是200ms。

既然KT6368A不行,那就换一个可以编程的蓝牙模块,比如TI的CC2541模块、Nordic NRF51822模块都可以,因为我原来支持过NXP的QN9021芯片,对它相对熟一点,所以就用QN9021来实现了。

用QN9021来实现上述软件功能(蓝牙接收手机发送过来的一串数据,然后转码输出)我本来以为分分钟就搞定了,结果实际调试起来并不是想象的那么简单。因为常规的蓝牙透传使用方式是串口接收数据然后蓝牙发送,这个需求正好是一个反向的操作。其中涉及到几个关键的问题:

  1. 手机端发送过来的是一串长度可能长、可能短的数据。因为QN9021是BLE 4.0芯片,一次发送字节最多是20个字节,所以要考虑超过20字节的情况。
  2. 蓝牙芯片一边蓝牙接收数据,一边串口发送数据,要考虑串口没有发送完,蓝牙又来数据的的情况。
  3. 手机发送的不同键值,程序里要实现转码(有的是对应发送2个8字节数据,有的是对应4个8字节数据,每个8字节数据中间都是200ms)的代码实现问题。

有经验的程序高手可能不觉得是什么问题,但是对我这样好久没实际写代码的人,还是折腾了不少时间。

上述问题1可以通过手机端分包来解决,问题2解决办法是加一个队列,把蓝牙接收的数据放到队列里缓存起来,另外一个地方从队列取数串口发送。队列如何用C语言实现,让我直接写我肯定写不出来,我用了github上的一个开源代码:https://github.com/kuaileguyu...。问题3我是在200ms定时器函数里做了一个小状态机来解决的,状态机通过switch/case和标志位就可以实现。

最后我们再来总结下这几种方案,
image.png

这几种方案从硬件角度来看,都具备BLE和USB功能,只不过软件部分跑的地方不同,最终都可以实现所需要的功能。

至于在实际项目或产品中,到底选取哪一种方案,实际上是需要综合考虑多方面的因素的,比如开发周期、成本、软件开发难易、甚至芯片是否好买等。

下一步我会再研究第一个方案的实现,即CH551+KT6368A,后面大概率用这个方案,原因大家应该都明白。

作者:wuyage
来源:https://mp.weixin.qq.com/s/Hfn6BRz8lCgj76ceasIdsw
aijishu_TopSemic_1.jpg

推荐阅读

更多芯片嵌入式电子技术分享请关注Topsemic嵌入式极术专栏
推荐阅读
关注数
3258
内容数
45
让芯片使用更简单。 专注分享:嵌入式,单片机,STM32,ARM,RTOS,Linux, 软硬件,半导体,电子技术等相关内容。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息