前言
很多时候我们希望能够通过手机动态修改设备的MAC地址、配对密码或者蓝牙设备信息等,其实原理上都是可以实现的。大概思路其实就是把我们需要修改的内容发送到FLASH中保存,然后再用FLASH中的内容替代原先的内容。最后重启服务就行了。这里我们通过修改设备的名称来验证一下。
实验分析
官方的协议栈实现中默认都会提供Generic Access(GAP)服务(UUID:0x1800)和Generic Attribute(GATT)服务(UUID:0x1801)。
/* GATT specific UUIDs */
#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */
#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */
/* GAP specific UUIDs */
#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */
#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */
#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */
#define BLE_UUID_GAP_CHARACTERISTIC_PPF 0x2A02 /**< Peripheral Privacy Flag Characteristic. */
#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */
#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */
/** @} */
即便一个工程中没有定义任何服务,程序烧写到板子上,手机连接上也能看到这两个服务。
工程名称就是利用了第一个服务Generic Access。该服务为通用属性规范服务,改服务为设备提供了一种确定信息的方式,包括设备自身的名称,外观特性,首先连接参数等。使用其中的设备名属性,就能实现我们需要的动态修改设备名称。
实现方式就是手机连接上设备后访问这个服务下的设备名属性,然后通过这个属性写新的名字,设备这边判断手机发送过来的写是不是对Generic Access服务下的设备名属性的写操作。如果是就保存名字到FLASH中,更新设备名。这样当设备重启或者断开连接后手机这边再扫描就能看到新的设备名字了。
同样,我们在上篇的代码上面做修改。
首先考虑的是修改名称的问题,也就是把设备名称如何更新,手机发送名称数据过来,定义这个处理函数如下,通过UUID来判断事件是不是写Generic Access,并更新到FLASH存储器中。
第一个字节存放的是标识符表示FLASH中的数据是否有效的name,device_name[0] = 0xB5表示有效name,device_name[1]表示name的长度。
static void Device_name_change(ble_evt_t * p_ble_evt)
{
pstorage_handle_t dest_block_id;
ble_gatts_evt_write_t *p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
if((p_evt_write->context.char_uuid.uuid == BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME)
&& (p_ble_evt->header.evt_id == BLE_GATTS_EVT_WRITE))
{
device_name[0] = 0xB5;
device_name[1] = p_evt_write->len;
memcpy(device_name+2, p_evt_write->data, p_evt_write->len);
pstorage_block_identifier_get(&NameAddr,0,&dest_block_id);
pstorage_update(&NameAddr, &device_name[0],16,0);
}
}
然后将这个事件处理函数加到事件派发函数ble_evt_dispatch中:
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
//Sam Add
Device_name_change(p_ble_evt);
if(p_ble_evt->header.evt_id == BLE_GAP_EVT_DISCONNECTED)
{
advertising_init();
}
//
dm_ble_evt_handler(p_ble_evt);
ble_conn_params_on_ble_evt(p_ble_evt);
bsp_btn_ble_on_ble_evt(p_ble_evt);
on_ble_evt(p_ble_evt);
ble_advertising_on_ble_evt(p_ble_evt);
/*YOUR_JOB add calls to _on_ble_evt functions from each service your application is using
ble_xxs_on_ble_evt(&m_xxs, p_ble_evt);
ble_yys_on_ble_evt(&m_yys, p_ble_evt);
*/
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
ble_bas_on_ble_evt(&m_bas, p_ble_evt);
}
当然,这之前需要对FLASH进行初始化,初始化过程在前面的实验中有讲过。
![](/qrcode.jpg)
static void my_cb(pstorage_handle_t *handle, uint8_t op_code, uint32_t result,uint8_t *p_data, uint32_t data_len)
{
switch(op_code)
{
case PSTORAGE_UPDATE_OP_CODE:
if(result == NRF_SUCCESS)
{
printf("end");
}
else
{
}
break;
}
}
void flash_init(void)
{
pstorage_handle_t dest_block_id;
uint32_t err_code;
pstorage_module_param_t param;
param.block_count = 1;
param.block_size = 16;//块的大小为16
param.cb = my_cb;
err_code = pstorage_init();
err_code = pstorage_register(¶m, &NameAddr);
pstorage_block_identifier_get(&NameAddr,0,&dest_block_id);
err_code = pstorage_load(device_name, &dest_block_id, 16, 0);
APP_ERROR_CHECK(err_code);
}
其中,回调函数必须写。
同样,协议栈的派发函数也要添加进去,这样FLASH的存储才能够协议栈状态下运行。在协议栈中当FLASH操作完成后,需要对sys_evt事件进行处理,sd协议栈会上抛给app相应的sys_evt事件(类似sd会上抛给APP BLE的事件)。同时因为pstorage的实现是基于状态机的。比如我们上面使用的update,它的实现是分步做的,先擦除交换区,然后将数据写到交换区,然后修改再写回。每个时刻下一步要做的事都由当前的操作返回后的状态机决定。所以pstorage需要获得sys_evt(FLASH的操作返回),然后进行下一步的处理。
static void ble_stack_init(void)
{
uint32_t err_code;
......
......
// Register with the SoftDevice handler module for BLE events.
err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);
APP_ERROR_CHECK(err_code);
}
函数处理FLASH访问结果事件,这个函数在官方库中已经给出,直接调用即可:
static void sys_evt_dispatch(uint32_t sys_evt)
{
pstorage_sys_event_handler(sys_evt);
......
......
}
前面设置了传输第一个字符为0xB5,为判断是否传输是否正确,当然也可以用其他字符。那么在gap_params_init函数中修改,添加修改名称判断,如果判断传输正确,收到的是正确的名称,则设置GAP设备名称。
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
// err_code = sd_ble_gap_device_name_set(&sec_mode,
// (const uint8_t *)DEVICE_NAME,
// strlen(DEVICE_NAME));
//Sam Add
if(device_name[0] == 0xB5)
{
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *)(device_name+2),
device_name[1]);
}
else
{
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *)DEVICE_NAME,
strlen(DEVICE_NAME));
}
//
APP_ERROR_CHECK(err_code);
/* YOUR_JOB: Use an appearance value matching the application's use case.
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_);
APP_ERROR_CHECK(err_code); */
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_UNKNOWN);
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
}
前面已经实现了手机发送名称--->蓝牙接收存储--->判断是否正确名称--->修改Generic Access的过程。
但是连接的时候要显示新的名称,就需要重新来启动服务了。所以需要在事件派发函数中添加:
Device_name_change(p_ble_evt);
if(p_ble_evt->header.evt_id == BLE_GAP_EVT_DISCONNECTED)
{
advertising_init();
}
这样,当设备连接断开时就会重启广播。
结果验证
- 开发板串口连接PC,手机连接蓝牙
- 打开服务,写入新的名称
- 重连,就可以发现设备名称已经更新
总结
通过这个实验,我们学会了如何使用动态修改设备名称。