藍牙芯片NRF51822入門學習1:時間管理

轉自 http://blog.sina.com.cn/chenbb8

第一節:常用接口描述

開篇教程中,我們來學習nrf51822Libraries中時間管理模塊,它的源代碼和頭文件分別爲app_timer.c/app_timer.h

這是Nordic爲我們提供的虛擬定時器,這個定時器不同於硬件上的TIMER,而是基於RTC1實現的一種虛擬定時器,其將定時功能作爲了一個資源進行管理,所以會有初始化、創建等過程。

PS1: nrf51822SDK採用封裝思想,需要暴露給用戶的信息都在相關模塊的頭文件中;爲了提醒用戶不去看具體實現細節,我們可以發現相關的API,比如app_timer_create()的源碼部分都是沒有接口描述信息的,相關使用方法需要看app_timer.h或者翻閱SDK目錄下的Documentation\ index.html。還有一些隱藏細節的封裝技巧,感興趣的孩童可以看本篇第三節。

PS2:快速查找定位文件,可以安裝一個軟件:everything,輸入文件名就可以瞬間查找到想要的文件,然後再文件上右鍵選擇“open path”就可以打開文件所在的文件夾了。

 

1、參數宏APP_TIMER_INIT()

這個宏用於初始化app_timer模塊,這是一個參數宏,接口定義如下:

APP_TIMER_INIT(PRESCALER, MAX_TIMERS, OP_QUEUES_SIZE, USE_SCHEDULER)

其中PRESCALE     分頻比例,填入0的話,每秒就產生32768tick,定時最大長度爲0xFFFFFFtick,也就是說500多秒定時。

PS3:與ucos提供的時基tick不同,本SDK的主要在定時到達的時候進入RTC中斷,而不是每個TICK都進入。因此就算每秒就產生32768tick,也不會拖慢系統性能。

MAX_TIMERS       必須大於等於工程中創建的timer數量。

OP_QUEUES_SIZE   操作隊列的大小,具體意思看第三節。如果不作死,選擇等於MAX_TIMERS就行了。

USE_SCHEDULER    是否使用任務調度器,當前不使用


2、參數宏APP_TIMER_TICKS()

這個宏用於計算特定毫秒數相當於多少個tick。接口定義如下:

APP_TIMER_TICKS(MS, PRESCALER)

其中MS是單位爲毫秒的定時時間,PRESCALER是分頻比例。


3、函數app_timer_create()

用於創建一個timer,並獲取生成timer的控制句柄。接口定義如下:

uint32_t app_timer_create(app_timer_id_t *            p_timer_id,

                          app_timer_mode_t            mode,

                          app_timer_timeout_handler_t timeout_handler)

p_timer_id         讀取到創建的timer的句柄

mode             timer的類型,其中

                  APP_TIMER_MODE_SINGLE_SHOT是單次執行

                  APP_TIMER_MODE_REPEATED是循環執行

timeout_handler    被註冊到內核的回調函數,當timer超時後就會執行。


4、函數app_timer_start()

設置一個timer的定時間隔和上下文參數,並啓動這個timer。接口定義如下:

uint32_t app_timer_start(app_timer_id_t timer_id, uint32_t timeout_ticks, void * p_context)

timer_id           app_timer_create()裏創建的timer句柄

timeout_ticks       定時的tick數量,一般用APP_TIMER_TICKS()計算。

p_context          傳遞給超時回調函數的參數,不能指向局部的自動變量。


5app_timer_stop()

停止一個timer的運行。接口定義如下:

uint32_t app_timer_stop(app_timer_id_t timer_id)

timer_id           app_timer_create()裏創建的timer句柄

第二節:流水燈例子

實驗代碼位於main.c

首先定義幾個初始化用到的宏

#define APP_TIMER_PRESCALER         0  

#define APP_TIMER_MAX_TIMERS        1  

#define APP_TIMER_OP_QUEUE_SIZE     1  

TimerInit()中啓用LFCLK並初始化timer(官方例程ble_app_uart沒有開啓LFCLLK的動作,也許S110的底層會自動開啓LFCLK時鐘)

    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;

    NRF_CLOCK->TASKS_LFCLKSTART = 1;

    while(NRF_CLOCK->EVENTS_LFCLKSTARTED == 0)

    {

        //donoting

    }

    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;

    APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);

然後在BlinkInit()內創建一個流水燈所用到的timer,相關代碼是

    uint32_t ERR_CODE = app_timer_create(&BlinkHandle, APP_TIMER_MODE_SINGLE_SHOT, BlinkTimeoutHandler);//創建定時器,並讀取句柄號,單次執行

    APP_ERROR_CHECK(ERR_CODE);

其中BlinkHandle是用於保存句柄號的全局變量。

BlinkTimeoutHandler是爲流水燈編寫的超時回調函數,在這個函數內判斷當前LED燈是否點亮,是否需要操作下一個LED燈,並通過app_timer_start()設置下一輪的定時時間。

最後通過BlinkStart()來啓動流水燈的timer

void BlinkStart(bool IsForward)

{

    static bool ForwardFlag;

    ForwardFlag = IsForward;

    ……

    app_timer_start(BlinkHandle, LED_ON_TIME, (void*)&ForwardFlag);

}

最後mian()的執行順序如下:

int main()

{

    TimerInit();

    BlinkInit();

    BlinkStart(true);

    while(1)

    {

//        BlinkStart(false);

//        nrf_delay_ms(4000);

//        BlinkStart(true);

//        nrf_delay_ms(4000);

    }

}

 

