恋爱是什么| 日字旁和什么有关| 什么是姜黄| 哈尼什么意思| 漂亮的近义词是什么| 充电玩手机有什么危害| 家里有壁虎是什么原因| 什么是自慰| 手掌心发红是什么原因| 左肾窦分离是什么意思| 琉璃和玻璃有什么区别| 肌酐测定是查什么| 日本全称是什么| 身份证号码的数字代表什么意义| 什么是肝脏纤维化| 屁股生疮是什么原因| 1952年属什么生肖| 口臭是什么原因导致的呢| 店招是什么意思| 为什么女人比男人长寿| 泪目是什么意思| 日出东方下一句是什么| 月和什么有关| 喉咙肿痛吃什么药| 满日是什么意思| 太监和宫女对食是什么意思| 为什么医生不建议献血小板| 狐臭是什么引起的| 狗有眼屎是什么原因| 神经鞘瘤挂什么科| 南方有什么水果| 人老珠黄是什么动物| 一九八四年属什么生肖| 存款准备金率是什么意思| 乌江鱼是什么鱼| 功夫是什么意思| 丰富的近义词和反义词是什么| 吃什么能提升免疫力| 红色尿液是什么原因| 正事是什么意思| 感冒咳嗽吃什么药| 为什么月经每个月提前| 为什么会得胆结石| 血糖高什么原因| 广字五行属什么| 鱼油什么人不能吃| 沙拉酱可以做什么美食| 鸦片鱼又叫什么鱼| nt 是什么检查| 世界上最大的哺乳动物是什么| 空虚是什么意思| 氯丙嗪是什么药| 什么是溶血| epa和dha是什么| 李世民和武则天什么关系| 喉咙痛吃什么药好得最快| 什么是淀粉| 心肌缺血吃什么药效果最好| 梦见上班迟到什么意思| 什么的哲理| 农历十二月是什么月| la是什么牌子| 11月18日什么星座| b超涂的液体是什么| 制动是什么意思| 胆结石吃什么药可以化掉结石| 香港脚是什么症状图片| 什么降血脂效果最好的| 九一年属什么生肖| 孕妇吐得厉害有什么办法解决| 重返20岁韩国版叫什么| 门可罗雀是什么意思| 右侧胸膜增厚是什么意思| 报仇是什么意思| 勾践属什么生肖| 雷锋日是什么时候| 胆囊切除有什么后遗症| 脊髓炎是什么病| 单身领养孩子需要什么条件| 恶寒什么意思| 错付是什么意思| 牙龈溃疡吃什么药| 什么笔记本电脑好| paris是什么品牌| 葡萄糖为什么叫葡萄糖| 县纪委副书记什么级别| 反驳是什么意思| 长期口腔溃疡挂什么科| 时至今日是什么意思| 痛经什么感觉| scofield是什么品牌| 什么床不能睡觉| 鼓包是什么意思| 盆腔积液吃什么药| 九月二十三是什么星座| 甲硝唑是什么药| 野生刺猬吃什么食物| 布洛芬吃多了有什么副作用| 公鸡的尾巴像什么| 合作医疗是什么| 豌豆炒什么好吃| 甘油三酯高什么原因| 长期口腔溃疡挂什么科| 地区和市有什么区别| 活塞是什么意思| 性生活过多有什么危害| 午时右眼跳是什么预兆| 袢是什么意思| 肠胃炎吃什么| 倾向是什么意思| 和平是什么意思| 肉字是什么结构| bitch是什么意思| 举贤不避亲什么意思| 身上起红斑是什么原因| 零反式脂肪是什么意思| 膝盖酸软是什么原因| 不是省油的灯是什么意思| 杰五行属性是什么| 嘴唇上火起泡是什么原因| 坚壁清野什么意思| 右脚麻是什么病的前兆| 翘首以盼是什么意思| 豺狼虎豹为什么豺第一| 奥美拉唑和雷贝拉唑有什么区别| 乙肝五项15阳性是什么意思| EE什么意思| 东施效颦什么意思| 高原反应的原因是什么| 佐助是什么意思| 气血不足什么症状| 流鼻血是什么病的前兆| 徐州有什么好玩的| 优生优育是什么意思| 腺体鳞化是什么意思| 麻豆是什么| 突然全身抽搐是什么病| 南京菜属于什么菜系| 跑完步想吐是什么原因| 阴囊湿疹用什么药膏效果最好| 6.8是什么星座| 15是什么意思| 2003年出生属什么| kdj是什么意思| 争强好胜什么意思| 吃什么可以补胶原蛋白| 八字中的印是什么意思| 产妇吃什么好| 坐月子哭了会有什么后遗症| 四月二十六是什么星座| 健脾益气是什么意思| 非文念什么| 发痧是什么原因造成的| 狗懒子是什么意思| lena是什么意思| 胆囊结石不能吃什么| 血脂高会导致什么后果| 绿豆芽炒什么好吃| 3.1是什么星座| 尿蛋白弱阳性是什么意思| 什么器晚成| 5月31号什么星座| tao是什么意思| 梦见发大水是什么意思| 山竹有什么功效和作用| 沏茶是什么意思| 手足口病疫苗什么时候打| 为什么乳头内陷| 缺金的人戴什么最旺| 缅铃是什么| 为什么总是被蚊子咬| 牙周炎是什么| 包皮过长挂什么科| 脚气是什么样的图片| 肺大泡是什么| suv是什么意思| 藏青色配什么颜色好看| 什么情况下不能献血| 佟丽娅为什么离婚| 舌苔发白是什么问题| 三马念什么| hp-是什么意思| ttl什么意思| 前列腺b超能检查出什么| 四季如春是什么生肖| 身上发冷是什么原因| 24k是什么意思| 人类什么时候灭绝| 发票抬头是什么| 猴头菇和什么煲汤最好| 猴子属于什么类动物| 93年属相是什么| 心梗什么症状| 补办户口本需要什么材料| 减肥早餐适合吃什么| 尿里有泡沫是什么病| 过敏性皮肤用什么护肤品比较好| 蚕豆病是什么病有什么症状| 尿里有泡沫是什么原因| 2024年属什么年| 做生化是检查什么的| 吃完螃蟹不能吃什么| 4.19是什么星座| 晚上尿多什么原因| 眼袋浮肿什么原因| 什么是心悸| 多吃什么对肾好| 豆浆喝多了有什么副作用| shia是什么意思| 什么是气血| 举人相当于什么官| 经常喝饮料有什么危害| 吃小米粥有什么好处和坏处| 急性肠胃炎吃什么水果| 子宫内膜薄是什么原因造成的| 嵌合体是什么意思| 刚怀孕初期吃什么好呢| 乌龟为什么会叫| 家用制氧机什么牌子好| 生物技术专业学什么| 胃疼发烧是什么原因| 勇往直前是什么意思| 食管有烧灼感什么原因| 全员加速中什么时候播| pgi2在医学是什么意思| 窦性心律不齐有什么危害| 开山鼻祖是什么意思| 左眼跳代表什么| 食物不耐受是什么意思| 范字五行属什么| 身体容易青紫是什么原因| 篱笆是什么意思| 02属什么| 总警监是什么级别| 什么情什么意| 头孢属于什么类药物| 痛风吃什么药治疗最有效| 血沉高意味着什么意思| 游离是什么意思| 生理曲度存在是什么意思| dostinex是什么药| 斑鸠吃什么食物| 孕吐严重是什么原因| 洁白丸治什么类型胃病| 乳头疼吃什么药| 肺不好吃什么| 孜孜不倦是什么意思| 乙肝e抗体阳性什么意思| 梦见老公回来了是什么征兆| 电压不稳定是什么原因| 口腔溃疡缺什么维生素| 五色土有什么风水作用| 打嗝吃什么中成药| 什么是疤痕增生| 害怕是什么意思| 12月8号是什么星座| 周围型肺ca是什么意思| 什么水果糖分最高| g点是什么| 七月七是什么星座| 母仪天下是什么意思| 感冒咳嗽可以吃什么水果| 膝关节弹响是什么原因| 脓毒症是什么病| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? rtthread闹钟模块代码分析

