nRF52832——定时器 TIME
- 原理分析
- 定时器定时功能
- 寄存器方式
- 定时器库函数版本使用
- 定时器计数功能
- 计数器寄存器方式
- 计数器库函数方式
原理分析
和其他 MCU 处理器一样,在 nRF52832 中定时器的功能是十分强大的。其内部包含了 5 个定 时器 TIMER 模块:TIMER0、TIMER1、TIMER2、TIMER3、TIMER4,如下表:
定时器有着不同的位宽选择,位宽的大小直接决定了计数器的最大溢出时间。处理器可以通过 BITMODE 选择不同的位宽,该寄存器位于计数器内部。如下图所示。
BITMODE 寄存器:配置计数器使用的位宽。
下图为定时器内部结构图,下面结合结构图来详细分析下其基本工作原理以及相关概念:
-
时钟源
首先定时器 TIMER 工作在高频时钟源(HFLCK)下,同时包含了一个 4bit(1 / 2X)的分频 (PRESCALER),可以对高频时钟源(HFLCK)进行分频。框图入口处给了两个时钟源表示两种时 钟输入模式:1MHz 模式(PCLK1M)和 16MHz 模式(PCLK16M)。时钟源通过分频器分频后输出一 个频率 fTIMER ,系统将会通过这个参数来自动选择时钟源,而不需要工程师设置寄存器。- 当 fTIMER 1MHZ 时,系统自动选择 PCLK16M 作为时钟源。
- 当 fTIMER 1MHZ 时,系统会自动用 PCLK1M 替代 PCLK16M 作为时钟源以减少功耗。
-
分频器
分频器对输入的时钟源进行分频。输出的频率计算公式如下:
公式中的 HFLCK 不管是使用哪种时钟源输入,计算分频值的时候都使用 16MHz。PRESCALER 为一个 4bit 的分频器,分频值为 0~15。当 PRESCALER 的值大于 9 后,其计算值仍然为 9,即 fTIMER 的最小值为 16/29 。通过设置寄存器 PRESCALER 可以控制定时器的频率。
PRESCALER 寄存器:预分频寄存器
-
工作模式
定时器 TIMER 可以工作在两种模式下:定时器模式(timer)和计数器模式(counter)。工作模式通过寄存器 MODE 进行选择。- 当 MODE 设置为 0 的时候为定时器模式,
- 设置为 1 的时 候为计数器模式,
- 设置为 2 时,选择低功耗的计算器模式。
MODE 寄存器:定时器模式设置寄存器
-
比较/捕获功能
定时模式下设定比较(Compare)/捕获(Capture)寄存器 CC[n]的值,可以设置定时的时间(Timer value)
当定时时间的值跟 CC[n]寄存器的值相等时,将触发一个 COMPARE [n] event。COMPARE [n] event 可以触发中断。如果是周期性的触发,则需要在触发后清除计数值,否则会一直计数,直到溢出。 计数模式下,每次触发 COUNT 任务时,TIMER 的内部计数器 Counter 寄存器都会递增 1,同时,计数器模式下是不使用定时器的频率和预分频器,COUNT 任务在定时器模式下无效。通过设定一个 CAPTURE Task,捕获的计数器的值存储到 CC[n]寄存器内,然后对 CC[n]寄存器进行读取计数的值。 -
任务延迟和优先级
- 任务延迟:TIMER 启动后,CLEAR 任务,COUNT 任务和 STOP 任务将保证在 PCLK16M 的一 个时钟周期内生效。
- 任务优先级:如果同时触发 START 任务和 STOP 任务,即在 PCLK16M 的同一时段内,则优先执行STOP 任务。 下表是定时器的寄存器列表,详细说明如下
定时器定时功能
寄存器方式
建立工程目录如下,需要新建 main.c 主操作函数和 time.c 定时器驱动文件。
这里的定时器驱动文件 time.c 主要有两个作用:
- 初始化定时器参数
- 设置定时时间函数
完成这两个功能后,就可以在 main.c 主函数中直接调用驱动进行验证。
结合寄存器来详细分析下定时器的设置步骤,可以如下图
定时器首先需要设置的三个参数,分别为:定时器的模式、定时器的位宽、定时器的时钟频率。
这里示范需要进行定时操作,因此还需要设置比较寄存器里的值。如果初始化完成后,定时器就开始定时,当定时时间的值跟 CC[n]寄存器的值相等时,将触发一个 COMPARE [n] event,这时候我们关掉定时器定时。这样根据 CC[n]寄存器的值就是实现了一个指定的时间长度的。 根据前面的分析过程,这时我们参考前一节的寄存器列表,来配置一个定时器代码设置如下:
/** 函数用于在请求的毫秒数之后使用外围硬件定时器** 参数timer 对应的定时器模块* 参数 number_of_ms 计时器将计算的毫秒数。* 此函数将打开所请求的定时器,等待延迟,然后关闭该定时器。*/void nrf_timer_delay_ms(timer_t timer, uint_fast16_t volatile number_of_ms)
{volatile NRF_TIMER_Type * p_timer = timer_init(timer);if (p_timer == 0) {while(true) {// Do nothing.}}p_timer->MODE = TIMER_MODE_MODE_Timer; // 设置为定时器模式p_timer->PRESCALER = 9; // Prescaler 9 produces 31250 Hz timer frequency => 1 tick = 32 us.p_timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit; // 16 bit 模式.p_timer->TASKS_CLEAR = 1; // 清定时器.// With 32 us ticks, we need to multiply by 31.25 to get milliseconds.p_timer->CC[0] = number_of_ms * 31;p_timer->CC[0] += number_of_ms / 4; p_timer->TASKS_START = 1; // Start timer.while (p_timer->EVENTS_COMPARE[0] == 0){// Do nothing.}p_timer->EVENTS_COMPARE[0] = 0;p_timer->TASKS_STOP = 1; // Stop timer.
}
-
第 01 行:是 MODE 设置,也就是模式设置,我们设置定时器模式。
-
第 02 行:设置预分频值,PRESCALER 寄存器设置预分频计数器,前面原理里已经讲过,要产 生 Ftimer 时钟,必须把外部提供的高速时钟 HFCLK 首先进行分频。 代码里,我们设置为 9 分频,按照分频计算公式,Ftimer 定时器频率时钟为 31250 Hz。
-
第 03 行:是 BITMODE 寄存器,BITMODE 寄存器就是定时器的位宽。就比如一个水桶,这个 水桶的深度是多少,定时器的位宽就是定时器计满需要的次数,也就是最大的计算次数。 例如:我们设置为 16bit,计数的次数最大应该是 2 的 16 次方次。假设定时器频率时钟为 1kHz, 定时器 1ms 计数一次。 最大定时时间=最大的计数次数计数一次的时间=655351ms=65535ms 因此位宽与定时器频率共同决定了定时器可以定时的最大时间。
-
第 04 行:表示定时器开始计数之前需要清空,从 0 开始计算。 第
-
06~07 行:设定定时比较的值,也就是比较/捕获功能,当定时时间的值跟 CC[n]寄存器的值 相等时,将触发一个 COMPARE [n] event。由于分频后的 Ftimer 时钟下定时器 1/31250 s 计数一次, 1ms 需要计 31.25 次,那么 CC[n]存放定时时间(单位 ms)*31.25。
-
第 08 行:设置好了就开始启动定时器。
-
第 09 行:判断比较事件寄存器是否被置为 1,如果置为 1,表示定时器的值跟 CC[n]寄存器的 值相等了,则跳出循环。
-
第 12 行:把置为 1 的比较事件寄存器清 0。
-
第 13 行:定时时间到了停止定时器。
定时器初始化时,定时器的时钟由外部的高速时钟提供,我们必须首先初始化进行 HFCLK 时钟开启:
/*** @brief Function for timer initialization.*/
static volatile NRF_TIMER_Type * timer_init(timer_t timer)
{volatile NRF_TIMER_Type * p_timer;// 开始16 MHz晶振.NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;NRF_CLOCK->TASKS_HFCLKSTART = 1;// 等待外部振荡器启动while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {// Do nothing.}switch (timer){case TIMER0:p_timer = NRF_TIMER0;break;case TIMER1:p_timer = NRF_TIMER1;break;case TIMER2:p_timer = NRF_TIMER2;break;default:p_timer = 0;break;}return p_timer;
}
主操作函数就直接调用这里声明好的定时器函数 nrf_timer_delay_ms
即可。
#include <stdbool.h>
#include <stdint.h>
#include "nrf_delay.h"
#include "nrf_gpio.h"
#include "led.h"
#include "time.h"int main(void)
{// LED_Init();while (1){LED1_Toggle();//使用定时器0产生1s定时nrf_timer_delay_ms(TIMER0, TIMER_DELAY_MS);LED2_Toggle();// 使用定时器1产生1s定时nrf_timer_delay_ms(TIMER1, TIMER_DELAY_MS);LED3_Toggle();// 使用定时器2产生1s定时nrf_timer_delay_ms(TIMER2, TIMER_DELAY_MS);}
}
编译代码后,用 keil 把工程下载到青风 QY-nRF52832 蓝牙开发板上。LED 灯会已 1s 的定时时 间进行闪烁。
定时器库函数版本使用
为了在以后方便结合协议栈一起编程,学习使用官方定时器库函数的组件进行编程也是需要读者进一步做的工作。寄存器编程相比于组件库函数编程,优点便于理解整个定时器的工作原理,简单直观的设置寄存器。但是缺点是程序编写功能没有库函数完整。组件库编程的核心就是理解组件库函数的 API,在调用组件库函数时首先需要弄清其函数定义。 工程的建立可以以前面 GPIOE 的组件库函数工程为模板,这里面我们只需要改动的如下框中的几个地方,如下图:
主函数 main.c 文件,sdk_config.h 配置文件这两个文件需要我们编写和修改的。而 nrfx_timer.c 文件则需要我们添加的库文件。nrfx_timer.c 文件的路径在 SDK 的//modules/nrfx/drivers/include 文件夹里,添加库文件完成后,注意在 Options for Target 选项卡的 C/C++中,点击 include paths 路径选项中添加硬件驱动库的文件路径,如下图所示:
工程搭建完毕后,首先我们需要来修改 sdk_config.h 配置文件,库函数的使用是需要对库功能 进行使能的,因此需要在 sdk_config.h 配置文件中,设置对应模块的使能选项。关于定时器的配代码选项较多,我们不就一一展开,大家可以直接把对应的配置代码复制到自己建立的工程中的 sdk_config.h 文件里。如果复制代码后,在 sdk_config.h 配置文件的 configuration wizard 配置导航卡中看见如下两个参数选项被勾选,表明配置修改成功,如下图:
定时器使用组件库编程的基本原理和寄存器的编程基本一致。下面我们来介绍下需要调用的几个定时器相关设置函数:
首先需要在主函数前定义使用哪个定时器,使用 NRF_DRV_TIMER_INSTANCE(id)
中 ID 来定义,根据前面原理介绍,这里 ID 可以为 0,1,2,3,4 五个值,设置方式如下:
const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);//设置使用的定时器
同时需要在 sdk_config.h
中 TIMER_ENABLED
中对 TIMER0_ENABLED
进行勾选。
然后配置定时器 0,对定时器初始化,使用库中的 nrf_drv_timer_init()
函数。该函数用于定义定时器的相关配置,以及中断回调的函数,如下图:
这里的参数 p_config
,这个参数是 nrfx_timer_config_t
结构体类型,这个结构体内给定了定时器的相关配置函数。
/*** @brief Timer driver instance configuration structure.*/
typedef struct
{nrf_timer_frequency_t frequency; ///< Frequency.nrf_timer_mode_t mode; ///< Mode of operation.nrf_timer_bit_width_t bit_width; ///< Bit width.uint8_t interrupt_priority; ///< Interrupt priority.void * p_context; ///< Context passed to interrupt handler.
} nrfx_timer_config_t;
如果结构体设置为 NULL, 则默认使用初始设置,在 nrfx_time.h
中定义了默认配置的结构体。
/*** @brief Timer driver instance default configuration.*/
#define NRFX_TIMER_DEFAULT_CONFIG \
{ \.frequency = (nrf_timer_frequency_t)NRFX_TIMER_DEFAULT_CONFIG_FREQUENCY,\.mode = (nrf_timer_mode_t)NRFX_TIMER_DEFAULT_CONFIG_MODE, \.bit_width = (nrf_timer_bit_width_t)NRFX_TIMER_DEFAULT_CONFIG_BIT_WIDTH,\.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY, \.p_context = NULL \
}
这几个定义参数的值在 sdk_config.h 配置文件中进行了赋值,这个默认配置参数值是可以自由修改,具体代码如下所示:
// <e> NRFX_TIMER_ENABLED - nrfx_timer - TIMER periperal driver
//==========================================================
#ifndef NRFX_TIMER_ENABLED
#define NRFX_TIMER_ENABLED 0
#endif
// <q> NRFX_TIMER0_ENABLED - Enable TIMER0 instance#ifndef NRFX_TIMER0_ENABLED
#define NRFX_TIMER0_ENABLED 0
#endif// <q> NRFX_TIMER1_ENABLED - Enable TIMER1 instance#ifndef NRFX_TIMER1_ENABLED
#define NRFX_TIMER1_ENABLED 0
#endif// <q> NRFX_TIMER2_ENABLED - Enable TIMER2 instance#ifndef NRFX_TIMER2_ENABLED
#define NRFX_TIMER2_ENABLED 0
#endif// <q> NRFX_TIMER3_ENABLED - Enable TIMER3 instance#ifndef NRFX_TIMER3_ENABLED
#define NRFX_TIMER3_ENABLED 0
#endif// <q> NRFX_TIMER4_ENABLED - Enable TIMER4 instance#ifndef NRFX_TIMER4_ENABLED
#define NRFX_TIMER4_ENABLED 0
#endif// <o> NRFX_TIMER_DEFAULT_CONFIG_FREQUENCY - Timer frequency if in Timer mode// <0=> 16 MHz
// <1=> 8 MHz
// <2=> 4 MHz
// <3=> 2 MHz
// <4=> 1 MHz
// <5=> 500 kHz
// <6=> 250 kHz
// <7=> 125 kHz
// <8=> 62.5 kHz
// <9=> 31.25 kHz #ifndef NRFX_TIMER_DEFAULT_CONFIG_FREQUENCY
#define NRFX_TIMER_DEFAULT_CONFIG_FREQUENCY 0
#endif// <o> NRFX_TIMER_DEFAULT_CONFIG_MODE - Timer mode or operation// <0=> Timer
// <1=> Counter #ifndef NRFX_TIMER_DEFAULT_CONFIG_MODE
#define NRFX_TIMER_DEFAULT_CONFIG_MODE 0
#endif// <o> NRFX_TIMER_DEFAULT_CONFIG_BIT_WIDTH - Timer counter bit width// <0=> 16 bit
// <1=> 8 bit
// <2=> 24 bit
// <3=> 32 bit #ifndef NRFX_TIMER_DEFAULT_CONFIG_BIT_WIDTH
#define NRFX_TIMER_DEFAULT_CONFIG_BIT_WIDTH 0
#endif// <o> NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY - Interrupt priority// <0=> 0 (highest)
// <1=> 1
// <2=> 2
// <3=> 3
// <4=> 4
// <5=> 5
// <6=> 6
// <7=> 7 #ifndef NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY
#define NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY 7
#endif// <e> NRFX_TIMER_CONFIG_LOG_ENABLED - Enables logging in the module.
//==========================================================
#ifndef NRFX_TIMER_CONFIG_LOG_ENABLED
#define NRFX_TIMER_CONFIG_LOG_ENABLED 0
#endif
// <o> NRFX_TIMER_CONFIG_LOG_LEVEL - Default Severity level// <0=> Off
// <1=> Error
// <2=> Warning
// <3=> Info
// <4=> Debug #ifndef NRFX_TIMER_CONFIG_LOG_LEVEL
#define NRFX_TIMER_CONFIG_LOG_LEVEL 3
#endif// <o> NRFX_TIMER_CONFIG_INFO_COLOR - ANSI escape code prefix.// <0=> Default
// <1=> Black
// <2=> Red
// <3=> Green
// <4=> Yellow
// <5=> Blue
// <6=> Magenta
// <7=> Cyan
// <8=> White #ifndef NRFX_TIMER_CONFIG_INFO_COLOR
#define NRFX_TIMER_CONFIG_INFO_COLOR 0
#endif// <o> NRFX_TIMER_CONFIG_DEBUG_COLOR - ANSI escape code prefix.// <0=> Default
// <1=> Black
// <2=> Red
// <3=> Green
// <4=> Yellow
// <5=> Blue
// <6=> Magenta
// <7=> Cyan
// <8=> White #ifndef NRFX_TIMER_CONFIG_DEBUG_COLOR
#define NRFX_TIMER_CONFIG_DEBUG_COLOR 0
#endif
读者可以直接在文本 Text Editor 中直接修改,或者在 configuration wizard 配置导航卡中点击修改,导航卡中修改如下图
默认设置 31.25KHz 时钟,定时器模式,16bit 位宽,中断优先级为低。这个参数可以根据自 己的要求进行修改。
采用库函数实现定时器定时,基本原理和寄存器方式相同,就是通过定时器的值和 cc[n] 寄存器的值进行比较。如果相等就触发比较事件,这时就停止定时器或者触发比较事件中断。本例 我们采用中断方式来处理定时,那么首先介绍下面两个组件库函数的 API:
nrfx_timer_ms_to_ticks
用于计算指定定时时间下 cc[n] 寄存器的值,用于比较事件的触发。该函数可以方便算出 cc[n] 寄存器的值。
nrfx_timer_extended_compare
用于使能定时器比较通道,使能比较中断,设置触发比较寄存器cc[n] 等参数
该函数中的参数都比较好理解,其中timer_short_mask
停止或者清除比较事件和定时器任务的快捷方式,就是在寄存器 SHORTS 中设置的内容,可以对照 SHORTS 寄存器列表进行比较:
/*** @brief Types of timer shortcuts.*/
typedef enum
{NRF_TIMER_SHORT_COMPARE0_STOP_MASK = TIMER_SHORTS_COMPARE0_STOP_Msk, ///< Shortcut for stopping the timer based on compare 0.NRF_TIMER_SHORT_COMPARE1_STOP_MASK = TIMER_SHORTS_COMPARE1_STOP_Msk, ///< Shortcut for stopping the timer based on compare 1.NRF_TIMER_SHORT_COMPARE2_STOP_MASK = TIMER_SHORTS_COMPARE2_STOP_Msk, ///< Shortcut for stopping the timer based on compare 2.NRF_TIMER_SHORT_COMPARE3_STOP_MASK = TIMER_SHORTS_COMPARE3_STOP_Msk, ///< Shortcut for stopping the timer based on compare 3.
#if defined(TIMER_INTENSET_COMPARE4_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_SHORT_COMPARE4_STOP_MASK = TIMER_SHORTS_COMPARE4_STOP_Msk, ///< Shortcut for stopping the timer based on compare 4.
#endif
#if defined(TIMER_INTENSET_COMPARE5_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_SHORT_COMPARE5_STOP_MASK = TIMER_SHORTS_COMPARE5_STOP_Msk, ///< Shortcut for stopping the timer based on compare 5.
#endifNRF_TIMER_SHORT_COMPARE0_CLEAR_MASK = TIMER_SHORTS_COMPARE0_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 0.NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK = TIMER_SHORTS_COMPARE1_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 1.NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK = TIMER_SHORTS_COMPARE2_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 2.NRF_TIMER_SHORT_COMPARE3_CLEAR_MASK = TIMER_SHORTS_COMPARE3_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 3.
#if defined(TIMER_INTENSET_COMPARE4_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_SHORT_COMPARE4_CLEAR_MASK = TIMER_SHORTS_COMPARE4_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 4.
#endif
#if defined(TIMER_INTENSET_COMPARE5_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_SHORT_COMPARE5_CLEAR_MASK = TIMER_SHORTS_COMPARE5_CLEAR_Msk, ///< Shortcut for clearing the timer based on compare 5.
#endif
} nrf_timer_short_mask_t;
也就是说存在两种快捷方式:
- 第一种:停止定时器。当定时器计数的值和比较寄存器 CC[n]的值相等的时候,触发了 COMPARE [n] event 比较事件,可以用这个事件可以快捷的停止定时器。也就是说一旦发生 COMPARE [n] event 比较事件定时器就会被停止运行。
- 第二种:清零定时器的计数值。当定时器计数的值和比较寄存器 CC[n]的值相等的时候,触发了 COMPARE [n] event 比较事件,可以用这个事件可以快捷的清零定时器。这样的后果就是导 致了定时器从 0 重新开始计数。我们可以用这种方式实现定时器的快捷定时。
所以主操作函数如下:
/***主函数*/
int main(void)
{uint32_t time_ms = 1000; //定时器比较事件的时间uint32_t time_ticks;uint32_t err_code = NRF_SUCCESS;//配置开发板所有的灯bsp_board_init(BSP_INIT_LEDS);//配置定时器相关事件nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;err_code = nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer_led_event_handler);APP_ERROR_CHECK(err_code);time_ticks = nrf_drv_timer_ms_to_ticks(&TIMER_LED, time_ms);//触发定时器比较nrf_drv_timer_extended_compare(&TIMER_LED, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
// nrf_drv_timer_extended_compare(
// &TIMER_LED, NRF_TIMER_CC_CHANNEL0, 31250, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);//使能定时器nrf_drv_timer_enable(&TIMER_LED);while (1){}
}
使能定时器操作,打开定时器开始计时。提供了两个库函数 API 来实现打开和关闭定时器。
-
nrfx_timer_enable()
-
nrfx_timer_disable()
特别注意:nrfx_timer_disable
函数内部是调用触发处理寄存器 TASKS_SHUTDOWN,来实 现关闭计时器的任务。因为一旦定时器被开启后,就会有消耗电流,这种情况下如果采用 TASKS_SHUTDOWN 的方式关闭定时器,能够最大限度的减少电流功耗。而 TASKS_STOP 停止定时定时器的方式则不会。
所以当定时时间到了,将产生一个 NRF_TIMER_EVENT_COMPARE()
事件类型在回调中断处理执行 LED 灯翻转:
/*** 定时器中断*/
void timer_led_event_handler(nrf_timer_event_t event_type, void* p_context)
{static uint32_t i;uint32_t led_to_invert = ((i++) % LEDS_NUMBER);switch (event_type){case NRF_TIMER_EVENT_COMPARE0:bsp_board_led_invert(led_to_invert);break;default://Do nothing.break;}
}
这个 NRF_TIMER_EVENT_COMPARE0
回调处理事件是在定时器中断中进行判断的,在回调中进行处理。库函数 nrf_time.h
文件中,提供了 nrf_timer_event_t
结构体对应定义了 6 个中断事件, 读者可以根据自己的设置进行选择:
/*** @brief Timer events.*/
typedef enum
{/*lint -save -e30*/NRF_TIMER_EVENT_COMPARE0 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[0]), ///< Event from compare channel 0.NRF_TIMER_EVENT_COMPARE1 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[1]), ///< Event from compare channel 1.NRF_TIMER_EVENT_COMPARE2 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[2]), ///< Event from compare channel 2.NRF_TIMER_EVENT_COMPARE3 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[3]), ///< Event from compare channel 3.
#if defined(TIMER_INTENSET_COMPARE4_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_EVENT_COMPARE4 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[4]), ///< Event from compare channel 4.
#endif
#if defined(TIMER_INTENSET_COMPARE5_Msk) || defined(__NRFX_DOXYGEN__)NRF_TIMER_EVENT_COMPARE5 = offsetof(NRF_TIMER_Type, EVENTS_COMPARE[5]), ///< Event from compare channel 5.
#endif/*lint -restore*/
} nrf_timer_event_t;
因此,定时器库函数进行定时,实验整体思路和寄存器配置方式相同。编写后把实验程序下载 到 nRF52832 开发板后的实验现象如下,LED1~4 灯以 1s 的定时轮流闪烁。
定时器计数功能
计数器寄存器方式
计数模式下,每次触发 COUNT 任务时,TIMER 的内部计数器 counter 都会递增 1。但是计数 器 counter 计数器内的值是无法读取的,这时需要通过设定一个 CAPTURE Task,捕获的计数器 counter 的值存储到 CC[n]寄存器内,然后再对 CC[n]寄存器进行读取,读取的值就是计数器计数的值,如下图
下面就开始搭建工程,本例中需要使用串口打印计数值,因此工程可以直接采用串口 uart 的例 程框架。同时寄存器方式下不需要添加库文件,因此工程目录相比串口例程没有任何变化。
在 main.c 主函数中,我们首先编写定时器初始化函数。由于计数器模式下不使用定时器的频率和预分频器,同样 COUNT 任务在定时器模式下无效。因此初始化的时候,只需要初始化两项:设置计数器模式和设置定时器的位宽。
void timer0_init(void)
{NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // Set the timer in counter Mode.NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_24Bit; // 24-bit mode.
}
然后通过置位 TASKS_START 寄存器启动定时器。设置寄存器 TASKS_COUNT 为 1,则触发 COUNT 任务,这时才能够开始计数。
计数器的触发有两种方式:
- 第一种:通过 TASKS_COUNT 计数任务寄存器手动赋值,你赋值为 1,计数器就计数一次。 赋值 N 次,就计数 N 次,相当于计数你手动触发的次数。这样实际上是毫无意义的,本例仅仅演示这种方式。
- 第二钟:通过 PPI 一端的 event 事件,去触发 PPI 另外一端 TASKS_COUNT 任务,那么事件触发的次数就是计数器计数的次数。这种方式就可以用来对外部信号进行捕获计数,常用于实现脉宽测量,下一章会对这种用法进行举例。 当把 TASKS_CAPTURE[0]寄存器值 1,启动捕获功能。这时就可以捕获计数器 counter 的值,并且存储到 CC[n]寄存器内,然后再对 CC[n]寄存器进行读取了。具体代码如下
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */void uart_error_handle(app_uart_evt_t * p_event)
{if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR){APP_ERROR_HANDLER(p_event->data.error_communication);}else if (p_event->evt_type == APP_UART_FIFO_ERROR){APP_ERROR_HANDLER(p_event->data.error_code);}
}#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLEDvoid timer0_init(void)
{NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // Set the timer in counter Mode.NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_24Bit; // 24-bit mode.
}/*** @brief Function for main application entry.*/
int main(void)
{uint32_t err_code;uint32_t timVal = 0;timer0_init();const app_uart_comm_params_t comm_params ={RX_PIN_NUMBER,TX_PIN_NUMBER,RTS_PIN_NUMBER,CTS_PIN_NUMBER,UART_HWFC,false,
#if defined (UART_PRESENT)NRF_UART_BAUDRATE_115200
#elseNRF_UARTE_BAUDRATE_115200
#endif};APP_UART_FIFO_INIT(&comm_params,UART_RX_BUF_SIZE,UART_TX_BUF_SIZE,uart_error_handle,APP_IRQ_PRIORITY_LOWEST,err_code);APP_ERROR_CHECK(err_code);//启动定时器 NRF_TIMER0->TASKS_START=1; while (1){printf(" 2024.3.1 helloworld!\r\n"); NRF_TIMER0->TASKS_CAPTURE[0] = 1;NRF_TIMER0->TASKS_COUNT = 1;//获取计数值timVal = NRF_TIMER0->CC[0];//串口打印计数值printf("conut value: %d\r\n", timVal);nrf_delay_ms(1000);}}
编译程序后下载到青风 nrf52832 开发板内。打开串口调试助手,选择开发板串口端号,设置波 特率为 115200,数据位为 8,停止位为 1。因为程序设置计数器会 1s 计数一次,我们通过串口 1s 打印一次计数值,那么打印的计数值应该是 1、2、3…依次输出的。
计数器库函数方式
计数器库函数组件工程的建立可以以前面 UARTE 的组件库函数工程为模板,这里面我们只需要改动的如下图
其中 nrfx_timer.c
库文件,然后点击 include paths 路径选项中添加硬件驱动库的文件路径。同时需要来修改 sdk_config.h 配置文件,库函数的使用是需要对库功能进行使能的。因此需要在 sdk_config.h 配置文件中,设置对应模块的使能选项。这两个过程和定时器组件例程的工程搭建方法相同,这里不再累述。
首先需要定义一个定时器的实例结构体,声明为 TIMER_COUNTER,如下所示: const nrfx_timer_t TIMER_COUNTER = NRFX_TIMER_INSTANCE;这个声明需要使能在配置文件 sdk_config.h 里使能对应的定时器模块。
然后对该定时器进行初始化,初始化调用的函数为 nrfx_timer_init,本节采用计算器方式,只 需把默认配置中的模式改为计数器模式,位宽采用默认位宽就可以。把这个过程封装成一个定时器 初始化函数 timer0_init,代码如下
#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLEDvoid timer0_init(void)
{uint32_t err_code = NRF_SUCCESS;//定义定时器配置结构体,并使用默认配置参数初始化结构体nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG; //Timer0配置为计数模式timer_cfg.mode = NRF_TIMER_MODE_COUNTER;//初始化定时器,定时器工作于计数模式时,没有事件,所以无需回调函数err_code = nrfx_timer_init(&TIMER_COUNTER, &timer_cfg, my_timer_event_handler);//err_code = nrfx_timer_init(&TIMER_COUNTER, &timer_cfg, NULL);APP_ERROR_CHECK(err_code);}
在主函数中,直接调用封装的计数器初始化函数 timer0_init(),然后采用 nrfx_timer_enable 函数打开计数器,该函数在前面已经介绍过。和寄存器计数器方式同样的,本例采用 1s 触发一次计数,然后启动捕获,捕获 CC[n]寄存器里的值并且读出来,用串口助手进行打印。这个过程需要调用下面两个函数:
nrfx_timer_increment()
函数,用于触发一次计数:
nrfx_timer_capture()
函数,用于启动捕获,把捕获的值放入到 CC[n] 寄存器里的值并且读出来。
主函数操作代码如下:
/*** @brief Function for main application entry.*/
int main(void)
{uint32_t err_code;uint32_t timVal = 0;// timer0_init();const app_uart_comm_params_t comm_params ={RX_PIN_NUMBER,TX_PIN_NUMBER,RTS_PIN_NUMBER,CTS_PIN_NUMBER,UART_HWFC,false,
#if defined (UART_PRESENT)NRF_UART_BAUDRATE_115200
#elseNRF_UARTE_BAUDRATE_115200
#endif};APP_UART_FIFO_INIT(&comm_params,UART_RX_BUF_SIZE,UART_TX_BUF_SIZE,uart_error_handle,APP_IRQ_PRIORITY_LOWEST,err_code);APP_ERROR_CHECK(err_code);//启动定时器nrfx_timer_enable(&TIMER_COUNTER); while (1){printf(" 2024.5.1 helloworld!\r\n");/* 计数器加1 */nrfx_timer_increment(&TIMER_COUNTER); //获取计数值timVal = nrfx_timer_capture(&TIMER_COUNTER,NRF_TIMER_CC_CHANNEL0);//串口打印计数值printf("conut value: %d\r\n", timVal);nrf_delay_ms(1000);}}
编译程序后下载到 nrf52832 开发板内。打开串口调试助手,选择开发板串口端号,设置波特率为 115200,数据位为 8,停止位为 1。程序设置计数器会 1s 计数一次,我们通过串口 1s 打 印一次计数值,那么打印的计数值应该是 1、2、3…依次输出的。