RTC

Материал из MIK32 микроконтроллер

Модуль реализует функцию часов реального времени. Основные возможности:

- 24-часовой режим работы;

- функция календаря с корректировкой для високосного года;

- программируемый будильник с возможностью генерации прерываний;

- двоично-десятичное кодирование данных.

Пример использования RTC

В данном примере производится инициализация модуля RTC, настройка времени и даты. Затем настраивается будильник. Программа выводит время и дату по UART. При срабатывании будильника выводится сообщение "Alarm!", после чего продолжается отсчет времени.

Инициализация

Инициализация модуля RTC проводится с помощью функции rtc_init. С помощью данной функции включается тактирование необходимых блоков и выбирается внешний осцилятор OSC32K как источник тактирования RTC.

Функция rtc_init

void rtc_init()
{
    //Включаем тактирование необходимых блоков и модуля выбора режима GPIO 
    PM->CLK_APB_P_SET |= PM_CLOCK_GPIO_0_M | PM_CLOCK_WDT_M;
    PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M | PM_CLOCK_RTC_M;
    for (volatile int i = 0; i < 10; i++);

    /*
    * CLOCKS_BU - Регистр управления тактированием батарейного домена
    * 
    * OCS32K_en - Включение/отключение внешнего осцилятора на 32 KГц
    * RC32K_en - Включение/отключение LSI32К
    * Adj_RC32К - Поправочные коэффициенты LSI32К
    * RTC_Clk_Mux - Выбор источника тактирования часов реального времени:
    *               0 – внутренний LSI32К;
    *               1 – внешний осциллятор OSC32K
    * OSC32K_sm - Режим повышенного потребления, активный уровень “0” для OSC32K
    */
    WU->CLOCKS_BU = WU_CLOCKS_BU_RTC_CLK_MUX_OSC32K_M;
    for (volatile int i = 0; i < 100; i++);
    WU->CLOCKS_BU = (0<<WU_CLOCKS_BU_OSC32K_PD_S) | WU_CLOCKS_BU_RTC_CLK_MUX_OSC32K_M;
    xprintf("Запуск с внешним осцилятором OSC32K\n");

    // Сброс RTC
    WU->RTC_CONRTOL = WU_RTC_RESET_CLEAR_M;
}

Настройка времени и даты

Время и дата настраиваются в регистрах RRTC_TIME и RRTC_DATE соответственно. Перед изменением этих регистров необходимо сбросить бит EN в регистре RRTC_CTRL. После записи в любой регистр и пока бит FLAG в регистре RRTC_CTRL читается равным «1», запрещено выполнять любую новую запись.

Время устанавливается функцией rtc_set_time

void rtc_set_time(uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second);

Функция принимает следующие аргументы:

- uint8_t dow - день недели: 1 - Пн; 2 - Вт; 3 - Ср; 4 - Чт; 5 - Пт; 6 - Сб; 7 - Вс

- uint8_t hour - часы

- uint8_t minute - минуты

- uint8_t second - секунды

Дата устанавливается функцией rtc_set_date

void rtc_set_date( uint8_t century, uint8_t day, uint8_t month, uint8_t year);

Функция принимает следующие аргументы:

- uint8_t century - век

- uint8_t day - число

- uint8_t month - месяц (Ноябрь - 11)

- uint8_t year - год (2022 год - 22)

Пример настройки времени/даты

uint8_t dow = 2;
uint8_t hour = 20;
uint8_t minute = 30;
uint8_t second = 00;

uint8_t century = 21;
uint8_t day = 19;
uint8_t month = 07;
uint8_t year = 22;
rtc_set_time(dow, hour, minute, second);
rtc_set_date(century, day, month, year);

В данном примере время и дата устанавливается так: 21 век, 19.07.22, вторник, 20:30:00.0.

Будильник

Модуль поддерживает функцию будильника по совпадению времени/даты. В качестве источника сравнения может быть выбрано одно или несколько полей. В случае выбора нескольких полей, совпадением считается равенство всех выбранных полей.

Список полей, доступных для сравнения, представлен ниже:

- секунды;

- минуты;

- часы;

- дни;

- день недели;

- месяцы;

- года;

- века.


Будильник имеет два регистра:

- RRTC_TALRM – регистр хранит время, при совпадении которого со значением регистра RRTC_TIME, будет сгенерировано соответствующее прерывание;

- RRTC_DALRM – регистр хранит дату, при совпадении которой со значением регистра RRTC_DATE, будет сгенерировано соответствующее прерывание.


Структура регистра RRTC_TALRM полностью идентична регистру RRTC_TIME за исключением битов управления:

- RTC_TALRM_CS_M - разрешает сравнения секунд, когда установлен;

- RTC_TALRM_CM_M - разрешает сравнения минут, когда установлен;