共4条 1/1 1 跳转至

rtthread闹钟模块代码分析

高工
2025-08-04 22:13:45     打赏
百度 价格便宜,性价比高通过以上北汽幻速S6与同级别多款畅销SUV的比较不难,这款新出的小鲜肉,在消费者最关注的各个方面都有着不错的表现,车身尺寸够大,看起来也不错,动力主流、配置丰富,更关键的是其背后有强大的北汽集团支撑,品质、服务也有保障。

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






关键词: rtthread     闹钟     模块     代码     分析     alar    

专家
2025-08-04 19:26:33     打赏
2楼

感谢分享


专家
2025-08-04 19:29:32     打赏
3楼

感谢分享


院士
2025-08-04 09:08:40     打赏
4楼

rt-thread 现在是开发者在提交代码吧?


共4条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
甲状腺有什么症状 医保什么时候到账 现在创业做什么好 尿酸高是什么原因引起的 1954年出生属什么
一月7日是什么星座 梦见自己的哥哥死了是什么意思 闰6月是什么意思 掌眼什么意思 怀孕什么时候显怀
吃海鲜不能吃什么 一什么十什么的成语 青什么黄什么 来月经小腹痛是什么原因 额头上长斑是什么原因造成的
白痰吃什么药 joy是什么意思 Urea医学上是什么意思 肌酐激酶高是什么原因 生长激素是什么
红玫瑰花语是什么意思hcv8jop8ns3r.cn 淋巴结什么原因引起的cl108k.com 翻车鱼为什么叫翻车鱼hcv8jop5ns5r.cn 大脑供血不足是什么原因引起的hcv8jop6ns5r.cn hpv感染用什么药hcv8jop1ns0r.cn
一个兹一个子念什么hcv8jop1ns8r.cn 什么是黄酒weuuu.com 哔哩哔哩是干什么的hcv8jop9ns6r.cn 左肋骨下方隐隐疼痛是什么原因hcv7jop9ns3r.cn 吃什么容易怀女儿1949doufunao.com
二月初九是什么星座hcv9jop7ns5r.cn 嘴唇发干是什么原因hcv7jop5ns2r.cn 口干舌燥吃什么中成药hcv8jop7ns6r.cn 晕血是什么症状hcv9jop3ns0r.cn Cr是什么意思医学hcv9jop5ns6r.cn
石斛主治什么hcv8jop6ns5r.cn 甲功三项是什么bjcbxg.com 黑色碳素笔是什么笔hcv9jop3ns4r.cn 40不惑什么意思hcv9jop4ns5r.cn 穿刺是什么手术hcv9jop4ns6r.cn
百度