1. 想要实现的功能
1.主机能连接多个从机(主机作为控制器,从机作为节点)。
2.主机能使用不同的按键控制不同的节点(按键和节点一一对应,与从机的连接顺序无关)。
3.主机扫描过滤器使用设备全称,记录和从机的连接句柄,并进行控制。
2. 从机
从机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_peripheral\ble_app_blinky\pca10040\s132\arm5_no_packs
以使用两个从机为例,从机的蓝牙名称分别改为Nordic_BedRoom和Nordic_DrawingRoom。
3. 主机
主机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_central\ble_app_multilink_central\pca10040\s132\arm5_no_packs
如果不记录连接句柄的话从机连接顺序的不同会导致不能控制指定的从机,所以.h文件需要定义一些自己需要的内容:
#ifndef _MY_BLE_DEVIVE_
#define _MY_BLE_DEVIVE_
typedef enum{
INIT = 1,
SCAN,
CONN,
READ,
WRITE,
DISCONN,
}PROC_TYPE;
typedef enum{
bedroom_device = 0, //从机1,由主机的按键1控制
drawingroom_device, //从机2,由主机的按键2控制
init_device, //无效,初始化句柄用
}control_device;
typedef struct{
bool led_state; //led状态
uint8_t my_scan_state :1; //扫描到从机
uint8_t my_conn_state :1; //连接到从机
uint16_t my_conn_handle; //连接句柄
}my_conn_record; //从机记录结构体
void my_device_manage(control_device dev,PROC_TYPE type,uint16_t handle); //从机操作管理函数
#endif
.c文件中定义:
BLE_LBS_C_ARRAY_DEF(m_lbs_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);
#define BedRoom_BUTTON BSP_BUTTON_0 //主机按键1控制从机1LED状态
#define DrawingRoom_BUTTON BSP_BUTTON_1 //主机按键2控制从机2LED状态
#define BedRoomRead_BUTTON BSP_BUTTON_2 //主机按键3读取从机1LED状态
#define DrawingRoomRead_BUTTON BSP_BUTTON_3 //主机按键4读取从机2LED状态
my_conn_record conn_record[NRF_SDH_BLE_CENTRAL_LINK_COUNT]; //从机记录结构体数组
//conn_record结构体数组的顺序同control_device中枚举的设备顺序一致
void BedRoomProcessFunc(PROC_TYPE type) //从机1处理函数
{
ret_code_t err_code;
switch(type)
{
case READ:
led_status_read_from_device(bedroom_device);
if (err_code == NRF_SUCCESS)
{
NRF_LOG_INFO("Read BedRoom LED state");
}
break;
case WRITE:
{
conn_record[bedroom_device].led_state = !conn_record[bedroom_device].led_state;
err_code = led_status_send_to_device(bedroom_device,conn_record[bedroom_device].led_state);
if (err_code == NRF_SUCCESS)
{
NRF_LOG_INFO("BedRoom write LED state %d", conn_record[bedroom_device].led_state);
}
}break;
default:
break;
}
}
void DrawingRoomProcessFunc(PROC_TYPE type) //从机2处理函数
{
ret_code_t err_code;
switch(type)
{
case READ:
err_code = led_status_read_from_device(drawingroom_device);
if (err_code == NRF_SUCCESS)
{
NRF_LOG_INFO("Read DrawingRoom LED state");
}
break;
case WRITE:
{
conn_record[drawingroom_device].led_state = !conn_record[drawingroom_device].led_state;
err_code = led_status_send_to_device(drawingroom_device,conn_record[drawingroom_device].led_state);
if (err_code == NRF_SUCCESS)
{
NRF_LOG_INFO("DrawingRoom write LED state %d", conn_record[drawingroom_device].led_state);
}
}break;
default:
break;
}
}
void (*ProcessFunc[2])(PROC_TYPE type) = //从机处理函数列表
{
BedRoomProcessFunc,
DrawingRoomProcessFunc,
};
void my_device_manage(control_device dev,PROC_TYPE type,uint16_t handle) //从机操作管理函数
{
switch(type)
{
case INIT: //初始化,将conn_record结构体数组的句柄初始化为BLE_CONN_HANDLE_INVALID(0xFFFF)
for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
conn_record[i].my_conn_handle = handle;
}
break;
case SCAN: //记录哪个从机被主机扫描到
conn_record[dev].my_scan_state = true;
conn_record[dev].my_conn_state = false;
conn_record[dev].my_conn_handle = handle;
break;
case CONN: //记录哪个从机被主机连接
conn_record[dev].my_scan_state = false;
conn_record[dev].my_conn_state = true;
conn_record[dev].my_conn_handle = handle;
break;
case READ:
case WRITE: //从机状态读写处理
if((conn_record[dev].my_conn_state)&&(conn_record[dev].my_conn_handle != BLE_CONN_HANDLE_INVALID))
{
ProcessFunc[dev](type);
}
else
{
NRF_LOG_INFO("%s device error", m_target_periph_name[dev]);
}
break;
case DISCONN: //记录哪个从机断开,并将其连接句柄设置为BLE_CONN_HANDLE_INVALID(0xFFFF)
conn_record[dev].my_scan_state = false;
conn_record[dev].my_conn_state = false;
conn_record[dev].my_conn_handle = handle;
break;
default:
break;
}
}
主函数中执行my_device_manage()函数把conn_record数组成员的连接句柄my_conn_handle 全部设置为BLE_CONN_HANDLE_INVALID(0xFFFF)。my_device_manage()函数中状态机除了CONN都会把连接句柄设置为0xFFFF,目的是未连接的从机不对其进行读写,第一个从机连接后连接句柄会是0,这样的话就会出现在读写从机时使用conn_record中记录的连接句柄匹配m_lbs_c中的句柄时错误的匹配到0。
3.1 主从机连接个数设置
宏定义:
使用一个主机,两个从机,所以主机要支持两路连接,NRF_SDH_BLE_CENTRAL_LINK_COUNT设置为2。最大支持20路连接,也就是可以连接20个从机,在ble_gap.h中有相关定义,支持的最大角色数目是20(主机连接个数+从机连接个数)。
3.2 扫描过滤
由于两个从机的设备名称不一样,例程中扫描过滤器使用的是设备全称过滤,需要将设备名称改为从机的名称:
static char const m_target_periph_name[NRF_SDH_BLE_CENTRAL_LINK_COUNT][NRF_BLE_SCAN_NAME_MAX_LEN] =
{{"Nordic_BedRoom"},{"Nordic_DrawingRoom"}};
要通过设备名称过滤的从机数:
在scan_init()函数中设置扫描过滤器:
nrf_ble_scan_filter_set()函数中调用nrf_ble_scan_name_filter_add()函数,里面会判断设备名称是否添加过滤器,没有添加的话就添加。函数中scan_filters.name_filter.name_cnt为已经添加过滤器的数量,scan_filters.name_filter.target_name[index]为具体的要连接的从机设备名称,过滤器添加顺序与control_device中枚举的从机设备顺序一致,因此也与conn_record结构体数组的从机设备顺序一致。
static ret_code_t nrf_ble_scan_name_filter_add(nrf_ble_scan_t * const p_scan_ctx,
char const * p_name)
{
uint8_t index;
uint8_t * counter = &p_scan_ctx->scan_filters.name_filter.name_cnt;
uint8_t name_len = strlen(p_name);
// Check the name length.
if ((name_len == 0) || (name_len > NRF_BLE_SCAN_NAME_MAX_LEN))
{
return NRF_ERROR_DATA_SIZE;
}
// If no memory for filter.
if (*counter >= NRF_BLE_SCAN_NAME_CNT)
{
return NRF_ERROR_NO_MEM;
}
// Check for duplicated filter.
for (index = 0; index < NRF_BLE_SCAN_NAME_CNT; index++)
{
if (!strcmp(p_scan_ctx->scan_filters.name_filter.target_name[index], p_name))
{
return NRF_SUCCESS;
}
}
// Add name to filter.
memcpy(p_scan_ctx->scan_filters.name_filter.target_name[(*counter)++],
p_name,
strlen(p_name));
NRF_LOG_DEBUG("Adding filter on %s name", p_name);
return NRF_SUCCESS;
}
scan_start()函数简单做一下修改:
主机扫描到从机后生成事件调用函数nrf_ble_scan_on_ble_evt(),然后执行函数nrf_ble_scan_on_adv_report(),会根据扫描过滤器设置去判断是不是要连接的从机。
在adv_name_compare函数中会按顺序比对扫描到的从机是不是要连接的从机,那么也就可以使用自己写的my_device_manage函数来记录哪个要连接的从机被扫描到了。
static bool adv_name_compare(ble_gap_evt_adv_report_t const * p_adv_report,
nrf_ble_scan_t const * const p_scan_ctx)
{
nrf_ble_scan_name_filter_t const * p_name_filter = &p_scan_ctx->scan_filters.name_filter;
uint8_t counter =
p_scan_ctx->scan_filters.name_filter.name_cnt;
uint8_t index;
uint16_t data_len;
data_len = p_adv_report->data.len;
// Compare the name found with the name filter.
for (index = 0; index < counter; index++)
{
if (ble_advdata_name_find(p_adv_report->data.p_data,
data_len,
p_name_filter->target_name[index]))
{
my_device_manage((control_device)index,SCAN,BLE_CONN_HANDLE_INVALID);//记录哪个要连接的从机被扫描到
return true;
}
}
return false;
}
3.3 连接和断开连接
在主从机连接或者断开连接的时候会产生相应的事件,在ble_evt_handler()函数中也进行连接句柄的记录:
case BLE_GAP_EVT_CONNECTED:
{
/*其他
*/
for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
if((conn_record[i].my_scan_state == true)&&(conn_record[i].my_conn_state == false))
{
my_device_manage((control_device)i,CONN,p_gap_evt->conn_handle);
}
}
/*其他
*/
} break; // BLE_GAP_EVT_CONNECTED
case BLE_GAP_EVT_DISCONNECTED:
{
/*其他
*/
for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
if((p_gap_evt->conn_handle == conn_record[i].my_conn_handle)&&(conn_record[i].my_conn_handle != BLE_CONN_HANDLE_INVALID))
{
my_device_manage((control_device)i,DISCONN,BLE_CONN_HANDLE_INVALID);
}
}
/*其他
*/
} break;
3.4 按键处理
按键初始化:
按键事件处理函数,只在按键按下时动作:
static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
switch (pin_no)
{
case BedRoom_BUTTON://控制从机1LED状态
if(button_action)
{
my_device_manage(bedroom_device,WRITE,BLE_CONN_HANDLE_INVALID);
}
break;
case DrawingRoom_BUTTON://控制从机2LED状态
if(button_action)
{
my_device_manage(drawingroom_device,WRITE,BLE_CONN_HANDLE_INVALID);
}
break;
case BedRoomRead_BUTTON://读取从机1LED状态
if(button_action)
{
my_device_manage(bedroom_device,READ,BLE_CONN_HANDLE_INVALID);
}
break;
case DrawingRoomRead_BUTTON://读取从机2LED状态
if(button_action)
{
my_device_manage(drawingroom_device,READ,BLE_CONN_HANDLE_INVALID);
}
break;
default:
APP_ERROR_HANDLER(pin_no);
break;
}
}
3.5 从机读写
在my_device_manage()函数中对从机读写之前会判断是不是处于连接状态,连接句柄是不是有效:
如果有效就执行相应的从机处理函数,以从机1的处理函数为例:
3.5.1 写
由于从机1模拟的是LED控制器,所以在写之前加了一行状态翻转,然后调用ble_lbs_led_status_send函数来对从机写。conn_record结构体数组的顺序是从机1(bedroom_device)、从机2(drawingroom_device),并且已经记录了相应的连接句柄,所以需要跟m_lbs_c数组的句柄比对,在m_lbs_c中找到要控制的从机1。
static ret_code_t led_status_send_to_device(control_device dev,uint8_t button_action)
{
ret_code_t err_code;
for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
if(conn_record[dev].my_conn_handle == m_lbs_c[i].conn_handle)
{
err_code = ble_lbs_led_status_send(&m_lbs_c[i], button_action);
if (err_code != NRF_SUCCESS &&
err_code != BLE_ERROR_INVALID_CONN_HANDLE &&
err_code != NRF_ERROR_INVALID_STATE)
{
return err_code;
}
}
}
return NRF_SUCCESS;
}
3.5.1 读
在ble_lbs_c.h文件中ble_lbs_c_evt_t结构体的params成员中增加LED状态:
ble_lbs_c_evt_type_t中增加LED事件:
在lbs的GATT事件回调函数状态机中增加读事件:
在on_hvx()函数中除了按键通知处理之外增加LED读处理:
static void on_hvx(ble_lbs_c_t * p_ble_lbs_c, ble_evt_t const * p_ble_evt)
{
// Check if the event is on the link for this instance.
if (p_ble_lbs_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle)
{
return;
}
// Check if this is a Button notification.
if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_lbs_c->peer_lbs_db.button_handle)//按键通知
{
if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1)
{
ble_lbs_c_evt_t ble_lbs_c_evt;
ble_lbs_c_evt.evt_type = BLE_LBS_C_EVT_BUTTON_NOTIFICATION;
ble_lbs_c_evt.conn_handle = p_ble_lbs_c->conn_handle;
ble_lbs_c_evt.params.button.button_state = p_ble_evt->evt.gattc_evt.params.hvx.data[0];
p_ble_lbs_c->evt_handler(p_ble_lbs_c, &ble_lbs_c_evt);
}
}
if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_lbs_c->peer_lbs_db.led_handle)//led读
{
if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1)
{
ble_lbs_c_evt_t ble_lbs_c_evt;
ble_lbs_c_evt.evt_type = BLE_LBS_C_EVT_LED_READRESP;
ble_lbs_c_evt.conn_handle = p_ble_lbs_c->conn_handle;
ble_lbs_c_evt.params.led.led_state = p_ble_evt->evt.gattc_evt.params.hvx.data[0];
p_ble_lbs_c->evt_handler(p_ble_lbs_c, &ble_lbs_c_evt);
}
}
}
在lbs的客户端回调函数状态机中增加读LED事件处理,并记录至conn_record中:
static void lbs_c_evt_handler(ble_lbs_c_t * p_lbs_c, ble_lbs_c_evt_t * p_lbs_c_evt)
{
switch (p_lbs_c_evt->evt_type)
{
case BLE_LBS_C_EVT_DISCOVERY_COMPLETE:
{
/*其他
*/
} break; // BLE_LBS_C_EVT_DISCOVERY_COMPLETE
case BLE_LBS_C_EVT_BUTTON_NOTIFICATION:
{
/*其他
*/
} break; // BLE_LBS_C_EVT_BUTTON_NOTIFICATION
case BLE_LBS_C_EVT_LED_READRESP:
{
for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
if(conn_record[i].my_conn_handle == p_lbs_c_evt->conn_handle)
{
NRF_LOG_INFO("%s curr state:%d", m_target_periph_name[i],p_lbs_c_evt->params.led.led_state);
conn_record[i].led_state = p_lbs_c_evt->params.led.led_state;
}
}
} break; // BLE_LBS_C_EVT_LED_READRESP
default:
// No implementation needed.
break;
}
}
读LED状态函数为led_status_read_from_device(),在从机1处理函数BedRoomProcessFunc()中调用:
static ret_code_t led_status_read_from_device(control_device dev)
{
ret_code_t err_code;
for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
{
if(conn_record[dev].my_conn_handle == m_lbs_c[i].conn_handle)
{
err_code = ble_lbs_led_status_get(&m_lbs_c[i]);
if (err_code != NRF_SUCCESS &&
err_code != BLE_ERROR_INVALID_CONN_HANDLE &&
err_code != NRF_ERROR_INVALID_STATE)
{
return err_code;
}
}
}
return NRF_SUCCESS;
}
仿照例程中的ble_lbs_led_status_send()函数写一个读LED状态的函数:
uint32_t ble_lbs_led_status_get(ble_lbs_c_t * p_ble_lbs_c)
{
VERIFY_PARAM_NOT_NULL(p_ble_lbs_c);
if (p_ble_lbs_c->conn_handle == BLE_CONN_HANDLE_INVALID)
{
return NRF_ERROR_INVALID_STATE;
}
NRF_LOG_DEBUG("Read LED status");
nrf_ble_gq_req_t read_req;
memset(&read_req, 0, sizeof(nrf_ble_gq_req_t));
read_req.type = NRF_BLE_GQ_REQ_GATTC_READ;
read_req.error_handler.cb = gatt_error_handler;
read_req.error_handler.p_ctx = p_ble_lbs_c;
read_req.params.gattc_read.handle = p_ble_lbs_c->peer_lbs_db.led_handle;
read_req.params.gattc_read.offset = 0;
return nrf_ble_gq_item_add(p_ble_lbs_c->p_gatt_queue, &read_req, p_ble_lbs_c->conn_handle);
}
4运行效果
按照control_device枚举的顺序,conn_record结构体数组成员顺序对应从机1(bedroom_device)和从机2(drawingroom_device)。从机1先连接,从机2后连接:
从机2先连接,从机1后连接:
仅连接从机1,正常读写从机1LED状态,读写从机2LED状态提示错误: