nRF52实践:利用RTC2实现1毫秒定时

475 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

上一文章介绍了 timer 的用法,并用 SDK 提示的接口实现 1ms 的定时功能,但不太准确。本文利用 RTC2 来实现毫秒级定时功能。

概述

除了定时器外,有时需要统计耗时或打上时间戳,很多系统都会提供时间戳函数接口。但 nrf52832 没有提供获取毫秒的函数,需要自己实现。使用 RTC2 产生毫秒级别计数,主要是用单独的 RTC,不影响应用层的软件定时器。

设计

时钟源设置为 32768 Hz,目标频率计算公式为: 32768 Hz /(PRESCALER+1) ,预分频系数 PRESCALER 计算公式:32768 Hz/目标频率 - 1。PRESCALER 寄存器为 12 比特,计算得目标频率最小值为 8 Hz,此时每次时钟滴答中断时间间隔为 0.125 秒,经过8次可得到准确的1秒定时时间。不过并非本文所需。

设计如下接口:

  • 初始化 rtc_init
  • 获取时间时间戳 rtc_gettime
  • 设置时间戳 rtc_settime

使用NRF_DRV_RTC_INSTANCE(2)声明 RTC2 实例。设置全局的无符号整数s_timestamp,其记录时间戳的值,在中断中累加该值,初始值为0,为测试溢出,实现另一接口rtc_settime对其值进行修改。

方法1-992 Hz版本

代码

根据前面的公式,992 Hz 的预分频系统为32,公式 32768/(32+1) = 992.96

初始化函数如下:

