确切的说,闹钟模块应该属于rtc功能的附加功能,他主要是基于rtc计时,外加rtc提供的超时中断来实现闹钟功能,属于纯应用逻辑。
源码分析
源码路径
components\drivers\rtc\alarm.c
闹钟核心处理线程
// 将struct tm当天的时间转化成s
rt_inline rt_uint32_t alarm_mkdaysec(struct tm *time)
{
rt_uint32_t sec;
sec = time->tm_sec;
sec += time->tm_min * 60;
sec += time->tm_hour * 3600;
return (sec);
}
// 设置闹钟,需要注意的是,设置闹钟所使用的结构体变成了 struct rt_alarm
static rt_err_t alarm_set(struct rt_alarm *alarm)
{
rt_device_t device;
struct rt_rtc_wkalarm wkalarm;
rt_err_t ret;
device = rt_device_find("rtc");
if (device == RT_NULL)
{
return (RT_ERROR);
}
if (alarm->flag & RT_ALARM_STATE_START)
wkalarm.enable = RT_TRUE;
else
wkalarm.enable = RT_FALSE;
wkalarm.tm_sec = alarm->wktime.tm_sec;
wkalarm.tm_min = alarm->wktime.tm_min;
wkalarm.tm_hour = alarm->wktime.tm_hour;
wkalarm.tm_mday = alarm->wktime.tm_mday;
wkalarm.tm_mon = alarm->wktime.tm_mon;
wkalarm.tm_year = alarm->wktime.tm_year;
ret = rt_device_control(device, RT_DEVICE_CTRL_RTC_SET_ALARM, &wkalarm);
if ((ret == RT_EOK) && wkalarm.enable)
{
ret = rt_device_control(device, RT_DEVICE_CTRL_RTC_GET_ALARM, &wkalarm);
if (ret == RT_EOK)
{
// 如果使能了,则更新当前闹钟的参数,因为部分芯片的RTC闹钟部分最小时间单位并不是s,所以需要将秒默认设为0
alarm->wktime.tm_sec = wkalarm.tm_sec;
alarm->wktime.tm_min = wkalarm.tm_min;
alarm->wktime.tm_hour = wkalarm.tm_hour;
alarm->wktime.tm_mday = wkalarm.tm_mday;
alarm->wktime.tm_mon = wkalarm.tm_mon;
alarm->wktime.tm_year = wkalarm.tm_year;
}
}
return (ret);
}
// 闹钟唤醒处理
static void alarm_wakeup(struct rt_alarm *alarm, struct tm *now)
{
rt_uint32_t sec_alarm, sec_now;
rt_bool_t wakeup = RT_FALSE;
time_t timestamp;
// 获取当前时间和闹钟时间(当天对应时间)
sec_alarm = alarm_mkdaysec(&alarm->wktime);
sec_now = alarm_mkdaysec(now);
if (alarm->flag & RT_ALARM_STATE_START)
{
switch (alarm->flag & 0xFF00)
{
case RT_ALARM_ONESHOT:
{ // 如果是单次闹钟且当前时间超过了闹钟所要求的时间,则关闭该闹钟并置需要闹铃标记
// 转换成1970年至今的秒级的时间
sec_alarm = timegm(&alarm->wktime);
sec_now = timegm(now);
if (((sec_now - sec_alarm) <= RT_ALARM_DELAY) && (sec_now >= sec_alarm))
{
// 关闭闹钟
alarm->flag &= ~RT_ALARM_STATE_START;
alarm_set(alarm);
// 置闹铃标记
wakeup = RT_TRUE;
}
}
break;
case RT_ALARM_SECOND:
{ // 如果是每秒都响的闹钟,则更新闹钟时间信息并置闹铃标记
alarm->wktime.tm_hour = now->tm_hour;
alarm->wktime.tm_min = now->tm_min;
alarm->wktime.tm_sec = now->tm_sec + 1;
if (alarm->wktime.tm_sec > 59)
{
alarm->wktime.tm_sec = 0;
alarm->wktime.tm_min = alarm->wktime.tm_min + 1;
if (alarm->wktime.tm_min > 59)
{
alarm->wktime.tm_min = 0;
alarm->wktime.tm_hour = alarm->wktime.tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
}
}
wakeup = RT_TRUE;
}
break;
case RT_ALARM_MINUTE:
{ // 如果是每分钟响一次的闹钟,则更新闹钟时间并置闹铃标记
alarm->wktime.tm_hour = now->tm_hour;
if (alarm->wktime.tm_sec == now->tm_sec)
{
alarm->wktime.tm_min = now->tm_min + 1;
if (alarm->wktime.tm_min > 59)
{
alarm->wktime.tm_min = 0;
alarm->wktime.tm_hour = alarm->wktime.tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
}
wakeup = RT_TRUE;
}
}
break;
case RT_ALARM_HOUR:
{ // 如果是每小时响一次的闹钟,则更新闹钟时间并置闹铃标记
if ((alarm->wktime.tm_min == now->tm_min) &&
(alarm->wktime.tm_sec == now->tm_sec))
{
alarm->wktime.tm_hour = now->tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
wakeup = RT_TRUE;
}
}
break;
case RT_ALARM_DAILY:
{// 如果是每天响的闹钟,则直接置闹铃标记(因为闹钟处理其实都是按天来操作的,超过一天,wktime不需要更新)
if (((sec_now - sec_alarm) <= RT_ALARM_DELAY) && (sec_now >= sec_alarm))
wakeup = RT_TRUE;
}
break;
case RT_ALARM_WEEKLY:
{ // 如果是每周响的闹钟,则直接置闹铃标记
if (alarm->wktime.tm_wday == now->tm_wday)
{
sec_alarm += alarm->wktime.tm_wday * 24 * 3600;
sec_now += now->tm_wday * 24 * 3600;
if (sec_now == sec_alarm)
wakeup = RT_TRUE;
}
}
break;
case RT_ALARM_MONTHLY:
{ // 如果是每月响的闹钟,则直接置闹铃标记
if (alarm->wktime.tm_mday == now->tm_mday)
{
if ((sec_now - sec_alarm) <= RT_ALARM_DELAY)
wakeup = RT_TRUE;
}
}
break;
case RT_ALARM_YAERLY:
{ // 如果是每年响的闹钟,则直接置闹铃标记
if ((alarm->wktime.tm_mday == now->tm_mday) && \
(alarm->wktime.tm_mon == now->tm_mon))
{
if ((sec_now - sec_alarm) <= RT_ALARM_DELAY)
wakeup = RT_TRUE;
}
}
break;
}
if ((wakeup == RT_TRUE) && (alarm->callback != RT_NULL))
{ // 如果闹铃标记置位且闹钟的回调函数被注册,则调用闹钟处理回调函数
timestamp = (time_t)0;
get_timestamp(×tamp);
alarm->callback(alarm, timestamp);
}
}
}
static void alarm_update(rt_uint32_t event)
{
struct rt_alarm *alm_prev = RT_NULL, *alm_next = RT_NULL;
struct rt_alarm *alarm;
rt_int32_t sec_now, sec_alarm, sec_tmp;
rt_int32_t sec_next = 24 * 3600, sec_prev = 0;
time_t timestamp = (time_t)0;
struct tm now;
rt_list_t *next;
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
if (!rt_list_isempty(&_container.head))
{
// 获取struct tm格式的时钟信息
get_timestamp(×tamp);
gmtime_r(×tamp, &now);
// 依次查询闹钟列表并处理闹钟任务
for (next = _container.head.next; next != &_container.head; next = next->next)
{
alarm = rt_list_entry(next, struct rt_alarm, list);
alarm_wakeup(alarm, &now);
}
// 获取当天已经经过的秒数
get_timestamp(×tamp);
gmtime_r(×tamp, &now);
sec_now = alarm_mkdaysec(&now);
// 找到离当前时间最近的两个使能的闹钟(一个是今天会执行的,一个是明天会执行的)
for (next = _container.head.next; next != &_container.head; next = next->next)
{
alarm = rt_list_entry(next, struct rt_alarm, list);
sec_alarm = alarm_mkdaysec(&alarm->wktime);
if (alarm->flag & RT_ALARM_STATE_START)
{
sec_tmp = sec_alarm - sec_now;
if (sec_tmp > 0)
{
if (sec_tmp < sec_next)
{
sec_next = sec_tmp;
alm_next = alarm;
}
}
else
{
if (sec_tmp < sec_prev)
{
sec_prev = sec_tmp;
alm_prev = alarm;
}
}
}
}
// 如果当天还有一个未使用的闹钟,则将该闹钟的时间设置到RTC模块中去
if (sec_next < 24 * 3600)
{
if (alarm_set(alm_next) == RT_EOK)
_container.current = alm_next;
}
else if (sec_prev < 0)
{
// 如果当天没有启用的闹钟了,则启用后一天的第一个闹钟
if (alarm_set(alm_prev) == RT_EOK)
_container.current = alm_prev;
}
else
{
if (_container.current != RT_NULL)
{ // 如果都找不到,则启用目前指定启用的闹钟并清空当前指定的闹钟
alarm_set(_container.current);
if (!(_container.current->flag & RT_ALARM_STATE_START))
_container.current = RT_NULL;
}
}
}
rt_mutex_release(&_container.mutex);
}
static void rt_alarmsvc_thread_init(void *param)
{
rt_uint32_t recv;
_container.current = RT_NULL; // 设置当前闹钟为控
while (1)
{
if (rt_event_recv(&_container.event, 0xFFFF,
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &recv) == RT_EOK)
{
alarm_update(recv); // 收到消息后跳转至闹钟处理函数
}
}
}
// 注册alarm线程并运行线程
int rt_alarm_system_init(void)
{
rt_thread_t tid;
rt_list_init(&_container.head); // 初始化闹钟列表
rt_event_init(&_container.event, "alarmsvc", RT_IPC_FLAG_FIFO); // 初始化用于闹钟事件处理的事件集
rt_mutex_init(&_container.mutex, "alarmsvc", RT_IPC_FLAG_PRIO); // 初始化闹钟列表操作的互斥表
tid = rt_thread_create("alarmsvc",
rt_alarmsvc_thread_init, RT_NULL,
2048, 10, 5);
if (tid != RT_NULL)
rt_thread_startup(tid);
return 0;
}
INIT_PREV_EXPORT(rt_alarm_system_init);注册闹钟入口
rt_alarm_t rt_alarm_create(rt_alarm_callback_t callback, struct rt_alarm_setup *setup)
{
struct rt_alarm *alarm;
if (setup == RT_NULL)
return (RT_NULL);
alarm = rt_malloc(sizeof(struct rt_alarm));
if (alarm == RT_NULL)
return (RT_NULL);
rt_list_init(&alarm->list);
alarm->wktime = setup->wktime;
alarm->flag = setup->flag & 0xFF00;
alarm->callback = callback;
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
rt_list_insert_after(&_container.head, &alarm->list);
rt_mutex_release(&_container.mutex);
return (alarm);
}可以发现,注册函数本质上就是往闹钟列表内添加一个新的闹钟。
闹钟删除入口
rt_err_t rt_alarm_delete(rt_alarm_t alarm)
{
rt_err_t ret = RT_EOK;
if (alarm == RT_NULL)
return -RT_ERROR;
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
// 设置关闭闹钟标记
alarm->flag &= ~RT_ALARM_STATE_START;
if (_container.current == alarm)
{ // 如果要删除的闹钟是当前在使用的闹钟,则关闭闹钟
ret = alarm_set(alarm);
_container.current = RT_NULL;
alarm_update(0);
}
// 移除闹钟并释放资源
rt_list_remove(&alarm->list);
rt_free(alarm);
rt_mutex_release(&_container.mutex);
return (ret);
}闹钟停用入口
rt_err_t rt_alarm_stop(rt_alarm_t alarm)
{
rt_err_t ret = RT_EOK;
if (alarm == RT_NULL)
return (RT_ERROR);
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
if (!(alarm->flag & RT_ALARM_STATE_START))
goto _exit;
/* stop alarm */
alarm->flag &= ~RT_ALARM_STATE_START;
if (_container.current == alarm)
{
ret = alarm_set(alarm);
_container.current = RT_NULL;
}
if (ret == RT_EOK)
alarm_update(0);
_exit:
rt_mutex_release(&_container.mutex);
return (ret);
}停用入口就很简单了,把标记置为非启用,若当前在正在使用,则关闭该闹钟,另外需要重新检查一下闹钟是能列表,以防异常。
闹钟启用入口
static int days_of_year_month(int tm_year, int tm_mon)
{
int ret, year;
year = tm_year + 1900;
if (tm_mon == 1)
{ // 如果是2月,若是闰年,则返回29天,若是普通年,则返回28天
ret = 28 + ((!(year % 4) && (year % 100)) || !(year % 400));
}
else if (((tm_mon <= 6) && (tm_mon % 2 == 0)) || ((tm_mon > 6) && (tm_mon % 2 == 1)))
{ // 大月返回31天
ret = 31;
}
else
{ // 其他月份返回30天
ret = 30;
}
return (ret);
}
// 判断日期是否有效
static rt_bool_t is_valid_date(struct tm *date)
{
// struct tm时间要求时间在1900年至2037年之间,因此不在这个范围内的时间都是无效的
if ((date->tm_year < 0) || (date->tm_year > RT_RTC_YEARS_MAX))
{
return (RT_FALSE);
}
// 月份参数需要保证在12个月以内
if ((date->tm_mon < 0) || (date->tm_mon > 11))
{
return (RT_FALSE);
}
// 天的参数需要保证符合要求每月天数要求
if ((date->tm_mday < 1) || \
(date->tm_mday > days_of_year_month(date->tm_year, date->tm_mon)))
{
return (RT_FALSE);
}
return (RT_TRUE);
}
// 设置闹钟参数
static rt_err_t alarm_setup(rt_alarm_t alarm, struct tm *wktime)
{
rt_err_t ret = -RT_ERROR;
time_t timestamp = (time_t)0;
struct tm *setup, now;
setup = &alarm->wktime;
*setup = *wktime; // 这个操作没看明白,结构体能够直接赋值?
// 获取struct tm格式的当前时间
get_timestamp(×tamp);
gmtime_r(×tamp, &now);
// 如果设置的时间无效,则使用系统获取的时间
if ((setup->tm_sec > 59) || (setup->tm_sec < 0))
setup->tm_sec = now.tm_sec;
if ((setup->tm_min > 59) || (setup->tm_min < 0))
setup->tm_min = now.tm_min;
if ((setup->tm_hour > 23) || (setup->tm_hour < 0))
setup->tm_hour = now.tm_hour;
switch (alarm->flag & 0xFF00)
{
case RT_ALARM_SECOND:
{ // 如果是按秒响的闹钟,则直接设置下次闹铃时间为当前时间的下一秒
alarm->wktime.tm_hour = now.tm_hour;
alarm->wktime.tm_min = now.tm_min;
alarm->wktime.tm_sec = now.tm_sec + 1;
if (alarm->wktime.tm_sec > 59)
{
alarm->wktime.tm_sec = 0;
alarm->wktime.tm_min = alarm->wktime.tm_min + 1;
if (alarm->wktime.tm_min > 59)
{
alarm->wktime.tm_min = 0;
alarm->wktime.tm_hour = alarm->wktime.tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
}
}
}
break;
case RT_ALARM_MINUTE:
{ // 如果是按照分响的闹钟,则设置下次闹铃时间为下一分钟
alarm->wktime.tm_hour = now.tm_hour;
alarm->wktime.tm_min = now.tm_min + 1;
if (alarm->wktime.tm_min > 59)
{
alarm->wktime.tm_min = 0;
alarm->wktime.tm_hour = alarm->wktime.tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
}
}
break;
case RT_ALARM_HOUR:
{ // 如果是按照小时响的闹钟,则设置下一次响铃时间为下一小时
alarm->wktime.tm_hour = now.tm_hour + 1;
if (alarm->wktime.tm_hour > 23)
{
alarm->wktime.tm_hour = 0;
}
}
break;
case RT_ALARM_DAILY:
{ // 如果是按天响的闹钟,则不需要做任何处理
/* do nothing but needed */
}
break;
case RT_ALARM_ONESHOT:
{ // 如果是单次响的闹钟,则仅仅需要确保设置下的闹钟有效即可
if (setup->tm_year == RT_ALARM_TM_NOW)
setup->tm_year = now.tm_year;
if (setup->tm_mon == RT_ALARM_TM_NOW)
setup->tm_mon = now.tm_mon;
if (setup->tm_mday == RT_ALARM_TM_NOW)
setup->tm_mday = now.tm_mday;
if (!is_valid_date(setup))
goto _exit;
}
break;
case RT_ALARM_WEEKLY:
{ // 如果是按周响的闹钟,则仅仅需要保证闹钟周参数符合一周七天即可
if ((setup->tm_wday < 0) || (setup->tm_wday > 6))
setup->tm_wday = now.tm_wday;
}
break;
case RT_ALARM_MONTHLY:
{ // 如果是按月响的闹钟,则仅仅需要保证闹钟的月份参数符合标准即可
// 但存在一个问题,每个月的天数不是不一样嘛?为何这里没做区别判断?
if ((setup->tm_mday < 1) || (setup->tm_mday > 31))
setup->tm_mday = now.tm_mday;
}
break;
case RT_ALARM_YAERLY:
{ // 如果是按年的闹钟,则需要保证所有参数都是OK的
if ((setup->tm_mon < 0) || (setup->tm_mon > 11))
setup->tm_mon = now.tm_mon;
if (setup->tm_mon == 1)
{
// 如果是2月,则需要保证天数在29日内
if ((setup->tm_mday < 1) || (setup->tm_mday > 29))
setup->tm_mday = now.tm_mday;
}
else if (((setup->tm_mon <= 6) && (setup->tm_mon % 2 == 0)) || \
((setup->tm_mon > 6) && (setup->tm_mon % 2 == 1)))
{
// 如果是 1,3,5 ,7,8,10,12月,则天数需要保证在31天内
if ((setup->tm_mday < 1) || (setup->tm_mday > 31))
setup->tm_mday = now.tm_mday;
}
else
{
// 非前面两种情况,需保证每月天数在30天内
if ((setup->tm_mday < 1) || (setup->tm_mday > 30))
setup->tm_mday = now.tm_mday;
}
}
break;
default:
{
goto _exit;
}
}
if ((setup->tm_hour == 23) && (setup->tm_min == 59) && (setup->tm_sec == 59))
{
// 如果闹钟时间是在23:59:59,则设置闹铃时间在58s,估计是为了防止59分处理不过来才这么操作
setup->tm_sec = 60 - RT_ALARM_DELAY;
}
// 设置闹钟已初始化标记
alarm->flag |= RT_ALARM_STATE_INITED;
ret = RT_EOK;
_exit:
return (ret);
}
rt_err_t rt_alarm_start(rt_alarm_t alarm)
{
rt_int32_t sec_now, sec_old, sec_new;
rt_err_t ret = RT_EOK;
time_t timestamp = (time_t)0;
struct tm now;
// 检查输入信息是否符合标准
if (alarm == RT_NULL)
return (RT_ERROR);
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
if (!(alarm->flag & RT_ALARM_STATE_START))
{ // 只有当前闹钟是停用状态才启用
// 更新alarm参数
if (alarm_setup(alarm, &alarm->wktime) != RT_EOK)
{
ret = -RT_ERROR;
goto _exit;
}
// 获取系统时间并转换成struct tm格式
get_timestamp(×tamp);
gmtime_r(×tamp, &now);
// 启用闹钟
alarm->flag |= RT_ALARM_STATE_START;
if (_container.current == RT_NULL)
{ // 如果当前没有使用的闹钟,则直接启用闹钟
ret = alarm_set(alarm);
}
else
{ // 如果当前有启用的闹钟,则判断当前闹钟是否是下一个需要启用的闹钟,如果是,则启用闹钟,如果不是,则启用原先的闹钟
sec_now = alarm_mkdaysec(&now);
sec_old = alarm_mkdaysec(&_container.current->wktime);
sec_new = alarm_mkdaysec(&alarm->wktime);
if ((sec_new < sec_old) && (sec_new > sec_now))
{ // 如果新的闹钟比目前启用的闹钟更早响铃,则启用新闹钟
ret = alarm_set(alarm);
}
else if ((sec_new > sec_now) && (sec_old < sec_now))
{ // 如果新的闹钟比当前时间大,且老的闹钟已经响铃过,则启用新闹钟
ret = alarm_set(alarm);
}
else if ((sec_new < sec_old) && (sec_old < sec_now))
{ // 如果新的闹钟比老的闹钟时间小,且老的闹钟已经响过了,则启用新的闹钟
ret = alarm_set(alarm);
}
else
{
ret = RT_EOK;
goto _exit;
}
}
if (ret == RT_EOK)
{ // 将目前启用的闹钟置为当前使能的闹钟
_container.current = alarm;
}
}
_exit:
rt_mutex_release(&_container.mutex);
return (ret);
}闹钟控制入口
rt_err_t rt_alarm_control(rt_alarm_t alarm, int cmd, void *arg)
{
rt_err_t ret = -RT_ERROR;
RT_ASSERT(alarm != RT_NULL);
rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
switch (cmd)
{
case RT_ALARM_CTRL_MODIFY:
{
struct rt_alarm_setup *setup;
RT_ASSERT(arg != RT_NULL);
setup = arg;
rt_alarm_stop(alarm);
alarm->flag = setup->flag & 0xFF00;
alarm->wktime = setup->wktime;
ret = alarm_setup(alarm, &alarm->wktime);
}
break;
}
rt_mutex_release(&_container.mutex);
return (ret);
}闹钟控制入口,其实就是更新闹钟的参数信息。
总结
通过对源码的分析,我们大致了解了alarm模块的使用逻辑,首先,启用alarm模块,系统会默认起一个用于处理alarm事件的任务。其次,可以发现,alarm模块其实依赖于RTC模块提供的闹铃中断任务。最后,系统通过调用闹钟对外提供的接口来实现闹钟的一系列操作。
而个人认为目前版本的闹钟代码实现思路有些混乱,典型的点为:
1. _container.current为何需要单独拉出来,而且从代码上看,这单独拉出来的操作并没有带来什么好处,反而造成了代码维护逻辑混乱
2. alarm_setup中的每月有多少天的处理,貌似没那么完美,设置不考虑是否是闰年,反而后面校验是判断是否是闰年
我要赚赏金
