一主多從
- 1. 想要實作的功能
- 2. 從機
- 3. 主機
-
- 3.1 主從機連接配接個數設定
- 3.2 掃描過濾
- 3.3 連接配接和斷開連接配接
- 3.4 按鍵處理
- 3.5 從機讀寫
-
- 3.5.1 寫
- 3.5.1 讀
- 4運作效果
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狀态提示錯誤: