CubieBoard博客-HomeCubieTech公司网站

cubie.cc CubieBoard中文论坛

 找回密码
 立即注册
搜索
热搜: unable
查看: 1596|回复: 0

初探BLE 蓝牙电池服务

[复制链接]

114

主题

1

好友

1万

积分

版主

Rank: 7Rank: 7Rank: 7

贡献
2521
金钱
5851
威望
2521
买家信用
卖家信用
积分
13880
发表于 2018-9-22 11:42:21 |显示全部楼层
BLE电池服务是有关电池电量方面的服务,如果需要它,在初始化时将它加入到蓝牙协议栈,电池任务是蓝牙兴趣小组SIG定义的公有服务,UUID是固定的,在蓝牙技术联盟 官网上可以查询到哪些是公有服务:

点击后面的版本号可以下载相应的配置文件规格和服务规格,这里请下载电池规格,后面需要使用。

先看下设备启动后,手机连接设备后APP上的效果:

1:标明这个一个电池服务(Service)
2:电池服务的UUID
3:电池level特性(Characteristic)
4:电池特性UUID
5:电池特性的属性(Properties)
6 : 电池描述(Descriptor)
7:电池描述的UUID
8:使能通知(CCCD)
9:读取属性值,按下APP显示电池电量
10:使能通知。按下标识10将改变

依次按下8,9效果如下:



看了上面这个电池profile结构,我们再来对比看基本profile结构就更加清晰了:


下面看看SDK中是如何初始化电池服务:

在main.c中app_main函数的bt_enable(bt_ready)是初始化ble相关服务的入口函数,定位到这个函数:
/** @brief Enable Bluetooth
*
*  Enable Bluetooth. Must be the called before any calls that
*  require communication with the local Bluetooth hardware.
*
*  @param cb Callback to notify completion or NULL to perform the
*  enabling synchronously.
*
*  @return Zero on success or (negative) error code otherwise.
*/
int bt_enable(bt_ready_cb_t cb);
这是个使能ble的函数,关键看下调用时传入的参数定义:

__init_once_text static void bt_ready(int err)
{
        if (err) {
                printk("Bluetooth init failed (err %d)\n", err);
                return;
        }

        printk("Bluetooth initialized\n");
          bt_ready_flag = 1;
        bas_init(adc_batt_read);
        dis_init(CONFIG_SOC, "PT corp.");/* TODO */
        if (!use_wp_profie) {
                HidInit(&hidAppHidConfig);
                hog_init();
        } else {
                wp_init();
        }
#if CONFIG_OTA_WITH_APP
        wdxs_profile_init();
#endif
    /* clear overlay bss
    memset((void *)Image$$RW_IRAM_INIT_ONCE$$Base, 0, (u32_t)Image$$RW_IRAM3$$Base - (u32_t)Image$$RW_IRAM_INIT_ONCE$$Base);*/
        rmc_sm_execute(RMC_MSG_POWER_UP);

}

这里初始化了很多服务,其中有电池服务,设备信息服务,HID设备,私有服务等,这里我们看下电池服务的初始化:

__init_once_text void bas_init(hw_read_batt_func_t func)
{
        bt_gatt_service_register(&bas_svc);
        hw_read_batt_func = func;
}

func为bas_init(adc_batt_read)调用时传入adc_batt_read参数,用于更新APP上的电池电量。adc_batt_read定义如下:
void adc_batt_read(uint8_t *pLevel)
{
        *pLevel = batt_level;
}
batt_level这个参数为电池adc采集到的数据,电池数据采集在system_app_batt.c中完成。

bt_gatt_service_register为蓝牙协议栈API来注册电池服务,看下声明:
/* Server API */

/** @brief Register GATT service.
*
*  Register GATT service. Applications can make use of
*  macros such as BT_GATT_PRIMARY_SERVICE, BT_GATT_CHARACTERISTIC,
*  BT_GATT_DESCRIPTOR, etc.
*
*  @param svc Service containing the available attributes
*
*  @return 0 in case of success or negative value in case of error.
*/
int bt_gatt_service_register(struct bt_gatt_service *svc);
参数传入服务的属性,看下电池的属性:

/* Battery Service Declaration */
static struct bt_gatt_attr attrs[] = {
        BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS),
        BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL,
                               BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY),
        BT_GATT_DESCRIPTOR(BT_UUID_BAS_BATTERY_LEVEL, BT_GATT_PERM_READ,
                           read_blvl, NULL, &battery),
        BT_GATT_CCC(blvl_ccc_cfg, blvl_ccc_cfg_changed),
};
static struct bt_gatt_service bas_svc = BT_GATT_SERVICE(attrs);

这里使用了很多蓝牙协议栈的API:
1、BT_GATT_PRIMARY_SERVICE
-声明电池服务的UUID及属性,电池的UUID是公有的,不可自定义,可读属性。
2、BT_GATT_CHARACTERISTIC
-声明电池特征属性,这里需要查看刚才下载的电池配置文件说明,看下电池服务需要什么:


