确切的说,闹钟模块应该属于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中的每月有多少天的处理,貌似没那么完美,设置不考虑是否是闰年,反而后面校验是判断是否是闰年