蓝牙协议栈:simplelink_cc2640r2_sdk_1_40_00_45
IAR版本:IAR for ARM 8.11.3
开发板:CC2640R2F
蓝牙版本:BLE4.2
配对模式:PasscodeEntry 密码输入模式
主机端仅具有GAPBOND_IO_CAP_KEYBOARD_ONLY按键能力,从机端仅具有显示能力GAPBOND_IO_CAP_DISPLAY_ONLY。
从机端:
如果用手机做主机,开发板做从机,开发板显示密码,手机端输入密码,如果密码一直则配对成功。从机端使用协议栈自带例程simple_peropheral即可实现与手机的配对绑定,但此过程需要修改配对模式为uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;从机端初始化配对请求模式,否则主机端是不能收到配对过程中的输入密码请求的。
在此过程需要注意:手机大多是双模蓝牙,而手机配置界面大多是BDR蓝牙(非低功耗蓝牙),所以大多手机配置界面的自带蓝牙是不能与开发板进行配对绑定的,需要使用手机app,安卓建议nRF Connect,IOS端建议使用lightblue。
主机端
两个开发板一主一从,开发板作为从机实现与手机的配对与绑定后,从机端程序便可实现与另一开发板的配对绑定,我是基于simple_central例程修改主机端代码的。
初始化配置,在simple_central.c的static voidSimpleBLECentral_init(void)函数中,配置配对模式的初始化以及回调注册:
接下来编写回调函数:uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;// 配置主机端初始化配对请求; uint8_t bonding = TRUE;//配对完成后,进行绑定 uint8_t mitm = TRUE;// 使用authenticated pairing uint8_t ioCap = GAPBOND_IO_CAP_KEYBOARD_ONLY; GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode); GAPBondMgr_SetParameter(GAPBOND_BONDING_ENABLED, sizeof(uint8_t), &bonding); GAPBondMgr_SetParameter(GAPBOND_MITM_PROTECTION, sizeof(uint8_t), &mitm); GAPBondMgr_SetParameter(GAPBOND_IO_CAPABILITIES, sizeof(uint8_t), &ioCap); GAPBondMgr_Register(&SimpleBLECentral_bondCB);
static gapBondCBs_t SimpleBLECentral_bondCB = { (pfnPasscodeCB_t)SimpleBLECentral_passcodeCB, // Passcode callback SimpleBLECentral_pairStateCB // Pairing / Bonding state Callback }; static void SimpleBLECentral_passcodeCB(uint8_t *deviceAddr, uint16_t connHandle, uint8_t uiInputs, uint8_t uiOutputs, uint32_t numComparison) { gapPasskeyNeededEvent_t *pData; // Allocate space for the passcode event. if ((pData = ICall_malloc(sizeof(gapPasskeyNeededEvent_t)))) { memcpy(pData->deviceAddr, deviceAddr, B_ADDR_LEN); pData->connectionHandle = connHandle; pData->uiInputs = uiInputs; pData->uiOutputs = uiOutputs; pData->numComparison = numComparison; // Enqueue the event. SimpleBLECentral_enqueueMsg(SBC_PASSCODE_NEEDED_EVT, 0, (uint8_t *) pData); } } static void SimpleBLECentral_pairStateCB(uint16_t connHandle, uint8_t state, uint8_t status) { uint8_t *pData; // Allocate space for the event data. if ((pData = ICall_malloc(sizeof(uint8_t)))) { *pData = status; // Queue the event. SimpleBLECentral_enqueueMsg(SBC_PAIRING_STATE_EVT, state, pData); } }
在static_processAppMsg(sbcEvt_t *pMsg)函数中添加对于SBC_PASSCODE_NEEDED_EVT和SBC_PAIRING_STATE_EVT两个事件的处理,代码如下至此,整个配对绑定过程,需要开发人员编写的代码便完成了,其余都是由底层实现,关于底层实现 BLE4.0配对绑定过程的底层剖析这篇文章讲解很详细,有需要的朋友可以自行查阅。static void SimpleBLECentral_processAppMsg(sbcEvt_t *pMsg) { switch (pMsg->hdr.event) { case SBC_STATE_CHANGE_EVT: SimpleBLECentral_processStackMsg((ICall_Hdr *)pMsg->pData); // Free the stack message ICall_freeMsg(pMsg->pData); break; case SBC_KEY_CHANGE_EVT: SimpleBLECentral_handleKeys(0, pMsg->hdr.state); break; case SBC_RSSI_READ_EVT: { readRssi_t *pRssi = (readRssi_t *)pMsg->pData; // If link is up and RSSI reads active if (pRssi->connHandle != GAP_CONNHANDLE_ALL && linkDB_Up(pRssi->connHandle)) { // Restart timer Util_restartClock(pRssi->pClock, pRssi->period); // Read RSSI VOID HCI_ReadRssiCmd(pRssi->connHandle); } } break; // Pairing event case SBC_PAIRING_STATE_EVT: { SimpleBLECentral_processPairState(pMsg->hdr.state, *pMsg->pData); ICall_free(pMsg->pData); break; } // Passcode event case SBC_PASSCODE_NEEDED_EVT: { SimpleBLECentral_processPasscode(connHandle,(gapPasskeyNeededEvent_t *)pMsg->pData); ICall_free(pMsg->pData); break; } default: // Do nothing. break; } } static void SimpleBLECentral_processPasscode(uint16_t connectionHandle, gapPasskeyNeededEvent_t *pData) { if (pData->uiInputs) // if we are to enter passkey { #if STATIC_PASSCODE passcode = 123456; //此处使用的是静态密码,不需要主机端手动输入,只要默认和从机端一致,就可实现配对 GAPBondMgr_PasscodeRsp(connectionHandle, SUCCESS, passcode); //发送密码响应 #else // user will enter passcode waiting_for_passcode = TRUE; //此处由主机端手动输入从机端显示的配对密码 passcode_connHandle = connectionHandle; #endif Display_print0(dispHandle, 4, 0, "Enter Passcode:"); Display_print1(dispHandle, 5, 0, "%d", passcode); } else if (pData->uiOutputs) // if we are to display passkey { #if STATIC_PASSCODE passcode = 123456; #else // Create random passcode passcode = Util_GetTRNG(); passcode %= 1000000; #endif Display_print1(dispHandle, 4, 0, "Passcode: %d", passcode); // Send passcode response GAPBondMgr_PasscodeRsp(connectionHandle, SUCCESS, passcode); } } static void SimpleBLECentral_processPairState(uint8_t state, uint8_t status) { if (state == GAPBOND_PAIRING_STATE_STARTED) { Display_print0(dispHandle, 2, 0, "Pairing started"); } else if (state == GAPBOND_PAIRING_STATE_COMPLETE) { if (status == SUCCESS) { Display_print0(dispHandle, 2, 0, "Pairing success"); } else { Display_print1(dispHandle, 2, 0, "Pairing fail: %d", status); } } else if (state == GAPBOND_PAIRING_STATE_BONDED) { if (status == SUCCESS) { Display_print0(dispHandle, 2, 0, "Bonding success"); } } else if (state == GAPBOND_PAIRING_STATE_BOND_SAVED) { if (status == SUCCESS) { Display_print0(dispHandle, 2, 0, "Bond save success"); } else { Display_print1(dispHandle, 2, 0, "Bond save failed: %d", status); } } }
绑定之后便存在解绑过程,经实际测试,单独在主机端解除绑定,在下次连接时会进行重新配对绑定,若单独在从机端解除绑定,主机连接从机会一直提示连接失败。
解除绑定是在连接断开后才执行,解绑有两种方式:一种是解除所有绑定设备 :
此模式调用这个函数,便可实现,不再赘述。GAPBondMgr_SetParameter(GAPBOND_ERASE_ALLBONDS, 0, NULL);
另一种是只解除单个设备绑定:解除单个设备绑定过程由于对参数含义不清楚,实现此功能颇费周折,在此详细说下各个参数的含义。GAPBondMgr_SetParameter(GAPBOND_ERASE_SINGLEBOND, B_ADDR_LEN + 1, DevAddr);
参数1:GAPBONG_ERASE_SINGLEBONG这个参数很容易理解,解除单个设备绑定;
参数2:B_ADDR_LEN+1表示要解绑设备的地址长度加1,此处为何加1呢?详见参数3的说明;
参数3:此参数是由要解绑设备的地址类型和地址共同组合的一个数组,所以要在地址长度上加1,此外连接完成后的设备地址是由低字节向高字节的,而此处是需要待解绑地址的字节由高到低,所以需要反向字节,具体操作如下:uint8_t idx=0; idx = GAPBondMgr_ResolveAddr(pEvent->linkCmpl.devAddrType, pEvent->linkCmpl.devAddr, NULL);//找到待解绑设备的索引 //此处pEvent->linkCmpl.devAddrType是待解绑设备的地址类型,pEvent->linkCmpl.devAddr是待解绑地址,这是连接完成后的地址, Display_print1(dispHandle, 3, 0, "del idx: %d", idx); if(idx < GAP_BONDINGS_MAX)//在bond manager中找到地址 { DevAddr[0] = pEvent->linkCmpl.devAddrType; //此处将待解绑地址的地址类型复制在DevAddr的最低字节; memcpy(lsdevaddr, pEvent->linkCmpl.devAddr, B_ADDR_LEN); //此处将待解绑地址复制到lsdevaddr中 //反向字节 VOID osal_revmemcpy(&DevAddr[1],lsdevaddr,B_ADDR_LEN);//将lsdevaddr的顺序颠倒放置在DevAddr的1~B_ADDR_LEN+1字节中 } //最后将得到的字节传输至单个解绑的第三个参数 GAPBondMgr_SetParameter(GAPBOND_ERASE_SINGLEBOND, B_ADDR_LEN + 1, DevAddr);