开发板介绍
EFR32BG22 无线 Gecko SoC,Cortex-M33内核,最高运行频率76.8MHz,512kB Flash和32kB RAM,支持蓝牙5.2。板载传感器资源丰富,有温湿度传感器,UV和光照传感器,磁力计,6轴加速度传感器,双麦克风还有用户按键和LED,可以方便快速实现产品原型功能验证。
功能介绍和实现
本期的任务需求,需要通过BLE读取温度传感器值,到达门限值后再控制板上LED点亮。拿到这次的开发板之后,装上官方测试App Thunderboard和蓝牙调试工具EFR Connect试玩了一下,可以发现其实官方的示例程序其实已经提供了实现这个两个功能。
剩下需要做的是开发上位机程序,定时读取官方例程Environment Sensing Service中Temperature值
与设定的门限比较,当超过门限时写Automation IO Service中代表LED的Characteristic
上位机程序可以选择为PC,手机,或者支持蓝牙低功耗的MCU开发;由于我的笔记本电脑自带蓝牙,支持蓝牙低功耗功能,这次我就选择了在开发PC上位机程序,开发工具选择了支持蓝牙低功耗功能的Qt5.14(编译工具链为vs2017+,mingw+gcc工具链目前不支持蓝牙功能)。
但是事情并没有一开始想象的那么顺利。编写代码实现了通过蓝牙读取温度值后,尝试写LED状态时却发现,Qt蓝牙库提供的特征值读写接口是基于Service和特征值UUID的。也就是说,如果同一个Service下面的多个Characteristic的UUID相同,Qt蓝牙库就没有办法区分到底要读写哪一个Characteristic。
用Qt上位机程序加官方演示固件的方案看来是行不通了。剩下要么是使用其他方法实现上位机程序,要么就是修改开发板程序了;和群里的小伙伴们讨论了一下,发现大家都是差不多思路,要么修改官方配套app,要么使用树莓派或者其他开发板完成上位机程序。既然有这么多人提供了各种上位机开发方案,我决定还是做点不一样的,重新在开发板上实现一套程序,也正好学学怎么把本期的开发板用起来。
下位机蓝牙配置
上位机程序基本框架也不需要再做太大改动了,本次主要就说说下位机程序的开发。首先,要实现读取温度值和控制LED开关,可以通过配置两个Characteristic来实现;实现温度读取的Characteristic我希望能够配置成可以定时读取温度发送给上位机的形式,那么这个Characteristic的属性可以配置成可读、可通知;UUID的话,就选用GATT规范中定义的标准温度UUID 0x2A6E;接下来我还希望能够控制Characteristic的读写过程,所以ValueSettings这一栏选择USER;最后勾选ID这一栏并填写一个有意义的名字,方便我们在代码中访问这个Characteristic。
实现LED控制的Characteristic首先必须是可写的,另外我也希望可以通过上位机可以读取到当前LED状态,那么这个属性应该配置成可读可写;UUID就不再使用标准规范中的UUID,填写了一个自定义值;和温度读取一样,为了控制LED读写过程,Value Settings也选择USER。
除了实现上面的两个基本功能外,我还希望能够在开发板上配置温度采样周期,温度告警门限,和一个温度告警功能;方便在离线状态下,也能实现告警功能。那么这里我们需要重复上面的动作,分别再添加一个自定义Characteristic实现采样周期配置,一个自定义Characteristic实现告警门限配置和一个标准Alert Level Characteristic实现告警状态通知。最后为了方便管理,我把实现本次功能的5个Characteristic放在同一个自定义Service下。
温度读取功能配置
芯科提供的IDE工具同样可以非常方便地配置温度读取功能;打开项目下面的.slcp文件,进入software components选项卡,在左边的Bluetooth目录的Sensor子目录中找到Relative Humidity and Temperature sensor,点击右上角的Install便可完成温湿度传感器功能的安装。
完成安装后,还需要配置i2c总线才能真正实现MCU和温湿度传感器的通讯。点击View Dependencies查看依赖关系,点击Platform>Driver>I2CSPM后的View跳转到I2CSPM详情界面,再点击右上角的Configure进入I2CSPM配置界面;在配置界面完成i2c实例,管脚,速率等配置。查看ThunderBoard原理图可以发现,板载温湿度传感器通过一颗TS3A4751芯片与MCU相连,SCL管脚对应PD3,SDA管脚对应PD2;其他配置可以根据实际情况修改。
完成i2c配置后,还需要修改gecko_sdk_3.0.0\app\bluetooth\common\sensor_select\sl_sensor_select.c中sl_sensor_select功能的第一行代码。原有代码:
sl_i2cspm_t *i2cspm_sensor = sl_i2cspm_sensor;
改成:
sl_i2cspm_t *i2cspm_sensor = sl_i2cspm_inst0;
才能让温湿度传感器组件正确引用i2c实例。
LED控制功能配置
LED的功能配置相对来说要简单得多,在software components选项卡中找到Platform>Driver目录下的Simple LED组件并安装。完成安装后点击右上角Configure按钮配置管脚。同样,查看原理图可以看到LED正极通过一个2k7的电阻连接到MCU的PB0管脚,负极接地。那么LED配置选择高电平点亮,管脚修改成PB0即可。
功能代码实现
官方生成代码结构也比较简单。程序进入main函数后,依次执行系统初始化和用户应用初始化,然后进入一个while(1)循环,在循环中依次执行系统循环和用户app循环,最后检测系统状态是否可以进入休眠状态以节省能耗。需要用代码实现的功能主要有三点:
1. 在用户应用初始化中添加需要初始化的内容;这次的功能只需要添加温湿度传感器的初始化即可。
SL_WEAK void app_init(void)
{
sl_sensor_rht_init();
}
2. 在处理用户蓝牙事件的函数中添加本次功能实现用到的Characteristic相关事件处理;不建议在这个蓝牙事件中添加过于复杂的功能或者需要等待时间较长的外设通讯操作,这里我们只更新必要的变量,功能代码实现放在用户app循环中处理。这里具体内容可以直接查看代码。
void sl_bt_on_event(sl_bt_msg_t *evt)
{
...
switch (SL_BT_MSG_ID(evt->header)) {
case sl_bt_evt_connection_closed_id:
/* 连接断开时的操作 */
break;
case sl_bt_evt_gatt_server_user_read_request_id:
/* 上位机发送gatt读请求时的操作 */
break;
case sl_bt_evt_gatt_server_user_write_request_id:
/* 上位机发送gatt写请求时的操作 */
break;
case sl_bt_evt_gatt_server_characteristic_status_id:
/* characteristic状态变化事件,可以用于判断notify状态 */
break;
...
}
...
}
3. 在用户app循环中实现定时温度读取、告警判断,LED状态变更等功能代码。
SL_WEAK void app_process_action(void)
{
sl_status_t ret = SL_STATUS_OK;
static uint8_t s_led_status = 0;
static uint8_t s_temp_intval = 10;
static uint8_t s_alarm_status = 0;
static uint32_t tick_last = 0;
uint32_t rh = 0;
int32_t te = 0;
int16_t te16 = 0;
uint32_t tick_gap =
sl_sleeptimer_ms_to_tick((uint16_t)temp_sample_intval *
1000);
/* 读取系统tick,实现周期读取 */
uint32_t tick_now = sl_sleeptimer_get_tick_count();
if (tick_now - tick_last > tick_gap){
tick_last += tick_gap;
/* 读取并转换成GATT规范中定义的温度格式 */
ret = sl_sensor_rht_get(&rh, &te);
sl_app_assert(SL_STATUS_OK == ret, "[E: %#04x] Si70xx
sensor read failed\n", ret);
te16 = te / 10;
temperature[0] = te16 & 0xff;
temperature[1] = (te16 >> 8) & 0xff;
/* 温度值通知 */
if (temp_notify_ena){
ret =
sl_bt_gatt_server_send_characteristic_notification(
temp_notify_conn,
gattdb_char_temp,
2, (uint8_t *)(&temperature[0]), NULL);
sl_app_assert(SL_STATUS_OK == ret,
"[E: %#04x] send notification failed\n",
ret);
}
/* 告警状态通知 */
if (alarm_notify_ena){
if (te16 >= temp_thresold){
alarm_status = 2;
}
else{
alarm_status = 0;
}
/* 仅告警状态发生变化时才发出新通知 */
if (s_alarm_status != alarm_status){
s_alarm_status = alarm_status;
ret =
sl_bt_gatt_server_send_characteristic_notification(
alarm_notify_conn,
gattdb_alert_level,
1, (uint8_t *)(&alarm_status), NULL);
sl_app_assert(SL_STATUS_OK == ret, "[E: %#04x]
send notification failed\n", ret);
}
}
}
/* 更新led状态 */
if (s_led_status != led_status){
s_led_status = led_status;
if (s_led_status & 1){
sl_led_turn_on(&sl_led_led0);
}else{
sl_led_turn_off(&sl_led_led0);
}
}
}
功能验证
见视频。
小结
不得不说funpack活动是非常实在的,全额返还购买开发板费用。每一期要实现的任务也不算很复杂,时间也比较充足,但完成任务就能返还费用确实能够激起大家参与活动、完成任务、用好开发板的积极性。之前也是一直想学习学习蓝牙低功耗的相关知识,资料看了一大堆,蓝牙开发板也买了不少,但是一直没有真正用起来;直到这次funpack活动才算是真正的入门了ble。特别是这一期silicon labs提供的IDE工具,可以很方便的实现gatt服务端的配置,自动生成代码结构也比较清晰,基本上鼠标点一点就能快速实现一个原型应用。最后也希望今后有更多类似活动,祝硬禾越办越好!