nrf52840蓝牙协议栈从机BLE串口

  nrf52840蓝牙协议栈从机BLE串口,参考蓝牙SDK的example中的ble_app_uart样例。本文主要是分析ble_app_uart样例。
  蓝牙从机串口的工作模式是:主机通过蓝牙发送数据到从机,从机接收到蓝牙数据后通过串口转发出去;从机从串口接收数据,将数据通过蓝牙发送给主机。
  蓝牙串口主要有三部分的工作,第一部分是建立串口,第二部分是建立BLE,第三部分是搭建蓝牙和串口的双向数据通道。本文只重点分析串口的建立和蓝牙服务的建立及处理,其余的蓝牙通用的配置分析参见文章nrf52840蓝牙协议栈样例分析

一、串口

1.1、串口初始化

  串口的接收数据使用串口中断,所以需要在需要在 sdk_config.h 文件中勾选 UARTE功能。在main.c文件中,串口初始化函数为:

/**@brief  Function for initializing the UART module.
 */
/**@snippet [UART Initialization] */
static void uart_init(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_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);
}

  在串口初始化函数里面,首先声明一个comm_params结构体,这个结构体是串口参数配置的基本内容。uart 的 初 始 化 函 数有两个:APP_UART_FIFO_INIT 和APP_UART_INIT, 一个是带 FIFO 缓冲的初始化串口函数, 一个是不带 FIFO 缓冲的初始化函数,一般情况下使用带软件缓冲的 FIFO 的函数, 减小数据溢出错误的发生几率。
  APP_UART_FIFO_INIT函数为:

/**@brief Macro for safe initialization of the UART module in a single user instance when using
 *        a FIFO together with UART.
 *
 * @param[in]   P_COMM_PARAMS   Pointer to a UART communication structure: app_uart_comm_params_t
 * @param[in]   RX_BUF_SIZE     Size of desired RX buffer, must be a power of 2 or ZERO (No FIFO).
 * @param[in]   TX_BUF_SIZE     Size of desired TX buffer, must be a power of 2 or ZERO (No FIFO).
 * @param[in]   EVT_HANDLER   Event handler function to be called when an event occurs in the
 *                              UART module.
 * @param[in]   IRQ_PRIO        IRQ priority, app_irq_priority_t, for the UART module irq handler.
 * @param[out]  ERR_CODE        The return value of the UART initialization function will be
 *                              written to this parameter.
 *
 * @note Since this macro allocates a buffer and registers the module as a GPIOTE user when flow
 *       control is enabled, it must only be called once.
 */
#define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \
    do                                                                                             \
    {                                                                                              \
        app_uart_buffers_t buffers;                                                                \
        static uint8_t     rx_buf[RX_BUF_SIZE];                                                    \
        static uint8_t     tx_buf[TX_BUF_SIZE];                                                    \
                                                                                                   \
        buffers.rx_buf      = rx_buf;                                                              \
        buffers.rx_buf_size = sizeof (rx_buf);                                                     \
        buffers.tx_buf      = tx_buf;                                                              \
        buffers.tx_buf_size = sizeof (tx_buf);                                                     \
        ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO);                  \
    } while (0)

  函数内部申请两个软件 BUF 缓冲空间提供给 RX 和 TX。 然后调用函数 app_uart_init 进行串口的初始化。 app_uart_init 的原型为:

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)

  该函数的参数的作用为:
  p_comm_params Pin 和通信参数。
  p_buffers RX 和 TX 缓冲区, NULL 是 FIFO 不使用。
  event_handler中断回调函数。
  irq_priority 中断优先级。

1.2、串口中断接收数据

  我们在uart_init函数内初始化串口的时候设置带 FIFO 缓冲的串口, 在 APP_UART_FIFO_INIT 函数中, 申请一个 uart_event_handle中断回调处理函数, 具体代码如下所示:

/**@brief   Function for handling app_uart events.
 *
 * @details This function will receive a single character from the app_uart module and append it to
 *          a string. The string will be be sent over BLE when the last character received was a
 *          'new line' '\n' (hex 0x0A) or if the string has reached the maximum data length.
 */
