一、system_init
在system_init中创建ble_multi_link_task任务,先是开启了BLE管理器,再注册ble_multi_link_task任务
/* Initialize BLE Manager */
ble_mgr_init();
/* Start the Multi-Link application task. */
OS_TASK_CREATE("Multi-Link", /* The text name assigned to the task, for
debug only; not used by the kernel. */
ble_multi_link_task, /* The function that implements the task. */
NULL, /* The parameter passed to the task. */
200 * OS_STACK_WORD_SIZE, /* The number of bytes to allocate to the
stack of the task. */
mainBLE_MULTI_LINK_TASK_PRIORITY,/* The priority assigned to the task. */
handle); /* The task handle. */
OS_ASSERT(handle);
二、ble_multi_link_task
从上到下依次做了
注册一个看门狗
wdog_id = sys_watchdog_register(false);
使能BLE
status = ble_enable();
设置设备角色为外围设备和中心设备
ble_gap_role_set(GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE);
注册本任务到BLE框架中,用于后面接收BLE栈的事件通知
ble_register_app();
设置设备名称,设备名是存储在GATT服务中的一般在设备连接后来读取,与广播数据中的设备名是未连接状态下就可以被扫描看见的也就是手机上扫描BLE时显示的,他们两个可以一样但性质不一样。
ble_gap_device_name_set("Dialog Multi-link", ATT_PERM_READ);
添加注册一个服务,bd_addr_write_cb后续在讲
dlg_mls_init(bd_addr_write_cb);
设置广播数据和开始广播
ble_gap_adv_data_set(sizeof(adv_data), adv_data, 0, NULL);
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
主循环,定时通知看门狗,等待BLE发送通知 ,再根据不同的BLE事件进行不同的回调处理
if (notif & BLE_APP_NOTIFY_MASK)这里是在判断接收到的通知是不是属于BLE发来的,如果是就进行BLE事件的处理。
完整代码
/**
****************************************************************************************
*
* @file ble_multi_link_task.c
*
* @brief Multi-Link Demo task
*
* Copyright (C) 2015-2019 Dialog Semiconductor.
* This computer program includes Confidential, Proprietary Information
* of Dialog Semiconductor. All Rights Reserved.
*
****************************************************************************************
*/
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include "sdk_queue.h"
#include "osal.h"
#include "timers.h"
#include "osal.h"
#include "sys_watchdog.h"
#include "ble_att.h"
#include "ble_common.h"
#include "ble_gap.h"
#include "ble_gatts.h"
#include "ble_service.h"
#include "dlg_mls.h"
/*
* Multi-link demo advertising data
*/
static const uint8_t adv_data[] = {
0x12, GAP_DATA_TYPE_LOCAL_NAME,
'D', 'i', 'a', 'l', 'o', 'g', ' ', 'M', 'u', 'l', 't', 'i', '-', 'l', 'i', 'n', 'l'
};
static const gap_conn_params_t cp = {
.interval_min = BLE_CONN_INTERVAL_FROM_MS(50), // 50.00 ms
.interval_max = BLE_CONN_INTERVAL_FROM_MS(70), // 70.00 ms
.slave_latency = 0,
.sup_timeout = BLE_SUPERVISION_TMO_FROM_MS(420), // 420.00 ms
};
typedef struct {
void *next;
bd_address_t addr;
uint16_t conn_idx;
} conn_dev_t;
__RETAINED static enum conn_cancel_reason {
CONN_CANCEL_REASON_ADV_ERR,
CONN_CANCEL_REASON_DEV_NOT_RESPONDING,
} conn_canceled_reason;
__RETAINED static queue_t connections;
__RETAINED_RW static uint16_t master_dev_conn_idx = BLE_CONN_IDX_INVALID;
__RETAINED static bool connecting;
__RETAINED static bd_address_t new_dev_addr;
/*
* Debug functions
*/
static const char *format_bd_address(const bd_address_t *addr)
{
static char buf[19];
int i;
for (i = 0; i < sizeof(addr->addr); i++) {
int idx;
// for printout, address should be reversed
idx = sizeof(addr->addr) - i - 1;
sprintf(&buf[i * 3], "%02X:", addr->addr[idx]);
}
buf[sizeof(buf) - 2] = '\0';
return buf;
}
static void print_connection_func(void *data, void *user_data)
{
const conn_dev_t *conn_dev = data;
int *num = user_data;
(*num)++;
printf("%2d | %5d | %s\r\n", *num, conn_dev->conn_idx,
format_bd_address(&conn_dev->addr));
}
static void print_connections(void)
{
int num = 0;
printf("\r\n");
printf("Nr | Index | Address\r\n");
queue_foreach(&connections, print_connection_func, &num);
if (!num) {
printf("(no active connections)\r\n");
}
printf("\r\n");
}
bool list_elem_match(const void *elem, const void *ud)
{
conn_dev_t *conn_dev = (conn_dev_t *) elem;
uint16_t *conn_idx = (uint16_t *) ud;
return conn_dev->conn_idx == *conn_idx;
}
static void bd_addr_write_cb(ble_service_t *svc, uint16_t conn_idx, const bd_address_t *addr)
{
// printf("bd_addr_write_cb,addr_type_t:%d",addr->addr_type);
// // 以十六进制格式打印地址
// printf("Address (Hex): ");
// for(int i = 0; i < 6; i++) {
// printf("%02X", addr->addr[i]);
// if (i < 5) {
// printf(":");
// }
// }
ble_error_t status;
status = ble_gap_connect(addr, &cp);
if (status != BLE_STATUS_OK && status != BLE_ERROR_BUSY) {
printf("%s: failed. Status=%d\r\n", __func__, status);
}
if (status == BLE_ERROR_BUSY) {
new_dev_addr = *addr;
conn_canceled_reason = CONN_CANCEL_REASON_DEV_NOT_RESPONDING;
/*
* ble_gap_connect() will return a BLE_ERROR_BUSY status when another connection
* procedure is already ongoing. To be able to connect to another device, cancel
* the last connection request to retry connecting with the new device.
*/
ble_gap_connect_cancel();
}
connecting = (status == BLE_STATUS_OK);
}
static void handle_evt_gap_connected(ble_evt_gap_connected_t *evt)
{
conn_dev_t *conn_dev;
printf("Device connected\r\n");
printf("\tConnection index: %u\r\n", evt->conn_idx);
printf("\tAddress: %s %s\r\n",
evt->peer_address.addr_type == PUBLIC_ADDRESS ? "public" : "private",
ble_address_to_string(&evt->peer_address));
conn_dev = OS_MALLOC(sizeof(conn_dev_t));
conn_dev->addr.addr_type = evt->peer_address.addr_type;
memcpy(conn_dev->addr.addr, evt->peer_address.addr, sizeof(conn_dev->addr.addr));
conn_dev->conn_idx = evt->conn_idx;
queue_push_front(&connections, (void *)conn_dev);
if (master_dev_conn_idx == BLE_CONN_IDX_INVALID) {
master_dev_conn_idx = evt->conn_idx;
}
connecting = false;
print_connections();
}
static void handle_evt_gap_conn_completed(ble_evt_gap_connection_completed_t *evt)
{
if (master_dev_conn_idx != BLE_CONN_IDX_INVALID &&
conn_canceled_reason != CONN_CANCEL_REASON_DEV_NOT_RESPONDING) {
return;
}
/* Process only then if the connection was completed not by intended cancellation then */
if (evt->status != BLE_ERROR_CANCELED) {
return;
}
if (conn_canceled_reason == CONN_CANCEL_REASON_DEV_NOT_RESPONDING) {
ble_error_t status;
printf("%s: Last connection request was canceled. Connecting to new device...\r\n",
__func__);
status = ble_gap_connect(&new_dev_addr, &cp);
printf("%s: Status=%d\r\n", __func__, status);
} else if (conn_canceled_reason == CONN_CANCEL_REASON_ADV_ERR) {
printf("%s: cancel the connect\r\n", __func__);
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
printf("Advertising is on again after error\r\n");
} else {
printf("Undefined cancellation reason\r\n");
}
}
static void handle_evt_gap_disconnected(ble_evt_gap_disconnected_t *evt)
{
printf("Device disconnected\r\n");
printf("\tConnection index: %u\r\n", evt->conn_idx);
printf("\tBD address of disconnected device: %s, %s\r\n",
evt->address.addr_type == PUBLIC_ADDRESS ? "public" : "private",
ble_address_to_string(&evt->address));
printf("\tReason of disconnection: 0x%02x\r\n", evt->reason);
conn_dev_t *conn_dev = queue_remove(&connections, list_elem_match, &evt->conn_idx);
if (conn_dev) {
OS_FREE(conn_dev);
}
print_connections();
if (master_dev_conn_idx == evt->conn_idx) {
master_dev_conn_idx = BLE_CONN_IDX_INVALID;
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
printf("Advertising is on again\r\n");
}
}
static void handle_evt_gap_adv_completed(ble_evt_gap_adv_completed_t *evt)
{
printf("Advertising completed\r\n");
printf("\tAdvertising type: %d\r\n", evt->adv_type);
printf("\tCompletion status: 0x%02x\r\n", evt->status);
if (connecting && (evt->status == BLE_ERROR_NOT_ALLOWED)) {
connecting = false;
conn_canceled_reason = CONN_CANCEL_REASON_ADV_ERR;
ble_gap_connect_cancel();
}
}
static void handle_evt_gap_pair_req(ble_evt_gap_pair_req_t *evt)
{
printf("Pair request\r\n");
printf("\tConnection index: %d\r\n", evt->conn_idx);
printf("\tBond: %s\r\n", evt->bond ? "true" : "false");
ble_gap_pair_reply(evt->conn_idx, true, evt->bond);
}
static void handle_evt_gap_security_request(ble_evt_gap_security_request_t *evt)
{
ble_error_t status;
status = ble_gap_pair(evt->conn_idx, evt->bond);
if (status != BLE_STATUS_OK) {
printf("%s: failed. Status=%d\r\n", __func__, status);
}
}
void ble_multi_link_task(void *params)
{
ble_error_t status;
int8_t wdog_id;
/* Register ble_multi_link task to be monitored by watchdog */
wdog_id = sys_watchdog_register(false);
/* Enable BLE */
status = ble_enable();
if (status == BLE_STATUS_OK) {
/* Set all roles */
ble_gap_role_set(GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE);
} else {
printf("%s: failed. Status=%d\r\n", __func__, status);
}
/* Register task to BLE framework to receive BLE event notifications */
ble_register_app();
/* Set device name */
ble_gap_device_name_set("Dialog Multi-link", ATT_PERM_READ);
/* Add Multi-Link Service */
dlg_mls_init(bd_addr_write_cb);
/* Set advertising data and start advertising */
ble_gap_adv_data_set(sizeof(adv_data), adv_data, 0, NULL);
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
printf("Advertising is on\r\n");
for (;;) {
OS_BASE_TYPE ret;
uint32_t notif;
/* Notify watchdog on each loop */
sys_watchdog_notify(wdog_id);
/* Suspend watchdog while blocking on OS_TASK_NOTIFY_WAIT() */
sys_watchdog_suspend(wdog_id);
/*
* Wait on any of the notification bits, then clear them all
*/
ret = OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS, ¬if, OS_TASK_NOTIFY_FOREVER);
/* This must block forever, until a task notification is received. So, the
return value must be OS_TASK_NOTIFY_SUCCESS */
OS_ASSERT(ret == OS_TASK_NOTIFY_SUCCESS);
/* Resume watchdog */
sys_watchdog_notify_and_resume(wdog_id);
/* Notified from BLE Manager? */
if (notif & BLE_APP_NOTIFY_MASK) {
ble_evt_hdr_t *hdr;
hdr = ble_get_event(false); //有没有事件
if (!hdr) {
goto no_event; //没有事件
}
if (!ble_service_handle_event(hdr)) { //有事件但没有被处理
switch (hdr->evt_code) {
case BLE_EVT_GAP_CONNECTED:
handle_evt_gap_connected((ble_evt_gap_connected_t *) hdr);
break;
case BLE_EVT_GAP_CONNECTION_COMPLETED:
handle_evt_gap_conn_completed((ble_evt_gap_connection_completed_t *) hdr);
break;
case BLE_EVT_GAP_DISCONNECTED:
handle_evt_gap_disconnected(
(ble_evt_gap_disconnected_t *) hdr);
break;
case BLE_EVT_GAP_ADV_COMPLETED:
handle_evt_gap_adv_completed(
(ble_evt_gap_adv_completed_t *) hdr);
break;
case BLE_EVT_GAP_PAIR_REQ:
handle_evt_gap_pair_req((ble_evt_gap_pair_req_t *) hdr);
break;
case BLE_EVT_GAP_SECURITY_REQUEST:
handle_evt_gap_security_request(
(ble_evt_gap_security_request_t *) hdr);
break;
default:
ble_handle_event_default(hdr);
break;
}
}
OS_FREE(hdr);
no_event: //处理没有事件的情况
/* Notify again if there are more events to process in queue */
if (ble_has_event()) { //检查是否还有其他事件待处理
//如果还有事件,重新通知当前任务,确保任务会再次被唤醒来处理这些事件
OS_TASK_NOTIFY(OS_GET_CURRENT_TASK(), BLE_APP_NOTIFY_MASK,
OS_NOTIFY_SET_BITS);
}
}
}
}
三、dlg_mls_init
从上到下依次做了
申请并清空了一个自定义的服务结构体
ml_service_t *dlg_mls;
dlg_mls = OS_MALLOC(sizeof(ml_service_t));
memset(dlg_mls, 0, sizeof(ml_service_t));ml_service_t 结构体如下
typedef struct {
ble_service_t svc; //BLE 服务的基本结构体
bd_addr_write_cb_t addr_write_cb; //指向外设地址写回调函数的指针
uint16_t periph_addr_val_h; //服务的特征句柄,用于后面处理写请求
} ml_service_t;
计算获得服务的属性数量
num_attr = ble_gatts_get_num_attr(0, 1, 0);
3个参数分别为
0
: 服务中的包含数。1
: 特征数。0
: 特征描述符的数量。
将服务的 UUID 字符串(
UUID_DLG_MLS
)转换为att_uuid_t
类型的 UUID,并存储在uuid
变量中。ble_uuid_from_string(UUID_DLG_MLS, &uuid);
添加一个新的 BLE 服务:
ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);
&uuid
: 指向服务的 UUID。GATT_SERVICE_PRIMARY
: 表示这是一个主服务。num_attr
: 服务的属性数量(包括特征、描述符等)。将特征的 UUID 字符串(
UUID_DLG_MLS_PERIPHERAL_ADDR
)转换为att_uuid_t
类型的 UUID,并存储在uuid
变量中。ble_uuid_from_string(UUID_DLG_MLS_PERIPHERAL_ADDR, &uuid);
添加一个新的特征到刚刚创建的服务中:
ble_gatts_add_characteristic(&uuid, GATT_PROP_WRITE_NO_RESP, ATT_PERM_WRITE, 7, 0, NULL, &dlg_mls->periph_addr_val_h);
&uuid
: 指向特征的 UUID。GATT_PROP_WRITE_NO_RESP
: 特征属性,表示特征支持无应答的写操作。ATT_PERM_WRITE
: 特征的权限,表示该特征可以被写入。7
: 特征值的最大长度(字节数)。0
: 不使用额外的安全权限。NULL
: 初始值为空。&dlg_mls->periph_addr_val_h
: 存储特征的句柄,稍后会用于标识这个特征。注册服务及其所有特征:
ble_gatts_register_service(&dlg_mls->svc.start_h, &dlg_mls->periph_addr_val_h, 0);
&dlg_mls->svc.start_h
: 存储服务的起始句柄。&dlg_mls->periph_addr_val_h
: 存储特征的句柄(已在上一步中设置)。0
: 表示无更多的特征需要注册。计算并设置服务的结束句柄。服务的结束句柄等于起始句柄加上属性数量。
dlg_mls->svc.end_h = dlg_mls->svc.start_h + num_attr;
设置服务的写请求处理函数,将
handle_write_req
函数指针赋值给服务的write_req
字段,以处理特征的写请求。dlg_mls->svc.write_req = handle_write_req;
将传入的回调函数
addr_write_cb
赋值给dlg_mls
结构体的addr_write_cb
字段。这个回调将在写请求处理过程中被调用。这里就是前面的dlg_mls_init(bd_addr_write_cb);参数dlg_mls->addr_write_cb = addr_write_cb;
将刚刚创建并初始化好的服务添加到 BLE 框架中,使其可以被外部设备访问。
ble_service_add(&dlg_mls->svc);
完整代码
/**
****************************************************************************************
*
* @file dlg_mls.c
*
* @brief Dialog Multi-Link Service sample implementation
*
* Copyright (C) 2015-2017 Dialog Semiconductor.
* This computer program includes Confidential, Proprietary Information
* of Dialog Semiconductor. All Rights Reserved.
*
****************************************************************************************
*/
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "osal.h"
#include "ble_gatts.h"
#include "ble_uuid.h"
#include "svc_defines.h"
#include "dlg_mls.h"
#define UUID_DLG_MLS "3292546e-0a42-4348-aa38-33aab6f9af93"
#define UUID_DLG_MLS_PERIPHERAL_ADDR "3292546e-0a42-4348-aa38-33aab6f9af94"
typedef struct {
ble_service_t svc;
bd_addr_write_cb_t addr_write_cb;
uint16_t periph_addr_val_h;
} ml_service_t;
static att_error_t handle_peripheral_address_write(ml_service_t *dlg_mls, uint16_t conn_idx,
uint16_t offset, uint16_t length, const uint8_t *value)
{
// printf("handle_peripheral_address_write\r\n");
if (!dlg_mls->addr_write_cb) {
return ATT_ERROR_WRITE_NOT_PERMITTED;
}
if (length != sizeof(bd_address_t) || !value) {
return ATT_ERROR_INVALID_VALUE_LENGTH;
}
if (value[0] != PUBLIC_ADDRESS && value[0] != PRIVATE_ADDRESS) {
return ATT_ERROR_APPLICATION_ERROR;
}
dlg_mls->addr_write_cb(&dlg_mls->svc, conn_idx, (bd_address_t *) value);
return ATT_ERROR_OK;
}
static void handle_write_req(ble_service_t *svc, const ble_evt_gatts_write_req_t *evt)
{
// printf("handle_write_req\r\n");
ml_service_t *dlg_mls = (ml_service_t *) svc;
att_error_t status = ATT_ERROR_ATTRIBUTE_NOT_FOUND;
if (evt->handle == dlg_mls->periph_addr_val_h) {
status = handle_peripheral_address_write(dlg_mls, evt->conn_idx, evt->offset,
evt->length, evt->value);
}
ble_gatts_write_cfm(evt->conn_idx, evt->handle, status);
}
ble_service_t *dlg_mls_init(bd_addr_write_cb_t addr_write_cb)
{
uint16_t num_attr;
ml_service_t *dlg_mls;
att_uuid_t uuid;
dlg_mls = OS_MALLOC(sizeof(ml_service_t));
memset(dlg_mls, 0, sizeof(ml_service_t));
num_attr = ble_gatts_get_num_attr(0, 1, 0);
ble_uuid_from_string(UUID_DLG_MLS, &uuid);
ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);
ble_uuid_from_string(UUID_DLG_MLS_PERIPHERAL_ADDR, &uuid);
ble_gatts_add_characteristic(&uuid, GATT_PROP_WRITE_NO_RESP, ATT_PERM_WRITE,
7, 0, NULL, &dlg_mls->periph_addr_val_h);
ble_gatts_register_service(&dlg_mls->svc.start_h, &dlg_mls->periph_addr_val_h, 0);
dlg_mls->svc.end_h = dlg_mls->svc.start_h + num_attr;
dlg_mls->svc.write_req = handle_write_req;
dlg_mls->addr_write_cb = addr_write_cb;
ble_service_add(&dlg_mls->svc);
return &dlg_mls->svc;
}
创建完后生成的服务,其服务的UUID与一个特征值的UUID一一对应
四、向上写数据的流程
- 当手机端向 BLE 设备写入数据时,设备会触发
handle_write_req
函数,该函数负责处理所有写入请求。- 在
handle_write_req
检查写请求的handle
是否与dlg_mls->periph_addr_val_h
匹配,这意味着客户端请求写入的是设备的外设地址特性。如果匹配,则调用handle_peripheral_address_write
函数来处理写入操作,并将结果返回到status
。- 在handle_peripheral_address_write做数据检查,检查都通过了,调用
addr_write_cb
回调函数,将写入的地址数据传递给回调函数进行进一步处理。addr_write_cb(&dlg_mls->svc, conn_idx, (bd_address_t *) value);
回调函数
&dlg_mls->svc
:
- 这是一个指向 BLE 服务 (
ble_service_t
) 的指针,表示当前正在处理的 BLE 服务实例。在回调函数中,这个指针可以用于获取或操作服务的相关信息。
conn_idx
:
- 这是一个
uint16_t
类型的值,表示当前连接的索引。它唯一标识了设备与客户端之间的连接。在回调函数中,可以用它来区分来自不同连接的请求。
(bd_address_t *) value
:
- 这是一个指向
bd_address_t
类型的指针,bd_address_t
结构体表示一个蓝牙设备的地址(包含地址类型和实际地址)。value
是一个指向原始数据的uint8_t
指针,在这里被强制转换为bd_address_t
类型。在回调函数中,可以通过这个指针来访问写入的设备地址。- 写入操作完成后,设备将结果通过
ble_gatts_write_cfm
函数反馈给手机端,告知操作是否成功。