自学内容网 自学内容网

rtthread学习笔记系列-- 31 RTC

https://github.com/wdfk-prog/RT-Thread-Study

31 RTC

31.1 RTC驱动

1.rt_hw_rtc_register注册RTC设备

  1. 实现RTC设备的操作函数
  2. 提供控制命令

        case RT_DEVICE_CTRL_RTC_GET_TIME:

            ret = TRY_DO_RTC_FUNC(rtc_device, get_secs, args);

            break;

        case RT_DEVICE_CTRL_RTC_SET_TIME:

            ret = TRY_DO_RTC_FUNC(rtc_device, set_secs, args);

            break;

        case RT_DEVICE_CTRL_RTC_GET_TIMEVAL:

            ret = TRY_DO_RTC_FUNC(rtc_device, get_timeval, args);

            break;

        case RT_DEVICE_CTRL_RTC_SET_TIMEVAL:

            ret = TRY_DO_RTC_FUNC(rtc_device, set_timeval, args);

            break;

        case RT_DEVICE_CTRL_RTC_GET_ALARM:

            ret = TRY_DO_RTC_FUNC(rtc_device, get_alarm, args);

            break;

        case RT_DEVICE_CTRL_RTC_SET_ALARM:

            ret = TRY_DO_RTC_FUNC(rtc_device, set_alarm, args);

            break;

31.2 软件RTC

  1. 使用 rt_tick_get获取时间

31.3 alarm模块

alarm 闹钟功能是基于 RTC 设备实现的,根据用户设定的闹钟时间,当时间到时触发 alarm 中断,执行闹钟事件,在硬件上 RTC 提供的 Alarm 是有限的,RT-Thread 将 Alarm 在软件层次上封装成了一个组件,原理上可以实现无限个闹钟,但每个闹钟只有最后一次设定有效

各项操作加锁保护

31.3.1 初始化

  1. 初始化链表,互斥量,事件
  2. 创建alarm线程

    rt_list_init(&container->head);

    rt_mutex_init(&container->mutex, "alarm", RT_IPC_FLAG_FIFO);

    rt_event_init(&container->event, "alarm", RT_IPC_FLAG_FIFO);


```c

    rt_list_init(&container->head);

    rt_mutex_init(&container->mutex, "alarm", RT_IPC_FLAG_FIFO);

    rt_event_init(&container->event, "alarm", RT_IPC_FLAG_FIFO);


```c

struct rt_alarm_container

{

    rt_list_t head;

    struct rt_mutex mutex;

    struct rt_event event;

    struct rt_alarm *current;

};

3.rt_alarmsvc_thread_init线程中阻塞接收事件,唤醒执行 alarm_update

  1. 使用 rt_alarm_update发生事件

31.3.2 create

  1. 创建alarm对象,并初始化
  2. 初始化alarm的链表
  3. 注册唤醒方式及时间
  4. 插入到container链表

struct rt_alarm_setup

{

    rt_uint32_t flag;                /* alarm flag */

    struct tm wktime;                /* when will the alarm wake up user */

};

31.3.3 start

1.alarm_setup 设置唤醒时间

  • 获取当前时间,转成格林威治时间

2.alarm_set 设置alarm对象

  • container没有当前alarm对象,则设置当前alarm对象
  • container有当前alarm对象,则根据当前时间与当前alarm对象时间和设置的alarm对象时间执行比较,设置container当前alarm对象.

    sec_now = alarm_mkdaysec(&now);

    sec_old = alarm_mkdaysec(&_container.current->wktime);

    sec_new = alarm_mkdaysec(&alarm->wktime);

    //sec_now ---- sec_new ---- sec_old

    if ((sec_new < sec_old) && (sec_new > sec_now))

    {

        ret = alarm_set(alarm);

    }

    //sec_old ---- sec_now ---- sec_new

    elseif ((sec_new > sec_now) && (sec_old < sec_now))

    {

        ret = alarm_set(alarm);

    }

    //sec_new ---- sec_old ---- sec_now

    elseif ((sec_new < sec_old) && (sec_old < sec_now))

    {

        ret = alarm_set(alarm);

    }

    /* sec_old ---- sec_new ---- sec_now

     * sec_now ---- sec_old ---- sec_new

     * sec_new ---- sec_now ---- sec_old

     */

    else

    {

        ret = RT_EOK;

        goto _exit;

    }

31.3.4 alarm_wakeup

  1. 根据模式执行判断,进而唤醒操作

31.3.5 alarm_update

  1. 获取当前时间
  2. 检查container链表是否为空
  • 不为空,循环遍历链表,执行 alarm_wakeup
  1. 再次检查链表,计算下一个alarm的时间并设置

31.4 STM32 HAL RTC

31.4.1 初始化 UTIL_TIMER_Init

  1. 链表初始化

/**

  * @brief Timers list head pointer

  *

  */

staticUTIL_TIMER_Object_t *TimerListHead = NULL;

  1. 调用 InitTimer初始化定时器

31.4.2 Stop

  1. 停止的是当前定时器对象

    if( TimerListHead == TimerObject ) /* Stop the Head */

    {

        TimerListHead->IsPending = 0;

        //链表具体下一个对象

        if( TimerListHead->Next != NULL )

        {

            //链表头指向下一个对象,并设置超时时间

            TimerListHead = TimerListHead->Next;

            TimerSetTimeout( TimerListHead );

        }

        else

        {

            //停止定时器

            UTIL_TimerDriver.StopTimerEvt( );

            TimerListHead = NULL;

        }

    }

  1. 停止的是非当前定时器对象
  • 先找到当前定时器对象
  • 从定时器链表中删除当前定时器对象

    while( cur != NULL )

    {

        if( cur == TimerObject )

        {

            if( cur->Next != NULL )

            {

                cur = cur->Next;

                prev->Next = cur;

            }

            else

            {

                cur = NULL;

                prev->Next = cur;

            }

            break;

        }

        else

        {

            prev = cur;

            cur = cur->Next;

        }

    }