/**@snippet [Handling the data received over 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++;

            if ((data_array[index - 1] == '\n') ||
                (data_array[index - 1] == '\r') ||
                (index >= m_ble_nus_max_data_len))
            {
                if (index > 1)
                {
                    NRF_LOG_DEBUG("Ready to send data over BLE NUS");
                    NRF_LOG_HEXDUMP_DEBUG(data_array, index);

                    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;
    }
}

  这个派发函数根据分配的事件类型来分配中断事件处理类型。在接收到数据事件类型里面调用app_uart_get()函数来接收数据。该函数原型为:

uint32_t app_uart_get(uint8_t * p_byte);

  参数 p_byte 指针指向下一个接收字节存放的地址。
  返回值: 如果收到成功收到字节, 则返回成功。
  在这个中断函数内要发送蓝牙数据。实现蓝牙从机接收串口数据,通过蓝牙发送给主机的功能。

1.3、串口发送数据

  串口发送数据函数的原型为:uint32_t app_uart_put(uint8_t byte)
  返回值: NRF_SUCCESS 如果通过 TX 缓冲把字节发送出去, 则返回成功。
  返回值: NRF_ERROR_NO_MEM 如果在 TX 缓冲中没有更多的空间。 常用在流控控制中
  返回值: NRF_ERROR_INTERNAL 如果串口驱动报错。

二、BLE

2.1、蓝牙服务初始化

  蓝牙服务初始化函数为:

/**@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;
    nrf_ble_qwr_init_t qwr_init = {0};

    // 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 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);
}

  在添加服务的时候,现将nus_data_handler函数的指针指向蓝牙数据处理的地方。

2.1.1、初始化蓝牙服务

  初始化蓝牙服务函数为

uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init)
{
    ret_code_t            err_code;
    ble_uuid_t            ble_uuid;
    ble_uuid128_t         nus_base_uuid = NUS_BASE_UUID;
    ble_add_char_params_t add_char_params;

    VERIFY_PARAM_NOT_NULL(p_nus);
    VERIFY_PARAM_NOT_NULL(p_nus_init);

    // Initialize the service structure.
    p_nus->data_handler = p_nus_init->data_handler;

    /**@snippet [Adding proprietary Service to the SoftDevice] */
    // Add a custom base UUID.
    err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
    VERIFY_SUCCESS(err_code);

    ble_uuid.type = p_nus->uuid_type;
    ble_uuid.uuid = BLE_UUID_NUS_SERVICE;

    // Add the service.
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                        &ble_uuid,
                                        &p_nus->service_handle);
    /**@snippet [Adding proprietary Service to the SoftDevice] */
    VERIFY_SUCCESS(err_code);

    // Add the RX Characteristic.
    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid                     = BLE_UUID_NUS_RX_CHARACTERISTIC;
    add_char_params.uuid_type                = p_nus->uuid_type;
    add_char_params.max_len                  = BLE_NUS_MAX_RX_CHAR_LEN;
    add_char_params.init_len                 = sizeof(uint8_t);
    add_char_params.is_var_len               = true;
    add_char_params.char_props.write         = 1;
    add_char_params.char_props.write_wo_resp = 1;

    add_char_params.read_access  = SEC_OPEN;
    add_char_params.write_access = SEC_OPEN;

    err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add the TX Characteristic.
    /**@snippet [Adding proprietary characteristic to the SoftDevice] */
    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid              = BLE_UUID_NUS_TX_CHARACTERISTIC;
    add_char_params.uuid_type         = p_nus->uuid_type;
    add_char_params.max_len           = BLE_NUS_MAX_TX_CHAR_LEN;
    add_char_params.init_len          = sizeof(uint8_t);
    add_char_params.is_var_len        = true;
    add_char_params.char_props.notify = 1;

    add_char_params.read_access       = SEC_OPEN;
    add_char_params.write_access      = SEC_OPEN;
    add_char_params.cccd_write_access = SEC_OPEN;

    return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles);
    /**@snippet [Adding proprietary characteristic to the SoftDevice] */
}


  该函数首先设置主服务的UUID,然后将nus_data_handler函数的指针指向 p_nus->data_handler。nus_data_handler函数主要是进行数据接收的处理。最后添加蓝牙接收和蓝牙传输的特征值。

2.2、蓝牙事件派发函数

  当底层协议栈需要通知应用程序一些有关它的事情的时候,就会发生对应的协议栈事件,这个事件就会上抛给应用,触发应用层执行响应的操作。
  在ble_nus.h文件内,有

/**@brief   Macro for defining a ble_nus instance.
 *
 * @param     _name            Name of the instance.
 * @param[in] _nus_max_clients Maximum number of NUS clients connected at a time.
 * @hideinitializer
 */
#define BLE_NUS_DEF(_name, _nus_max_clients)                      \
    BLE_LINK_CTX_MANAGER_DEF(CONCAT_2(_name, _link_ctx_storage),  \
                             (_nus_max_clients),                  \
                             sizeof(ble_nus_client_context_t));   \
    static ble_nus_t _name =                                      \
    {                                                             \
        .p_link_ctx_storage = &CONCAT_2(_name, _link_ctx_storage) \
    };                                                            \
    NRF_SDH_BLE_OBSERVER(_name ## _obs,                           \
                         BLE_NUS_BLE_OBSERVER_PRIO,               \
                         ble_nus_on_ble_evt,                      \
                         &_name)

  在这里定义了一个ble_nus_on_ble_evt函数来处理协议栈事件。

void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
    if ((p_context == NULL) || (p_ble_evt == NULL))
    {
        return;
    }

    ble_nus_t * p_nus = (ble_nus_t *)p_context;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_nus, p_ble_evt);
            break;

        case BLE_GATTS_EVT_WRITE:
            on_write(p_nus, p_ble_evt);
            break;

        case BLE_GATTS_EVT_HVN_TX_COMPLETE:
            on_hvx_tx_complete(p_nus, p_ble_evt);
            break;

        default:
            // No implementation needed.
            break;
    }
}

  当主机写入数据,会触发ble_nus_on_ble_evt函数内BLE_GATTS_EVT_WRITE的条件,进而触发on_write函数