上面指出电池服务需要Battery Level这个特性,可读不可写,并且是通知类型

3、BT_GATT_DESCRIPTOR声明描述符属性,电池配置文件说明中也对这方面有说明:


图中指出可读并且是通知类型,battery为电池电量

这里关键的是read_blvl,定义如下:
static ssize_t read_blvl(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                         void *buf, u16_t len, u16_t offset)
{
        if (hw_read_batt_func) {
                (hw_read_batt_func)(&battery);
        }
        const char *value = attr->user_data;

        return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
                                 sizeof(*value));
}
这里同步hw_read_batt_func和battery的值,bt_gatt_attr_read读取属性值,将结果存储到缓冲区中。当按下APP图上的标号“9”会执行该函数读取电池电量。

bt_gatt_attr_read声明如下:
/** @brief Generic Read Attribute value helper.
*
*  Read attribute value storing the result into buffer.
*
*  @param conn Connection object.
*  @param attr Attribute to read.
*  @param buf Buffer to store the value.
*  @param buf_len Buffer length.
*  @param offset Start offset.
*  @param value Attribute value.
*  @param value_len Length of the attribute value.
*
*  @return int number of bytes read in case of success or negative values in
*  case of error.
*/
ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                          void *buf, u16_t buf_len, u16_t offset,
                          const void *value, u16_t value_len);

4、BT_GATT_CCC声明客户端特征配置,通知类型的使能
void bas_notify(void)
{
        if (!simulate_blvl) {
                return;
        }
        
        if (hw_read_batt_func) {
                (hw_read_batt_func)(&battery);
        }

        bt_gatt_notify(NULL, &attrs[2], &battery, sizeof(battery));
}
如果APP图上“8”不使能,上述函数中的simulate_blvl为0,APP将不更新实际电池电量。

上面只是讲解了如何初始化电池服务及注册服务,并且电池数据来自于batt_level,但又是如何更新电池值呢?

更新电池电量采用事件定时触发的方式:
void system_app_timer_event_handle(u32_t timer_event)
{
        switch(timer_event) {
                case ID_ADV_TIMEOUT:
                        rmc_sm_execute(RMC_MSG_TIMER_ADV_TIMEOUT);
                        break;
                case ID_DIRECT_ADV_TIMEOUT:
                        rmc_sm_execute(RMC_MSG_TIMER_DIRECT_ADV_TIMEOUT);
                        break;
                case ID_NO_ACT_TIMEOUT:
                        rmc_sm_execute(RMC_MSG_TIMER_NO_ACT_TIMEOUT);
                        break;
                case ID_ADC_BATTERY_TIMEOUT:
                        update_battry_value();
                        bas_notify();
                        rmc_timer_start(ID_ADC_BATTERY_TIMEOUT);
                        break;
                case ID_PAIR_COMB_KEY_TIMEOUT:
                        rmc_clear_pair_info();
                        rmc_sm_execute(RMC_MSG_CLEAR_PIAR_KEY);
                        break;
                default:
                        printk(" error unkown timer event %d \n",timer_event);
                        break;        
        }
}

当定时器达到这个ID_ADC_BATTERY_TIMEOUT时间,就会执行该case分支更新电池电量,
update_battry_value();
bas_notify();

update_battry_value函数采集实际的电池电量保存到batt_level变量,bas_notify()为电池通知,定义如下:
void bas_notify(void)
{
        if (!simulate_blvl) {
                return;
        }
        
        if (hw_read_batt_func) {
                (hw_read_batt_func)(&battery);
        }

        bt_gatt_notify(NULL, &attrs[2], &battery, sizeof(battery));
}
通过下面函数的调用hw_read_batt_func 即为batt_level,再通过hw_read_batt_func把电池电量给battery。bas_init(adc_batt_read);

void adc_batt_read(uint8_t *pLevel)
{
        *pLevel = batt_level;
}

__init_once_text void bas_init(hw_read_batt_func_t func)
{
        bt_gatt_service_register(&bas_svc);
        hw_read_batt_func = func;
}

再看下bt_gatt_notify函数的声明:
/** @brief Notify attribute value change.
*
*  Send notification of attribute value change, if connection is NULL notify
*  all peer that have notification enabled via CCC otherwise do a direct
*  notification only the given connection.
*
*  The attribute object must be the so called Characteristic Value Descriptor,
*  its usually declared with BT_GATT_DESCRIPTOR after BT_GATT_CHARACTERISTIC
*  and before BT_GATT_CCC.
*
*  @param conn Connection object.
*  @param attr Characteristic Value Descriptor attribute.
*  @param data Pointer to Attribute data.
*  @param len Attribute value length.
*/
int bt_gatt_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                   const void *data, u16_t len);

通知属性值变化,发送属性值更改通知
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

QQ|Archiver|手机版|邮件群发|cubie.cc---深刻的嵌入式技术和应用讨论中文社区 ( 粤ICP备13051116号-1  

GMT+8, 2019-12-6 19:39 , Processed in 0.016013 second(s), 7 queries , Apc On.

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc. | Style by Coxxs

回顶部