void rtc_init(void)
{
    ret_code_t err_code;
         
    nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
    // 32: 32768/(32+1) = 992.96  计数为992或993 即1秒有992或993个tick,每个 1/992.96 = 1.007ms
    config.prescaler = 32;
​
    err_code = nrf_drv_rtc_init(&m_rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_rtc_tick_enable(&m_rtc, true);
    nrf_drv_rtc_enable(&m_rtc);
}

中断处理函数如下:

static void rtc_handler(nrf_drv_rtc_int_type_t int_type)
{
    if(int_type == NRF_DRV_RTC_INT_TICK)
    {
        s_timestamp++;
    }
}

获取、设置时间函数:

uint32_t rtc_gettime(void)
{
    return s_timestamp;
}
​
void rtc_settime(uint32_t t)
{
    s_timestamp = t;
}

测试

主要测试代码片段:

    rtc_settime(4294967280);
    for (int i = 0; i < 5; i++)
    {
    t1 = rtc_gettime();
    delay_ms(1000);
    t2 = rtc_gettime();
    uint32_t delta = t2 - t1;
​
    NRF_LOG_INFO("t1: %u t2: %u delta: %d", t1, t2, delta);
​
    rtc_settime(0xffffffff - 100 - i);
    }

为了测试溢出情况,通过rtc_settime函数修改时间初始值, 结果如下:

00> <info> app: t1: 4294967280 t2: 979 delta: 995
00> <info> app: t1: 4294967195 t2: 892 delta: 993
00> <info> app: t1: 4294967194 t2: 891 delta: 993
00> <info> app: t1: 4294967193 t2: 890 delta: 993
00> <info> app: t1: 4294967192 t2: 889 delta: 993
非溢出的:
00> <info> app: t1: 0 t2: 992 delta: 992
00> <info> app: t1: 992 t2: 1985 delta: 993
00> <info> app: t1: 1985 t2: 2977 delta: 992
00> <info> app: t1: 2977 t2: 3969 delta: 992
00> <info> app: t1: 3969 t2: 4962 delta: 993

从日志可得到结论:溢出不会导致计时的准确性。

方法2-1000 Hz版本

代码

根据前面的公式,设置步骤为 1024 Hz,对应预分频系统为31,公式 32768/(31+1) = 1024。由于每秒超过 1000 次 tick,需要在中断函数中削减过多的值,使其达到1000(左右)。

初始化与前面相差不大:

void rtc_init(void)
{
    ret_code_t err_code;
    nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
​
    // 31: 32768/(31+1) = 1024 计数为1024, 即1秒有1024个tick,每个tick为 1/1024 = 0.9765ms
    config.prescaler = 31;
    err_code = nrf_drv_rtc_init(&m_rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_rtc_tick_enable(&m_rtc, true);
    nrf_drv_rtc_enable(&m_rtc);
}

中断函数如下:

static void rtc_handler(nrf_drv_rtc_int_type_t int_type)
{
    if(int_type == NRF_DRV_RTC_INT_TICK)
    {
        static uint16_t a = 0;
        a += 1000;
        if (a >= 1024)
        {
            a -= 1024;
            s_timestamp++;
        }
    }
}

由于每秒1024个tick,因此在函数中判断,是为了保证1秒内 s_timestamp 累加 1000 次,经测试,有时为999、1001,虽然不是十分准确,算平均值已经很接近了。思路来自 nordic 官方论坛,具体见文末地址链接。

测试

测试代码片段如下:

   //rtc_settime(4294967195);
   for (int i = 0; i < 5; i++)
    {
    t1 = rtc_gettime();
    delay_ms(1000);
    t2 = rtc_gettime();
    uint32_t delta = t2 - t1;
​
    NRF_LOG_INFO("t1: %u t2: %u delta: %d", t1, t2, delta);
​
    rtc_settime(0xffffffff - 100 - i);
    }

结果:

00> <info> app: t1: 0 t2: 1000 delta: 1000
00> <info> app: t1: 1000 t2: 2001 delta: 1001
00> <info> app: t1: 2001 t2: 3002 delta: 1001
00> <info> app: t1: 3002 t2: 4003 delta: 1001
00> <info> app: t1: 4003 t2: 5004 delta: 1001
00> <info> app: t1: 5004 t2: 6004 delta: 1000

使用rtc_settime设置时间戳初始值进行溢出测试,结果如下:

00> <info> app: t1: 4294967195 t2: 900 delta: 1001
00> <info> app: t1: 4294967194 t2: 898 delta: 1000
00> <info> app: t1: 4294967193 t2: 898 delta: 1001
00> <info> app: t1: 4294967192 t2: 897 delta: 1001
00> <info> app: t1: 4294967191 t2: 895 delta: 1000

溢出情况下,计时依然准确。

完整代码

完整代码如下:

#include <time.h>
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"
#include "nrfx_rtc.h"
#include "nrf_rtc.h"
#include "nrf_drv_rtc.h"
 
#include "rtc.h"#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"// RTC2
static const nrf_drv_rtc_t m_rtc = NRF_DRV_RTC_INSTANCE(2);
​
static void rtc_handler(nrf_drv_rtc_int_type_t int_type);
​
volatile uint32_t s_timestamp = 0;
​
static void rtc_handler(nrf_drv_rtc_int_type_t int_type)
{
    if(int_type == NRF_DRV_RTC_INT_TICK)
    {
        // 每秒1024个tick,这里判断,是为了保证1秒内s_timestamp累加1000次
        // 经测试,有时为999、1001,平均接近,不是十分准确
        static uint16_t a = 0;
        a += 1000;
        if (a >= 1024)
        {
            a -= 1024;
            s_timestamp++;
        }
    }
}
​
void rtc_init(void)
{
    ret_code_t err_code;
         
    nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
    // 8Hz: 32768/8-1 = 4095  设置此值,中断中判断7次
    // config.prescaler = 4095;
​
    // 32: 32768/(32+1) = 992.96  计数为992或993 即1秒有992或993个tick,每个 1/992.96 = 1.007ms
    // 31: 32768/(31+1) = 1024 计数为1024, 即1秒有1024个tick,每个tick为 1/1024 = 0.9765ms
    config.prescaler = 31;
     
    err_code = nrf_drv_rtc_init(&m_rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_rtc_tick_enable(&m_rtc, true);
    nrf_drv_rtc_enable(&m_rtc);
}
​
void rtc_start(void)
{
​
}
​
void rtc_stop(void)
{
​
}
​
uint32_t rtc_gettime(void)
{
    return s_timestamp;
}
​
void rtc_settime(uint32_t t)
{
    s_timestamp = t;
}

参考:

blog.csdn.net/u012846795/…

devzone.nordicsemi.com/f/nordic-q-…