/**@brief Function for handling the @ref BLE_GATTS_EVT_WRITE event from the SoftDevice.
 *
 * @param[in] p_nus     Nordic UART Service structure.
 * @param[in] p_ble_evt Pointer to the event received from BLE stack.
 */
static void on_write(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
{
    ret_code_t                    err_code;
    ble_nus_evt_t                 evt;
    ble_nus_client_context_t    * p_client;
    ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    err_code = blcm_link_ctx_get(p_nus->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_client);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Link context for 0x%02X connection handle could not be fetched.",
                      p_ble_evt->evt.gatts_evt.conn_handle);
    }

    memset(&evt, 0, sizeof(ble_nus_evt_t));
    evt.p_nus       = p_nus;
    evt.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
    evt.p_link_ctx  = p_client;

    if ((p_evt_write->handle == p_nus->tx_handles.cccd_handle) &&
        (p_evt_write->len == 2))//判断是不是CCCD写使能,如果是,配置通知使能为真
    {
        if (p_client != NULL)
        {
            if (ble_srv_is_notification_enabled(p_evt_write->data))
            {
                p_client->is_notification_enabled = true;
                evt.type                          = BLE_NUS_EVT_COMM_STARTED;
            }
            else
            {
                p_client->is_notification_enabled = false;
                evt.type                          = BLE_NUS_EVT_COMM_STOPPED;
            }

            if (p_nus->data_handler != NULL)
            {
                p_nus->data_handler(&evt);
            }

        }
    }
    else if ((p_evt_write->handle == p_nus->rx_handles.value_handle) &&
             (p_nus->data_handler != NULL))
    {
        evt.type                  = BLE_NUS_EVT_RX_DATA;
        evt.params.rx_data.p_data = p_evt_write->data;
        evt.params.rx_data.length = p_evt_write->len;

        p_nus->data_handler(&evt);
    }
    else
    {
        // Do Nothing. This event is not relevant for this service.
    }
}

  最后一句的p_nus->data_handler(&evt);是调用data_handler函数处理接收的数据。data_handler是一个函数指针,指向的是nus_data_handler函数,该函数在服务初始化services_init时赋值给了data_handler。

三、搭建双向数据通道

3.1、串口转蓝牙

  在蓝牙中断函数uart_event_handle内,通过app_uart_get函数读取串口数据,再通过ble_nus_data_send函数将数据发送出去。

3.2、蓝牙转串口

  在蓝牙事件的派发函数ble_nus_on_ble_evt内处理蓝牙写事件,调用on_write函数进行蓝牙写数据的处理。on_write内通过函数指针指向了  nus_data_handler函数,nus_data_handler函数调用app_uart_put函数将数据通过串口发送出去。
  如果是为了方便理解,on_write函数内可以直接写一个串口发送的函数,而不用通过指针进行跳转。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
nrf52840是一款低功耗蓝牙芯片,它具有强大的处理能力和丰富的外设接口,广泛应用于物联网和无线通信领域。协议栈初始化是在使用nrf52840芯片进行蓝牙通信时的一个重要步骤。 在nrf52840上进行协议栈初始化时,需要进行以下几个步骤: 1. 配置引脚:首先需要配置芯片的引脚,包括蓝牙通信所需的引脚和外设接口的引脚。可以使用芯片厂商提供的开发工具或者SDK来进行引脚配置。 2. 初始化时钟:接下来需要初始化芯片的时钟,确保系统时钟正常工作。可以使用芯片厂商提供的时钟初始化函数来完成。 3. 配置协议栈参数:在进行协议栈初始化之前,需要配置一些协议栈参数,例如蓝牙设备名称、设备地址等。这些参数可以通过调用相应的API函数来设置。 4. 初始化协议栈:最后一步是初始化协议栈本身。可以使用芯片厂商提供的协议栈初始化函数来完成。在初始化过程中,协议栈会进行一系列的初始化操作,包括分配内存、注册回调函数等。 如果nrf52840协议栈初始化不过,可能是由于以下几个原因: 1. 引脚配置错误:检查引脚配置是否正确,确保蓝牙通信所需的引脚和外设接口的引脚配置正确。 2. 时钟初始化错误:检查时钟初始化是否正确,确保系统时钟正常工作。 3. 协议栈参数配置错误:检查协议栈参数的配置是否正确,例如蓝牙设备名称、设备地址等。 4. 协议栈初始化函数调用错误:检查协议栈初始化函数的调用是否正确,确保使用了正确的函数和参数。 如果以上步骤都没有问题,还是无法通过协议栈初始化,可以参考芯片厂商提供的文档或者开发社区中的相关讨论,寻求帮助解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值