nRF52832——基於SDK15.2 加入串列埠透傳服務
【背景】:專案需求,需要手機和裝置進行藍芽雙向資料傳輸,需要在原工程基礎上加入此通訊工程,使用的是SDK15.2,而官方原始碼已經帶有使用的藍芽串列埠透傳例程,所以參照此例程,移植到現有的工程中即可。但是移植過程卻遇到非常蛋疼的事。
【移植要點】:下面主要提及要點,與SDK12.3的差異;
1、加入串列埠驅動相關
關鍵的是串列埠事件處理回撥函式當串列埠收到資料後,BLE傳送給主機,即手機
//uart事件處理回撥函式 void uart_event_handle(app_uart_evt_t * p_event) { static uint8_t data_array[BLE_NUS_MAX_DATA_LEN]; static uint8_t index = 0; uint32_t err_code; switch (p_event->evt_type) { case APP_UART_DATA_READY: //串列埠接收資料事件 UNUSED_VARIABLE(app_uart_get(&data_array[index])); index++; //判斷資料是否接收完成,判斷條件:資料長度達到20位元組或者接收到"\n" if ((data_array[index - 1] == '\n') || (data_array[index - 1] == '\r') || (index >= m_ble_nus_max_data_len)) { if (index > 1) { do { uint16_t length = (uint16_t)index; err_code = ble_nus_data_send(&m_nus, data_array, &length, m_conn_handle); if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_RESOURCES) && (err_code != NRF_ERROR_NOT_FOUND)) { APP_ERROR_CHECK(err_code); } } while (err_code == NRF_ERROR_RESOURCES); } index = 0; } break; case APP_UART_COMMUNICATION_ERROR: APP_ERROR_HANDLER(p_event->data.error_communication); break; case APP_UART_FIFO_ERROR: APP_ERROR_HANDLER(p_event->data.error_code); break; default: break; } } /**************************************************************************************** * 描 述 : uart初始化配置函式。波特率115200bps,流控關閉。 * 入 參 : 無 * 返回值 : 無 ***************************************************************************************/ void uart_config(void) { uint32_t err_code; app_uart_comm_params_t const comm_params = { .rx_pin_no = RX_PIN_NUMBER, .tx_pin_no = TX_PIN_NUMBER, .rts_pin_no = RTS_PIN_NUMBER, .cts_pin_no = CTS_PIN_NUMBER, .flow_control = APP_UART_FLOW_CONTROL_DISABLED, .use_parity = false, #if defined (UART_PRESENT) .baud_rate = NRF_UART_BAUDRATE_115200 #else .baud_rate = NRF_UARTE_BAUDRATE_115200 #endif }; //初始化app uart,註冊uart事件回撥函式 APP_UART_FIFO_INIT(&comm_params, UART_RX_BUF_SIZE, UART_TX_BUF_SIZE, uart_event_handle, APP_IRQ_PRIORITY_LOWEST, err_code); APP_ERROR_CHECK(err_code); }
2、加入串列埠透傳服務和特徵
1)所需的工程檔案:...components\ble\ble_services目錄下的ble_nus資料夾(注意帶_c的表示主機),將其中的.c .h檔案加入到自己的工程中,另外SDK15.2 的ble_nus.c中還使用到了ble_link_ctx_manager,在目錄...\components\ble\ble_link_ctx_manager下,因此也需要加入到工程中;
2)程式中加入的要點:
a、定義結構體變數m_nus,即串列埠透傳的例項,在應用程式中通過m_us就可以操作相關透傳服務,這個定義方法和SDK12.3中不一樣,SDK15.2中,nus的事件處理函式ble_nus_on_ble_evt在定義m_nus實體的時候就相當於註冊了,SDK15.2中沒有ble_evt_dispatch這樣的事件派發函式,不需要往這個函式中單獨加nus的事件處理函式,這個還是很方便的;
BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); /**< BLE NUS service instance. */
b、初始化服務
在main.c中 services_init函式中加入串列埠透傳服務,這裡要注意的是該服務屬於自定義服務,需要定義UUID,這個服務中包含兩個特徵 傳送和接收,所以,定義串列埠透傳服務UUID 0X0001,兩個特徵:
Rx:有notify屬性,裝置收到串列埠資料後,將資料傳送給主機,UUID 0X0002;
Tx:有write屬性,主機通過write該特徵值將資料發給裝置,UUID 0X0003;
這裡注意,新增自定義服務後,還需要在sdk_config.h修改NRF_SDH_BLE_VS_UUID_COUNT的值,加幾個自定義服務,這個值就是多少,同時加後若程式執行提示mem空間不足,需要更改target下的ram空間起始地址,一個service 多佔用0x10空間;
static void services_init(void)
{
ret_code_t err_code;
ble_dis_init_t dis_init;
nrf_ble_qwr_init_t qwr_init = {0};
ble_nus_init_t nus_init;
uint8_t body_sensor_location;
// Initialize Queued Write Module.
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
// Initialize Device Information Service.
memset(&dis_init, 0, sizeof(dis_init));
ble_srv_ascii_to_utf8(&dis_init.manufact_name_str, (char *)MANUFACTURER_NAME);
dis_init.dis_char_rd_sec = SEC_OPEN;
err_code = ble_dis_init(&dis_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);
}
c、編寫nus_data_handler事件處理函式:
由上節中可以看到,初始化串列埠透傳服務,需要提供一個事件控制代碼,用於處理BLE接收的資料,此處編寫該控制代碼函式,將接受的資料通過串列埠列印:
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type ==BLE_NUS_EVT_RX_DATA ) // p_evt->type ==BLE_NUS_EVT_COMM_STARTED
{
uint32_t err_code;
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);
for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++)
{
do
{
err_code = app_uart_put(p_evt->params.rx_data.p_data[i]);
if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY))
{
NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", err_code);
APP_ERROR_CHECK(err_code);
}
} while (err_code == NRF_ERROR_BUSY);
}
if (p_evt->params.rx_data.p_data[p_evt->params.rx_data.length - 1] == '\r')
{
while (app_uart_put('\n') == NRF_ERROR_BUSY);
}
}
}
d、因SDK15.2支援長包傳輸,若想加入此功能,在gatt_init();中設定MTU,NRF_SDH_BLE_GATT_MAX_MTU_SIZE最大可設定為247,單次傳輸 實際資料長度244位元組;
/**@brief Function for handling events from the GATT library. */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
{
m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
NRF_LOG_INFO("Data len is set to 0x%X(%d)", m_ble_nus_max_data_len, m_ble_nus_max_data_len);
}
NRF_LOG_DEBUG("ATT MTU exchange completed. central 0x%x peripheral 0x%x",
p_gatt->att_mtu_desired_central,
p_gatt->att_mtu_desired_periph);
}
/**@brief Function for initializing the GATT module. */
static void gatt_init(void)
{
ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
APP_ERROR_CHECK(err_code);
}
e、非常關鍵的一點,在main.c中,加入自定義串列埠透傳服務 services_init(); 一定要放在 advertising_init();前面,否則程式會出現意想不到的錯誤~現在原理我也沒弄明白, 在NORDIC官網上發了帖子,還有得到回覆,有了解的兄弟歡迎留言討論。