【背景】:项目需求,需要手机和设备进行蓝牙双向数据传输,需要在原工程基础上加入此通信工程,使用的是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官网上发了帖子,还有得到回复,有了解的兄弟欢迎留言讨论。
———————————————————————————————————————
本文为博主原创文章,转载请注明出处!
若本文对您有些许帮助,轻抬您发财的小手,关注/评论/点赞/收藏,就是对我最大的支持!
祝君升职加薪,鹏程万里!