阅读的源代码:核心主要是位于BLE/project/SimpleBLEPeripheral部分
阅读的参考文档:TI_BLE_Software_Developer's_Guide.pdf,BLE_CC2540_DeepDive_Training_2011.pdf,TI_BLE_Sample_Applications_Guide.pdf,SIG的Core_V4.0.pdf
在BLE的源码架构中,感觉是好复杂,还好TI对协议栈不开源,不然就得累死。能力有限只能把整个架构从最简单的主从工作模式入手。
1.BLE中主从机建立连接,到配对和绑定的过程如下图。
正如上图所示,最简单一次蓝牙通信需要以上相关步骤,包括discovery device,connect,pairing,bond等4个主要部分。
2.BLE中的GAP和GATT
初始接触,感觉十分的抽象,到现在为止,GAP个人认为就是监控上图中的交互状态,比如从广播变成连接,到配对等。
GATT通俗理解为用于主从机之间的客户端和服务器端的数据交互,以Attribute Table来体现。
GAP Role Profile:在GAP剧本里所处的4个角色:广播Advertise,主机central,从机Peripheral,观察者Observer。
GATT Attribute:通用属性配置文件。
3.SimpleBLEPeripheral_Init函数解析。
无论是在主机还是从机,任何的蓝牙都将这部分内容作为自己的APPlication,在init中都会完成Bluetooth的GAP,GATT,SM等相关初始化。
void SimpleBLEPeripheral_Init( uint8 task_id )
{
simpleBLEPeripheral_TaskID = task_id;
// Setup the GAP Peripheral Role Profile //GAP外设剧本
{
#if defined( CC2540_MINIDK )
// For the CC2540DK-MINI keyfob, device doesn't start advertising until button is pressed
uint8 initial_advertising_enable = FALSE;
#else
// For other hardware platforms, device starts advertising upon initialization
uint8 initial_advertising_enable = TRUE;//广播使能
#endif
// By setting this to zero, the device will go into the waiting state after
// being discoverable for 30.72 second, and will not being advertising again
// until the enabler is set back to TRUE
uint16 gapRole_AdvertOffTime = 0;
uint8 enable_update_request = DEFAULT_ENABLE_UPDATE_REQUEST;//使能请求更新
uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL; //最小连接间隔0.1ms
uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL; //最大连接间隔 1ms
uint16 desired_slave_latency = DEFAULT_DESIRED_SLAVE_LATENCY;//0
uint16 desired_conn_timeout = DEFAULT_DESIRED_CONN_TIMEOUT;//10s,定义该参数表示在该时间段内建立的连接不成功
// Set the GAP Role Parameters
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );
GAPRole_SetParameter( GAPROLE_ADVERT_OFF_TIME, sizeof( uint16 ), &gapRole_AdvertOffTime );
GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( scanRspData ), scanRspData );//当主机扫描到广播后会发出扫描请求,从机就发回该数据到主机
GAPRole_SetParameter( GAPROLE_ADVERT_DATA, sizeof( advertData ), advertData );//广播参数
GAPRole_SetParameter( GAPROLE_PARAM_UPDATE_ENABLE, sizeof( uint8 ), &enable_update_request );
GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );
GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );
GAPRole_SetParameter( GAPROLE_SLAVE_LATENCY, sizeof( uint16 ), &desired_slave_latency );
GAPRole_SetParameter( GAPROLE_TIMEOUT_MULTIPLIER, sizeof( uint16 ), &desired_conn_timeout );
}
// Set the GAP Characteristics, Set GAP GATT Server parameter
GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName );//GAP GATT服务器参数设置
// Set advertising interval
{
uint16 advInt = DEFAULT_ADVERTISING_INTERVAL;
GAP_SetParamValue( TGAP_LIM_DISC_ADV_INT_MIN, advInt );
GAP_SetParamValue( TGAP_LIM_DISC_ADV_INT_MAX, advInt );
GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MIN, advInt );
GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MAX, advInt );
}
// Setup the GAP Bond Manager //GAP 绑定管理器设置
{
uint32 passkey = 0; // passkey "000000",密钥
uint8 pairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;//配对模式,配置成等待主机的配对请求
uint8 mitm = TRUE;
uint8 ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY;
uint8 bonding = TRUE;
GAPBondMgr_SetParameter( GAPBOND_DEFAULT_PASSCODE, sizeof ( uint32 ), &passkey );
GAPBondMgr_SetParameter( GAPBOND_PAIRING_MODE, sizeof ( uint8 ), &pairMode );
GAPBondMgr_SetParameter( GAPBOND_MITM_PROTECTION, sizeof ( uint8 ), &mitm );
GAPBondMgr_SetParameter( GAPBOND_IO_CAPABILITIES, sizeof ( uint8 ), &ioCap );
GAPBondMgr_SetParameter( GAPBOND_BONDING_ENABLED, sizeof ( uint8 ), &bonding );
}
// Initialize GATT attributes
GGS_AddService( GATT_ALL_SERVICES ); // GAP
GATTServApp_AddService( GATT_ALL_SERVICES ); // GATT attributes
DevInfo_AddService(); // Device Information Service
SimpleProfile_AddService( GATT_ALL_SERVICES ); // Simple GATT Profile
#if defined FEATURE_OAD
VOID OADTarget_AddService(); // OAD Profile
#endif
// Setup the SimpleProfile Characteristic Values,对相关数值做初始化
{
uint8 charValue1 = 1;
uint8 charValue2 = 2;
uint8 charValue3 = 3;
uint8 charValue4 = 4;
uint8 charValue5[SIMPLEPROFILE_CHAR5_LEN] = { 1, 2, 3, 4, 5 };
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR1, sizeof ( uint8 ), &charValue1 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR2, sizeof ( uint8 ), &charValue2 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR3, sizeof ( uint8 ), &charValue3 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR4, sizeof ( uint8 ), &charValue4 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5 );
}
#if defined( CC2540_MINIDK )
SK_AddService( GATT_ALL_SERVICES ); // Simple Keys Profile
// Register for all key events - This app will handle all key events
RegisterForKeys( simpleBLEPeripheral_TaskID );//注册按键,在这里当按键按下时,该任务会收到一个事件发来的消息
// makes sure LEDs are off
HalLedSet( (HAL_LED_1 | HAL_LED_2), HAL_LED_MODE_OFF );
// For keyfob board set GPIO pins into a power-optimized state
// Note that there is still some leakage current from the buzzer,
// accelerometer, LEDs, and buttons on the PCB.
P0SEL = 0; // Configure Port 0 as GPIO
P1SEL = 0; // Configure Port 1 as GPIO
P2SEL = 0; // Configure Port 2 as GPIO
P0DIR = 0xFC; // Port 0 pins P0.0 and P0.1 as input (buttons),
// all others (P0.2-P0.7) as output
P1DIR = 0xFF; // All port 1 pins (P1.0-P1.7) as output
P2DIR = 0x1F; // All port 1 pins (P2.0-P2.4) as output
P0 = 0x03; // All pins on port 0 to low except for P0.0 and P0.1 (buttons)
P1 = 0; // All pins on port 1 to low
P2 = 0; // All pins on port 2 to low
#endif // #if defined( CC2540_MINIDK )
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
#if defined FEATURE_OAD
#if defined (HAL_IMAGE_A)
HalLcdWriteStringValue( "BLE Peri-A", OAD_VER_NUM( _imgHdr.ver ), 16, HAL_LCD_LINE_1 );
#else
HalLcdWriteStringValue( "BLE Peri-B", OAD_VER_NUM( _imgHdr.ver ), 16, HAL_LCD_LINE_1 );
#endif // HAL_IMAGE_A
#else
HalLcdWriteString( "BLE Peripheral", HAL_LCD_LINE_1 );
#endif // FEATURE_OAD
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
// Register callback with SimpleGATTprofile
VOID SimpleProfile_RegisterAppCBs( &simpleBLEPeripheral_SimpleProfileCBs );//给应用注册回调函数
// Enable clock divide on halt
// This reduces active current while radio is active and CC254x MCU
// is halted
HCI_EXT_ClkDivOnHaltCmd( HCI_EXT_ENABLE_CLK_DIVIDE_ON_HALT );
#if defined ( DC_DC_P0_7 )
// Enable stack to toggle bypass control on TPS62730 (DC/DC converter)
HCI_EXT_MapPmIoPortCmd( HCI_EXT_PM_IO_PORT_P0, HCI_EXT_PM_IO_PORT_PIN7 );
#endif // defined ( DC_DC_P0_7 )
// Setup a delayed profile startup
osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT );//标志SBP_START_DEVICE_EVT启动该event
4.GAP部分的初始化和相关API以及,事件的处理过程。
在上3中的init中可以看到GAP作为Peripheral Role需要设置的核心参数如下
GAPROLE_ADVERT_ENABLED:广播使能。
GAPROLE_ADVERT_DATA:广播时的参数,
GAPROLE_SCAN_RSP_DATA:从机扫描响应,返回的数据包
GAPROLE_MIN_CONN_INTERVAL:处于连接状态后的设备,都会有个hop,一段时间内进行数据交互,以保证两者是连接的。当前后两次交互时,需要等待的最小间隔时间
GAPROLE_MAX_CONN_INTERVAL:...需要等待的最大间隔时间
GAPROLE_SLAVE_LATENCY:处于连接后,从机可以做出不响应连接请求的间隔数目,即跳过n个交互的连接。
GAPROLE_TIMEOUT_MULTIPLIER:从上次成功连接到这次连接成功的最大允许延时。如果规定时间内未成功则认为本次连接失败,丢弃。该值必须比有效连接的间隔大。
GAPROLE_PARAM_UPDATE_ENABLE:请求主机更新参数,主机可以接受也可以拒绝。
GAP通过在启动设备事件的任务处理中启动设备,其实主要是向GAP中注册回调函数,让系统在发现自身运行状态变化时,调用该函数,方便应用层进行相关操作。
VOID GAPRole_StartDevice( &simpleBLEPeripheral_PeripheralCBs );//启动设备,注册回调函数,用于监督设备的状态变化:广播、连接、配对、绑定等。
static void peripheralStateNotificationCB( gaprole_States_t newState )//传入参数由GPA自己输入,内部调用回调函数给用户
switch ( newState )
{
case GAPROLE_STARTED://在GAPRole_StartDevice后发生状态变化
{
uint8 ownAddress[B_ADDR_LEN];
uint8 systemId[DEVINFO_SYSTEM_ID_LEN];
GAPRole_GetParameter(GAPROLE_BD_ADDR, ownAddress);
// use 6 bytes of device address for 8 bytes of system ID value
systemId[0] = ownAddress[0];
systemId[1] = ownAddress[1];
systemId[2] = ownAddress[2];
// set middle bytes to zero
systemId[4] = 0x00;
systemId[3] = 0x00;
// shift three bytes up
systemId[7] = ownAddress[5];
systemId[6] = ownAddress[4];
systemId[5] = ownAddress[3];
DevInfo_SetParameter(DEVINFO_SYSTEM_ID, DEVINFO_SYSTEM_ID_LEN, systemId);
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
// Display device address
HalLcdWriteString( bdAddr2Str( ownAddress ), HAL_LCD_LINE_2 );
HalLcdWriteString( "Initialized", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
case GAPROLE_ADVERTISING:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "Advertising", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
case GAPROLE_CONNECTED:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "Connected", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
case GAPROLE_WAITING:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "Disconnected", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
case GAPROLE_WAITING_AFTER_TIMEOUT:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "Timed Out", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
case GAPROLE_ERROR:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "Error", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
default:
{
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteString( "", HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
}
break;
}
可以看到GAP Role 作为外设从机时,各种状态的变化,而这些状态 变化都由GAP(不开源部分)调用回调函数,将当前状态参数传入,以使得不同的APP做出反应。体现了一种回调函数设计的便捷性。
5.GATT Server的相关设置函数。
// Initialize GATT attributes
GGS_AddService( GATT_ALL_SERVICES ); // GAP Service
GATTServApp_AddService( GATT_ALL_SERVICES ); // GATT attributes
DevInfo_AddService(); // Device Information Service
SimpleProfile_AddService( GATT_ALL_SERVICES ); // Simple GATT Profile
通常一个GATT中GAP server和GATT server是必须强制存在的(Mandatory)以及自己设计的profile server.
作为GATT的server和client,主要通过Attribute来进行交互,当client请求server读取数据时,通过如下注册的回调函数来进行访问。
// Register callback with SimpleGATTprofile
VOID SimpleProfile_RegisterAppCBs( &simpleBLEPeripheral_SimpleProfileCBs );//给应用注册回调函数
在回调函数中对时间做出处理。