nRF52实践:BLE中LED和BUTTON模块研究

254 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

本文介绍 BLE中简单外设模块研究。

SDK 中封装了 LED 和 BUTTON 外设接口,很多例程都使用了这些接口。本文针对这2个外设进行简单的跟踪。并不区别是否带BLE。

板级初始化

bsp_xx实现了板级的驱动,通过宏并屏蔽具体硬件。对于nrf52832,在文件components\boards\pca10040.h定义一些外设的引脚。在components\boards\boards.c实现了初始化及LED、BUTTON底层控制操作。

对于LED和按键,已默认有引脚的定义,在实际中则使用索引,比如第0号LED为是LED_1,按键亦然。

LED定义:

#define LEDS_NUMBER    4#define LED_START      17
#define LED_1          17
#define LED_2          18
#define LED_3          19
#define LED_4          20
#define LED_STOP       20#define LEDS_ACTIVE_STATE 0#define LEDS_INV_MASK  LEDS_MASK#define LEDS_LIST { LED_1, LED_2, LED_3, LED_4 }
#define BSP_LED_0      LED_1
#define BSP_LED_1      LED_2
#define BSP_LED_2      LED_3
#define BSP_LED_3      LED_4

按键定义:

#define BUTTONS_NUMBER 4#define BUTTON_START   13
#define BUTTON_1       13
#define BUTTON_2       14
#define BUTTON_3       15
#define BUTTON_4       16
#define BUTTON_STOP    16
#define BUTTON_PULL    NRF_GPIO_PIN_PULLUP#define BUTTONS_ACTIVE_STATE 0#define BUTTONS_LIST { BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4 }#define BSP_BUTTON_0   BUTTON_1
#define BSP_BUTTON_1   BUTTON_2
#define BSP_BUTTON_2   BUTTON_3
#define BSP_BUTTON_3   BUTTON_4

板级初始化有2个:bsp_board_init、bsp_init。怎么理解?

调用:

bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS);
bsp_board_init(BSP_INIT_LEDS);
​
bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS,
                        ant_advanced_burst_bsp_evt_handler);
bsp_init(BSP_INIT_LEDS, NULL);

板级初始化实现文件:components\boards\boards.c

bsp_board_init
--> 根据标志,调用 bsp_board_leds_init
--> 根据标志,调用 bsp_board_buttons_init

bsp_init函数会初始LED和按键。并初始化定时器。

led定时事件:

app_timer_create(&m_bsp_leds_tmr, APP_TIMER_MODE_SINGLE_SHOT, leds_timer_handler);

事件处理函数leds_timer_handler调用leds_timer_handler,其调用bsp_led_indication设置特定的指示状态(有对应的宏,例程中会使用到)。根据不同状态,亮灭灯,获取新的定时参数,再重启定时器

BUTTON定时事件:

app_timer_create(&m_bsp_button_tmr, APP_TIMER_MODE_SINGLE_SHOT, button_timer_handler);
button_timer_handler
--> bsp_button_event_handler

LED灯

实现文件:components\libraries\bsp.c。

使用方法

LED初始化:bsp_board_init

亮灭LED灯:bsp_board_led_on bsp_board_led_off 参数为LED索引

全部亮灭:bsp_board_leds_on bsp_board_leds_off (内部会遍历LED数组)

反转LED:bsp_board_led_invert

源码跟踪

应用程序调用bsp_indication_set即可,内部实现了定时器任务。第一次调用该函数后,就会进入状态机。

bsp_indication_set
--> bsp_led_indication  根据状态执行不同的处理。比如扫描状态,
                        先获取表示该状态的GPIO(LED1)高低电平,如高,则off,反低则on,
                        期间计算下次延时时长,保存状态,再启动定时器,实现闪烁。

流程跟踪:

bsp_init
--> 创建定时器 m_bsp_leds_tmr, 回调函数为 leds_timer_handler,
    --> 在回调函数中,调用 bsp_led_indication。

按键 BUTTON

在文件components\libraries\button\app_button.c中实现。

使用方法

定义结构体,再初始化之。结构体定义引脚以及处理函数,初始化第三个参数为定时检测时间间隔。

