这一次我们尝试创建一个串口服务来实现手机与开发板的数据透传,其实无非就跟学习创建LED读写服务的时候一样创建一个私有服务而已。废话少说,我们直接开始实验。
首先,我们要知道NRF51822的串口通信引脚:RX:P0.11,TX:P0.9,RTS:P0.10,CTS:P0.8。
接着,新建串口驱动文件:ble_nus.c和ble_nus.h
然后,写入串口初始化函数并添加到main函数中:
/**@brief Function for initializing the UART module.
*/
/**@snippet [UART Initialization] */
static void uart_init(void)
{
uint32_t err_code;
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
APP_UART_FLOW_CONTROL_DISABLED,
false,
UART_BAUDRATE_BAUDRATE_Baud38400
};//配置串口参数
APP_UART_FIFO_INIT( &comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_event_handle,
APP_IRQ_PRIORITY_LOW,
err_code);
APP_ERROR_CHECK(err_code);//配置串口FIFO缓冲,并初始化串口
}
/**@snippet [UART Initialization] */
/**@brief UART communication structure holding configuration settings for the peripheral.
*/
typedef struct
{
uint8_t rx_pin_no; /**< RX pin number. */
uint8_t tx_pin_no; /**< TX pin number. */
uint8_t rts_pin_no; /**< RTS pin number, only used if flow control is enabled. */
uint8_t cts_pin_no; /**< CTS pin number, only used if flow control is enabled. */
app_uart_flow_control_t flow_control; /**< Flow control setting, if flow control is used, the system will use low power UART mode, based on CTS signal. */
bool use_parity; /**< Even parity if TRUE, no parity if FALSE. */
uint32_t baud_rate; /**< Baud rate configuration. */
} app_uart_comm_params_t;
这里我们可以看到这里定义了一个app_uart_comm_params_t类型的变量,这个类型在app_uart.h文件内,是官方提供用户的初始化参数结构体,提高开发效率。这里我们在路径中包含:项目目录\components\libraries\uart。
串口设置里,首先配置串口端口,波特率等串口参数,再使用函数APP_UART_FIFO_INIT配置串口缓冲并且初始化串口,APP_UART_FIFO_INIT函数中调用了app_uart_init初始化函数,把我们配置的串口参数传导设置中,函数具体如下,有4个形参,分别为1:串口通用参数;2:串口缓冲;3:串口中断事件;4:串口中断优先级;
uint32_t app_uart_init(const app_uart_comm_params_t * p_comm_params,
app_uart_buffers_t * p_buffers,
app_uart_event_handler_t event_handler,
app_irq_priority_t irq_priority)
{
uint32_t err_code;
m_event_handler = event_handler;
if (p_buffers == NULL)
{
return NRF_ERROR_INVALID_PARAM;
}
// Configure buffer RX buffer.
err_code = app_fifo_init(&m_rx_fifo, p_buffers->rx_buf, p_buffers->rx_buf_size);
if (err_code != NRF_SUCCESS)
{
// Propagate error code.
return err_code;
}
// Configure buffer TX buffer.
err_code = app_fifo_init(&m_tx_fifo, p_buffers->tx_buf, p_buffers->tx_buf_size);
if (err_code != NRF_SUCCESS)
{
// Propagate error code.
return err_code;
}
nrf_drv_uart_config_t config = NRF_DRV_UART_DEFAULT_CONFIG;
config.baudrate = (nrf_uart_baudrate_t)p_comm_params->baud_rate;
config.hwfc = (p_comm_params->flow_control == APP_UART_FLOW_CONTROL_DISABLED) ?
NRF_UART_HWFC_DISABLED : NRF_UART_HWFC_ENABLED;
config.interrupt_priority = irq_priority;
config.parity = p_comm_params->use_parity ? NRF_UART_PARITY_INCLUDED : NRF_UART_PARITY_EXCLUDED;
config.pselcts = p_comm_params->cts_pin_no;
config.pselrts = p_comm_params->rts_pin_no;
config.pselrxd = p_comm_params->rx_pin_no;
config.pseltxd = p_comm_params->tx_pin_no;
err_code = nrf_drv_uart_init(&config, uart_event_handler);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
nrf_drv_uart_rx_enable();
return nrf_drv_uart_rx(rx_buffer,1);
}
接下来,就是协议栈相关初始化。首先,在services_init函数中添加自己的服务:
/**@brief Function for initializing services that will be used by the application.
*/
static void services_init(void)
{
uint32_t err_code;
ble_nus_init_t nus_init;
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);
}
在这里,我们将nus_data_handler作为串口传输处理句柄,作为回调函数。这个函数用来处理串口UART发送数据和从接收串口端来的数据。这个句柄函数也非常简单,直接调用外设函数:
/**@snippet [Handling the data received over BLE] */
static void nus_data_handler(ble_nus_t * p_nus, uint8_t * p_data, uint16_t length)
{
for (uint32_t i = 0; i < length; i++)
{
while(app_uart_put(p_data[i]) != NRF_SUCCESS);
}
while(app_uart_put('\n') != NRF_SUCCESS);
}
/**@snippet [Handling the data received over BLE] */
然后添加串口蓝牙服务声明ble_nus_init函数。
接着,我们来设计服务驱动文件:
ble_nus.h:
/**@brief Nordic UART Service structure. * * @details This structure contains status information related to the service. */ struct ble_nus_s { uint8_t uuid_type; /**< UUID type for Nordic UART Service Base UUID. */ uint16_t service_handle; /**< Handle of Nordic UART Service (as provided by the S110 SoftDevice). */ ble_gatts_char_handles_t tx_handles; /**< Handles related to the TX characteristic (as provided by the S110 SoftDevice). */ ble_gatts_char_handles_t rx_handles; /**< Handles related to the RX characteristic (as provided by the S110 SoftDevice). */ uint16_t conn_handle; /**< Handle of the current connection (as provided by the S110 SoftDevice). BLE_CONN_HANDLE_INVALID if not in a connection. */ bool is_notification_enabled; /**< Variable to indicate if the peer has enabled notification of the RX characteristic.*/ ble_nus_data_handler_t data_handler; /**< Event handler to be called for handling received data. */ }; uint32_t ble_nus_init(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init); void ble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt); uint32_t ble_nus_string_send(ble_nus_t * p_nus, uint8_t * p_string, uint16_t length);
这里用到一个ble_nus_t结构体指针,这个类型用于引用这个服务实例;ble_nus_init_t用于初始化参数;ble_evt是常见的事件类型,在官方ble.h头文件中定义。
ble_nus_string_send用于串口连接状态变化,并进行反馈。
这里再着重看一下ble_nus_t结构体中添加ble_gatts_char_handles_t类型的tx_handles和rx_handles特征值操作句柄:
/**@brief GATT Characteristic Definition Handles. */ typedef struct { uint16_t value_handle; /**< 处理的特征值. */ uint16_t user_desc_handle; /**< 用户描述句柄:句柄向用户说明,或ble_gatt_handle_invalid不存在. */ uint16_t cccd_handle; /**< 客户的特征描述符配置(CCCD)句柄. */ uint16_t sccd_handle; /**< 服务器的特征描述符配置(SCCD)句柄. */ } ble_gatts_char_handles_t;
客户端特性配置描述符(CCCD),这个描述符是给任何支持通知或指示功能的特性额外增加的。在CCCD中写入“1”使能通知功能,写入“2”使能指示功能,写入“0”同时禁止通知和指示功能。
一个特性至少包含2个属性:一个属性用于声明,一个属性用于存放特性的值。
所有通过GATT服务传输的数据必须映射成一些列的特性,可以把特性中的这些数据看成是一个个捆绑起来的数据,每个特性就是一个自我包容而独立的数据点。例如,如果几个数据总是一起变化,那么我们可以把他们集中在一个特性里。
任何在特性中的属性不是定义为属性值就是描述符。描述符是一个额外的属性以提供更多特性的信息,它提供一个用户可以识别的特性描述的实例。
接着,我们在应用层中把串口的事件处理函数加入ble_evt_dispatch函数中:
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
ble_conn_params_on_ble_evt(p_ble_evt);
ble_nus_on_ble_evt(&m_nus, p_ble_evt);
on_ble_evt(p_ble_evt);
ble_advertising_on_ble_evt(p_ble_evt);
bsp_btn_ble_on_ble_evt(p_ble_evt);
}
最后,我们将uart_init注册的uart_event_handle函数实体加上:
//timer handler
uint8_t clean_index_cnt = 0;
uint8_t uart_index = 0;
static void time_timeout_handler(void *p_context)
{
UNUSED_PARAMETER(p_context);
if(clean_index_cnt < 20)
clean_index_cnt++;
if(clean_index_cnt >= 10)
{
uart_index = 0;
}
}
static bool checksum(uint8_t *pbuffer)
{
uint8_t i;
uint8_t sum = 0;
for(i=0;i<(pbuffer[1]+2);i++)
{
sum += pbuffer[i];
}
if(sum == pbuffer[(pbuffer[1]+2)])
return true;
else
return false;
}
void uart_event_handle(app_uart_evt_t * p_event)
{
static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
uint32_t err_code;
switch (p_event->evt_type)
{
case APP_UART_DATA_READY:
UNUSED_VARIABLE(app_uart_get(&data_array[uart_index]));
uart_index++;
clean_index_cnt = 0;
if(uart_index == (data_array[1] + 3))
{
if(checksum(data_array))
{
err_code = ble_nus_string_send(&m_nus, data_array, uart_index);
if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
}
uart_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;
}
}
APP_TIMER_DEF(m_timer_id);
static void timers_init(void)
{
uint32_t err_code;
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false);
err_code = app_timer_create(&m_timer_id, APP_TIMER_MODE_REPEATED, time_timeout_handler);
APP_ERROR_CHECK(err_code);
}
这里,我为了可以兼容项目的数据帧,将处理函数加了一些处理,同时开一个定时器做串口的超时处理。
定时器在main函数中启动:
到这里,就已经完成了串口服务的所有操作。
通过这个实验,我们学会了如何开发一个蓝牙串口透传功能。