nRF52832蓝牙通信的方式是NUS主从通信,NUS具体实现方式即申请一个NUS服务,这里不多作阐述。所谓多主一从,就是多个蓝牙主机(手机),与一个从机间的通信,解决了传统蓝牙间只能一对一数据传输的弊端,官方例程中有实现了多主一从的点灯程序,以下介绍的是多主一从的数据传输,废话少说,下面是实现方式和注意要点。
在sdk_config.h中找到这三个宏定义,显然,这里就是定义蓝牙最大连接数目的地方,第一个是最大主机连接数,第二个是最大从机连接数,第三个是总最大连接数,这次我们最大可连接四个主机,那么只需要在这里修改第一个和第三个即可。
// NRF_SDH_BLE_PERIPHERAL_LINK_COUNT - Maximum number of peripheral links.
#ifndef NRF_SDH_BLE_PERIPHERAL_LINK_COUNT
#define NRF_SDH_BLE_PERIPHERAL_LINK_COUNT 4
#endif
// NRF_SDH_BLE_CENTRAL_LINK_COUNT - Maximum number of central links.
#ifndef NRF_SDH_BLE_CENTRAL_LINK_COUNT
#define NRF_SDH_BLE_CENTRAL_LINK_COUNT 0
#endif
// NRF_SDH_BLE_TOTAL_LINK_COUNT - Total link count.
// Maximum number of total concurrent connections using the default configuration.
#ifndef NRF_SDH_BLE_TOTAL_LINK_COUNT
#define NRF_SDH_BLE_TOTAL_LINK_COUNT 4
#endif
先来看看代码
int main(void)
{
log_init(); // LOG初始化
timers_init(); // 定时器初始化
leds_init(); // led初始化
power_management_init();// 电源管理初始化
ble_stack_init(); // BLE堆栈初始化
gap_params_init(); // 初始化GAP
gatt_init(); // 初始化GATT
services_init(); // BLE服务初始化
advertising_init(); // 广播初始化
conn_params_init(); // 连接参数初始化
UART_Init(APP_UartEvtHandle); //串口初始化
UART_Write("3.5_ble_peripheral_multi\n", sizeof("3.5_ble_peripheral_multi"));
NRF_LOG_INFO("Multiperipheral example started.");
advertising_start(); // 开始广播
for (;;)
{
idle_state_handle();
}
}
这是main函数的代码,我这边只对与基本初始化不同的地方进行阐述。
先看到ble_stack_init()
static void ble_stack_init(void)
{
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
这里没什么不同,主要是其中定义的回调函数ble_evt_handler
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
ret_code_t err_code;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connected(&p_ble_evt->evt.gap_evt);
break;
case BLE_GAP_EVT_DISCONNECTED:
on_disconnected(&p_ble_evt->evt.gap_evt);
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
// Pairing not supported
err_code = sd_ble_gap_sec_params_reply(p_ble_evt->evt.gap_evt.conn_handle,
BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
NULL,
NULL);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
NRF_LOG_DEBUG("PHY update request.");
ble_gap_phys_t const phys =
{
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
} break;
case BLE_GATTS_EVT_SYS_ATTR_MISSING:
// No system attributes have been stored.
err_code = sd_ble_gatts_sys_attr_set(p_ble_evt->evt.gap_evt.conn_handle, NULL, 0, 0);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTC_EVT_TIMEOUT:
// Disconnect on GATT Client timeout event.
NRF_LOG_DEBUG("GATT Client Timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_TIMEOUT:
// Disconnect on GATT Server timeout event.
NRF_LOG_DEBUG("GATT Server Timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
default:
// No implementation needed.
break;
}
}
此回调函数中处理了几个事件,其中连接事件和断开事件需要修改,可以看到这两个事件分别调用了 on_connected() 和 on_disconnected() 这两个函数。
static void on_connected(const ble_gap_evt_t * const p_gap_evt)
{
ret_code_t err_code;
uint32_t periph_link_cnt = ble_conn_state_peripheral_conn_count(); // Number of peripheral links.
bsp_board_led_on(CONNECTED_LED);
UART_Printf("Connection with link 0x%x established.\n", p_gap_evt->conn_handle);
// Assign connection handle to available instance of QWR module.
for (uint32_t i = 0; i < NRF_SDH_BLE_PERIPHERAL_LINK_COUNT; i++)
{
if (m_qwr[i].conn_handle == BLE_CONN_HANDLE_INVALID)
{
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr[i], p_gap_evt->conn_handle);
APP_ERROR_CHECK(err_code);
break;
}
}
if (periph_link_cnt == NRF_SDH_BLE_PERIPHERAL_LINK_COUNT)
{
bsp_board_led_off(ADVERTISING_LED);
}
else
{
// Continue advertising. More connections can be established because the maximum link count has not been reached.
advertising_start();
}
nrf_delay_ms(500);
bsp_board_led_off(CONNECTED_LED);
}
在 on_connected() 这个函数中,用 nrf_ble_qwr_conn_handle_assign() 函数对每一个主机设备分配一个任务句柄,相当于是连接后要做的事,由于我们这里是第三个参数NULL,所以并不做任何处理,但这个步骤不可省略。
static void on_disconnected(ble_gap_evt_t const * const p_gap_evt)
{
ret_code_t err_code;
uint32_t periph_link_cnt = ble_conn_state_peripheral_conn_count(); // Number of peripheral links.
UART_Printf("Connection 0x%x has been disconnected. Reason: 0x%X\n",
p_gap_evt->conn_handle,
p_gap_evt->params.disconnected.reason);
if (periph_link_cnt == 0)
{
APP_ERROR_CHECK(err_code);
}
if (periph_link_cnt == (NRF_SDH_BLE_PERIPHERAL_LINK_COUNT - 1))
{
// Advertising is not running when all connections are taken, and must therefore be started.
advertising_start();
}
}
接下来继续看到 on_disconnected() 这个函数,值得注意的是,我们在这里需要对当前的主机连接数进行判断,因为当当前连接数到达最大连接数时,广播讲停止,而当前连接数不是再是最大连接数时,需要我们重新开启广播。
当我们蓝牙建立了连接后,我们需要什么样的服务,就要通过服务初始化函数services_init() 去申请注册,作为多主一从,则需要初始化每个连接的排队写服务,在官方例程中还另外初始化了点灯服务,而我们这里则初始化的是NUS服务,即串口透传。
另外别忘了在前面定义NUS的最大实例数。
BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); //NUS实例化
static void services_init(void)
{
ret_code_t err_code;
ble_nus_init_t nus_init;
nrf_ble_qwr_init_t qwr_init = {0};
// Initialize Queued Write Module instances.
qwr_init.error_handler = nrf_qwr_error_handler;
for (uint32_t i = 0; i < LINK_TOTAL; i++)
{
err_code = nrf_ble_qwr_init(&m_qwr[i], &qwr_init);
APP_ERROR_CHECK(err_code);
}
// Initialize NUS.
memset(&nus_init, 0, sizeof(nus_init));
nus_init.data_handler = nus_data_handler;
err_code = ble_nus_init(&m_nus, &nus_init);
APP_ERROR_CHECK(err_code);
// // Initialize LBS.
// init.led_write_handler = led_write_handler;
// err_code = ble_lbs_init(&m_lbs, &init);
// APP_ERROR_CHECK(err_code);
ble_conn_state_init();
}
那么在这个NUS的回调函数nus_data_handler 中,我们实现了对数据的接收和串口打印。
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA)
{
UART_Printf("Recive on connection handle 0x%04x.\n", p_evt->conn_handle);
//UART_Write((uint8_t*)p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
ble_nus_chars_received_uart_print((uint8_t*)p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
NRF_LOG_DEBUG("Received data from BLE NUS. Writing data on UART.");
NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
}
else if (p_evt->type == BLE_NUS_EVT_TX_RDY)
{
NRF_LOG_DEBUG(" Service is ready to accept new data to be transmitted..");
}
else if(p_evt->type == BLE_NUS_EVT_COMM_STARTED)
{
NRF_LOG_DEBUG("NUS Notification Enable.");
}
else if (p_evt->type == BLE_NUS_EVT_COMM_STOPPED)
{
NRF_LOG_DEBUG("NUS Notification Disable.");
}
}
最后是串口打印的初始化和回调,这里不多解释
void UART_Init(uart_user_callback pHandle)
{
m_uart_callback = pHandle;
uart_fifo_init();
uart_timer_init();
//baudrate = 115200
nrf_drv_uart_config_t uartConfig = NRF_DRV_UART_DEFAULT_CONFIG;
uartConfig.pseltxd = TX_PIN_NUMBER;
uartConfig.pselrxd = RX_PIN_NUMBER;
//uartConfig.pselcts = CTS_PIN_NUMBER;
//uartConfig.pselrts = RTS_PIN_NUMBER;
nrf_drv_uart_init(&m_Uart,&uartConfig,uart_event_handler);
nrf_drv_uart_rx(&m_Uart, rxBuf,1);
}
void APP_UartEvtHandle(uart_evt_t* pEvt)
{
uint16_t len = 0;
ble_conn_state_conn_handle_list_t conn_handles = ble_conn_state_periph_handles();
switch(pEvt->evt_type)
{
case UART_EVT_RX_TIMEOUT:
case UART_EVT_RX_OVERFLOW:
len = UART_Read(Buf,pEvt->status);
if(len > BLE_NUS_MAX_DATA_LEN) break;
for (uint8_t i = 0; i < conn_handles.len; i++)
{
// UART_Printf("Sent on connection handle 0x%04x.\n", conn_handles.conn_handles[i]);
ble_nus_data_send(&m_nus, Buf, &len, conn_handles.conn_handles[i]);
}
break;
}
}
北欧半导体为我们提供了一套简便快捷的蓝牙BLE主从多机通信方案,让蓝牙的应用和开发变得容易。
谷雨物联网在线文档
Nordic官方文档
官方多主一从例程