该教程讲解如何实现用手机来动态修改设备名,达到手机连接设备后修改设备名称,然后断开连接再扫描能够看到新的设备名。
教程基于sdk9.0 下的uartdemo,如下目录中xxx\Keil_v5\ARM\Pack\NordicSemiconductor\nRF_Examples\9.0.0\ble_peripheral\ble_app_uart
Nordic的协议栈实现中默认都会有一个 Generic Access服务和Generic Attribute服务(有的BLE app可能显示不了名字,只显示这两个服务的UUID,分别是0x1800和0x1801)。
即一个工程中即使没有定义任何服务,烧写到板子上后,手机连接上也能看到这两个服务。
我们动态修改设备名就是利用了第一个服务Generic Access。
该服务为通用属性规范服务,改服务为设备提供了一种确定信息的方式,包括设备自身的名称,外观特性,首先连接参数等。
使用其中的 设备名属性,就能实现我们需要的动态修改设备名。
实现方式就是 手机连接上设备后访问这个服务下的设备名属性,然后通过这个属性写新的名字,设备这边判断手机发送过来的写是不是对Generic Access服务下的 设备名 属性的写操作。如果是就保存名字到flash中。并且更新设备名,这样当设备重启或者断开连接后 手机这边再扫描就能看到新的设备名字了。
PS:当然你也可以专门建立一个服务然后添加一个特征值用来当做修改名字用。接收手机发送过来的新名字然后存储修改。 不过既然默认存在的Generic Access服务中已经有了设备名属性,这里就直接用,不再去专门创建服务
因为涉及到flash的存储首先 添加flash相关的处理(关于flash的操作看前面的教程 如何在协议栈中使用flash)
//添加 sys_evt事件处理
static void sys_evt_dispatch(uint32_t sys_evt)
{
pstorage_sys_event_handler(sys_evt);
}
//协议栈初始化中注册上面的派发函数
static void ble_stack_init(void)
{
uint32_t err_code;
// Initialize SoftDevice.
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL);
………………
………………
………………
// Subscribe for BLE events.
err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);
APP_ERROR_CHECK(err_code);
//添加 sys_evt事件处理函数 注册。 flash需要用到
err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);
APP_ERROR_CHECK(err_code);
}
首先初始化flash,并且定义自己的flash操作完成后的回调函数,在main.c文件的最上面添加如下代码
//第一个字节存放的是标识符表示 flash中的数据是否是有效的 name
//device_name[0]=0xAA 表示是有效name,device_name[1]表示name的长度
#define NAME_SIZE 32
uint8_t device_name[NAME_SIZE];
pstorage_handle_t my_name_addr; //记录name存放的flash地址
//flash操作完成后的回调函数。 并没有做什么有用的事,但是注册flash块//的时候需要有回调函数所以这里需要定义
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("update end");
}
break;
}
}
//定义flash初始化函数
void my_flash_init(void){
uint32_t err_code;
pstorage_module_param_t param;
//申请一个块 用来存放name
param.block_count = 1;
param.block_size = NAME_SIZE;
param.cb = my_cb;
err_code = pstorage_init();
printf("init err_code:%d\r\n",err_code);
err_code = pstorage_register(¶m, &my_name_addr);
printf("register err_code:%d\r\n",err_code);
//加载flash内容。后面会判断name是否有效,如果有效就会用改name
//否则使用默认name
err_code = pstorage_load(device_name, &my_name_addr, NAME_SIZE, 0);
printf("load err_code:%d\r\n",err_code);
}
然后将该flash初始化函数放到main函数中
int main(void)
{
uint32_t err_code;
bool erase_bonds;
uint8_t start_string[] = START_STRING;
// Initialize.
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
uart_init();
//添加flash初始化,因为用到了打印需要放到uart_init函数之后
//因为gap_params_init函数中会判断使用flash中的名字还是默认名字
//所以需要放到该函数之前
my_flash_init();
buttons_leds_init(&erase_bonds);
ble_stack_init();
gap_params_init();
services_init();
advertising_init();
conn_params_init();
printf("%s\r\n",start_string);
err_code = ble_advertising_start(BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
// Enter main loop.
for (;;)
{
power_manage();
}
}
然后修改 main函数中调用的gap_params_init函数。该函数中判断my_flash_init 函数中加载的flash内容是否是有效name,是就使用,不是就用默认name.
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);
//flash数据有效则使用flash中的名字
if ( device_name[0] == 0xaa ){
//有效 使用新名字
//device_name[1]为名字长度
printf("update name\r\n");
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) device_name+2,
device_name[1]);
}else{
printf("default name\r\n");
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) DEVICE_NAME,
strlen(DEVICE_NAME));
}
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;
……………
…………
}
关于flash中的内容添加完了,然后是处理手机发送过来的新名字。
定义事件处理函数
static void name_change(ble_evt_t * p_ble_evt)
{
ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
//通过UUID来判断事件是不是写Generic Access服务中的名字属性
if((p_evt_write->context.char_uuid.uuid == BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME) &&
(p_ble_evt->header.evt_id == BLE_GATTS_EVT_WRITE))
{
printf("name change \r\n");
device_name[0] = 0xaa;
device_name[1] = p_evt_write->len;
memcpy(device_name+2, p_evt_write->data, p_evt_write->len);
pstorage_update(&my_name_addr, device_name, NAME_SIZE, 0 );
}
}
然后再将这个事件处理函数加到 事件派发函数ble_evt_dispatch中
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
name_change(p_ble_evt);
ble_conn_params_on_ble_evt(p_ble_evt);
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
on_ble_evt(p_ble_evt);
ble_advertising_on_ble_evt(p_ble_evt);
bsp_btn_ble_on_ble_evt(p_ble_evt);
}
到这里基本实现了 手机修改设备名字。不过连接上修改名字后断开然后点击扫描发现名字并未更新,需要复位一下硬件设备才能看到设备名字变了。
想在不复位的情况下,断开连接然后点扫描就能看到新的设备名还需要在断开连接后重新初始化 广播数据。
继续修改事件派发函数ble_evt_dispatch
//应为该函数是在下面定义的,这里使用到,所以要声明一下
void advertising_init(void);
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
name_change(p_ble_evt);
//添加代码,在断开连接事件后初始化广播数据
if ( p_ble_evt->header.evt_id == BLE_GAP_EVT_DISCONNECTED ){
advertising_init();
}
ble_conn_params_on_ble_evt(p_ble_evt);
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
on_ble_evt(p_ble_evt);
ble_advertising_on_ble_evt(p_ble_evt);
bsp_btn_ble_on_ble_evt(p_ble_evt);
}
现在编译后烧写到板子,手机连接后进入 Generic Access服务(有的可能只看到UUID 0x1800),然后点击device name(UUID 0x2A00)属性,写新名字。
然后断开连接。 点击扫描按键就可以看到新的设备名字了