一、简介
NRF52832 中的 RTC 是 Real-time Counter 实时计数器,而不是 Real-time Clock 实时时间 。所以为了实现实时时钟,需要创建一个1秒定时器增加时间戳的值。
1.1 选用RTC2
实现万年历功能有三种方式:
- 新建一个1秒定时的APP_TIMER。
- 优点:创建方便。
- 缺点:实时性太差。
APP_TIMER采用轮询执行而非抢占的方式,假如其它APP_TIMER耗时较长,例如有一个APP_TIMER在采集心率,万年历的APP_TIMER就必须等采集心率完成才能执行。
- 在APP_TIMER的中断中插入计算时间戳的代码
- 优点:修改代码较少。
- 缺点:代码耦合性高,易出现未知问题。
我们都知道APP_TIMER的实现就是使用了RTC1,那么我们就可以利用上RTC1的特点,适当修改代码来实现我们的需求,但这样容易造成代码耦合性高,不易维护和管理。
- 用一个新的RTC来实现。
- 优点:不会影响原来APP_TIMER的功能,模块独立,方便管理。
- 缺点:需要深入了解芯片的RTC,根据RTC特性来实现自己的功能。
综上所述,采用了第三种方案来实现。用 RTC2 来实现。
1.2 RTC内部结构
RTC 模块具有一个 24 位计数器、12 位预分频器、捕获/比较寄存器和一个滴答事件生成器用于低功耗、无滴答 RTOS 的实现。RTC 模块需要低频时钟源提供运行时钟,因此在使用 RTC 之前,软件必须明确启动 LFCLK 时钟。
1.3 RTC时钟源
实时计数器 RTC 运行于 LFCLK 下。COUNTER 的分辨率为30.517us。当 HFCLK 关闭和 PCLK16M 也不用时,RTC 也可以运行。
因为在协议栈工程也是用到 LFCLK 时钟,所以在 ble_stack_init 函数中 LFCLK 已经被使能了。
1.4 频率计算方式
如实现8HZ的频率,则PRESCALER 寄存器应该设为
32768/8-1 = 4095
1.5 RTC中断事件
- TICK 滴答中断 ,可选择为 RTOS 提供常规中断源,而无需使用 ARM SysTick 功能。使用 RTC TICK 事件而不是 SysTick 可以在关闭 CPU 的同时保持 RTOS 调度处于活动状态。在每个时钟滴答(即COUNTER计数一次)都会产生这个事件,COUNTER的技术值就会加1。
- Overflow 溢出中断 ,在 COUNTER 溢出时产生。
- COMPARE 比较中断,COUNTER 计数值与 cc[0-3] 中的值相等时产生, COMPARE 的一些 task, 如 clear,stop,start 存在 us 级和 ns 级的延迟,使用 RTC 来计时应该考虑这些可能的延迟。
综合考虑,采用 TICK 中断来实现。
由图可知当 PRESCALER 设为1时,则 TICK 中断频率为 f= (32768/(1+1))
我们要实现 8Hz,则 PRESCALER = 32768/8-1 = 4095;PRESCALER 寄存器只有 12bit,2^12-1 = 4095,所以最大也只有 8Hz 了。
二、移植文件
注意:以下出现缺失common.h文件错误,去除即可。uint8改为uint8_t或unsigned char或自己宏定义
链接:https://pan.baidu.com/s/1AGAImL-ydFAJmjuXAgw_HA 提取码:o45p
将 board_rtc.c 和 board_rtc.h 两个文件加入工程的Application文件夹下
2.1 board_rtc.c
/*********************************************************************
* INCLUDES
*/
#include <time.h>
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"
#include "nrfx_rtc.h"
#include "board_rtc.h"
#include "common.h"
static void rtcCallbackFunc(nrf_drv_rtc_int_type_t interruptType);
/*********************************************************************
* GLOBAL VARIABLES
*/
volatile uint32 g_timestamp = 1412800000; // 时间戳
/*********************************************************************
* LOCAL VARIABLES
*/
static const nrf_drv_rtc_t s_rtcHandle = NRF_DRV_RTC_INSTANCE(2); // Declaring an instance of nrf_drv_rtc for RTC2.
static uint8 s_timeCount1second = 0;
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief RTC时间的初始化函数
@param 无
@return 无
*/
void RTC_Init(void)
{
ret_code_t errCode;
nrf_drv_rtc_config_t rtcConfig = NRF_DRV_RTC_DEFAULT_CONFIG; //Initialize RTC instance
rtcConfig.prescaler = 4095; // 如实现8HZ的频率,则PRESCALER寄存器应该设为32768/8-1 = 4095
errCode = nrf_drv_rtc_init(&s_rtcHandle, &rtcConfig, rtcCallbackFunc);
APP_ERROR_CHECK(errCode);
nrf_drv_rtc_tick_enable(&s_rtcHandle, true); // Enable tick event & interrupt
nrf_drv_rtc_enable(&s_rtcHandle); // Power on RTC instance
}
/**
@brief 设置RTC时间
@param timestampNow -[in] 当前时间戳
@return 无
*/
void RTC_SetTime(uint32 timestampNow)
{
g_timestamp = timestampNow;
}
/**
@brief 获取RTC时间
@param 无
@return 当前时间戳
*/
uint32 RTC_GetTime(void)
{
return g_timestamp;
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief RTC计数回调函数
@param interruptType - [in] 中断类型
@return 无
*/
static void rtcCallbackFunc(nrf_drv_rtc_int_type_t interruptType)
{
if(interruptType == NRF_DRV_RTC_INT_COMPARE0) // 中断类型:比较中断
{
}
else if(interruptType == NRF_DRV_RTC_INT_TICK) // 中断类型:滴答中断
{
if(s_timeCount1second >= 7) // 125ms * 8 = 1s
{
s_timeCount1second = 0;
g_timestamp++;
}
else
{
s_timeCount1second++;
}
}
}
/****************************************************END OF FILE****************************************************/
2.2 board_rtc.h
#ifndef _BOARD_RTC_H_
#define _BOARD_RTC_H_
/*********************************************************************
* INCLUDES
*/
#include "common.h"
/*********************************************************************
* GLOBAL VARIABLES
*/
extern volatile uint32 g_timestamp;
/*********************************************************************
* API FUNCTIONS
*/
void RTC_Init(void);
void RTC_SetTime(uint32 timestampNow);
uint32 RTC_GetTime(void);
#endif /* _BOARD_RTC_H_ */
三、API调用
需包含头文件 board_rtc.h
RTC_Init
功能 | 初始化RTC实时时钟驱动 |
---|---|
函数定义 | void RTC_Init(void) |
参数 | 无 |
返回 | 无 |
RTC_SetTime
功能 | 设置RTC时间戳 |
---|---|
函数定义 | void RTC_SetTime(uint32 timestampNow) |
参数 | timestampNow:时间戳 |
返回 | 无 |
RTC_GetTime
功能 | 获取RTC时间戳 |
---|---|
函数定义 | uint32 RTC_GetTime(void) |
参数 | 无 |
返回 | 当前时间戳 |
四、SDK配置
点击 sdk_config.h 文件
选择 Configuration Wizard
nRF_Drivers 中勾选RTC2相关选项
五、使用例子
1)添加头文件
#include "board_rtc.h"
2)添加初始化代码(SDK15.3 中 ble_peripheral 的 ble_app_template 工程 main() 函数中)
加入 RTC_Init() 并在初始化后调用 RTC_SetTime 配置时间,在需要用RTC时间时调用 RTC_GetTime 获取时间戳
int main(void)
{
bool erase_bonds;
/*-------------------------- 外设驱动初始化 ---------------------------*/
// Initialize.
log_init(); // 日志驱动初始化
timers_init(); // 定时器驱动初始化(在此加入自定义定时器)
RTC_Init(); // RTC驱动初始化
/*-------------------------- 蓝牙协议栈初始化 ---------------------------*/
power_management_init();
ble_stack_init(); // 协议栈初始化
gap_params_init();
gatt_init();
advertising_init(); // 广播初始化
services_init(); // 服务初始化
conn_params_init(); // 连接参数初始化
peer_manager_init();
/*-------------------------- 开启应用 ---------------------------*/
// Start execution.
NRF_LOG_INFO("Template example started.");
advertising_start(erase_bonds); // 开启广播
application_timers_start(); // 定时器应用开启(在此开启自定义定时器)
RTC_SetTime(g_timestamp); // 设置RTC时间
// Enter main loop.
for(;;)
{
idle_state_handle();
}
}
3)获取当前几点
uint32 GetHourNow(void)
{
struct tm *pLocaclTime;
pLocaclTime = localtime((time_t*)&g_timestamp);
return pLocaclTime->tm_hour;
}
4)tm时间结构体
struct tm
{
int tm_sec; /* seconds after the minute - [0,59] */
int tm_min; /* minutes after the hour - [0,59] */
int tm_hour; /* hours after the midnight - [0,23] */
int tm_mday; /* day of the month - [1,31] */
int tm_mon; /* months since January - [0,11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday - [0,6] */
int tm_yday; /* days since Jan 1st - [0,365] */
int tm_isdst; /* Daylight Savings Time flag */
};
• 由 Leung 写于 2020 年 1 月 8 日