RTC定时开机闹钟

2023-02-23,,

  RTC是Real Time Clock的简称,它在硬件电路上单独供电,当系统关机时,CPU和其他外部硬件设备全部掉电,但是RTC仍然继续工作.

  HWCR (Hibernate Wakeup Control Register)是一个控制休眠唤醒的寄存器,如果我们要使用休眠状态下RTC唤醒的功能,我们需要打开它的第0位ELAM(RTC Alarm Wakeup enable),当ELAM置1时,使能ELAM功能。

  RTCSR (RTC Second Registe)是一个32位的寄存器,它的值以1Hz的频率加1,即每秒自动加1。

  RTCSAR (RTC Second Alarm Register)是一个以秒为单位的闹钟寄存器,我们可以将设置的格林威治时间转换成相应的秒数然后写进这个寄存器,即完成了我们设置的闹钟。我们打开HWCR中的ELAM,按power键关机,当RTC检测到RTCSR == RTCSAR的值时,RTC将会唤醒CPU,并从XBOOT开始进行开机启动。

  对于设置RTCSAR的操作,我们可以用一个应用rtc_test.c来完成:

 #include <stdio.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h> /*
* This expects the new RTC class driver framework, working with
* clocks that will often not be clones of what the PC-AT had.
* Use the command line to specify another RTC if you need one.
*/
static const char default_rtc[] = "/dev/rtc0";
int main(int argc, char **argv)
{
int i, fd, retval, irqcount = , alarm_time = ;
unsigned long tmp, data;
struct rtc_time rtc_tm;
const char *rtc = default_rtc;
switch (argc)
{
case :
rtc = argv[];
/* FALLTHROUGH */
case :
break;
default:
fprintf(stderr, "usage: rtctest [rtcdev]\n");
return ;
}
fd = open(rtc, O_RDONLY);
if (fd == -)
{
perror(rtc);
exit(errno);
}
fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n"); #if 0
/* Turn on update interrupts (one per second) */
retval = ioctl(fd, RTC_UIE_ON, );
if (retval == -)
{
if (errno == ENOTTY)
{
fprintf(stderr, "\n...Update IRQs not supported.\n");
goto test_READ;
}
perror("RTC_UIE_ON ioctl");
exit(errno);
}
fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading %s:\n",rtc);
fflush(stderr);
for (i=; i<; i++)
{
/* This read will block */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -)
{
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:");
fflush(stderr);
for (i=; i<; i++)
{
struct timeval tv = {, }; /* 5 second timeout on select */
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/* The select will wait until an RTC interrupt happens. */
retval = select(fd+, &readfds, NULL, NULL, &tv);
if (retval == -)
{
perror("select");
exit(errno);
}
/* This read won't block unlike the select-less case above. */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -)
{
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
/* Turn off update interrupts */
retval = ioctl(fd, RTC_UIE_OFF, );
if (retval == -)
{
perror("RTC_UIE_OFF ioctl");
exit(errno);
}
#endif
//test_READ:
/* Read the RTC time/date */ printf("Set the alarm time after: ");
scanf("%d", &alarm_time);
fprintf(stderr, "seconds");
//fprintf(stderr, "Set the alarm time after %d seconds", alarm_time); retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
if (retval == -)
{
perror("RTC_RD_TIME ioctl");
exit(errno);
}
fprintf(stderr, "\n\nCurrent RTC date\time is %d-%d-%d, %02d:%02d:%02d.\n",rtc_tm.tm_mday, rtc_tm.tm_mon + , rtc_tm.tm_year + ,rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
/* Set the alarm to 5 sec in the future, and check for rollover */
rtc_tm.tm_sec += alarm_time;
if (rtc_tm.tm_sec >= )
{
rtc_tm.tm_sec %= ;
rtc_tm.tm_min++;
}
if (rtc_tm.tm_min == )
{
rtc_tm.tm_min = ;
rtc_tm.tm_hour++;
}
if (rtc_tm.tm_hour == )
rtc_tm.tm_hour = ;
retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);
if (retval == -)
{
if (errno == ENOTTY)
{
fprintf(stderr,"\n...Alarm IRQs not supported.\n");
//goto test_PIE;
}
perror("RTC_ALM_SET ioctl");
exit(errno);
}
/* Read the current alarm settings */
retval = ioctl(fd, RTC_ALM_READ, &rtc_tm);
if (retval == -)
{
perror("RTC_ALM_READ ioctl");
exit(errno);
}
fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n",rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
fflush(stderr);
/* Enable alarm interrupts */
retval = ioctl(fd, RTC_AIE_ON, );
if (retval == -)
{
perror("RTC_AIE_ON ioctl");
exit(errno);
}
#if 0
//fprintf(stderr, "Waiting %d seconds for alarm...", alarm_time);
//fflush(stderr);
///* This blocks until the alarm ring causes an interrupt */
//retval = read(fd, &data, sizeof(unsigned long));
//if (retval == -1)
//{
// perror("read");
// exit(errno);
//}
//irqcount++;
//fprintf(stderr, " okay. Alarm rang.\n");
/* Disable alarm interrupts */
retval = ioctl(fd, RTC_AIE_OFF, );
if (retval == -)
{
perror("RTC_AIE_OFF ioctl");
exit(errno);
}
#endif
#if 0
test_PIE:
/* Read periodic IRQ rate */
retval = ioctl(fd, RTC_IRQP_READ, &tmp);
if (retval == -)
{
/* not all RTCs support periodic IRQs */
if (errno == ENOTTY)
{
fprintf(stderr, "\nNo periodic IRQ support\n");
goto done;
}
perror("RTC_IRQP_READ ioctl");
exit(errno);
}
fprintf(stderr, "\nPeriodic IRQ rate is %ldHz.\n", tmp);
fprintf(stderr, "Counting 20 interrupts at:");
fflush(stderr);
/* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */
for (tmp=; tmp<=; tmp*=)
{
retval = ioctl(fd, RTC_IRQP_SET, tmp);
if (retval == -)
{
/* not all RTCs can change their periodic IRQ rate */
if (errno == ENOTTY)
{
fprintf(stderr,"\n...Periodic IRQ rate is fixed\n");
goto done;
}
perror("RTC_IRQP_SET ioctl");
exit(errno);
}
fprintf(stderr, "\n%ldHz:\t", tmp);
fflush(stderr);
/* Enable periodic interrupts */
retval = ioctl(fd, RTC_PIE_ON, );
if (retval == -)
{
perror("RTC_PIE_ON ioctl");
exit(errno);
}
for (i=; i<; i++)
{
/* This blocks */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -)
{
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
/* Disable periodic interrupts */
retval = ioctl(fd, RTC_PIE_OFF, );
if (retval == -)
{
perror("RTC_PIE_OFF ioctl");
exit(errno);
}
}
done:
#endif
fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n");
close(fd);
return ;
}

  上面的程序中我只保留了设置RTC ALARM的操作,其他的全注释掉了。设置ALARM时需要ioctl RTC_ALM_SET和RTC_AIE_ON两个宏定义,其中RTC_ALM_SET设置的闹钟只有在24内有效,我们通过120~132行的代码也可以看出应用只设置了以时分秒为单位的值,假入我们将天/月/年也设上,133行的

retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);

会调到kernel/drivers/rtc/rtc-dev.c的rtc_dev_ioctl中:

     case RTC_ALM_SET:
mutex_unlock(&rtc->ops_lock); if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
return -EFAULT; alarm.enabled = ;
alarm.pending = ;
alarm.time.tm_wday = -;
alarm.time.tm_yday = -;
alarm.time.tm_isdst = -; /* RTC_ALM_SET alarms may be up to 24 hours in the future.
* Rather than expecting every RTC to implement "don't care"
* for day/month/year fields, just force the alarm to have
* the right values for those fields.
*
* RTC_WKALM_SET should be used instead. Not only does it
* eliminate the need for a separate RTC_AIE_ON call, it
* doesn't have the "alarm 23:59:59 in the future" race.
*
* NOTE: some legacy code may have used invalid fields as
* wildcards, exposing hardware "periodic alarm" capabilities.
* Not supported here.
*/
{
unsigned long now, then; err = rtc_read_time(rtc, &tm);
if (err < )
return err;
rtc_tm_to_time(&tm, &now); alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
err = rtc_valid_tm(&alarm.time);
if (err < )
return err;
rtc_tm_to_time(&alarm.time, &then); /* alarm may need to wrap into tomorrow */
if (then < now) {
rtc_time_to_tm(now + * * , &tm);
alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
}
} return rtc_set_alarm(rtc, &alarm);

  在rtc-dev.c的rtc_dev_ioctl中,RTC_ALM_SET的case会先将RTC寄存器RTCSR中的时间读出来,并转换成格林威治时间,应用设置下来的rtc_tm的天/月/年会被读取出来的tm中的天/月/年所覆盖,所以即使应用设置了也没有用。如果应用需要设置超过24小时以外的闹钟可以ioctl RTC_WKALM_SET 的rtc_tm到驱动中。

  ioctl RTC_ALM_SET后还需再ioctl一次RTC_AIE_ON。如果应用不ioctl RTC_AIE_ON的话,应用设置的rtc_tm不会被设到rtc驱动中struct rtc_class_ops的.set_alarm成员指针指向的函数,即rtc_tm的值不会被设到RTCSAR寄存器中,闹钟设置不会生效。

  在Android4.3环境上交叉编译成rtc_test的可执行文件,rtc_test.c和Android.mk都放在external/test/下,其中Android.mk书写如下:

 LOCAL_PATH := $(call my-dir)

 include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := rtc_test
LOCAL_SRC_FILES := $(call all-subdir-c-files)
LOCAL_C_INCLUDES:= $(LOCAL_C_INCLUDES)\
$(PATH)\
kernel/arch/mips/xburst/soc-/include\
kernel/arch/mips/include/asm/mach-generic
include $(BUILD_EXECUTABLE)

  在项目目录执行 source build/envsetup.h和lunch后,再到external/test下执行mm,编译好的rtc_test放在out/target/product/xxxx/system/bin/rtc_test

adb push rtc_test到开发板的/system/bin下,并设置成777权限后即可使用此应用设置RTC的ALARM。

  执行rtc_test时需要我们输入一个距离现在时间的定时闹钟,假如我们输入60,即60s,然后按power键关机,待到过了60s(RTCAR == RTCSAR)时,RTC会唤醒CPU启动开机流程。

  由于我们在按power键关机后有几种不同的状态,比如板子不接USB充电线/板子接USB充电线/板子接USB充电线但隔了一段时间后又拔除,这几种情况都处于不同的XBOOT流程,所以我们还需在XBOOT中加入处于不同关机状态的RTC唤醒代码。

RTC定时开机闹钟的相关教程结束。

《RTC定时开机闹钟.doc》

下载本文的Word格式文档,以方便收藏与打印。