- RTC_TALRM_CH_M - разрешает сравнения часов, когда установлен;

- RTC_TALRM_CDOW_M - разрешает сравнения дней недели, когда установлен.


Структура регистра RRTC_DALRM полностью идентична регистру RRTC_DATE за исключением битов управления:

- RTC_DALRM_CD_M - разрешает сравнения дней, когда установлен;

- RTC_DALRM_CM_M - разрешает сравнения месяцев, когда установлен;

- RTC_DALRM_CY_M - разрешает сравнения годов, когда установлен;

- RTC_DALRM_CC_M - разрешает сравнения веков, когда установлен.


Время будильника устанавливается функцией rtc_alarm_set_time

void rtc_alarm_set_time(uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second, uint32_t alarm_mask);

Функция принимает следующие аргументы:

- uint8_t dow - день недели: 1 - Пн; 2 - Вт; 3 - Ср; 4 - Чт; 5 - Пт; 6 - Сб; 7 - Вс

- uint8_t hour - часы

- uint8_t minute - минуты

- uint8_t second - секунды

- uint32_t alarm_mask - маска сравнения. CDOW - день недели; CH - часы; CM - минуты; CS - секунды

Дата устанавливается функцией rtc_set_date

void rtc_alarm_set_date( uint8_t century, uint8_t day, uint8_t month, uint8_t year, uint32_t alarm_mask);

Функция принимает следующие аргументы:

- uint8_t century - век

- uint8_t day - число

- uint8_t month - месяц (Ноябрь - 11)

- uint8_t year - год (2022 год - 22)

- uint32_t alarm_mask - маска сравнения. CC - век; CD - день; CM - месяц; CY - год

Пример настройки времени/даты будильника. Будильник настраивается через 1 минуту от установленного времени RTC. Сравнение проводится по всем полям.

uint32_t alarm_time_mask = RTC_TALRM_CDOW_M | RTC_TALRM_CH_M | RTC_TALRM_CM_M | RTC_TALRM_CS_M;
uint32_t alarm_date_mask = RTC_DALRM_CC_M | RTC_DALRM_CD_M | RTC_DALRM_CM_M | RTC_DALRM_CY_M;
rtc_alarm_set_time(dow, hour, minute + 1, second, alarm_time_mask);
rtc_alarm_set_date(century, day, month, year, alarm_date_mask);

После настройки RTC и будильника с помощью функции rtc_enable устанавливается в 1 бит EN регистра RRTC_CTRL и начинается работа таймера.

Через 1 минуту после запуска устанавливается флаг ALRM в регистре RRTC_CTRL и срабатывает прерывание, если оно разрешено битом INTE регистра RRTC_CTRL. Флаг ALRM сбрасывается при помощи управляющего ПО.

Полный код

Пример для ведомого - ссылка

main.c

#include "common.h"
#include "rtc.h"
#include "wakeup.h"
#include "power_manager.h"
// #include "epic.h"

void rtc_wait_flag()
{
    uint32_t retry_limit = 10000;
    for (uint32_t i = 0; i < retry_limit; i++)
    {
        if ((RTC->CTRL & RTC_CTRL_FLAG_M) == 0)
        {
            return;
        }
    }
    
    xprintf("Ожидание установки CTRL.FLAG в 0 превышено\n");
}

void rtc_disable()
{
    // Для записи даты нужно сбросить бит EN в регистре CTRL
    RTC->CTRL &= ~RTC_CTRL_EN_M;
    rtc_wait_flag();
}

void rtc_enable()
{
    // Установка бита EN включает модуль RTC
    RTC->CTRL |= RTC_CTRL_EN_M;
    rtc_wait_flag();
}

void rtc_init()
{
    //Включаем тактирование необходимых блоков и модуля выбора режима GPIO 
    PM->CLK_APB_P_SET |= PM_CLOCK_GPIO_0_M | PM_CLOCK_WDT_M;
    PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M | PM_CLOCK_RTC_M;
    for (volatile int i = 0; i < 10; i++);

    /*
    * CLOCKS_BU - Регистр управления тактированием батарейного домена
    * 
    * OCS32K_en - Включение/отключение внешнего осцилятора на 32 KГц
    * RC32K_en - Включение/отключение LSI32К
    * Adj_RC32К - Поправочные коэффициенты LSI32К
    * RTC_Clk_Mux - Выбор источника тактирования часов реального времени:
    *               0 – внутренний LSI32К;
    *               1 – внешний осциллятор OSC32K
    * OSC32K_sm - Режим повышенного потребления, активный уровень “0” для OSC32K
    */
    WU->CLOCKS_BU = WU_CLOCKS_BU_RTC_CLK_MUX_OSC32K_M;
    for (volatile int i = 0; i < 100; i++);
    WU->CLOCKS_BU = (0<<WU_CLOCKS_BU_OSC32K_PD_S) | WU_CLOCKS_BU_RTC_CLK_MUX_OSC32K_M;
    xprintf("Запуск с внешним осцилятором OSC32K\n");

    // Сброс RTC
    WU->RTC_CONRTOL = WU_RTC_RESET_CLEAR_M;
}

