淺析藍牙nrf51822程序框架

這篇先分析一下提供模板的框架部分程序。
這裏以模板的代碼(燈和按鍵)爲例:
http://download.csdn.net/download/dfsae/9987318
1.主函數
NRF51822的框架還是採用事件驅動框架。先從主函數進行分析


int main(void)
{
    // Initialize
    leds_init();           //led初始化,硬件配置
    timers_init();
    gpiote_init();          //中斷初始化
    buttons_init();
    ble_stack_init();
    scheduler_init();    
    gap_params_init();
    services_init();
    advertising_init();
    conn_params_init();
    sec_params_init();

    // Start execution
    timers_start();
    advertising_start();

    // Enter main loop
    for (;;)
    {
        app_sched_execute();
        power_manage();
    }
}

主函數裏做一些初始化,再啓動定時器和廣播,在主循環裏實現任務調度和電源管理power_manage();

1.1.定時器

NRF51822的定時器由隊列進行多個定時器的管理。

1.1.1.數據結構

定時器主要放在timer_node_t結構體組成數組中進行集中管理,存儲的方式具體看timers_init中的解析。
timer_node_t的結構如下:

typedef struct
{
    timer_alloc_state_t         state;    /**< 定時器分配狀態 */
    app_timer_mode_t            mode;     /**< 定時器模式 */
    uint32_t                    ticks_to_expire;  /**< 上一次定時器中斷到終止的ticks. */
    uint32_t                    ticks_at_start;   /**< 當前當定時器啓動的RTC計數值. */
    uint32_t                    ticks_first_interval;  /**< 第一次定時器間隔的ticks */
    uint32_t                    ticks_periodic_interval; /**< 時間週期 */
    bool                        is_running;   /**< True代表運行, False其他. */
    app_timer_timeout_handler_t p_timeout_handler;   /**< 指向當定時器倒是後調用的函數 */
    void *                      p_context;  /**<通用目標指針. 當定時器到時時,將進行超時處理。 */
    app_timer_id_t              next;     /**<下一個運行定時器的id*/
} timer_node_t;

app_timer.c中爲定時器隊列提供了基礎的添加移除操作。

1.1.2.初始化函數

主函數中調用timers_init實現定時器的初始化

static void timers_init(void)
{
    // Initialize timer module, making it use the scheduler
    APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, true);
}

#define APP_TIMER_INIT(PRESCALER, MAX_TIMERS, OP_QUEUES_SIZE, USE_SCHEDULER)             \
    do                                                                        \
    {                                                                                 \
        static uint32_t APP_TIMER_BUF[CEIL_DIV(APP_TIMER_BUF_SIZE((MAX_TIMERS),          \
                                       (OP_QUEUES_SIZE) + 1),                     \
                                       sizeof(uint32_t))];                     \
        uint32_t ERR_CODE = app_timer_init((PRESCALER),                          \
                                      (MAX_TIMERS),                          \
                                      (OP_QUEUES_SIZE) + 1,                    \
                                       APP_TIMER_BUF,                            \
                                       (USE_SCHEDULER) ? app_timer_evt_schedule : NULL); \
        APP_ERROR_CHECK(ERR_CODE);                                                     \
    } while (0)

該初始化調用了app_timer.c中的app_timer_init,同時根據USE_SCHEDULER來設置回調函數app_timer_evt_schedule。

static __INLINE uint32_t app_timer_evt_schedule(app_timer_timeout_handler_t timeout_handler,
                                                void *                      p_context)
{
    app_timer_event_t timer_event;

    timer_event.timeout_handler = timeout_handler;
    timer_event.p_context       = p_context;

    return app_sched_event_put(&timer_event, sizeof(timer_event), app_timer_evt_get);
}

app_timer_evt_schedule中做了:
1>.生成一個事件。
2>.通過事件調度的API(app_sched_event_put)發送事件。

