基于NXP的蓝牙BLE协议栈代码分析

重要概念点总结

1.profile 规范。包含有service服务,如电量。
2.service,每一个服务可能包含一个或多个特征值。
3.characteristic 特征值。通信载体,电量为20%,20%即是特征值的value。主从机之间通信,通过读写特征值实现。
4.UUID 统一识别码。刚才提到的service和characteristic,都需要一个唯一的uuid来标识。

连接与通信过程中,用到协议栈这两层
GAP 主要进行建立连接的过程,类似握手。
GATT 传输的数据段通用规范。这些很短的数据段被称为属性(Attribute),主要包括service服务、characteristic 特征值,特征值描述characteristic descriptors。

通信过程:由中心设备( 客户端client,主站)发起,外围设备(服务器server,从站)响应。一般情况下,客户端进行读写特征值,实现通信。服务器还可以通过发“通知”(Notification)或“指示”(indication)进行通信。
Notification 服务器发出数据,无需客户端回复。
indication 服务器发出数据,需要客户端回复后再继续发。

代码分析

Main Task 是主任务,初始化。
App_Thread 和Main Task共用堆栈。处理协议栈发送的所有事件和消息。
App_Thread 主要通过App_HandleHostMessageInput处理协议栈发送给APP的队列。通过回调机制处理队列中的消息。

开启调用流程:
App_Thread->App_HandleHostMessageInput->BleApp_GenericCallback->BleApp_Config->BleApp_Start->App_StartScanning or BleApp_Advertise。

以下以主站代码进行分析:
一. 协议栈开启前注册回调函数

    App_RegisterGattServerCallback(BleApp_GattServerCallback);
    App_RegisterGattClientProcedureCallback(BleApp_GattClientCallback);
    GattServer_RegisterHandlesForWriteNotifications(NumberOfElements((mCharMonitoredHandles)), mCharMonitoredHandles);
    BleServDisc_RegisterCallback(BleApp_ServiceDiscoveryCallback);
    App_RegisterGattClientNotificationCallback(BleApp_GattNotificationCallback);

	//App_RegisterGattClientIndicationCallback(BleApp_GattNotificationCallback); 

作为主站,重点关注
1.BleApp_GattClientCallback
2.BleApp_ServiceDiscoveryCallback
这俩个回调,前者是客户端通用回调,客户端的所有操作的响应都会在这个回调中触发,在如客户端发现服务,读写特征值,读写特征值描述等等。
后者是服务发现回调,在发现服务后,可对服务里的特征值句柄进行保存。

二. 开启扫描函数:

App_StartScanning(&gScanParams, BleApp_ScanningCallback, FALSE);

开启扫描或广播后,在对应回调函数中进行判断并建立连接。

static void BleApp_ScanningCallback (gapScanningEvent_t* pScanningEvent)
{
	bleResult_t result = gBleStatusBase_c;
    switch (pScanningEvent->eventType)
    {
        case gDeviceScanned_c:
        {
            if (BleApp_CheckScanEvent(&pScanningEvent->eventData.scannedDevice))
            {        
                gConnReqParams.peerAddressType = pScanningEvent->eventData.scannedDevice.addressType;
                FLib_MemCpy(gConnReqParams.peerAddress, 
                            pScanningEvent->eventData.scannedDevice.aAddress,
                            sizeof(bleDeviceAddress_t));
                
                Gap_StopScanning();
				result = App_Connect(&gConnReqParams, BleApp_ConnectionCallback);
            }
        }        
        break;
        
        case gScanStateChanged_c:
        {
            mScanningOn = !mScanningOn;
                /* Node starts scanning */
            if (mScanningOn)
            {
                /* Start advertising timer */
                TMR_StartLowPowerTimer(mAppTimerId, 
                           gTmrLowPowerSecondTimer_c,
                           TmrSeconds(gScanningTime_c),
                           ScaningTimerCallback, NULL);  

                Led1Flashing();
            }
            /* Node is not scanning */
            else
            {                
                 TMR_StopTimer(mAppTimerId);   
            }
        }
        break;
    case gScanCommandFailed_c:
    {
        panic(0, 0, 0, 0);
        break;
    }
    default:
        break;
    }
}

扫描回调主要有两种事件:
1.已经扫描到设备(gDeviceScanned_c )
2.扫描状态改变(gScanStateChanged_c),①从一开始的不扫描到开始扫描,②从扫描到停止扫描。
注意这里开启扫描后开了一个定时器,时间到后调用回调ScaningTimerCallback,这里面会关闭扫描,这样就实现了扫描超时机制。但调试时发现并不是一开启扫描就可以扫到对应设备,所以可以将关闭扫描的代码干掉,让主机一直扫

