nordic的nrf sdk希望我们设置uuid的方式跟蓝牙技术联盟SIG的方式一样,也就是服务和特性的uuid是基于同一个base uuid修改产生的,比如base uuid是0x0000xxxx-0000-1000-8000-00805F9B34FB,那么服务和特性的128bit uuid就要基于此base uuid通过修改其中的xxxx而生成,这里的xxxx称为16bit的uuid。一般来说,base uuid和16bit的xxxx都是自定义的,但是xxxx的位置一定是在128bit uuid的第三和第四个字节,这就是标准的限制。
在nus_c的例程里,有这样一段代码:
#define NUS_BASE_UUID {{0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E}} /**< Used vendor-specific UUID. */
#define BLE_UUID_NUS_SERVICE 0x0001 /**< The UUID of the Nordic UART Service. */
#define BLE_UUID_NUS_RX_CHARACTERISTIC 0x0002 /**< The UUID of the RX Characteristic. */
#define BLE_UUID_NUS_TX_CHARACTERISTIC 0x0003 /**< The UUID of the TX Characteristic. */
这段代码自定义了一个BASE_UUID,注意它是按小端模式存储的(跟一般顺序反过来的),数组里面的第13和第14个字节对应到128bit uuid的第4和第3个字节,也就是对应到xxxx这两个字节共16bit,数组里面设置为0了。
由以上代码可以知道,三个uuid分别为:
6E400001-B5A3-F393-E0A9-E50E24DCCA9E(16bit uuid为0x0001 )
6E400002-B5A3-F393-E0A9-E50E24DCCA9E(16bit uuid为0x0002 )
6E400003-B5A3-F393-E0A9-E50E24DCCA9E(16bit uuid为0x0002 )
然后,在ble_nus_c.c里面的ble_nus_c_init()函数里面,调用sd_ble_uuid_vs_add()函数将base uuid添加到协议栈:
ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_ble_nus_c->uuid_type);
其中p_ble_nus_c->uuid_type是协议栈返回的uuid类型,0为非法,1为sig的base uuid,往后的数字就是咱们自己添加到协议栈的uuid编号,也就是说,如果我们只添加了一个base uuid到协议栈,那么uuid_type就会被协议栈设置为2。
接下来是特征的发现,在ble_nus_c_on_db_disc_evt()里面。
void ble_nus_c_on_db_disc_evt(ble_nus_c_t * p_ble_nus_c, ble_db_discovery_evt_t * p_evt)
{
ble_nus_c_evt_t nus_c_evt;
memset(&nus_c_evt,0,sizeof(ble_nus_c_evt_t));
ble_gatt_db_char_t * p_chars = p_evt->params.discovered_db.charateristics;
// Check if the NUS was discovered.
if ( (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE)
&& (p_evt->params.discovered_db.srv_uuid.uuid == BLE_UUID_NUS_SERVICE)
&& (p_evt->params.discovered_db.srv_uuid.type == p_ble_nus_c->uuid_type))
{
for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
{
switch (p_chars[i].characteristic.uuid.uuid)
{
case BLE_UUID_NUS_RX_CHARACTERISTIC:
nus_c_evt.handles.nus_rx_handle = p_chars[i].characteristic.handle_value;
break;
case BLE_UUID_NUS_TX_CHARACTERISTIC:
nus_c_evt.handles.nus_tx_handle = p_chars[i].characteristic.handle_value;
nus_c_evt.handles.nus_tx_cccd_handle = p_chars[i].cccd_handle;
break;
default:
break;
}
}
if (p_ble_nus_c->evt_handler != NULL)
{
nus_c_evt.conn_handle = p_evt->conn_handle;
nus_c_evt.evt_type = BLE_NUS_C_EVT_DISCOVERY_COMPLETE;
p_ble_nus_c->evt_handler(p_ble_nus_c, &nus_c_evt);
}
}
}
在服务的if判断中,它判断了uuid_type以及16位的uuid是否和我们想要的一致,实际上由于一个uuid_type对应一个base uuid,由base uuid和16位uuid是可以得到完整的128bit uuid的,所以可以认为是进行了完整的服务uuid的对比,也就是:
接下来看swtich,这里是遍历属性表,并把需要用到的特性handle记录下来,注意看这里只对16bit uuid作了比较,这是因为nrf sdk认为特性的base uuid 应该是要和服务的 base uuid 相同,所以就省去了uuid_type的对比。
那么问题来了,有的厂商的设备不是按照上面的方式定义uuid,服务和特性的base uuid不同可咋办。比如说,有一个设备,它的uuid如下:
Service: 49535343-FE7D-4AE5-8FA9-9FAFD205E455
data Characteristic: 49535343-1E4D-4BD9-BA61-23C647249616
cmd Characteristic: 49535343-8841-43F4-A8D4-ECBE34729BB3
看着真的挺离谱的,不按标准来真的麻烦。那么如果要连接这个设备,并发现服务和特性,应该咋搞呢,那还能咋搞,那就认为有多个base uuid呗。
首先还是按照xxxx在第3和4字节的标准来定义三个base uuid和三个16bit uuid,注意base uuid是小端存储的,因此数组第14和13字节都设置为0.
#define SERVICE_UUID_BASE {{0x55,0xe4,0x05,0xd2,0xaf,0x9f,0xa9,0x8f,0xe5,0x4a,0x7d,0xfe,0x00,0x00,0x53,0x49}}
#define DATA_UUID_BASE {{0x16,0x96,0x24,0x47,0xC6,0x23,0x61,0xBA,0xD9,0x4B,0x4d,0x1e,0x00,0x00,0x53,0x49}}
#define CMD_UUID_BASE {{0xb3,0x9b,0x72,0x34,0xbe,0xec,0xd4,0xa8,0xf4,0x43,0x41,0x88,0x00,0x00,0x53,0x49}}
#define BLE_UUID_SERVICE 0x5343
#define BLE_UUID_DATA_CHARACTERISTIC 0x5343
#define BLE_UUID_CMD_CHARACTERISTIC 0x5343
然后到ble_nus_c_init()里面,把三个uuid全注册到协议栈里面。一个base uuid对应一个uuid_type,uuid_type实际上就是协议栈分配的编号,为了保存对应的uuid_type,设置了一些全局变量。
static ble_uuid_t data_uuid; //用于存储特性1的uuid_type
static ble_uuid_t cmd_uuid; //用于存储特性2的uuid_type
//服务的uuid_type记录在p_ble_nus_c里面了,而这本来就是个全局变量,所以不另外存储服务的uuid_type
****** 以上两个全局变量是在ble_nus_c_init()之外定义的 *****
*********************************************************
******** 以下语句是在ble_nus_c_init()内的语句 ************
ble_uuid128_t service_base_uuid = SERVICE_UUID_BASE;
ble_uuid128_t data_base_uuid = DATA_UUID_BASE;
ble_uuid128_t cmd_base_uuid = CMD_UUID_BASE;
err_code = sd_ble_uuid_vs_add(&service_base_uuid , &p_ble_nus_c->uuid_type);
VERIFY_SUCCESS(err_code);
err_code = sd_ble_uuid_vs_add(&data_base_uuid , &data_uuid.type);
VERIFY_SUCCESS(err_code);
err_code = sd_ble_uuid_vs_add(&cmd_base_uuid , &cmd_uuid.type);
VERIFY_SUCCESS(err_code);
data_uuid.uuid = BLE_UUID_DATA_CHARACTERISTIC;
cmd_uuid.uuid = BLE_UUID_CMD_CHARACTERISTIC;
以上语句向协议栈注册了三个base uuid,需要在sdk_config.h里面修改uuid的数量,如下图所示:
同时,这样操作会增大协议栈所需的RAM,因此如果出现报错,应该把 log level 提升到dubug级别,观察协议栈的log输出是否提示说内存不足,并根据log输出的建议修改app ram的起始大小和总大小。下图为修改log level的截图。
根据log的提示去修改IRAM1:
在向协议栈注册base uuid之后,接下来看服务和特性的发现函数,在ble_nus_c_on_db_disc_evt()里面。
void ble_nus_c_on_db_disc_evt(ble_nus_c_t * p_ble_nus_c, ble_db_discovery_evt_t * p_evt)
{
ble_nus_c_evt_t nus_c_evt;
memset(&nus_c_evt,0,sizeof(ble_nus_c_evt_t));
ble_gatt_db_char_t * p_chars = p_evt->params.discovered_db.charateristics;
if ( (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE) &&
(p_evt->params.discovered_db.srv_uuid.uuid == BLE_UUID_SERVICE) &&
(p_evt->params.discovered_db.srv_uuid.type == p_ble_nus_c->uuid_type) )
{
for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
{
if(p_chars[i].characteristic.uuid.uuid == BLE_UUID_CMD_CHARACTERISTIC && p_chars[i].characteristic.uuid.type == cmd_uuid.type){
nus_c_evt.handles.cmd_handle = p_chars[i].characteristic.handle_value;
}else if(p_chars[i].characteristic.uuid.uuid == BLE_UUID_DATA_CHARACTERISTIC && p_chars[i].characteristic.uuid.type == data_uuid.type){
nus_c_evt.handles.data_handle = p_chars[i].characteristic.handle_value;
nus_c_evt.handles.cccd_handle = p_chars[i].cccd_handle;
}
}
if (p_ble_nus_c->evt_handler != NULL)
{
nus_c_evt.conn_handle = p_evt->conn_handle;
nus_c_evt.evt_type = BLE_NUS_C_EVT_DISCOVERY_COMPLETE;
p_ble_nus_c->evt_handler(p_ble_nus_c, &nus_c_evt);
}
}
}
以上函数跟之前的唯一变化就是,在遍历属性库的时候,需要同时对比特性的uuid_type和16bit uuid,而之前是仅对比16bit uuid的。多做了uuid_type的对比判断,其实就是多做了uuid base的对比判断,有了uuid base的对比判断,就能准确识别处所需要特性的handle,哪怕本案例中所有16bit uuid是一样的。
1. 16bit uuid一定是在128bit uuid的第三和第四个字节,这是标准、是规范。
2. 按照规范来设置uuid,那么当只有一个base uuid 时,在对服务的uuid_type和16bit uuid进行对比之后,特性只需要对比16bit uuid即可。
3. 如果不按照规范,即服务和特性的base uuid不同时,就需要向协议栈注册多个base uuid,并记录下对应的uuid_type(可以认为是该base uuid在协议栈中的编号)。
4. 需要修改sdk_config.h中uuid的个数,修改应用的RAM起始地址和大小。
5. 遍历属性表时,无论是服务和特性,都需要同时对16bit uuid和uuid type进行对比判断。