uint32_t app_timer_init(uint32_t                      prescaler,//預分頻器
                        uint8_t                       max_timers,//最大時間
                        uint8_t                       op_queues_size,
                        void *                        p_buffer,
                        app_timer_evt_schedule_func_t evt_schedule_func)
{
    int i;
    // 檢查緩衝區是否正確字對齊
    if (!is_word_aligned(p_buffer))
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    if (p_buffer == NULL)// 檢查空緩衝區
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    rtc1_stop(); // RTC停止防止定時器時間移除後重新初始化

    m_evt_schedule_func = evt_schedule_func;//如果有調度則:app_timer_evt_schedule

    // Initialize timer node array初始化定時器節點數組APP_TIMER_BUF
    m_node_array_size = max_timers;
    mp_nodes          = p_buffer;

    for (i = 0; i < max_timers; i++)
    {
        mp_nodes[i].state      = STATE_FREE;
        mp_nodes[i].is_running = false;
    }

    // Skip timer node array
    p_buffer = &((uint8_t *)p_buffer)[max_timers * sizeof(timer_node_t)];

    // Initialize users array
    m_user_array_size = APP_TIMER_INT_LEVELS;
    mp_users          = p_buffer;

    // Skip user array
    p_buffer = &((uint8_t *)p_buffer)[APP_TIMER_INT_LEVELS * sizeof(timer_user_t)];

    // 初始化 operation隊列
    for (i = 0; i < APP_TIMER_INT_LEVELS; i++)
    {
        timer_user_t * p_user = &mp_users[i];
        p_user->first              = 0;
        p_user->last               = 0;
        p_user->user_op_queue_size = op_queues_size;
        p_user->p_user_op_queue    = p_buffer;

        // Skip operation queue
        p_buffer = &((uint8_t *)p_buffer)[op_queues_size * sizeof(timer_user_op_t)];
    }

    m_timer_id_head             = TIMER_NULL;
    m_ticks_elapsed_q_read_ind  = 0;
    m_ticks_elapsed_q_write_ind = 0;

    NVIC_ClearPendingIRQ(SWI0_IRQn);
    NVIC_SetPriority(SWI0_IRQn, SWI0_IRQ_PRI);
    NVIC_EnableIRQ(SWI0_IRQn);

    rtc1_init(prescaler);
    m_ticks_latest = rtc1_counter_get();
    return NRF_SUCCESS;
}

定時器初始化主要做了以下:
1>.設置事件回調函數(如果有),綁定的是app_timer_evt_schedule函數。
2>.初始化分配傳進來數目的定時器,並分配好對應的空間
在app_timer.c中,定義了一些內部變量來管理整個定時器系統,一些參數放在傳入的內存中保存。內存中的存放如下:
在這裏插入圖片描述
最開始的內存中保存每個定時器的分配狀態(state)以及是否運行狀態(is_running),這兩個都是timer_node_t 結構體中的參數。
中間存放timer_user_t結構體的數據
最後一塊存放對應的timer_user_op_t結構體數據。
3>.設置開啓中斷

1.1.2.創建定時器函數

在一開始分配完成定時器後,後續定時器在使用之前可以由用戶自定義進行分配。分配只需調用app_timer_create函數即可。用戶傳入該定時器的工作模式和回調函數,正常情況下會找到空閒的定時器放在p_timer_id中返回,即用戶得到當前分配的定時器id。過程中只修改了對應ID的mp_nodes的值。比如在該例程中初始化button的最後就分配了一個定時器。

