一、方案确定
今天要实现手表的万年历功能。实现万年历功能有三种方式
1.新建一个1秒定时的APP_TIMER。
- 优点:创建方便。
- 缺点:实时性太差。
APP_TIMER采用轮询执行而非抢占的方式,假如其它APP_TIMER耗时较长,例如有一个APP_TIMER在采集心率,万年历的APP_TIMER就必须等采集心率完成才能执行。
2.在APP_TIMER的中断中插入计算时间戳的代码
- 优点:修改代码较少。
- 缺点:代码耦合性高,易出现未知问题。
我们都知道APP_TIMER的实现就是使用了RTC1,那么我们就可以利用上RTC1的特点,适当修改代码来实现我们的需求,但这样容易造成代码耦合性高,不易维护和管理。
3.用一个新的RTC来实现。
- 优点:不会影响原来APP_TIMER的功能,模块独立,方便管理。
- 缺点:需要深入了解芯片的RTC,根据RTC特性来实现自己的功能。
二、实现功能
综上所述,我这里采用了第三种方案来实现。用RTC2来实现。
1.首先来看看RTC的框架图
2.时钟源 : LFCLK
3.频率计算方式:
如实现8HZ的频率,则PRESCALER 寄存器应该设为
32768/8-1 = 4095
4 . 采用RTC中断方式来计时,RTC中断方式有三种
A :TICK 滴答中断,那么在每个时钟滴答(即COUNTER计数一次)都会产生这个事件,COUNTER的技术值就会加1。
B :Overflow 溢出中断,在COUNTER溢出是产生。
C:COMPARE,比较中断,COUNTER计数值与cc[0-3]中的值相等时产生, COMPARE 的一些task, 如clear,stop,start 存在 us 级和 ns 级的延迟,使用 RTC
来计时应该考虑这些可能的延迟。
- 综合考虑,我这里用TICK中断来实现。
TICK时序如下
由图可知当PRESCALER 设为1时,则TICK中断频率为f= (32768/(1+1))
我们要实现8Hz,则PRESCALER = 32768/8-1 = 4095; PRESCALER寄存器只有12bit,2^12-1 =4095,所以最大也只有8Hz了。
#include "nrf.h"
#include "nrf_gpio.h"
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"
#include "boards.h"
#include "app_error.h"
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "nrf_log.h"
#include "app_utc.h"
#include "main.h"
#define tm_t struct tm
static struct tm m_time;
static uint8_t m_time_count_1s =0;
volatile uint32_t m_timestamp=1530517467;
const nrf_drv_rtc_t rtc = NRF_DRV_RTC_INSTANCE(2); /**< Declaring an instance of nrf_drv_rtc for RTC0. */
static bool utc_to_calendar(uint32_t timestamp, tm_t* calendar);
/** @brief: Function for handling the RTC0 interrupts.
* Triggered on TICK and COMPARE0 match.
*/
static void rtc_handler(nrf_drv_rtc_int_type_t int_type)
{
if(int_type == NRF_DRV_RTC_INT_COMPARE0)
{
}
else if (int_type == NRF_DRV_RTC_INT_TICK)
{
if(m_time_count_1s >=7 )//ÖжÏƵÂÊ125ms£¬ËùÒÔ125*8 =1000s
{
m_time_count_1s = 0;
m_timestamp ++;
utc_to_calendar(m_timestamp, &m_time);
NRF_LOG_INFO(":%d/%d/%d, weeday %d",m_time.tm_year, m_time.tm_mon,m_time.tm_mday, m_time.tm_wday);
NRF_LOG_INFO("%d-%d-%d", m_time.tm_hour, m_time.tm_min, m_time.tm_sec);
}
else
{
m_time_count_1s++;
}
}
}
/** @brief Function initialization and configuration of RTC driver instance.
*/
void bsp_rtc_config(void)
{
uint32_t err_code;
//Initialize RTC instance
nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
config.prescaler = 4095;
err_code = nrf_drv_rtc_init(&rtc, &config, rtc_handler);
APP_ERROR_CHECK(err_code);
//Enable tick event & interrupt
nrf_drv_rtc_tick_enable(&rtc, true);
//Power on RTC instance
nrf_drv_rtc_enable(&rtc);
}
static void utc_to_calendar(uint32_t timestamp, tm_t* calendar)
{
tm_t *tm_p;
char *c;
tm_p = localtime((time_t*)×tamp);
memcpy(calendar, tm_p, sizeof(tm_t));
}
sdk_config.h中要配置使能开关
#ifndef RTC2_ENABLED
#define RTC2_ENABLED 1
#endif
m_timestamp是一个32位的时间戳,最后需要手机发送当前时间戳给设备,然后设备一秒钟产生一个中断,用来计秒数,然后通过time.h标准库函数来转换成实际时间。最后在main函数中调用bsp_rtc_config就可以了。
另外,因为协议栈也是用到LFCLK时钟,所以在ble_stack_init函数中LFCLK已经被使能了。
欢迎关注个人公众号“低功耗蓝牙技术研究及推广”