nRF52832——定时器 TIME

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.hTIMER_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…依次输出的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/772849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下的vscode + opencv4.8.0(C++) 配置

1.添加环境变量 D:\mingw64\bin 2.安装vscode 3.下载opencv 4.8.0 4.程序引用第三方库(opencv为例) 打开CMakeLists.txt&#xff0c;引入头文件&#xff0c;使用include_directories 加入头文件所在目录。静态链接库link_directories # 头文件 include_directories(D:/ope…

搭建 Apple Mac M1 stm32 开发环境

近期想学习 stm32 开发,看了些书和视频,买了开发板。开发板到了后就迫不及待的的进行尝试。由于我目前使用的电脑是 Apple M1 Pro,目前用的比较多的是 windows + keil。我先是在 mac 使用虚拟机,安装 win 环境来使用,但是我分别使用了 VMware 和 parallels desktop ,keil…

API成网络攻击常见载体,如何确保API安全?

根据Imperva发布的《2024年API安全状况报告》&#xff0c;API成为网络攻击者的常见载体&#xff0c;这是因为大部分互联网流量&#xff08;71%&#xff09;都是API调用&#xff0c;API是访问敏感数据的直接途径。根据安全公司Fastly的一项调查显示&#xff0c;95%的企业在过去1…

STM32之HAL开发——串口配置(源码)

串口收发原理框图&#xff08;F1系列&#xff09; 注意&#xff1a;数据寄存器有俩个一个是收一个是发&#xff0c;但是在标准库或者HAL库中没有特别区分开来是俩个寄存器&#xff01; USART 初始化结构体详解 HAL 库函数对每个外设都建立了一个初始化结构体&#xff0c;比如 …

标题:深入理解 ES6 中的变量声明:let、var 和 const

在 ES6&#xff08;ECMAScript 6&#xff09;语法中&#xff0c;新增了let和const关键字来声明变量&#xff0c;这为 JavaScript 变量的作用域和声明方式带来了一些重要的改进。在这篇博客中&#xff0c;我们将深入探讨let、var和const之间的区别&#xff0c;并了解它们如何影响…

I/O(输入/输出流的概述)

文章目录 前言一、流的概述二、输入/输出流 1.字节/字符输入流2.字节/字符输出流总结 前言 在变量、数组和对象中储存的数据是暂时的&#xff0c;程序结束后它们就会丢失。如果想要永久地储存程序创建的数据&#xff0c;需要将其保存在磁盘文件中&#xff0c;这样就可以在程序中…

C#_事件_多线程(基础)

文章目录 事件通过事件使用委托 多线程(基础)进程:线程: 多线程线程生命周期主线程Thread 类中的属性和方法创建线程管理线程销毁线程 昨天习题答案 事件 事件&#xff08;Event&#xff09;本质上来讲是一种特殊的多播委托&#xff0c;只能从声明它的类中进行调用,基本上说是…

MyBatis-Plus分页接口实现教程:Spring Boot中如何编写分页查询

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Unity VisionOS开发流程

Unity开发环境 Unity Pro, Unity Enterprise and Unity Industry 国际版 Mac Unity Editor(Apple silicon) visionOS Build Support (experimental) 实验版 Unity 2022.3.11f1 NOTE: 国际版与国服版Pro账通用&#xff0c;需要激活Pro的许可证。官方模板v0.6.2,非Pro版本会打…

稀碎从零算法笔记Day29-LeetCode:单词拆分

死磕dp的第二天了 题型&#xff1a;dp&#xff0c;字符串&#xff0c;二维数组&#xff0c;背包类 链接&#xff1a;139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果…

【探究图论中dfs记忆化,搜索,递推,回溯关系】跳棋,奶牛隔间, 小A和uim之大逃离 II

本篇很高能&#xff0c;如有错误欢迎指出&#xff0c;本人能力有限&#xff08;需要前置知识记忆化dfs&#xff0c;树形dp&#xff0c;bfsdp&#xff0c;tarjan&#xff09; 另外&#xff0c;本篇之所以属于图论&#xff0c;也是想让各位明白&#xff0c;dfs就是就是在跑图&am…

mysql80-DBA数据库学习2

权限管理 创建用户 create user user1localhost identified by QianFeng123; select * from mysql.user; 或者select * from mysql.user\G进行分行显示 密码要求&#xff1a; 1组成&#xff1a; 由小写字母、大写字母、数字、字符 中的三项组成 &#xff0c;也就是3/4 2长度…

单片机---独立按键

[3-1] 独立按键控制LED亮灭_哔哩哔哩_bilibili 按下的时候连接&#xff0c;松开的时候断开。 一头接GND&#xff08;电源负极&#xff09;&#xff0c;另一头接I/O口。 单片机上电时&#xff0c;所有I/O口为高电平。 按键没有按下&#xff0c;I/O口为高电平。 按键按下&…

标题:深入了解 ES6 模块化技术

在 ES6 版本之前&#xff0c;JavaScript 一直缺乏一个内置的模块系统&#xff0c;这给大型项目的开发带来了一些挑战。ES6 引入了模块化的概念&#xff0c;为 JavaScript 开发者提供了一种更好的组织和管理代码的方式。 模块是 JavaScript 的一种代码组织方式&#xff0c;它将代…

界面控件DevExpress WinForms/WPF v23.2 - 电子表格支持表单控件

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

QGraphicsView(平移/缩放/旋转)

简述 Graphics View提供了一个平台&#xff0c;用于大量自定义 2D 图元的管理与交互&#xff0c;框架包括一个事件传播架构&#xff0c;支持场景 Scene 中的图元 Item 进行精确的双精度交互功能。Item 可以处理键盘事件、鼠标按下、移动、释放和双击事件&#xff0c;同时也能跟…

鸿蒙OS封装【axios 网络请求】(类似Android的Okhttp3)

Okhttp.ets /*** 网络请求*/ import axios from ohos/axios import httpConstants from ../net/HttpConstants import errorCode from ../utils/errorCode import toast from ../utils/ToastUtils import router from ../utils/RouterUtils import SPUtils from ../utils/SPUt…

毕马威:量子计算成未来3-5年重大挑战

毕马威&#xff08;KPMG&#xff09;是一家全球性的专业服务网络&#xff0c;其历史可追溯到19世纪末。作为“四大”会计师事务所之一&#xff0c;毕马威在审计、税务和咨询服务领域享有盛誉。公司在全球范围内拥有多个办事处&#xff0c;服务遍及各个行业&#xff0c;包括金融…

5.1 物联网RK3399项目开发实录-Android开发之ADB使用(wulianjishu666)

物联网项目开发实例&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11VQMhHfIL9mZhNlls4wmjw?pwd0gfa 1. ADB 使用 1.1. 前言 ADB&#xff0c;全称 Android Debug Bridge&#xff0c;是 Android 的命令行调试工具&#xff0c;可以完成多种功能&#xff0c;如跟踪系…

CentOS Stream 8系统配置阿里云YUM源

Linux运维工具-ywtool 目录 一.系统环境二.修改yum文件2.1 CentOS-Stream-AppStream.repo2.2 CentOS-Stream-BaseOS.repo2.3 CentOS-Stream-Extras.repo 三.只有一个配置文件四.其他知识4.1 如果想要启用其他源,修改文件配置:enabled14.2 国内源链接 一.系统环境 CentOS Strea…