/** Установка времени
 *
 * \param dow День недели: 1 - Пн; 2 - Вт; 3 - Ср; 4 - Чт; 5 - Пт; 6 - Сб; 7 - Вс
 * \param hour Часы
 * \param minute Минуты
 * \param second Секунды
 * 
 */
void rtc_set_time(uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second)
{
    uint8_t DOW, TH, H, TM, M, TS, S;
    DOW = dow;
    TH = hour / 10;
    H = hour % 10;
    TM = minute / 10;
    M = minute % 10;
    TS = second / 10;
    S = second % 10; 

    uint32_t RTC_time = (DOW << RTC_TIME_DOW_S) | // День недели
                        (TH << RTC_TIME_TH_S)   | // Десятки часов
                        (H << RTC_TIME_H_S)     | // Еденицы часов
                        (TM << RTC_TIME_TM_S)   | // Десятки минут
                        (M << RTC_TIME_M_S)     | // Единицы минут 
                        (TS << RTC_TIME_TS_S)   | // Десятки секунд
                        (S << RTC_TIME_S_S)     | // Единицы секунд
                        (0 << RTC_TIME_TOS_S);    // Десятые секунды

    xprintf("Установка времени RTC\n");
    RTC->TIME = RTC_time;
    rtc_wait_flag();
}

/** Установка даты. Записывается в виде CC.YY.MM.DD
 * 
 * \param century Век
 * \param day Число
 * \param month Месяц (Ноябрь - 11)
 * \param year Год (2022 год - 22)
 * 
 */
void rtc_set_date( uint8_t century, uint8_t day, uint8_t month, uint8_t year)
{
    uint8_t TC, C, TY, Y, TM, M, TD, D;
    TC = century / 10;
    C = century % 10;
    TY = year / 10;
    Y = year % 10;
    TM = month / 10;
    M = month % 10;
    TD = day / 10;
    D = day % 10;

    uint32_t RTC_data = (TC << RTC_DATE_TC_S)  | // Десятки века
                        (C << RTC_DATE_C_S)   | // Единицы века
                        (TY << RTC_DATE_TY_S)  | // Десятки года
                        (Y << RTC_DATE_Y_S)   | // Единицы года
                        (TM << RTC_DATE_TM_S)  | // Десятки месяца
                        (M << RTC_DATE_M_S)   | // Единицы месяца
                        (TD << RTC_DATE_TD_S)  | // Десятки числа
                        (D << RTC_DATE_D_S);    // Единицы числа

    xprintf("Установка даты RTC\n");
    RTC->DATE = RTC_data;
    rtc_wait_flag();
}

/** Установка времени будильника
 *
 * \param dow День недели: 1 - Пн; 2 - Вт; 3 - Ср; 4 - Чт; 5 - Пт; 6 - Сб; 7 - Вс
 * \param hour Часы
 * \param minute Минуты
 * \param second Секунды
 * \param alarm_mask Маска сравнения. CDOW - день недели; CH - часы; CM - минуты; CS - секунды
 * 
 */
void rtc_alarm_set_time(uint8_t dow, uint8_t hour, uint8_t minute, uint8_t second, uint32_t alarm_mask)
{
    uint8_t DOW, TH, H, TM, M, TS, S;
    DOW = dow;
    TH = hour / 10;
    H = hour % 10;
    TM = minute / 10;
    M = minute % 10;
    TS = second / 10;
    S = second % 10; 

    uint32_t RTC_alarm_time = (DOW << RTC_TIME_DOW_S) | // День недели
                              (TH << RTC_TIME_TH_S)   | // Десятки часов
                              (H << RTC_TIME_H_S)     | // Еденицы часов
                              (TM << RTC_TIME_TM_S)   | // Десятки минут
                              (M << RTC_TIME_M_S)     | // Единицы минут 
                              (TS << RTC_TIME_TS_S)   | // Десятки секунд
                              (S << RTC_TIME_S_S)     | // Единицы секунд
                              (0 << RTC_TIME_TOS_S);    // Десятые секунды

    xprintf("Установка времени будильника\n");
    RTC->TALRM = RTC_alarm_time | alarm_mask;
    rtc_wait_flag();
}

