NRF51822新手教程(1)——自定义服务
实验目的:自定义Service私有服务并传输温湿度传感器数据。本实验基于SDK12.0中ble_app_blink示例代码进行更改。
1.定制私有服务UUID和特性UUID
1.1 背景知识——UUID
蓝牙技术联盟规定了一系列制定的UUID,其基本UUID:
0x0000xxxx-0000-1000-8000-00805F9B34FB
为进一步简化基本UUID,每一个蓝牙技术联盟(官方)定义的属性都有一个唯一的16位UUID,以代替上面基本UUID中的x部分。例如,心率测量特性使用0X2A37 作为它的16 位
UUID,因此它完整的128 位UUID 为:
0x00002A37-0000-1000-8000-00805F9B34FB
蓝牙技术联盟所用的基本UUID 不能用于任何定制的属性、服务和特性。
对于定制的属性,必须使用另外完整的128 位UUID (即需要自定义UUID)。
1.2 自定义UUID
自定义的基本UUID本质上可以任意定义,一般可用nRF Studio自动生成。本例中采用的基本UUID为:
0x0000xxxx-1212-EFDE-1523-785FEABCD123 (大小写无关)
定义完基本UUID后即可定义特性UUID,用来填补其中的x
本例中定义的特性温湿度特性UUID:
0x1526
实际keil工程中建议新建.h文件(ble_lbs.h),将上面的UUID定义:
#define LBS_UUID_BASE {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, \
0xDE, 0xEF, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00} //base UUID
#define LBS_UUID_TEM_CHAR 0x1526 // characrestic UUID
以上两个自定义的UUID需要记住,后续会经常用到。
UUID的作用就类似开门的钥匙,不同的UUID对应不同的传输数据
2 私有服务的特性
2.1 自定义数据结构体
在ble_lbs.h头文件中定义下列服务结构体:
struct ble_lbs_s
{
uint16_t service_handle; /**< Handle of LED Button Service (as provided by the BLE stack). */
ble_gatts_char_handles_t temperature_char_handles; //自定义服务的handle
uint8_t uuid_type; /**< UUID type for the LED Button Service. */
uint16_t conn_handle; /**< Handle of the current connection (as provided by the BLE stack). BLE_CONN_HANDLE_INVALID if not in a connection. */
};
typedef struct ble_lbs_s ble_lbs_t;
这个结构体是为了记录service/characteristic handles,方便其他位置调用.
2.2 定义服务初始化
服务的添加通过sd_ble_gatts_service_add()进行添加,该函数确定了服务的UUID和回调handle
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);
添加完服务后还需对数据特性进行添加
通过调用tem_char_add(p_lbs, p_lbs_init)定义具体的特性。
err_code = tem_char_add(p_lbs, p_lbs_init);
uint32_t ble_lbs_init(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init)
{
uint32_t err_code;
ble_uuid_t ble_uuid;
// Initialize service structure.
p_lbs->conn_handle = BLE_CONN_HANDLE_INVALID;
p_lbs->led_write_handler = p_lbs_init->led_write_handler;
// Add service.
ble_uuid128_t base_uuid = {LBS_UUID_BASE};
err_code = sd_ble_uuid_vs_add(&base_uuid, &p_lbs->uuid_type);
VERIFY_SUCCESS(err_code);
ble_uuid.type = p_lbs->uuid_type;
ble_uuid.uuid = LBS_UUID_SERVICE;
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);
VERIFY_SUCCESS(err_code);
// Add characteristics.
err_code = tem_char_add(p_lbs, p_lbs_init);
VERIFY_SUCCESS(err_code);
return NRF_SUCCESS;
}
2.3 定义具体特性
温湿度传感器数据特性需要可读和可通知,无需可写特性。
static uint32_t tem_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_TEM_CHAR; // 自定义的char uuid
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 = 2; //温、湿度两个8位数据,长度为2
attr_char_value.init_offs = 0;
attr_char_value.max_len = 2;
attr_char_value.p_value = NULL;
return sd_ble_gatts_characteristic_add(p_lbs->service_handle,
&char_md,
&attr_char_value,
&p_lbs->temperature_char_handles);
}
至此,自定义的服务和特性已经完成。接下来该考虑如何将温湿度传感器得到的数据发送到上位机(手机)中了。
3 温、湿度数据发送
3.1 nRF51822 定时器中断
要使数据定时发送,必然要用到内置的定时器和定时器中断处理函数。
nRF 的定时器配置在main()函数的初始化中分为两块,一块是定时器初始化函数timers_init(),一块是定时器开始计时函数application_timers_start();
3.1.1 定时器初始化timers_init()
需要添加的代码代码就是app_timer_create(),该函数入口有三个参数,分别为:计时器ID;计时模式;中断回调函数。
计时器ID在main.c文件中定义:
APP_TIMER_DEF(tempreture_humidity_timer_id); /**< measure_temperature_and_humidity. */
计时模式选择APP_TIMER_MODE_REPEATED,重复模式
中断回调函数需另外定义,这里传入回调函数名称。当计时溢出时会自动转入回调函数进行处理。
static void timers_init(void)
{
uint32_t err_code;
// Initialize timer module, making it use the scheduler
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false);
//添加温湿度测量中断
err_code = app_timer_create(&tempreture_humidity_timer_id,
APP_TIMER_MODE_REPEATED,
measurement_timer_handler);
APP_ERROR_CHECK(err_code);
}
static void measurement_timer_handler(void * p_context)
{
measure_temperature_and_humidity();//测量温湿度
}
static ble_lbs_t m_lbs;
void measure_temperature_and_humidity()
{
....
为了简洁,这里省略了IIC通讯读取传感器数据
....
uint8_t humidity = (uint8_t) humidityRH;
uint8_t temperature = (uint8_t) temperatureC;
DelayMicroSeconds(300000); // wait 0.3s for next measurement
ble_tem_change(&m_lbs,temperature,humidity);//蓝牙数据发送
}
可以看到,在定时器中断中将调用温湿度测量函数。为了简洁,在温湿度测量函数中省略的IIC的读取过程。当完成数据读取后,通过ble_tem_change()函数将温湿度数据发送。
3.1.2定时器开启
定时器开启函数一定不用忘记定义,并加入相应的计时器ID,不开启定时器一切白给。
static void application_timers_start(void)
{
uint32_t err_code;
// Start application timers.
err_code = app_timer_start(tempreture_humidity_timer_id, Temperature_Humidty_INTERVAL, NULL);
APP_ERROR_CHECK(err_code);
}
至此,如果一切顺利的话程序已经可以定时测量温湿度并调用数据发送函数ble_tem_change().接下来就是定义具体的数据发送。
4 数据更新发送
蓝牙数据发送通过sd_ble_gatts_hvx()函数
// 数据更新函数
uint16_t ble_tem_change(ble_lbs_t * p_lbs, uint8_t temperature,uint8_t humidity)
{
uint32_t err_code;
ble_gatts_hvx_params_t tem_params;
uint16_t len = 2;
uint8_t SHT_val[2];
SHT_val[0] = (uint8_t) temperature;
SHT_val[1] = (uint8_t) humidity;
if(p_lbs -> conn_handle != BLE_CONN_HANDLE_INVALID)
{
memset(&tem_params, 0, sizeof(tem_params));
tem_params.type = BLE_GATT_HVX_NOTIFICATION;
tem_params.handle = p_lbs->temperature_char_handles.value_handle;
tem_params.p_data = SHT_val;
tem_params.p_len = &len;
return sd_ble_gatts_hvx(p_lbs->conn_handle, &tem_params); // 发送数据更新
}
else{
err_code = NRF_ERROR_INVALID_STATE;
SEGGER_RTT_printf(0,"params.p_data error %% \n");
}
}
至此即可在手机端看到实际数据
可以看到在自定义的服务中,Value: (0x)18-44对应温湿度数值24-68.
自定义服务成功!