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 = 16MHz / (2PRESCALER),其中PRESCALER是分频系数,其可设置的范围是0~9。而分频系数、时钟频率和时钟源的关系如下:

           

  • 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启动后,计数值在每个时钟频率递增,当计数值达到比较值的时候(即定时时间到了),产生比较匹配事件,在事件处理函数中加入相应的处理代码即可实现定时处理任务的目的。常用的一般步骤为:

    1. 设置工作模式。
    2. 设定预分频(计数器不适用)。 
    3. 设定CC[n]寄存器的值。
    4. 使能中断(中断模式)。
    5. 启动START任务。
    6. 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)
	{

	}

}

  

    

    

时间: 2024-12-18 17:52:16

nrf51822-硬件(3)-定时器/计数器TIMER(IK-51822DK开发套件)的相关文章

毕业回馈-89c51之定时器/计数器(Timer/Count)

今天分享的是89c51系列单片机的内部资源定时器/计数器,在所有的嵌入式系统中都包含这两个内部功能. 首先先了解几个定时器/计数器相关的概念: ?时钟周期:时钟周期 T 是时序中最小的时间单位,具体计算的方法就是1/时钟源频率,(一般单片机采用的是11.0592mHz) ?机器周期:我们的单片机完成一个操作的最短时间.标准51单片机,一个机器周期是 12 个时钟周期,也就是 12/11059200 秒. ?定时器:当T/C工作在定时器时,对振荡源12分频的脉冲计数,即每个机器周期计数值加1,计数

51单片机中断机制(定时器/计数器)

单片机中断简介 52单片机一共有6个中断源,它们的符号,名称以及各产生的条件分别如下: INT0 - 外部中断0,由P3.2端口线引入,低电平或下降沿引起 INT1 - 外部中断1,由P3.3端口线引入,低电平或下降沿引起 T0    - 定时器/计数器0中断, 由T0计数器计满回零引起 T1    - 定时器/计数器1中断, 由T1计数器计满回零引起 T2    - 定时器/计数器2中断, 由T2计数器计满回零引起 TI/RI - 串行口中断,串行端口完成一帧字符发送/接收后引起 其中T2是5

单片机 定时器/计数器

一.什么是定时器/计数器 在51单片机中,定时器/计数器是用来实现定时功能,并且具有计数的功能,来实现对外部信号的计数. 二.定时器/计数器有什么类型 在51单片机中,分为软件定时器,不可编程硬件定时器,可编程定时器. 软件定时:CPU每执行一条指令时,是需要固定时间的,所以,通过执行空指令可以达到延时的效果,这样子做的代价是占用CPU时间,所以一般很少这么做. 不可编程硬件定时器:是由电路和硬件来完成定时功能的,一般采用基电路,外接定时部件(电阻和电容),通过改变电阻的阻值和电容的电容值来修改

N76E003的定时器/计数器 0和1

定时器/计数器 0和1N76E003系列定时器/计数器 0和1是2个16位定时器/计数器.每个都是由两个8位的寄存器组成的16位计数寄存器. 对于定时器/计数器0,高8位寄存器是TH0. 低8位寄存器是TL0. 同样定时器/计数器1也有两个8位寄存器, TH1 和TL1. TCON 和 TMOD 可以配置定时器/计数器0和1的工作模式. 通过TMOD中的 位来选择定时器或计数器功能. 每个定时器/计数器都有选择位,TMOD的第2位选择定时器/计数器0功能,TMOD的第6位选择定时器/计数器1功能

WinForm LED循环显示信息,使用定时器Threading.Timer

原文:WinForm LED循环显示信息,使用定时器Threading.Timer 这里用一个示例来演示timer如何使用.示例:LED屏幕显示描述:这个示例其实很简单,LED屏幕上显示3个信息:        1:排队叫号         2:催缴费         3:等待列表.因为LED屏幕大小的关系,列表需要分页显示. 正常情况下,这3个信息都需要从服务器上去获得,这里的示例只做简单的模拟, 界面很简单,如图,这里我就不美化了. Timer构造函数参数说明: Callback:一个 Ti

定时器计数器中断

定时器/计数器的工作由TMOD与TCON两个寄存器控制,TCON是控制寄存器,控制启动停止以及设置溢出标志,TMOD确定工作方式和功能.计数器溢出时会使得TCON寄存器中TF0或者TF1置1,并向CPU发出中断请求. TMOD 工作方式寄存器 TMOD在单片机复位时全部被清零,其高四位设置定时器1,低四位设置定时器0:四位的意义如下: GATE:门控制位,=0定时器的启动停止仅受TCON寄存器控制,=1时受TCON控制器和外部中断引脚电平状态共同控制: C/T:定时器与计数器模式选择,=1为计数

定时器/计数器实验报告

/************************************************* 实验名称:定时器实验一 实验目的:利用定时器/计数器让发光二极管以1HZ闪烁 *************************************************/ #include<reg52.h> sbit P10 = P1^0; unsigned char flag; void main() { flag = 0; P10 = 0; TH0 = 0xfe; TH0 = 0x

SmartOS之(C++)------硬件定时器类Timer

SmartOS (C++)的硬件定时器驱动,兼容STM32F0/F1/F4/GD32F10x/GD32F1x0 头文件 1 #ifndef __Timer_H__ 2 #define __Timer_H__ 3 4 #include "Sys.h" 5 6 // 定时器 7 class Timer 8 { 9 private: 10 TIM_TypeDef* _port; 11 byte _index; // 第几个定时器,从0开始 12 volatile bool _started;

自定义定时器(Timer)

最近做项目的时候,用到了java.util.Timer定时器类,也初步使用了,个人感觉不错.不过,在某些方面Timer类无法满足项目的需求,比如,在使用Timer时,调用schedule()方法之后(初始化),其循环周期无法改变,只有调用cancel()方法之后再重新启动才能将循环周期改变. 自己自定义了一个定时器线程,可开启.可关闭.可动态的改变循环周期,具体代码如下: /** * @ClassName: MyTimer * @Description: TODO 自定义定时器类 * @auth