前言
上一篇分享是HidEmuKbd工程和BTool的联合调试,整个实验下来,对HidEmuKbd如何建立连接、如何处理按键消息都有了很好地理解。既然能够和BTool联合调试,那么配合USB Dongle就可以实现和PC的通信了。因此,在之前工作的基础上,进一步实现一个PPT播放器,进一步挖掘套件的潜力、熟悉官方提供的例程。
硬件准备
CC2541MINI开发套件全部成员,即:Keyfob+USB Dongle+CC Debugger
整体思路
官方例程和无线键盘、无线遥控相关的工程有3个,如下图所示;
本文的设计就是借助这3个例程完成的。
仅从工程名称就可以看出,HIDAdvRemote和HIDAdvRemoteDongle是一对配套工程,其中HIDAdvRemote是无线遥控器的工程,HIDAdvRemoteDongle是USB Dongle的工程。这对工程的使用可以参考文档《TI_CC2541_ARC_User_Guide》,如下图所示。
从图中可以看出,这里的遥控器和套件里的Keyfob,差距还是很大的。
在上一篇分享中我们可以知道,HidEmuKbd工程对应的硬件平台是Keyfob,而且已经可以成功发出按键消息。所以,只要做到USB Dongle能够接收并正确处理HidEmuKbd发出的消息,那么Keyfob上的两个按键就“摇身一变”,成了标准键盘上的按键。
经过以上分析,可想而知,想要完成我们的设计,最好的办法是将HidEmuKbd和HIDAdvRemoteDongle配合使用。可行的方案有以下两种:
方案一:由于HIDAdvRemote和HIDAdvRemoteDongle工程是配套的,所以两者的Profile是一致的,或者说HIDAdvRemoteDongle是根据HIDAdvRemote的Profile对接收的数据进行处理的。那么,可以将HIDAdvRemote使用的Profile拿过来给HidEmuKbd工程使用,这样的话,HIDAdvRemoteDongle就可以成功解析出HidEmuKbd发出的消息。(网络上可以找到具体操作步骤)
方案二:按照HidEmuKbd工程的Profile修改HIDAdvRemoteDongle数据处理相关内容,从而成功解析出有用数据。
方案比较:从难易程度上来看,方案一比方案二简单很多,照葫芦画瓢,很容易就能够实现想要的功能。我按照这个方法把功能实现之后,发现自己没有任何实质性的收获。所以,我又尝试了方案二,选择这个方案,就必须对HIDAdvRemoteDongle例程有比较好的理解才行。这次没有文档可以参考了,只能硬啃代码。
分析和解决问题过程
-
不修改HidEmuKbd和HIDAdvRemoteDongle工程,是否能直接工作对两个例程不做修改,分别烧写入Keyfob和USB Dongle,按以下流程建立连接:
-
按下USB Dongle的S2键,开始扫描。此时红色LED闪烁。
-
按下Keyfob任意键,开始广播。连接建立成功后,USB Dongle上的LED会变绿。
-
连接建立成功后,按下Keyfob任意一个按键,发现USB Dongle红色LED会闪烁,但没有实现相应功能。这就表明,消息已经成功发出,也能够成功接收,但是功能为什么没实现呢?调试看看吧。
-
阅读源码,可以定位到相关函数为hidappSendInReport,其作用是将收到的HID report发出去。
-
在hidappSendInReport函数中设置断点,可以发现程序会停在下图所示的位置
-
我们发出的是key report,但是在Dongle端被解析成了mouse report,通过判断条件可以看出是report的句柄出了问题。
-
到了这里,又有一个快捷的解决办法:把endPoint的值换成USB_HID_KBD_EP就可以了(我没有试,是论坛里很早的一个帖子提出的解决办法)。
- 定位到最终的代码位置如下,注意中文注释即可。
case ATT_READ_BY_TYPE_RSP: // Response from Discover all Characteristics. // Success indicates packet with characteristic discoveries. if ( pPkt->hdr.status == SUCCESS ) { attReadByTypeRsp_t *pRsp = &pPkt->msg.readByTypeRsp; uint8 idx = 0; if ( serviceToDiscover == GATT_SERVICE_UUID ) { // We have discovered the GATT Service Characteristic handle serviceChangeHandle = BUILD_UINT16( pRsp->pDataList[3], pRsp->pDataList[4] ); // Break to skip next part, it doesn't apply here break; } // Search characteristics for those with notification permissions. while( idx < ((pRsp->numPairs * pRsp->len) - 1) ) { // Check permissions of characteristic for notification permission if ( (pRsp->pDataList[idx+2] & GATT_PROP_NOTIFY ) ) { /* uint16* pHandle = (mouseCharHandle == GATT_INVALID_HANDLE) ? &mouseCharHandle : &keyCharHandle ; if ( pHandle == &keyCharHandle && consumerCtrlCharHandle == GATT_INVALID_HANDLE ) { pHandle = ( keyCharHandle == GATT_INVALID_HANDLE ) ? &keyCharHandle : &consumerCtrlCharHandle; } */ // 此处决定了3个handle的赋值顺序,按照HIDEmuKbd工程的Profile确定取handle的顺序 // HIDEmuKbd工程没有mouse和cc report handle,为尽量少做更改,这里对赋值顺序进行了变更,而不是删除 //----------------------------------------------_ bgn of add by cuter ---------------------------------------------------------- // 2019.09.04 uint16* pHandle = (keyCharHandle == GATT_INVALID_HANDLE) ? &keyCharHandle : &mouseCharHandle ; if ( pHandle == &mouseCharHandle && consumerCtrlCharHandle == GATT_INVALID_HANDLE ) { pHandle = ( mouseCharHandle == GATT_INVALID_HANDLE ) ? &mouseCharHandle : &consumerCtrlCharHandle; } //----------------------------------------------- end of add by cuter ---------------------------------------------------------- if ( *pHandle == GATT_INVALID_HANDLE ) { *pHandle = BUILD_UINT16( pRsp->pDataList[idx+3], pRsp->pDataList[idx+4] );//从返回消息中提取handle } } idx += pRsp->len; } } // This indicates that there is no more characteristic data // to be discovered within the given handle range. else if ( pPkt->hdr.status == bleProcedureComplete ) { if ( serviceToDiscover == GATT_SERVICE_UUID ) { // Begin Service Discovery of HID Service serviceToDiscover = HID_SERV_UUID; hidappDiscoverService( connHandle, HID_SERV_UUID ); // Break to skip next part, it doesn't apply yet. break; } if ( enableCCCDs == TRUE ) { mouseCCCHandle = mouseCharHandle + 1; // Begin configuring the characteristics for notifications hidappEnableNotification( connHandle, mouseCCCHandle ); } else { serviceDiscComplete = TRUE; } } break;
测试视频
http://player.youku.com/embed/XNDM0MzQ4MDEyOA==' frameborder=0 'allowfullscreen
总结
整个学习过程中,对OSAL的消息处理机制、一些返回消息的数据结构、服务注册过程、Profile等方面有了更好地理解。基本上可以顺利地进行项目开发了。