下載程序到開發板後,可以觀察到LED燈依次被點亮。 

 

第三節:app_timer具體實現的源碼閱讀提示

本節送給喜(te)(bie)(dan)(teng)的孩子,本節只對app_time模塊做個粗略的概述,當對模塊有了宏觀的理解後,再讀源代碼就能事半功倍。

PS4:推薦使用source insight閱讀源碼,高亮標記(shift+F8)功能非常有用,本教程均提供了source insight的工程文件夾。


section1:我們打開app_timer.h

#define APP_TIMER_NODE_SIZE          40                        

#define APP_TIMER_USER_OP_SIZE       24                        

#define APP_TIMER_USER_SIZE          8                         

#define APP_TIMER_INT_LEVELS         3                         

這裏APP_TIMER_NODE_SIZEAPP_TIMER_USER_OP_SIZEAPP_TIMER_USER_SIZE分別聲明的是timer_node_ttimer_user_op_ttimer_user_t三個結構體的大小,用於參數宏APP_TIMER_INIT()內部分配數據空間。這樣的話就無需將這三個結構體的內部細節暴露給用戶了。而在源碼中使用靜態斷言STATIC_ASSERT()來判斷這三個宏的大小是否正確。

APP_TIMER_INT_LEVELS指的是SDK定義的三個中斷等級,APP_IRQ_PRIORITY_LOW(低優先級==3)、APP_IRQ_PRIORITY_HIGH(高優先級==1)、NRF_APP_PRIORITY_THREAD(用戶優先級!=[1或者3])。


section2app_timer模塊主要佔用內存的分配方式,這些內存的分配在APP_TIMER_INIT()中實現,分別爲timer節點(類型timer_node_t)、操作隊列對象(類型timer_user_t)和操作隊列存儲空間(類型timer_user_op_t)分配空間。

假定APP_TIMER_INIT()的參數MAX_TIMERS==2, OP_QUEUES_SIZE==4則它們的內存映射如下圖所示。


節點nodex就是我們創建的timer的對象,而由userx同對應的opx組成的隊列則是保存相關操作(比如start或者stop操作)的地方。初始化函數同時會給p_buffermp_users這兩個全局變量初始化,讓它們分別指向首個timer節點和首個操作隊列對象。這樣後面就能用

p_buffer[xx]的方式找到對應的對象了。

section3:延時鏈表

timer節點的類型爲timer_node_t,其內部有一個next屬性,這是用來實現延時鏈表的。當相應的timerstart後,它的node就會被加入到延時鏈表中:

如上圖所示,全局變量m_timer_id_head是延時鏈表的表頭,當表頭指向TIMER_NULL時候證明延時鏈表爲空。每個node都有一個ticks_to_expire屬性,作用如下:

如上圖所示,nodeAticks_to_expir指示了nodeA與上次超時(m_ticks_latest)之間的時間差;nodeBticks_to_expir則指示了與nodeA之間的時間差。

RTC1_Counterapp_timer模塊選用的底層RTC的計數器,當它自增到nodeA標記的值後,就會觸發一次RTC中斷,在中斷中執行nodeA內部所註冊的超時回調函數並更新m_ticks_latest的值後,觸發進入SWI0中斷中;在SWI0中斷內將nodeA從鏈表中刪掉,並根據m_ticks_latestnodeBticks_to_expire計算出新的NRF_RTC1->CC[0]值,用於定時時間到達後觸發新的RTC中斷。

假設用戶用過app_timer_start()指示需要將一個timer對應nodeE加入到鏈表中,則會通過記錄下來請求插入時候RTC1_Counter的值(記錄於ticks_at_start)和定時間隔(記錄於ticks_first_interval),計算插入到鏈表的那個位置。

而因爲已經執行而被刪掉的node,如果節點模式爲APP_TIMER_MODE_REPEATED,也會重新插回到鏈表中。

 

section4:操作隊列

app_timer中有3op隊列,對應APP_IRQ_PRIORITY_LOWAPP_IRQ_PRIORITY_HIGHNRF_APP_PRIORITY_THREAD三個優先等級。

當用戶調用app_timer_start()或者app_timer_stop()時候,相關的一些對延時鏈表的操作並不是立即執行的。而是通過FIFO操作寫入到當前環境對應的op隊列中(user_id_get()裏通過SCB->ICSR寄存獲取當前運行環境,可以參考《STM32F10X-programming-manual.pdf》第134頁的描述),然後觸發軟中斷SWI0,在軟中斷中讀取3個隊列中的操作數據,對延時鏈表內對應的節點進行操作。

 

section5:相關中斷

app_timer分別使用了兩個中斷:RTC1SWI0,其中SWI0是軟中斷,用戶可以分別通過NVIC_SetPendingIRQ(RTC1_IRQn)NVIC_SetPendingIRQ(SWI0_IRQn)直接觸發。

其中RTC1_IRQHandler()調用timer_timeouts_check()處理超時的情況,而SWI0_IRQHandler()調用timer_list_handler()對鏈表進行處理。當在timer_timeouts_check()內部需要對鏈表進行修改時候,就會觸發SWI0來處理鏈表問題,反之亦然。

因爲timer_timeouts_check()timer_list_handler()都對鏈表進行操作,因此要求他們的中斷優先等級必須一致,app_timer中默認都是APP_IRQ_PRIORITY_LOW級。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章