自注:是否可多个?

static void buttons_init(void)
{
    static app_button_cfg_t buttons[] =
    {
        {LEDBUTTON_BUTTON, false, BUTTON_PULL, button_event_handler}
    };
    err_code = app_button_init(buttons, ARRAY_SIZE(buttons),
                               BUTTON_DETECTION_DELAY);
}

处理函数:

static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
    ret_code_t err_code;
​
    switch (pin_no)
    {
        case LEDBUTTON_BUTTON:
            NRF_LOG_INFO("Send button state change.");
        default:
            APP_ERROR_HANDLER(pin_no);
            break;
    }
}
​
static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
    NRF_LOG_INFO("button_event_handler: pin_no: %u, button_action:%u\r\n",pin_no, button_action);
    if(TILE_BUTTON == pin_no && true == button_action)
    {
        NRF_LOG_INFO("button press detected successfully\r\n");
        tile_button_was_pressed();
    }
}

获取状态:app_button_is_pushed,参数为按键索引号。返回值为true表示按下,反之释放。

使用、禁止:app_button_enable app_button_disable

源码跟踪

结构体:

typedef struct
{
    uint8_t              pin_no;           /**< Pin to be used as a button. */
    uint8_t              active_state;     /**< APP_BUTTON_ACTIVE_HIGH or APP_BUTTON_ACTIVE_LOW. */
#if defined(BUTTON_HIGH_ACCURACY_ENABLED) && (BUTTON_HIGH_ACCURACY_ENABLED == 1)
    bool                 hi_accuracy;      /**< True if GPIOTE high accuracy (IN_EVENT) is used. */
#endif
    nrf_gpio_pin_pull_t  pull_cfg;         /**< Pull-up or -down configuration. */
    app_button_handler_t button_handler;   /**< Handler to be called when button is pushed. */
} app_button_cfg_t;

自定义回调函数:button_handler

代码跟踪:

app_button_init
--> 初始化 gpiote nrf_drv_gpiote_init
--> 赋值mp_buttons保存之
--> nrf_drv_gpiote_in_init
--> 创建定时器 m_detection_delay_timer_id,处理函数:detection_delay_timeout_handler
​
evt_handle
--> 遍历 mp_buttons,调用 evt_handle
    --> 用state_get获取按键状态
    --> 调用state_set设置状态,如 BTN_PRESSED
    --> usr_event,如 APP_BUTTON_PUSH
    --> 调用用户自定义的函数,即结构体 button_handler 指针函数

小结:定时检测按键状态,内部设置状态,但如何处理,在自定义函数响应。

按键状态:

typedef enum {
    BTN_IDLE,
    BTN_PRESS_ARMED,
    BTN_PRESS_DETECTED,
    BTN_PRESSED,
    BTN_RELEASE_DETECTED
} btn_state_t;

按键的动作:

APP_BUTTON_PUSH APP_BUTTON_RELEASE APP_BUTTON_ACTIVE_HIGH APP_BUTTON_ACTIVE_LOW

使用实例

主机例程中,在扫描开始函数中调用bsp_indication_set(BSP_INDICATE_SCANNING);,在连接事件中调用bsp_indication_set(BSP_INDICATE_CONNECTED);。即:底层实现了某个状态的功能,但何时使用则可自由控制。但状态指标是固定的,即广播或扫描时LED1闪烁。

如完全自由控制,则可调用bsp_board_led_on、bsp_board_led_off直接控制。可再做一个定时器。

按键使能,可在某个状态后调用app_button_enable。比如,只有连接上,按键才能使能(如按键发送数据)。连接断开,禁止app_button_disable

SDK中 按键1有特殊用途。按下为事件 BSP_EVENT_KEY_0,释放为事件 BSP_EVENT_SLEEP。应该在某处设置了,暂未查到。

可以用 bsp_event_to_button_action_assign 再次配置,参数依次为:索引、按下/释放动作、事件类型。但不能在bsp_init初始化。

高阶

SDK的例程已经实现了一套LED、BUTTON的处理接口。根据模块源码,可以单独再实现一套自定义的 LED 状态机。但要注意不能再使用原来的初始化接口。