NRF51822 Timer

首先熟悉一下NRF51822,这一篇文章讲得不错,链接如下:
http://wenku.baidu.com/link?url=JNSJuhxNtMn3HJg6q5rETABXbejF_dallu8CkGzQCb1azNtVv5MJeYbrI26KCl4RrPaq0C41wqkeDihF9RnG577vEhHY993ZksVlSV6FHV_
定时器简介 定时器可谓是熟悉一个芯片主频之后第二个要熟悉的模块。
51822的Timer/Counter结构如下图所示:
NRF51822 Timer
文章图片

时钟源

  • 51822的定时器的时钟源默认是是HFCLK,也就是16MHz。
  • 可以通过预分频PRESCALER来降低时钟频率,公式是:timer时钟频率=16M/2^PRESCALER;
    • 这里可能有个疑问,上面的图解中不是有两个时钟源 16M和1M吗,怎么这个公式只能通过16M来分频获得timer时钟。 这是因为51822为了降低功耗内部自动 做了时钟源切换,当 Ftimer <= 1M时会自动切换成1M时钟源
  • 举两个例子解释下
    • 如果需要timer的时钟为 4M,那么4 = Ftimer = 16M/2^2
      即我们只需设置分频寄存器 PRESCALER为2,就能或得4M的时钟给timer了
    • 当需要timer的时钟为500Khz时,根据公式 我们设置PRESCALER寄存器的值为5, 500kHZ = Ftimer = 16M/2^5。 这个时候Ftimer<=1M,所以51882内部会自动切换成1M的时钟源然后分频后获得500K的timer时钟。 不过这些都是51822自动切换的了
  • 也就是说设置timer时钟只要根据上面的公式设置就可以了,时钟源的切换是51822自动完成的
模式(MODE) TIMER可以处于两种模式:
- 0 定时模式:TIMER_MODE_MODE_Timer(默认)
- 1 计数模式:TIMER_MODE_MODE_Counter
它们的区别在于决定定时器里的计数器增加的时钟源不一样。如果我们设置了compare[n]事件产生时触发中断(关于事件与中断看前一篇GPIOTE),那么就可以在counter计数到与 cc[n]寄存器中的值相等时触发中断,也就能实现我们需要的定时器功能了
定时模式
  • Timer模块从PCLK16M/PCLK1M 处获得时钟源,然后经分频PRESCALER后得到的时钟作为timer模块的时钟( 上图Ftimer )。
  • -
计数模式
  • Timer模块以外部引脚输入的波动作为timer模块的时钟 。
位宽(BITMODE) 定时器位宽,决定用来存储计数的寄存器有多少位,最大是32bit
寄存器cc[n] 【NRF51822 Timer】定时就是通过这个值来设置的。
寄存器INTENSET 这个寄存器可以设置compare事件产生时触发中断。第16、17、18位分别对应compare[0]、compare[1]、compare[2]。
寄存器SHORTS 可以决定timer模块中的counter计数到cc[0]的时候值会不会清零,以达到可以重新计数的目的。
就是说当定时器内部的计数累加值和这个寄存器的值相等的时候就会触发compare事件
下面的例子会做一个一秒定时亮灯/灭灯的程序, 我们设置timer时钟为1M,即分频寄存器PRESCALER写值为4。 1M的时钟源则一个tick为1us,所以要定时1s,则cc[0]的值我们填入 1000000就行了。(这里也可以选择cc[1],cc[2],或cc[3],只要下面对应的compare事件产生中断设置成对应的就可以了)
定时器中断工作
  • 根据上面的模块结构图和说明想要实现定时器,定时器的工作氛围几个步骤。
    • 选择Timer/Countermo模块为timer模式,并设置bitmode(8,16,24,32位)。
    • 通过设置分频来设置timer的时钟。
    • 设置cc[n] (后面我们的例子选择使用cc0),来设置计数到多少产生compares[n]事件(当计数值技术到cc[n]的值时对应产生compare[n]事件)
    • 设置compare事件产生时触发中断。
    • 通过NVIC函数NVIC_EnableIRQ来使能MCU 的timer中断
    • 最后通过Timer/Counter模块的 START 启动timer
  • 代码示例
void timer2_init(void) { // 时钟 = 16 Mhz NRF_TIMER2->BITMODE= 0; //(TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos); // 设置16bit模式 NRF_TIMER2->PRESCALER = 9; // 2^9=512,得到31.25kHz的timer时钟 NRF_TIMER2->SHORTS= 1<<18; //(TIMER_SHORTS_COMPARE2_CLEAR_Enabled << TIMER_SHORTS_COMPARE2_CLEAR_Pos); //设置compare[2]事件产生时触发中断,在中断函数中清楚对应的compare// 2比较中断 NRF_TIMER2->MODE= 0; //TIMER_MODE_MODE_Timer; // 计时模式(默认) NRF_TIMER2->CC[2] = 31250U; //0x7A12UL //中断设置 NRF_TIMER2->INTENSET = 1<<18; //(TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos); //开启中断 NVIC_ClearPendingIRQ(TIMER2_IRQn); NVIC_SetPriority(TIMER2_IRQn, 3); NVIC_ClearPendingIRQ(TIMER2_IRQn); NVIC_EnableIRQ(TIMER2_IRQn); //启动timer NRF_TIMER2->TASKS_START = 1; }void TIMER2_IRQHandler(void)//定时器中断模式 { if ((NRF_TIMER2->EVENTS_COMPARE[2] == 1)&& (NRF_TIMER2->INTENSET & TIMER_INTENSET_COMPARE2_Msk)) { NRF_TIMER2->EVENTS_COMPARE[2] = 0; LED1_Toggle(); //电平翻转void LED2_Toggle(void) //NRF_TIMER2->TASKS_CLEAR = 1; //应该是1s秒中断一次,如果没有这一句就会两秒才中断一次 }}

  • 代码说明
    • mian需要初始化两个函数
      • led_init();
      • timer2_init();
    • 需要注意替换的地方
      • NRF_TIMER0、CC[0]、TIMER0_IRQn、EVENTS_COMPARE[0]、COMPARE0_Enabled
      • NRF_TIMER1、CC[1]、TIMER1_IRQn、EVENTS_COMPARE[1]、COMPARE1_Enabled
      • NRF_TIMER2、CC[2]、TIMER2_IRQn、EVENTS_COMPARE[2]、COMPARE2_Enabled
    • 调试过程中出现了一点BUG,本来设置的是1s中断一次,但是实验结果是2s,后面才发现是在中断结束后没有及时清除,刚好设置的CC[2]是31250,设置的Bit位宽又是16位,刚好65535差不错是31250的两倍,所以每次compare[2]都是从0一直计数累计到65535才从零开始,也就是说,每次与CC[2]比较相等的时候,已经过了差不多2s。
    • 但是好像在有上面那个BUG的情况下把位宽修改为32位也还是一样的2秒中断一次!一开始很疑惑,后面想了想,如果没有在中断结束后及时把compare寄存器的值清零的话,不管CC寄存器的值为多少,中断周期都是一整个大周期也就是大约2秒。设置位宽只是能让compare进位的步数颗粒度更小一点而已。
其他 建议定时器中断采用time1或者是time2,因为如果跑了协议栈就不能用timer0。

    推荐阅读