nrf51822-硬件(3)-定時器/計數器TIMER(IK-51822DK開發套件)
一、Timer 原理
定時器是單片機的重要外設之一,可用於定時、精確延時、計數等。而且Time在運行時不占用CPU時間,在配置好後,可以和CPU並行工作,實現精確的定時和計數。並可以通過軟件控制其來產生中斷,使用起來方便靈活。
在NRF51822中共有3個定時器,對應的編號為TIMER0、TIMER1、TIMER2,都可以分別工作在定時模式和計數模式下。其原理結構體如下:
Timer模塊從PCLK1M/PCLK16M處獲取時鐘源,然後經分頻後的到時鐘作為Timer的時鐘(即圖中的fTIMER),接著選擇相對於的模式。當處於定時模式時,Counter會在fTIMERr的每個tick 計數一次,當計數值與CC[n](n為0,1,2,3)寄存器中的值相等時就會觸發對應的Compare[n]事件,如果我們設置了Compare[n]事件產生時觸發中斷,那麽在Counter計數到與 CC[n]寄存器中的值相等時觸發中斷,從而實現定時器功能了。而當處於計數模式時,TIMER內部計數器在COUNT引腳上每個脈沖來臨的時候會計數一次。
- Timer時鐘源
Timer使用的時鐘來自PCLK1M或PCLK16M,系統會根據設置的Timer時鐘頻率fTIMER來決定用哪一個時鐘源(即無需軟件來操作時鐘源的設置)。當我們設置好了Timer時鐘頻率後,系統會根據時鐘頻率自動選擇時鐘源,選擇如下:
-
- 當fTIMER > 1MHz時,定時器模塊會選擇PCLK16M為時鐘源。
- 當fTIMER <= 1MHz時,定時器模塊會選擇PCLK1M為時鐘源,從而降低功耗。
- Timer時鐘頻率
Timer時鐘頻率fTIMER是以16MHz為基準,計算公式:fTIMER
- Timer位寬(位數)
nRF51822共有3個定時器,可配置各自的寄存器BITMODE將定時器的位寬設置為8/16/24/32位,但需註意的是,TIMER1和TIMER2只能設置為8位或16位,寄存器如下:
- Timer啟動和停止
Timer可以處於定時或計數模式下,但無論哪種模式下都是通過START任務寄存器來啟動Timer,通過觸發STOP認為寄存器來停止Timer的。在Timer停止後,觸發START任務寄存器可以讓其再次啟動,啟動後,定時器將繼續從之前被停止的值開始計數。相關的3個任務定時器:
-
- START:啟動定時/計數器
- STOP:停止定時/計數器
- SHURDOWN:讓定時/計數器掉電,後續無法通過START來啟動該定時/計數器,除非重新復位。
- 定時和計數
- 定時模式:Timer內部計數器的值在每個時鐘脈沖加1,此模式下,觸發任務寄存器COUNT不會影響到計數寄存器的值。
- 計數模式:每觸發一次任務寄存器COUNT,Timer內部計數器寄存器的值加1,此模式下,Timer的時鐘頻率和分頻系數沒有使用。
註意:設置分頻系數(PRESCALER寄存器)和位寬(BITMODE寄存器)時,必須要停止Timer後再設置,否則將會導致不可預知的後果。
- 溢出和清零
當Timer的計數寄存器的值增大到最大值時,計數寄存器溢出,此時計數寄存器的數值將會清零並自動從零開始計數。或者觸發任務寄存器CLEAR,計數寄存器的值也會被清零。
- 捕獲
Timer中的每個COMPAER[n]比較寄存器對應一個捕獲TASKS_CAPTURE[n],每次觸發任務i寄存器CAPTURE[n],捕獲值會被存儲到CC[n]寄存器中。
二、寄存器介紹
nRF51822的3個定時器(TIMER0~TIMER2)基址及相關寄存器如下:
- SHORTS:該寄存器可以把TASK和EVENT聯系起來。例如COMPARE[n]_CLEAR可以在內部計數器相當於CC[n]的時候清除內部計數器的計數值。
- INTENSET:中斷允許設定寄存器,當設定為"1"的時候,對應的COMPARE[n]事件產生的時候,會產生COMPARE[n]中斷。
三、相關的API函數
- Timer初始化函數 nrf_drv_timer_init(p_instance, p_config, timer_event_handler) ;
- Timer擴展比較模式下通通道設置函數 nrf_drv_timer_extended_compare( p_instance, cc_channel, cc_value, timer_short_mask, enable_int) :擴展比較模式下設置Timer通道。
- Timer啟動函數 nrf_drv_timer_enable(p_instance);
- Timer關閉函數 nrf_drv_timer_disable(p_instance):關閉Timer,註意是關閉而不是停止,函數中可以觸發任務TASKS_SHUTDOWN關閉Timer。
- 毫秒轉Ticks函數 nrf_drv_timer_ms_ti _ticks(p_instance, time_ms) :輸入的毫秒轉換成Ticks。
四、應用流程
Timer可以用於定時和計數兩種模式,定時模式下要設置捕獲/比較寄存器的比較值,這個比較值對應的就是定時的時間。可以同時設置多個捕獲/比較寄存器的值。Timer啟動後,計數值在每個時鐘頻率遞增,當計數值達到比較值的時候(即定時時間到了),產生比較匹配事件,在事件處理函數中加入相應的處理代碼即可實現定時處理任務的目的。常用的一般步驟為:
- 設置工作模式。
- 設定預分頻(計數器不適用)。
- 設定CC[n]寄存器的值。
- 使能中斷(中斷模式)。
- 啟動START任務。
- COMPARE EVENT到來,清除內部計數器的值,清楚中斷(中斷模式)。
以Timer作為定時器的應用流程為例(庫函數):
1、初始化Timer
首先要定義一個驅動程序實例,定義驅動程序實例時,可以命名為實例名稱,這樣使用起來比較直觀,例如定義一個用於定時驅動LED指示燈的驅動程序實例TIMER_LED。驅動程序實例的ID號對應的是Timer的編碼,如NRF_DRV_TIMER_INSEANCE(1)對應的是Timer1,而nRF51822有三個定時器,因此可定義的驅動程序實例的ID是0~2:
const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSEANCE(1);
接著定義一個定時器配置結構體餅對其成員變量進行賦值,其結構體為:
typedef struct { nrf_timer_frequency_t frequency; //時鐘頻率:可配置10種時鐘頻率(參考表格) nrf_timer_mode_t mode; //模式:定時或計數模式 nrf_timer_bit_width_t bit_width; //位寬:8/16/24/32位(PS:TIMER1 和 TIMER2 只能設置8或16位) uint8_t interrupt_priority; //中斷優先級 void * p_context; //傳遞給中斷函數的參數 } nrf_drv_timer_config_t
當然也可以直接調用庫函數中的默認配置,如:nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
定時器配置結構體定義好之後,調用nrf_drv_timer_init()函數使用配置結構體中的設置的數值來初始化Timer。在初始化前,還需註冊事件的回調函數,當產生比較事件是會調用該回調函數:
nrf_drv_timer_init(&TIMER_LED,&timer_cfg, timer_led_even_handler);
2、計算定時時間對應的ticks
對於Timer定時來說,定時時間實際對應的是Timer的ticks(時鐘滴答)數,但tisks和時間的對應關系並不明顯。如果每次修改時間都要計算一次ticks數會比較麻煩,因此在程序中我們會直接用一個函數來計算對應的ticks:
time_ticks = nrf_drv_timer_ms_to_ticks(&TIMER_LED, time_ms);
這樣每當我們需要計算時間對應的ticks時,將時間值傳給函數的參數time_ms(單位是毫秒),函數的返回值就是時間對應的ticks。
3、設置定時器通道和比較值
每個Timer都有各自的捕獲/比較通道,一般的定時使用一個通道就可以。以Timer0為例,設置定時器的捕獲比較通道0和它的比較值:
nrf_drv_timer_extened_compare( &TIMER_LED, NRF_TIMER_CC_CHANNEL2, time_ticks, NRF_TIMER_SHORT_CPMPARE2_CLEAR_MASK, true, );
4、啟動Timer
定時器配置完成後,調用nrf_drv_timer_enable函數即可啟動定時器開始計時:
nrf_drv_timer_enable(&TIMER_LED);
五、實例操作
1.定時驅動LED閃爍(庫函數)
1.創建工程
創建工程和配置基本配置在前兩節已做說明,此章不再陳述。
2.工程實驗
接著配置好相對應的頭文件目錄,且定義好相對應所需的宏定義,(PS: 添加頭文件的方式 :缺啥補啥,編譯顯示缺少什麽就添加什麽),並添加相應的實現函數,以下為我的配置:
註意:應記得勾選C99 Mode,否則可能出現不必要的錯誤。如:main.c(34): error: #29: expected an expression const nrf_drv_timer_t TIMER_LED = RF_DRV_TIMER_INSTANCE(0)等.....
除了配置基本的設置外,還需在配置文件“sdk_config.h”中啟用Timer程序模塊和Timer0,將宏定義TIMER_ENABLED和TIMER0_ENABLED設置為“1”,如圖所示:
PS:可能有的sdk_config.h找不到TIMER_ENABLED,SDK配置文件可以從SDK中其他例子拷貝過來修改(我用的是SDK 12.3.0B版本的)
3.實驗代碼
#include "boards.h" #include "nrf_gpio.h" #include "nrf_drv_timer.h" const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0); //定義Tiemr0的驅動實例。驅動實例的ID對應Timer的ID void timer_led_even_handler(nrf_timer_event_t event_type, void * p_context) //回調函數 { switch(event_type) { case NRF_TIMER_EVENT_COMPARE0: nrf_gpio_pin_toggle(LED_1); break; default: break; } } int main(void) { uint32_t timer_ms = 500; //定時0.5s uint32_t timer_ticks; //ticks數 uint32_t err_code = NRF_SUCCESS; nrf_gpio_cfg_output(LED_1); //配置LED_1的GPIO為輸出 nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG; //配置TIMER的配置為默認配置 err_code = nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer_led_even_handler); //初始化定時器 APP_ERROR_CHECK(err_code); timer_ticks = nrf_drv_timer_ms_to_ticks(&TIMER_LED, timer_ms); //將定時時間轉換為ticks數 nrf_drv_timer_extended_compare( &TIMER_LED, NRF_TIMER_CC_CHANNEL0, timer_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true); //設置定時器捕獲/比較通道及該通道的比較值,使能通達的中斷 nrf_drv_timer_enable(&TIMER_LED); //使能啟動定時器 while(true) { } }
nrf51822-硬件(3)-定時器/計數器TIMER(IK-51822DK開發套件)