/** Установка даты будильника. Дата в виде CC.YY.MM.DD
 * 
 * \param century Век
 * \param day Число
 * \param month Месяц (Ноябрь - 11)
 * \param year Год (2022 год - 22)
 * \param alarm_mask Маска сравнения. CC - век; CD - день; CM - месяц; CY - год
 * 
 */
void rtc_alarm_set_date( uint8_t century, uint8_t day, uint8_t month, uint8_t year, uint32_t alarm_mask) 
{
    uint8_t TC, C, TY, Y, TM, M, TD, D;
    TC = century / 10;
    C = century % 10;
    TY = year / 10;
    Y = year % 10;
    TM = month / 10;
    M = month % 10;
    TD = day / 10;
    D = day % 10;

    uint32_t RTC_alarm_data = (TC << RTC_DATE_TC_S)  | // Десятки века
                              (C << RTC_DATE_C_S)   | // Единицы века
                              (TY << RTC_DATE_TY_S)  | // Десятки года
                              (Y << RTC_DATE_Y_S)   | // Единицы года
                              (TM << RTC_DATE_TM_S)  | // Десятки месяца
                              (M << RTC_DATE_M_S)   | // Единицы месяца
                              (TD << RTC_DATE_TD_S)  | // Десятки числа
                              (D << RTC_DATE_D_S);    // Единицы числа

    xprintf("Установка даты будильника\n");
    RTC->DALRM = RTC_alarm_data | alarm_mask;
    rtc_wait_flag();
}

void rtc_check_date()
{
    uint8_t TC, C, TY, Y, TM, M, TD, D;
    uint32_t rtc_date_read = RTC->DATE;
    TC = (rtc_date_read & RTC_DATE_TC_M) >> RTC_DATE_TC_S;
    C = (rtc_date_read & RTC_DATE_C_M) >> RTC_DATE_C_S;
    TY = (rtc_date_read & RTC_DATE_TY_M) >> RTC_DATE_TY_S;
    Y = (rtc_date_read & RTC_DATE_Y_M) >> RTC_DATE_Y_S;
    TM = (rtc_date_read & RTC_DATE_TM_M) >> RTC_DATE_TM_S;
    M = (rtc_date_read & RTC_DATE_M_M) >> RTC_DATE_M_S;
    TD = (rtc_date_read & RTC_DATE_TD_M) >> RTC_DATE_TD_S;
    D = (rtc_date_read & RTC_DATE_D_M) >> RTC_DATE_D_S;

    xprintf("\n%d%d век\n", TC, C);
    xprintf("%d%d.%d%d.%d%d\n", TD, D, TM, M, TY, Y);
}

void rtc_check_time()
{
    switch (RTC->DOW)
    {
    case 1:
        xprintf("Понедельник\n");
        break;
    case 2:
        xprintf("Вторник\n");
        break;
    case 3:
        xprintf("Среда\n");
        break;
    case 4:
        xprintf("Четверг\n");
        break;
    case 5:
        xprintf("Пятница\n");
        break;
    case 6:
        xprintf("Суббота\n");
        break;
    case 7:
        xprintf("Воскресенье\n");
        break;
    }
    xprintf("%d%d:%d%d:%d%d.%d\n", RTC->TH, RTC->H, RTC->TM, RTC->M, RTC->TS, RTC->S, RTC->TOS);

}

void rtc_check()
{
    rtc_check_date();
    rtc_check_time();
}

int main()
{

    rtc_init();
    rtc_disable();
    
    uint8_t dow = 2;
    uint8_t hour = 20;
    uint8_t minute = 30;
    uint8_t second = 00;

    uint8_t century = 21;
    uint8_t day = 19;
    uint8_t month = 07;
    uint8_t year = 22;
    rtc_set_time(dow, hour, minute, second);
    rtc_set_date(century, day, month, year);

    uint32_t alarm_time_mask = RTC_TALRM_CDOW_M | RTC_TALRM_CH_M | RTC_TALRM_CM_M | RTC_TALRM_CS_M;
    uint32_t alarm_date_mask = RTC_DALRM_CC_M | RTC_DALRM_CD_M | RTC_DALRM_CM_M | RTC_DALRM_CY_M;
    rtc_alarm_set_time(dow, hour, minute + 1, second, alarm_time_mask);
    rtc_alarm_set_date(century, day, month, year, alarm_date_mask);

    xprintf("Включнеие RTC\n");
    rtc_enable();

    int counter = 1000000;

    while (1)
    {
        if (--counter < 0)
        {
            rtc_check();
            counter = 1000000;
        }

        if (RTC->CTRL & RTC_CTRL_ALRM_M)
        {
            for (volatile int i = 0; i < 1000000; i++); 
            xprintf("\nAlarm!\n");
            RTC->CTRL &= ~RTC_CTRL_ALRM_M;
            rtc_wait_flag();
        }
    }
}