浅析蓝牙nrf51822程序框架
這篇先分析一下提供模板的框架部分程序。
這里以模板的代碼(燈和按鍵)為例:
http://download.csdn.net/download/dfsae/9987318
1.主函數
NRF51822的框架還是采用事件驅動框架。先從主函數進行分析
主函數里做一些初始化,再啟動定時器和廣播,在主循環里實現任務調度和電源管理power_manage();
1.1.定時器
NRF51822的定時器由隊列進行多個定時器的管理。
1.1.1.數據結構
定時器主要放在timer_node_t結構體組成數組中進行集中管理,存儲的方式具體看timers_init中的解析。
timer_node_t的結構如下:
app_timer.c中為定時器隊列提供了基礎的添加移除操作。
1.1.2.初始化函數
主函數中調用timers_init實現定時器的初始化
static void timers_init(void) {// Initialize timer module, making it use the schedulerAPP_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)發送事件。
定時器初始化主要做了以下:
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 —— 定時器中斷處理函數
從中斷處理這里開始說起。
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為0ticks_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中斷中執行所有定時器更新
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 operationtimeout_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函數。
同樣,初始化中設置事件回調函數(如果有),綁定的是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, ¤t_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)進行交流。本文僅限于內部交流,禁止商用!
總結
以上是生活随笔為你收集整理的浅析蓝牙nrf51822程序框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NRF51822芯片简介和软硬件开发简介
- 下一篇: python 获取天气接口数据