31.4.3 Start

  1. 定时器链表为空,则设置当前定时器对象为链表头

    if( TimerListHead == NULL )

    {

      UTIL_TimerDriver.SetTimerContext();

      TimerInsertNewHeadTimer( TimerObject ); /* insert a timeout at now+obj->Timestamp */

    }

  1. 计算唤醒时间并插入定时器链表中

    elapsedTime = UTIL_TimerDriver.GetTimerElapsedTime( );

    TimerObject->Timestamp += elapsedTime;

    //时间小于链表头时间,则插入链表头

    if( TimerObject->Timestamp < TimerListHead->Timestamp )

    {

        TimerInsertNewHeadTimer( TimerObject);

    }

    //插入链表合适的时间节点

    else

    {

        TimerInsertTimer( TimerObject);

    }

  1. TimerInsertNewHeadTimer
  • 设置当前定时器对象为链表头
  • 设置超时时间
  1. TimerInsertTimer
  2. 判断插入定时器与链表中节点的时间大小
  3. 找到合适的位置插入
  4. 没找到插入尾部

31.4.4 中断回调


voidHAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)

{

    UTIL_TIMER_IRQ_Handler();

}

  1. 计算上一次唤醒时间与当前时间的差值
  2. 遍历定时器链表,找到超时的定时器对象
  • 超时设置timestamp为0
  • 没超时,设置timestamp为超时时间减去差值
  1. 执行超时回调,并更新链表

    /* Execute expired timer and update the list */

    while ((TimerListHead != NULL) && ((TimerListHead->Timestamp == 0U) 

    // 防止中断过程中RTC时间增加导致错误未执行

    || (TimerListHead->Timestamp < UTIL_TimerDriver.GetTimerElapsedTime(  ))))

    {

        cur = TimerListHead;

        TimerListHead = TimerListHead->Next;

        cur->IsPending = 0;

        cur->IsRunning = 0;

        cur->Callback(cur->argument);

        if(( cur->Mode == UTIL_TIMER_PERIODIC) && (cur->IsReloadStopped == 0U))

        {

            (void)UTIL_TIMER_Start(cur);

        }

    }

  1. 启动下一个 TimerListHead(如果它存在并且不是挂起的)

31.4.5 TIMER_IF_Init


UTIL_TIMER_Status_tTIMER_IF_Init(void)

{

    __HAL_RCC_RTC_ENABLE( );  // 使能RTC时钟

    HAL_RTC_Init( &RtcHandle );  // 初始化RTC

    if(!(g_rcc_csr & 0xf0000000)) {  // 如果备份寄存器的复位标志位未设置

        HAL_RTC_SetDate( &RtcHandle, &date, RTC_FORMAT_BIN );  // 设置RTC日期

        HAL_RTC_SetTime( &RtcHandle, &time, RTC_FORMAT_BIN );  // 设置RTC时间

    }

    HAL_RTCEx_EnableBypassShadow( &RtcHandle );  // 使能直接读取日历寄存器(不通过影子寄存器)

    HAL_NVIC_SetPriority( RTC_Alarm_IRQn, 1, 0 );  // 设置RTC闹钟中断的优先级

    HAL_NVIC_EnableIRQ( RTC_Alarm_IRQn );  // 使能RTC闹钟中断


    // Init alarm.

    HAL_RTC_DeactivateAlarm( &RtcHandle, RTC_ALARM_A );  // 关闭RTC闹钟A


    TIMER_IF_SetTimerContext( );  // 设置定时器上下文

}


31.4.6 TIMER_IF_StartTimer

  1. 停止定时器
  2. 计算 RTC_AlarmStructure
  3. 设置闹钟 HAL_RTC_SetAlarm_IT( &RtcHandle, &RtcAlarm, RTC_FORMAT_BIN );

31.4.7 TIMER_IF_StopTimer


  // Disable the Alarm A interrupt

  HAL_RTC_DeactivateAlarm( &RtcHandle, RTC_ALARM_A );


  // Clear RTC Alarm Flag

  __HAL_RTC_ALARM_CLEAR_FLAG( &RtcHandle, RTC_FLAG_ALRAF );

31.4.8 TIMER_IF_SetTimerContext

  • 获取日历值并写入 RtcTimerContext

HAL_RTC_AlarmIRQHandler


/**

  * @brief  Handle Alarm interrupt request.

  * @param  hrtc RTC handle

  * @retval None

  */

voidHAL_RTC_AlarmIRQHandler(RTC_HandleTypeDef *hrtc)

{

  uint32_t tmp = READ_REG(RTC->MISR) & READ_REG(hrtc->IsEnabled.RtcFeatures);


  if ((tmp & RTC_MISR_ALRAMF) != 0U)

  {

    /* Clear the AlarmA interrupt pending bit */

    WRITE_REG(RTC->SCR, RTC_SCR_CALRAF);

    HAL_RTC_AlarmAEventCallback(hrtc);

  }


  if ((tmp & RTC_MISR_ALRBMF) != 0U)

  {

    /* Clear the AlarmB interrupt pending bit */

    WRITE_REG(RTC->SCR, RTC_SCR_CALRBF);

    HAL_RTCEx_AlarmBEventCallback(hrtc);

  }


  /* Change RTC state */

  hrtc->State = HAL_RTC_STATE_READY;

}


原文地址:https://blog.csdn.net/qq_39665253/article/details/145152419

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!