三 . 扫描设备校验函数:

BleApp_CheckScanEvent(&pScanningEvent->eventData.scannedDevice)

在这个函数中校验需要连接的从站MAC地址,返回TURE,即可开启连接。

四. 连接回调

BleApp_ConnectionCallback

连接回调函数中调用开启状态机

BleApp_StateMachineHandler(peerDeviceId, mAppEvt_PeerConnected_c);

五. 状态机

BleApp_StateMachineHandler

mAppExchangeMtu_c状态下发现服务,可以通过UUID发现特定服务,也可以直接发现所有Primary服务。

BleServDisc_Start(peerDeviceId);      //封装后的发现所有服务函数
BleServDisc_FindService(peerDeviceId, 
                        gBleUuidType128_c,
                        (bleUuid_t*) &uuid_service_wireless_uart)
                        //封装后的通过UUID发现特定服务

六 .发现服务回调
发现服务后其实先回调了BleApp_GattClientCallback,在BleServDisc_SignalGattClientEvent中发现服务的所有特征值。这步无需修改。
需要关注的是BleApp_ServiceDiscoveryCallback,在这个回调中校验发现服务的UUID,然后校验特征值的UUID,保存服务需要的特征值句柄。
eg.保存一个16bitUUID的gBleSig_BatteryLevel_d特征值句柄

static void BleApp_StoreServiceHandles (deviceId_t peerDeviceId, gattService_t *pService)
{
    
    maPeerInformation[peerDeviceId].clientInfo.hService = pService->startHandle;
	for (uint8_t i = 0; i < pService->cNumCharacteristics; i++)
	{
		if ((pService->aCharacteristics[i].value.uuidType == gBleUuidType16_c) &&
			(pService->aCharacteristics[i].value.uuid.uuid16 == gBleSig_BatteryLevel_d))
			//校验服务中的特征值UUID
		{
		maPeerInformation[peerDeviceId].clientInfo.hUartStream = pService->aCharacteristics[i].value.handle;  //保存特征值句柄
		}
	}
}

有了句柄之后 可以在状态机中对特征值进行读写操作。完成通信。✌

主站开启通知的流程

在和一个从站通信过程中,从站在不停发送通知,但是主站无法触发通知接收的回调

App_RegisterGattClientNotificationCallback(BleApp_GattNotificationCallback);

这时主站需要使能通知接收,就是修改特征值描述的CCCD描述。使能流程:
1.确定从站发送通知的服务的特征值,读取对应特征值句柄。
2.通过特征值句柄发现特征值描述

myChar. aDescriptors = aDescriptors;
myChar.value.handle = maPeerInformation[peerDeviceId].clientInfo.hUartStream;
	bleResult_t result = GattClient_DiscoverAllCharacteristicDescriptors
	(
		peerDeviceId,
		&myChar,
		0xFFFF,
		mcMaxDescriptors_c
	);

3.在客户端回调函数中,在发现的特征值描述中找到CCCD,将使能通知的FLAG写入特征值描述

	 case gGattProcDiscoverAllCharacteristicDescriptors_c:
	 if (gGattProcSuccess_c == procedureResult)
	 {
		 /* Find CCCD */
		 for ( uint8_t j = 0; j < myChar. cNumDescriptors ; j++)
		 {
			 if (gBleUuidType16_c == myChar. aDescriptors [j]. uuidType
					 && gBleSig_CCCD_d == myChar. aDescriptors [j]. uuid . uuid16 )
			{
			 uint16_t value = gCccdNotification_c;
			 //packTwoByteValue(gCccdNotification_c, cccdValue); //官方给的伪代码有问题
			 bleResult_t result = GattClient_WriteCharacteristicDescriptor
									 (
										 serverDeviceId,
										 &myChar. aDescriptors [j],
										 sizeof(value),
										 (void*)&value
									 );
				 if (gBleSuccess_c != result)
				 {
				 /* Handle error */
				 }
	 			break;
	 		}
	 	}
	 }

4.继续在客户端回调中,读取特征值写入结果

	 case gGattProcWriteCharacteristicDescriptor_c:
	 if (gGattProcSuccess_c == procedureResult)
	 {
            int text = 1; //写入成功
		/* Notification successfully activated */
	 }
		else
		{
			/* Handle error */
			
		}

至此对应特征值的通知接收已经被使能,可通过通知接收回调函数接收通知数据。

扩展:

1.建立连接后最重要的一个步骤是交互MTU,主从站都可以发起。GattClient_ExchangeMtu()发起交互,在client_callback中回调。若从站发起,主站会在server_callback()中回调。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值