-
简介:
NRF52832 中的 RTC 是 Real-time Counter 实时计数器,而不是 Real-time Clock 实时时间 。所以为了实现实时时钟,需要通过RTC0的比较事件产生一个1s的计时,在每个中断事件中更新时间结构体t。
实现万年历功能有三种方式:
- 新建一个1秒定时的APP_TIMER。
- 优点:创建方便。
- 缺点:实时性太差。
- APP_TIMER采用轮询执行而非抢占的方式,假如其它APP_TIMER耗时较长,例如有一个APP_TIMER在采集心率,万年历的APP_TIMER就必须等采集心率完成才能执行。
- 在APP_TIMER的中断中插入计算时间戳的代码
- 优点:修改代码较少。
- 缺点:代码耦合性高,易出现未知问题。
- 我们都知道APP_TIMER的实现就是使用了RTC1,那么我们就可以利用上RTC1的特点,适当修改代码来实现我们的需求,但这样容易造成代码耦合性高,不易维护和管理。
- 用一个新的RTC来实现。
- 优点:不会影响原来APP_TIMER的功能,模块独立,方便管理。
- 缺点:需要深入了解芯片的RTC,根据RTC特性来实现自己的功能。
————————————————
版权声明:本文为CSDN博主「Leung_ManWah」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36347513/article/details/103893124
设计思路:
一、由RTC0的比较事件产生一个1s的计时
void nrf_cal_init(void)
{
//使用外部32.768 kHz 晶体振荡器作为低频时钟源
NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos;
//LFCLK启动完成事件清零
NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
//启动LFCLK
NRF_CLOCK->TASKS_LFCLKSTART = 1;
//等待LFCLK启动完成
while(NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
//配置RTC每秒产生一次比较匹配事件
CAL_RTC->PRESCALER = 4095;
CAL_RTC->EVTENSET = RTC_EVTENSET_COMPARE0_Msk;
CAL_RTC->INTENSET = RTC_INTENSET_COMPARE0_Msk;
CAL_RTC->CC[0] = 8;
CAL_RTC->TASKS_START = 1;
NVIC_SetPriority(CAL_RTC_IRQn, CAL_RTC_IRQ_Priority);
NVIC_EnableIRQ(CAL_RTC_IRQn);
}
//设置时间
void nrf_cal_set_time(uint32_t year, uint32_t month, uint32_t day, uint32_t hour, uint32_t minute, uint32_t second)
{
t.tm_year = year - 1900;
t.tm_mon = month;
t.tm_mday = day;
t.tm_hour = hour;
t.tm_min = minute;
t.tm_sec = second;
CAL_RTC->TASKS_CLEAR = 1;
}
//使用strftime格式化时间
char *nrf_cal_get_time_string(void)
{
static char cal_string[80];
strftime(cal_string, 80, "%Y-%m-%d-%H:%M:%S ", &t);
return cal_string;
}
//判断是不是闰年:能整除4但不能整除100,能整除400
static char not_leap(void)
{
if (!(t.tm_year%100))
{
return (char)(t.tm_year%400);
}
else
{
return (char)(t.tm_year%4);
}
}
void CAL_updata(void)
{
TimeUpdataFlag = UPDATA_SEC;
if (++t.tm_sec==60) //keep track of time, date, month, and year
{
TimeUpdataFlag = UPDATA_HM;
t.tm_sec=0;
if (++t.tm_min==60)
{
t.tm_min=0;
if (++t.tm_hour==24)
{
TimeUpdataFlag = UPDATA_DATE;
t.tm_hour=0;
if (++t.tm_mday==32)
{
t.tm_mon++;
t.tm_mday=1;
}
else if (t.tm_mday==31)
{
//4,6,9,11是小月(2月单独处理),一月30天
if ((t.tm_mon==(4-1)) || (t.tm_mon==(6-1)) || (t.tm_mon==(9-1)) || (t.tm_mon==(11-1)))
{
t.tm_mon++;
t.tm_mday=1;
}
}
else if (t.tm_mday==30)//2月是闰月,2月有29天
{
if(t.tm_mon==2)
{
t.tm_mon++;
t.tm_mday=1;
}
}
else if (t.tm_mday==29)//2月不是闰月,2月有28天
{
if((t.tm_mon==2) && (not_leap()))
{
t.tm_mon++;
t.tm_mday=1;
}
}
if (t.tm_mon==13)
{
t.tm_mon=1;
t.tm_year++;
}
}
}
}
}
//RTC中断服务函数
void CAL_RTC_IRQHandler(void)
{
//如果是RTC0通道0的比较匹配事件
if(CAL_RTC->EVENTS_COMPARE[0])
{
//清零事件
CAL_RTC->EVENTS_COMPARE[0] = 0;
//清零计数器
CAL_RTC->TASKS_CLEAR = 1;
//更新时间
CAL_updata();
}
}
二、定义一个时间结构体的变量t,记录时间,同时定义一个时间标志位(时间标志位的作用是用来刷新函数时的判断标准)
struct tm {
int tm_sec; /* seconds after the minute, 0 to 60
(0 - 60 allows for the occasional leap second) */
int tm_min; /* minutes after the hour, 0 to 59 */
int tm_hour; /* hours since midnight, 0 to 23 */
int tm_mday; /* day of the month, 1 to 31 */
int tm_mon; /* months since January, 0 to 11 */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday, 0 to 6 */
int tm_yday; /* days since January 1, 0 to 365 */
int tm_isdst; /* Daylight Savings Time flag */
union { /* ABI-required extra fields, in a variety of types */
struct {
int __extra_1, __extra_2;
};
struct {
long __extra_1_long, __extra_2_long;
};
struct {
char *__extra_1_cptr, *__extra_2_cptr;
};
struct {
void *__extra_1_vptr, *__extra_2_vptr;
};
};
};
三、oled更新显示。
void oled_updata(uint8_t flag)
{
uint8_t i;
char *p;
//获取格式化的时间字符串
p = nrf_cal_get_time_string();
//根据时间更新标志更新OLED显示内容
switch(flag)
{
//更新秒显示:只需更新秒显示即可
case UPDATA_SEC:
//更新秒显示
LCD_timestr(48,2,(uint8_t*)(p+17));
LCD_timestr(56,2,(uint8_t*)(p+18));
break;
//更新时和分显示:需要更新秒显示和时分显示
case UPDATA_HM:
//更新秒显示
LCD_timestr(48,2,(uint8_t*)(p+17));
LCD_timestr(56,2,(uint8_t*)(p+18));
//更新时分显示
LCD_timestr(0,2,(uint8_t*)(p+11));
LCD_timestr(8,2,(uint8_t*)(p+12));
LCD_timestr(16,2,(uint8_t*)(p+13));
LCD_timestr(24,2,(uint8_t*)(p+14));
LCD_timestr(32,2,(uint8_t*)(p+15));
// LCD_timestr(40,2,(uint8_t*)(p+16));
break;
//更新年月日显示:需要更新年月日和时分秒显示
case UPDATA_DATE:
//更新秒显示
LCD_timestr(48,2,(uint8_t*)(p+17));
LCD_timestr(56,2,(uint8_t*)(p+18));
//更新时分显示
LCD_timestr(0,2,(uint8_t*)(p+11));
LCD_timestr(8,2,(uint8_t*)(p+12));
LCD_timestr(16,2,(uint8_t*)(p+13));
LCD_timestr(24,2,(uint8_t*)(p+14));
LCD_timestr(32,2,(uint8_t*)(p+15));
//更新年月日显示
for(i=0;i<10;i++)
{
LCD_timestr(0+8*i,0,(uint8_t*)(p+i));
}
// break;
default:
break;
}
}
主函数:
void main
{
while (true)
{
//更新OLED显示
//检查时间更新标志
if(TimeUpdataFlag)
{
oled_updata(TimeUpdataFlag);
TimeUpdataFlag = 0;
}
}
}