uint32_t app_timer_create(app_timer_id_t *            p_timer_id,
                          app_timer_mode_t            mode,
                          app_timer_timeout_handler_t timeout_handler)
{
    int i;
    if (mp_nodes == NULL)
    {
        return NRF_ERROR_INVALID_STATE;
    }
    if (timeout_handler == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    if (p_timer_id == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }    

    // 尋找看空閒的定時器
    for (i = 0; i < m_node_array_size; i++)
    {
        if (mp_nodes[i].state == STATE_FREE)
        {
            mp_nodes[i].state             = STATE_ALLOCATED;
            mp_nodes[i].mode              = mode;
            mp_nodes[i].p_timeout_handler = timeout_handler;

            *p_timer_id = i;
            return NRF_SUCCESS;
        }
    }

    return NRF_ERROR_NO_MEM;
}

1.1.3.定時器中斷

app_timer.c中提供了一些對底層RTC進行操作的函數:
rtc1_init —— 初始化
rtc1_start —— 啓動定時器
rtc1_stop —— 終止定時器
rtc1_counter_get —— 獲得定時器的計數值
rtc1_compare0_set —— 設置過零比較器
RTC1_IRQHandler —— 定時器中斷處理函數
從中斷處理這裏開始說起。

void RTC1_IRQHandler(void)
{
    // 清除事件
    NRF_RTC1->EVENTS_COMPARE[0] = 0;
    NRF_RTC1->EVENTS_COMPARE[1] = 0;
    NRF_RTC1->EVENTS_COMPARE[2] = 0;
    NRF_RTC1->EVENTS_COMPARE[3] = 0;
    NRF_RTC1->EVENTS_TICK       = 0;
    NRF_RTC1->EVENTS_OVRFLW     = 0;
    timer_timeouts_check();// 檢測是否有到時間
}

timer_timeouts_check函數負責會設定的對應的應用是否到時間的定時器的檢測。

static void timer_timeouts_check(void)
{
    if (m_timer_id_head != TIMER_NULL)  //處理到時間的定時器
    {
        app_timer_id_t  timer_id;
        uint32_t        ticks_elapsed;
        uint32_t        ticks_expired;
        //  初始化實際經過的ticks爲0
        ticks_expired = 0;

        // ticks_elapsed(到期時間)在這裏被得到, 現在的計數和上次計數的差值
        ticks_elapsed = ticks_diff_get(rtc1_counter_get(), m_ticks_latest);

        // Auto variable containing the head of timers expiring 
        timer_id = m_timer_id_head;

        // 到時所有定時器 ticks_elapsed 並且獲得ticks_expired (到期時間)
        while (timer_id != TIMER_NULL)
        {
            timer_node_t * p_timer;
            p_timer = &mp_nodes[timer_id]; //獲得當前定時器節點

            // 未超時則什麼都不做
            if (ticks_elapsed < p_timer->ticks_to_expire)
            {
                break;
            }

            // 遞減ticks_elapsed(經過時間)值並獲得expired ticks (到期時間)
            ticks_elapsed -= p_timer->ticks_to_expire;
            ticks_expired += p_timer->ticks_to_expire;

            // 檢測下一個定時器
            timer_id = p_timer->next;

            //回調
            timeout_handler_exec(p_timer);
        }

        // 準備向m_ticks_elapsed隊列中加ticks過期的隊列 
        if (m_ticks_elapsed_q_read_ind == m_ticks_elapsed_q_write_ind)
        {
            // 讀需要等於寫序號。這意味着ticks_expired新值需要被存儲在新的地址
            // 在m_ticks_elapsed隊列(作爲雙緩衝區實現的。)
            // 檢測是否有隊列溢出
            if (++m_ticks_elapsed_q_write_ind == CONTEXT_QUEUE_SIZE_MAX)
            {
                // 隊列溢出. 因此,寫索引指向隊列的開始
                m_ticks_elapsed_q_write_ind = 0;
            }
        }

        // 隊列的ticks到時.
        m_ticks_elapsed[m_ticks_elapsed_q_write_ind] = ticks_expired;

        timer_list_handler_sched();
    }
}
static void timeout_handler_exec(timer_node_t * p_timer)
{
    if (m_evt_schedule_func != NULL)
    {
        uint32_t err_code = m_evt_schedule_func(p_timer->p_timeout_handler, 
                                                p_timer->p_context);
        APP_ERROR_CHECK(err_code);
    }
    else
    {
        p_timer->p_timeout_handler(p_timer->p_context);
    }
}

它這裏的ticks_elapsed和ticks_expired我也被繞的暈乎乎的。但是拋開這個。這個函數的本意是對超時的定時器用他們一開始設置的回調函數的回調。下面的按鍵可以參考。調用的回調函數有兩個m_evt_schedule_func 和 p_timer->p_timeout_handler。當有調度機制的時候調用前者,發送給調度內核,最後在主循環中來進行timer_create時綁定的回調函數調度。在這個例子中默認調用的都是app_timer_evt_schedule。如果沒有調度機制則直接調用timer_create時綁定的回調函數。

還有一個SWI0中斷,軟件中斷。
SWI0_IRQHandler ——SWI0中斷,程序裏很多地方會置位這個中斷。比如前面提到的timer_timeouts_check。
SWI0中斷中執行所有定時器更新

void SWI0_IRQHandler(void)
{
    timer_list_handler();
}

static void timer_list_handler(void)
{
    app_timer_id_t restart_list_head = TIMER_NULL;
    uint32_t       ticks_elapsed;
    uint32_t       ticks_previous;
    bool           ticks_have_elapsed;
    bool           compare_update;
    app_timer_id_t timer_id_head_old;

    // 備份上一次已知的tick和List頭
    ticks_previous    = m_ticks_latest;
    timer_id_head_old = m_timer_id_head;

    // 獲得過去的ticks數
    ticks_have_elapsed = elapsed_ticks_acquire(&ticks_elapsed);

    // 處理鏈表缺失
    compare_update = list_deletions_handler();

    //處理到時間的定時器
    if (ticks_have_elapsed)
    {
        expired_timers_handler(ticks_elapsed, ticks_previous, &restart_list_head);
        compare_update = true;
    }

    // 處理插入列表
    if (list_insertions_handler(restart_list_head))
    {
        compare_update = true;
    }

    // 必要時更新比較寄存器
    if (compare_update)
    {
        compare_reg_update(timer_id_head_old);
    }
}

1.1.4.啓動定時器

app_timer_start函數來啓動某個定時器。這個函數裏面有調用timer_start_op_schedule函數。這裏分配函數爲什麼有個參數是mp_users

uint32_t app_timer_start(app_timer_id_t timer_id, uint32_t timeout_ticks, void * p_context)
{
    uint32_t timeout_periodic;
    // Schedule timer start operation
    timeout_periodic = (mp_nodes[timer_id].mode == APP_TIMER_MODE_REPEATED) ? timeout_ticks : 0;

    return timer_start_op_schedule(user_id_get(),
                                   timer_id,
                                   timeout_ticks,
                                   timeout_periodic,
                                   p_context);
}

static uint32_t timer_start_op_schedule(timer_user_id_t user_id,
                                        app_timer_id_t  timer_id,
                                        uint32_t        timeout_initial,
                                        uint32_t        timeout_periodic,
                                        void *          p_context)
{
    app_timer_id_t last_index;

   //分配一個操作隊列
    timer_user_op_t * p_user_op = user_op_alloc(&mp_users[user_id], &last_index);
    if (p_user_op == NULL)
    {
        return NRF_ERROR_NO_MEM;
    }

    p_user_op->op_type                              = TIMER_USER_OP_TYPE_START;
    p_user_op->timer_id                             = timer_id;
    p_user_op->params.start.ticks_at_start          = rtc1_counter_get();
    p_user_op->params.start.ticks_first_interval    = timeout_initial;
    p_user_op->params.start.ticks_periodic_interval = timeout_periodic;
    p_user_op->params.start.p_context               = p_context;

    user_op_enque(&mp_users[user_id], last_index);    

    timer_list_handler_sched();

    return NRF_SUCCESS;
}



1.2.按鍵

按鍵初始化,在buttons數組中定義了所有的用到的按鍵及其配置。具體意思參考app_button_cfg_t 結構體。
按鍵這裏變量:
m_detection_delay_timer_id定時器。這個定時器用來計算延時,它在初始化中被創建,並設置計時時間到後回調detection_delay_timeout_handler函數。

static void buttons_init(void)
{
    // Note: Array must be static because a pointer to it will be saved in the Button handler
    //       module.
    static app_button_cfg_t buttons[] =
    {
        {WAKEUP_BUTTON_PIN, false, BUTTON_PULL, NULL},
        {LEDBUTTON_BUTTON_PIN_NO, false, BUTTON_PULL, button_event_handler}
    };

    APP_BUTTON_INIT(buttons, sizeof(buttons) / sizeof(buttons[0]), BUTTON_DETECTION_DELAY, true);
}
#define APP_BUTTON_INIT(BUTTONS, BUTTON_COUNT, DETECTION_DELAY, USE_SCHEDULER)  \
    do        \
    {                                          \
        uint32_t ERR_CODE = app_button_init((BUTTONS),       \
                                            (BUTTON_COUNT),         \
                                            (DETECTION_DELAY),    \
                                    (USE_SCHEDULER) ? app_button_evt_schedule : NULL); \
        APP_ERROR_CHECK(ERR_CODE);                         \
    } while (0)

同樣,初始化中設置事件回調函數(如果有),綁定的是app_button_evt_schedule函數。這個函數裏面的操作和定時器裏面的操作差不多。

uint32_t app_button_init(app_button_cfg_t *             p_buttons,
                         uint8_t                        button_count,
                         uint32_t                       detection_delay,
                         app_button_evt_schedule_func_t evt_schedule_func)
{
    uint32_t err_code;
    if (detection_delay < APP_TIMER_MIN_TIMEOUT_TICKS)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    //保存配置.
    mp_buttons          = p_buttons;
    m_button_count      = button_count;
    m_detection_delay   = detection_delay;
    m_evt_schedule_func = evt_schedule_func;

    uint32_t pins_transition_mask = 0;   

    while (button_count--)
    {
        app_button_cfg_t * p_btn = &p_buttons[button_count];
        nrf_gpio_cfg_input(p_btn->pin_no, p_btn->pull_cfg);   //硬件配置
        pins_transition_mask |= (1 << p_btn->pin_no); //創建用戶中斷註冊屏蔽位
    }

    // Register button module as a GPIOTE user.
    err_code = app_gpiote_user_register(&m_gpiote_user_id,
                                        pins_transition_mask,
                                        pins_transition_mask,
                                        gpiote_event_handler);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Create polling timer.
    return app_timer_create(&m_detection_delay_timer_id,
                            APP_TIMER_MODE_SINGLE_SHOT,
                            detection_delay_timeout_handler);
}

按鍵初始化中的操作主要是對按鍵部分管理的變量做了個初始化,然後配置了硬件和中斷部分,並且設置了中斷回調函數gpiote_event_handler,標記了引腳電平狀態。

當按鍵被按下後,系統首先會回調gpiote_event_handler函數。同時設置對應的延時參數後啓動的定時器計時。

static void gpiote_event_handler(uint32_t event_pins_low_to_high, uint32_t event_pins_high_to_low)
{
    uint32_t err_code;

    // 開始檢測計時器。如果定時器正在運行,檢測週期重新開始
    //注意: 使用app_timer_start()中的p_context參數來向定時器句柄傳遞引腳狀態 
    STATIC_ASSERT(sizeof(void *) == sizeof(uint32_t));

    err_code = app_timer_stop(m_detection_delay_timer_id);    //停止定時器
    if (err_code != NRF_SUCCESS)
    {
        // The impact in app_button of the app_timer queue running full is losing a button press.
        // The current implementation ensures that the system will continue working as normal. 
        return;
    }

    m_pin_transition.low_to_high = event_pins_low_to_high;
    m_pin_transition.high_to_low = event_pins_high_to_low;

    err_code = app_timer_start(m_detection_delay_timer_id,
                               m_detection_delay,
                               (void *)(event_pins_low_to_high | event_pins_high_to_low));
    if (err_code != NRF_SUCCESS)
    {
        // The impact in app_button of the app_timer queue running full is losing a button press.
        // The current implementation ensures that the system will continue working as normal. 
    }
}

當檢測延時時間達到後調用detection_delay_timeout_handler回調函數,這個函數裏面又會調用button_handler_execute按鍵按下的執行函數。在這個函數中會調用前面的回調函數app_button_evt_schedule發送事件給調度內核。當下次內核調度這個事件的時候,就會調度按鍵響應事件了,在這個例子中LEDBUTTON_BUTTON_PIN_NO按下調用button_event_handler,這個是修改服務中特性的值,這裏先不講。

static void detection_delay_timeout_handler(void * p_context)
{
    uint32_t err_code;
    uint32_t current_state_pins;

    //獲得當前引腳狀態
    err_code = app_gpiote_pins_state_get(m_gpiote_user_id, &current_state_pins);

    if (err_code != NRF_SUCCESS)
    {
        return;
    }

    uint8_t i;
    // 按下按鍵檢測,執行按鍵句柄
    for (i = 0; i < m_button_count; i++)
    {
        app_button_cfg_t * p_btn = &mp_buttons[i];

        if (((m_pin_transition.high_to_low & (1 << p_btn->pin_no)) != 0) 
             && (p_btn->button_handler != NULL))
        {
            //如果對應按鍵有效爲高電平,然後從高到低跳變的釋放過程
            if(p_btn->active_state == APP_BUTTON_ACTIVE_HIGH)
            {
                button_handler_execute(p_btn, APP_BUTTON_RELEASE);
            }
            //如果對應按鍵有效爲低電平,然後從高到低跳變的按下過程
            else
            {
                button_handler_execute(p_btn, APP_BUTTON_PUSH);
            }
        }
        else if (((m_pin_transition.low_to_high & (1 << p_btn->pin_no)) != 0) 
                && (p_btn->button_handler != NULL))
        {
            //如果對應按鍵有效爲高電平,然後從低到高跳變的按下過程
            if(p_btn->active_state == APP_BUTTON_ACTIVE_HIGH)
            {
                button_handler_execute(p_btn,APP_BUTTON_PUSH);
            }
            //如果對應按鍵有效爲低電平,然後從低到高跳變的釋放過程
            else
            {
                button_handler_execute(p_btn,APP_BUTTON_RELEASE);
            }
        }
    }
}


static void button_handler_execute(app_button_cfg_t * p_btn, uint32_t transition)
{
    if (m_evt_schedule_func != NULL)
    {
        uint32_t err_code = m_evt_schedule_func(p_btn->button_handler, p_btn->pin_no,transition);
        APP_ERROR_CHECK(err_code);
    }
    else
    {
        if(transition == APP_BUTTON_PUSH)
        {
            p_btn->button_handler(p_btn->pin_no, APP_BUTTON_PUSH);
        }
        else if(transition == APP_BUTTON_RELEASE)
        {
            p_btn->button_handler(p_btn->pin_no, APP_BUTTON_RELEASE);
        }
    }
}

有明白的可以加羣:805601459(備註CSDN)進行交流。本文僅限於內部交流,禁止商用!

發佈了11 篇原創文章 · 獲贊 7 · 訪問量 2425
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章