前言
上篇已经通过一个LED读写实验,学习了如何添加一个私有服务。现在,我们就在LED读写实验的基础上再增加一个按键通知的功能。
实验分析
首先,我们先看看开发板的硬件连接:
从这里可以看出,button0和button1分别对应P0.16~P0.17引脚,当按下按键的时候引脚电平会被拉低。熟悉单片机开发的同学应该了解,这种情况我们的IO需要配置成上拉以提高引脚的抗干扰能力。
这里我们可以直接使用官方提供的应用驱动文件:工程目录\components\libraries\button\app_button.c和app_button.h文件
app_button.h:
#define APP_BUTTON_ACTIVE_HIGH 1 /**< Indicates that a button is active high. */ #define APP_BUTTON_ACTIVE_LOW 0 /**< Indicates that a button is active low. */ /**@brief Button configuration structure. */ typedef struct { uint8_t pin_no; /**< Pin to be used as a button. */ uint8_t active_state; /**< APP_BUTTON_ACTIVE_HIGH or APP_BUTTON_ACTIVE_LOW. */ nrf_gpio_pin_pull_t pull_cfg; /**< Pull-up or -down configuration. */ app_button_handler_t button_handler; /**< Handler to be called when button is pushed. */ } app_button_cfg_t;
这里可以看到分别定义了按键的电平和按键初始化结构体,这两个将会在c文件中被调用。
app_button.c:
uint32_t app_button_init(app_button_cfg_t * p_buttons, uint8_t button_count, uint32_t detection_delay) { uint32_t err_code; if (detection_delay < APP_TIMER_MIN_TIMEOUT_TICKS) { return NRF_ERROR_INVALID_PARAM; } if (!nrf_drv_gpiote_is_init()) { err_code = nrf_drv_gpiote_init(); if (err_code != NRF_SUCCESS) { return err_code; } } // Save configuration. mp_buttons = p_buttons; m_button_count = button_count; m_detection_delay = detection_delay; m_pin_state = 0; m_pin_transition = 0; while (button_count--) { app_button_cfg_t * p_btn = &p_buttons[button_count]; nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false); config.pull = p_btn->pull_cfg; err_code = nrf_drv_gpiote_in_init(p_btn->pin_no, &config, gpiote_event_handler); if (err_code != NRF_SUCCESS) { return err_code; } } // Create polling timer. return app_timer_create(&m_detection_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, detection_delay_timeout_handler); }
c文件中,我们比较关心的是按键的初始化函数,有兴趣的同学可以了解下,这里不再做深入分析,知道官方提供这个函数即可。
接着,我们需要回到上篇我们添加的LED服务驱动文件里面,将按键的特征值加上即可,这里不再开新的服务。
ble_lbs.c:
/**@brief Function for adding the Button Characteristic. * * @param[in] p_lbs LED Button Service structure. * @param[in] p_lbs_init LED Button Service initialization structure. * * @retval NRF_SUCCESS on success, else an error value from the SoftDevice */ static uint32_t button_char_add(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init) { ble_gatts_char_md_t char_md; ble_gatts_attr_md_t cccd_md; ble_gatts_attr_t attr_char_value; ble_uuid_t ble_uuid; ble_gatts_attr_md_t attr_md; memset(&cccd_md, 0, sizeof(cccd_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm); cccd_md.vloc = BLE_GATTS_VLOC_STACK; memset(&char_md, 0, sizeof(char_md)); char_md.char_props.read = 1; char_md.char_props.notify = 1; char_md.p_char_user_desc = NULL; char_md.p_char_pf = NULL; char_md.p_user_desc_md = NULL; char_md.p_cccd_md = &cccd_md; char_md.p_sccd_md = NULL; ble_uuid.type = p_lbs->uuid_type; ble_uuid.uuid = LBS_UUID_BUTTON_CHAR; memset(&attr_md, 0, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&attr_md.write_perm); attr_md.vloc = BLE_GATTS_VLOC_STACK; attr_md.rd_auth = 0; attr_md.wr_auth = 0; attr_md.vlen = 0; memset(&attr_char_value, 0, sizeof(attr_char_value)); attr_char_value.p_uuid = &ble_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = sizeof(uint8_t); attr_char_value.init_offs = 0; attr_char_value.max_len = sizeof(uint8_t); attr_char_value.p_value = NULL; return sd_ble_gatts_characteristic_add(p_lbs->service_handle, &char_md, &attr_char_value, &p_lbs->button_char_handles); }
这里先定义特征值添加函数,使能read和notify功能。这个函数将会在ble_lbs_init函数中被调用。
uint32_t ble_lbs_on_button_change(ble_lbs_t * p_lbs, uint8_t button_state) { ble_gatts_hvx_params_t params; uint16_t len = sizeof(button_state); memset(¶ms, 0, sizeof(params)); params.type = BLE_GATT_HVX_NOTIFICATION; params.handle = p_lbs->button_char_handles.value_handle; params.p_data = &button_state; params.p_len = &len; return sd_ble_gatts_hvx(p_lbs->conn_handle, ¶ms); }
最后,我们还要把按键处理回调函数添加上。这个函数将会在应用层被调用。其实,也可以使用sd_ble_gatts_value_set函数一次性设置特征值,但是这个函数是更新一个可读的值。当作为通知使用的时候,调用sd_ble_gatts_hvx不需要设置特征值和值的长度。
main.c:
* * @param[in] pin_no The pin that the event applies to. * @param[in] button_action The button action (press/release). */ static void button_event_handler(uint8_t pin_no, uint8_t button_action) { uint32_t err_code; switch (pin_no) { case LEDBUTTON_BUTTON_PIN: err_code = ble_lbs_on_button_change(&m_lbs, button_action); if (err_code != NRF_SUCCESS && err_code != BLE_ERROR_INVALID_CONN_HANDLE && err_code != NRF_ERROR_INVALID_STATE) { APP_ERROR_CHECK(err_code); } break; default: APP_ERROR_HANDLER(pin_no); break; } } /**@brief Function for initializing the button handler module. */ static void buttons_init(void) { uint32_t err_code; //The array must be static because a pointer to it will be saved in the button handler module. static app_button_cfg_t buttons[] = { {LEDBUTTON_BUTTON_PIN, APP_BUTTON_ACTIVE_LOW, BUTTON_PULL, button_event_handler} }; err_code = app_button_init(buttons, sizeof(buttons) / sizeof(buttons[0]), BUTTON_DETECTION_DELAY); APP_ERROR_CHECK(err_code); }
应用层中,我们主要做的是添加按键初始化函数和按键处理函数,按键处理函数在初始化函数中被注册。最后,将初始化函数添加到main函数中即可。
到这里,就已经完成了任务。
结果验证
- 手机连接蓝牙,我们可以看到有一个新增的UUID为1523的服务,在其下包含一个UUID为1525和一个UUID为1524的特制值;
- 1524特征值里包含了read和notify两个属性;
- 打开测试软件的监听功能,按下button0按键,会发现按下收到0x01,释放收到0x00;
总结
通过这个实验我们可以了解如何使用官方提供的应用驱动代码,快速添加按键服务。其他的串口也可以使用官方的代码尝试添加fifo功能等。