За допомоги способу реалізації RTC, які висвітлені в цій статті, так і не вдалось враховувати дні, протягом яких MCU був у вимкненому стані, або режимі сну за допомоги HAL драйверу. Реалізація за допомоги CMSIS працює коректно. В другій частині цієї статті (продовженні) описано як вірішити проблему з датою, за допомоги невеличкої бібліотеки, де змінено деякі HAL функції роботи з RTC.
Передмова
В деяких проектах, виконаних на мікроконтролерах, є потреба в годиннику реального часу - RTC (real time clock). На мікроконтролерах STM32F1xx теж є RTC. Хоч і куций, але є і з ним можна працювати. Чому куций? Тому що час, який зберігається, а точніше продовжує йти - це звичайний 32-бітний лічильник секунд. І це все! Добра новина що, цей 32-бітний, лічильник знаходиться в домені BACKUP і продовжує додавати секунди навіть без основного живлення, якщо під'єднано до ніжки мікроконтролера VBAT - батарейку на 3 вольта. Розгляньмо підключення резервного живлення та способи реалізації RTC. Піддослідною платою розробника буде miniboard на чипові STM32F103C8T6. З підключеним дисплеєм WH1602 (HD44780). Бібліотека роботи з цим дисплеєм та його підключення у статті:
"STM32: Бібліотека для роботи з LCD WH1602(4) або іншим сумісним дисплеєм з контролером HD44780"
"STM32: Бібліотека для роботи з LCD WH1602(4) або іншим сумісним дисплеєм з контролером HD44780"
Схема підключення резервного живлення до VBAT
update 25.02.2019: Як виявилось, батарейку 3V, достатньо під'єднати до шпильки VBAT мікроконтролеру через діод D1, щоб основне живлення не потрапляло на батарейку. І це все, діод D2 можна не встановлювати, за бажанням. Коли мікроконтролер увімкнений, то і RTC живиться від загального живлення. Коли з мікроконтролеру зняти живлення, то RTC живиться від батарейки.
Щоб основне живлення не потрапляло на батарейку, а батарейка не живила всю схему, слід підключати, наприклад, по такій схемі:
Щоб основне живлення не потрапляло на батарейку, а батарейка не живила всю схему, слід підключати, наприклад, по такій схемі:
Схема підключення резервного живлення VBAT |
RTC за допомоги драйверу HAL
Реалізація RTC на HAL драйвері неповноцінна. А саме, лічильник RTC зберігає виключно секунди в межах доби. Дата зберігається просто в енергозалежній ОЗП і при вимкненні основного живлення значення дня, місяця і року скинуться до 01.01.00. З-за цього будильник можна встановити в межах доби. Звернемось до документації. В пункті 33.2.2 на сторінці 427 зазначено:
WARNING: Drivers RestrictionsRTC version used on STM32F1 families is version V1. All the features supported by V2
(other families) will be not supported on F1.
As on V2, main RTC features are managed by HW. But on F1, date feature is completely
managed by SW.
Then, there are some restrictions compared to other families:
(other families) will be not supported on F1.
As on V2, main RTC features are managed by HW. But on F1, date feature is completely
managed by SW.
Then, there are some restrictions compared to other families:
- Only format 24 hours supported in HAL (format 12 hours not supported)
- Date is saved in SRAM. Then, when MCU is in STOP or STANDBY mode, date will be lost. User should implement a way to save date before entering in low power mode (an example is provided with firmware package based on backup registers)
- Date is automatically updated each time a HAL_RTC_GetTime or HAL_RTC_GetDate is called.
- Alarm detection is limited to 1 day. It will expire only 1 time (no alarm repetition, need to program a new alarm)
Програмна підтримка колодару. Та ще й так кострубато. З-за цього накладені обмеження в порівнянні з іншими серіями мікроконтролерів. А саме:
Скористаємось нашим помічником CubeMX for STM32.- Тільки 24-годинний формат часу
- Дата зберігається в ОЗП і втрачається при перезавантаженні, втраті живлення чи переході в режим очікування. Треба самому потурбуватись про збереження дати в backup регістри.
- Сигнал тривоги обмежений однією добою. І потрібно наново встановлювати при потребі.
Налаштування і запуск RTC в CubeMX
Особливо налаштовувати нічого і не потрібно. Просто в пункті "Pinout" в гілці "RCC" пункт "Low Speed Clock (LSE)" виставте в "Crystal/Ceramic Resonator", а в гілці "RTC" активуйте пункт "Activate Clock Source". Активувати пункт "Activate Calendar" не потрібно. Бо тоді, при кожному новому увімкнені чи перезавантаженні мікроконтролера, буде встановлюватись дата, яку задали в налаштуваннях кожного разу. Зробіть як показано на малюнку:
RTC PinOut |
Далі переходимо до вкладки "Clock Configuration" і обираємо зовнішній кварцовий резонатор як на малюнку:
RTC Clock Configuration |
В пункті RTC Configuration нічого не змінював, залишив як є:
RTC Configuration |
У вкладці "NVIC Settings" за потреби оберіть переривання по RTC.
Код ініціалізації HAL
Генеруємо проект з кодом ініціалізації CubeMX. Відкривайте свій засіб розробки. Для ініціалізації годинника реального часу CubeMX генерує такі рядки до файлу main.c:
Структура з налаштуваннями RTC.
/* Private variables ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;
/* Private function prototypes -----------------------------------------------*/ static void MX_RTC_Init(void);
/* Initialize all configured peripherals */
MX_RTC_Init();
/* RTC init function */ static void MX_RTC_Init(void) { /*Initialize RTC Only*/ hrtc.Instance = RTC; hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND; hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } }
Якщо вам потрібно, щоб були переривання раз на секунду від RTC і ви зробили відповідну позначку в CubeMX у вкладці "NVIC Settings", то в файл stm32f1xx_it.c буде розміщено обробник переривань:
/** * @brief This function handles RTC global interrupt. */ void RTC_IRQHandler(void) { /* USER CODE BEGIN RTC_IRQn 0 */ /* USER CODE END RTC_IRQn 0 */ HAL_RTCEx_RTCIRQHandler(&hrtc); /* USER CODE BEGIN RTC_IRQn 1 */ /* USER CODE END RTC_IRQn 1 */ }
/* USER CODE BEGIN 2 */ HAL_RTCEx_SetSecond_IT(&hrtc); /* USER CODE END 2 */
Якщо потрібна корекція часу то можна скористатись функцією HAL_RTCEx_SetSmoothCalib, детально про цю функцію дивись у документації, про корректировку докладно тут.
Це все. Після цих маніпуляцій в 32-бітний регістр RTC кожну секунду буде додаватись одиниця. А при вимкнені основного живлення домен RTC буде живитись від резервного живлення, якщо ви його під'єднали. І годинник буде продовжувати працювати.
Але ж треба задати поточну дату і час, та показати дату і час на дисплей. А також не забуваймо, що нам самим треба потурбуватись про збереження дати в backup регістри. Ну що ж - почнемо!
Функція встановлення дати і часу
Щоб оперувати датою і часом, потрібно спочатку оголосити структури дати та часу:
Тепер можна встановлювати дату і час. Вхідні данні функції це рік, місяць, день, день тижня, години, хвилини, секунди та формат часу. Функція нічого не повертає:
RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate;
Тепер можна встановлювати дату і час. Вхідні данні функції це рік, місяць, день, день тижня, години, хвилини, секунди та формат часу. Функція нічого не повертає:
static void setDataAndTime(uint8_t year, uint8_t month, uint8_t day, uint8_t weekday, uint8_t hour, uint8_t min, uint8_t sec, uint32_t format) { /**Initialize RTC and set the Time and Date**/ sTime.Hours = hour; sTime.Minutes = min; sTime.Seconds = sec; if (HAL_RTC_SetTime(&hrtc, &sTime, format) != HAL_OK) { Error_Handler(); } sDate.WeekDay = weekday; sDate.Month = month; sDate.Date = day; sDate.Year = year; if (HAL_RTC_SetDate(&hrtc, &sDate, format) != HAL_OK) { Error_Handler(); } }
Функція збереження дати до backup регістрів
Так як для збереження дати потрібно 32 біти, а backup регістри мають по 16 біт, то нам потрібно для збереження дати аж два backup регістри. На вхід функція отримує в які саме регістри потрібно зберегти дату. Нічого не повертає:
static void writeDateToBackup(uint32_t bkp_reg1, uint32_t bkp_reg2) { HAL_RTC_GetDate(&hrtc, &sDate, FORMAT_BCD); HAL_RTCEx_BKUPWrite(&hrtc, bkp_reg2, (sDate.WeekDay << 8) | (sDate.Year)); HAL_RTCEx_BKUPWrite(&hrtc, bkp_reg1, (sDate.Date << 8) | (sDate.Month)); }
Функція відновлення дати з backup регістрів
І ще нам потрібна функція яка буде читати записану дату з backup регістрів, заповнювати структуру дати і встановлювати її як поточну. На вхід функція отримує з яких саме регістрів потрібно відновити дату. Нічого не повертає:
static void readBackupToDate(uint32_t bkp_reg1, uint32_t bkp_reg2) { sDate.Month = HAL_RTCEx_BKUPRead(&hrtc,bkp_reg1) & 0x00ff; sDate.Date = HAL_RTCEx_BKUPRead(&hrtc,bkp_reg1) >> 8; sDate.Year = HAL_RTCEx_BKUPRead(&hrtc,bkp_reg2) & 0x00ff; sDate.WeekDay =HAL_RTCEx_BKUPRead(&hrtc,bkp_reg2) >> 8; HAL_RTC_SetDate(&hrtc, &sDate, FORMAT_BCD); }
Функція перевірки нового дня
Ця функція потрібна щоб перевірити чи настав новий день. Як настав, то повертає "false", як той самий день, то повертає "true".
static bool checkNewDay(uint32_t bkp_reg1) { HAL_RTC_GetDate(&hrtc, &sDate, FORMAT_BCD); if(sDate.Date != HAL_RTCEx_BKUPRead(&hrtc,bkp_reg1) >> 8) { return false; } else { return true; } }
За допомоги цієї функції можна оновити при зміні дня данні про дату в backup регістри.
Як користуватись
Всю необхідну роботу по ініціалізації та запуску нашого RTC виконав CubeMX, залишилось всім цим скористатись.
Алгоритм буде приблизно такий:
В прикладі все враховується, час не втрачається при вимкнені пристрою, якщо під'єднано резервне живлення, як показано на схемі на початку статті. А дата залишається тією що була на момент відключення живлення, хоча і мають додатись пропущені дні. Поки не зміг "побороти" проблему.
Це тільки приклад для демонстрації роботи RTC. Функції можна вдосконалити чи переробити на свій лад. Показав сам принцип.
Висновок: Повноцінно користуватись можна. Недолік - займання аж двох BACKUP регістрів, які можуть знадобитись для більш важливіших цілей. Дата зберігається, але дні коли пристрій був у "відключці" - втрачаються. Краще реалізувати свою бібліотеку реалізації RTC з колодаром.
- Після ініціалізації RTC перевіряємо чи в backup регістрах, відведених для зберігання дати, щось є крім нуля? Як нуль, то можна встановити якусь початкову дату.
- Як не нуль, то запам'ятовуємо значення RTC лічильника в тимчасову змінну, тому що виклик функції запису збереженої дати зменшить значення RTC лічильника в межах доби. І час, коли MCU знаходився у відключеному стані - втратиться. Структуру дати заповнюємо даними з backup регістрів де зберігається дата перед вимкненням пристрою. Відновлюємо значення RTC лічильника з тимчасової змінної та викликаємо функцію HAL_RTC_GetTime або HAL_RTC_GetDate які приведуть час до стану "в межах доби", а якщо MCU був відключений більше ніж добу, то "пропущений" час додасця до дати автоматично;
- Далі вже можна показувати час та дату на дисплей, спочатку викликавши функцію HAL_RTC_GetTime та функцію HAL_RTC_GetDate, а вже потім організувати вивід на дисплей отриманих даних;
- Не забуваємо хоч раз на початку нової доби викликати функцію checkNewDay, щоб перевірити чи не настав новий день і зберігати до backup регістрів нове значення дати.
/* USER CODE BEGIN 2 */ lcdInit(); // ініціалізація дисплею uint8_t aShowTime[16] = {0}; // Після увімкнення, пробудження, скидання, потрібно викликати цю функцію HAL_RTC_WaitForSynchro(&hrtc); // Перевіряємо чи дата вже була збережена чи ні if (HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) == 0) { // як ні, то задамо якусь початкову дату і час setDataAndTime(0x17,RTC_MONTH_JANUARY,0x14,RTC_WEEKDAY_SATURDAY,0x21,0x48,0x00,FORMAT_BCD); // і запишемо до backup регістрів дату writeDateToBackup(RTC_BKP_DR1,RTC_BKP_DR2); } else { // якщо вже годинник встановлено, то... // запам'ятовуємо кількість секунд з лічильника RTC uint32_t timeCounter = (RTC->CNTH << 16) | RTC->CNTL; // відновимо з backup регістрів дату. Від лічильника RTC віднімиться час що виходить за межі доби readBackupToDate(RTC_BKP_DR1,RTC_BKP_DR2); // відновимо попереднє, збережене значення лічильника RTC RTC->CNTH = timeCounter>>16; RTC->CNTL = timeCounter; // Після запису до регістру CNT потрібно викликати цю функцію HAL_RTC_WaitForSynchro(&hrtc); // виклик цієї функції врахує пропущені дні що був MCU у вимкненому стані HAL_RTC_GetTime(&hrtc, &sTime, FORMAT_BIN); } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { // отримуємо поточні час і дату HAL_RTC_GetTime(&hrtc, &sTime, FORMAT_BIN); HAL_RTC_GetDate(&hrtc, &sDate, FORMAT_BIN); // виводимо дату і час на дисплей sprintf((char*)aShowTime,"\t%02d:%02d:%02d",sTime.Hours, sTime.Minutes, sTime.Seconds); lcdGoto(LCD_2nd_LINE,0); lcdPuts((char*)aShowTime); sprintf((char*)aShowTime,"\t%02d/%02d/%02d",sDate.Date,sDate.Month,sDate.Year); lcdGoto(LCD_1st_LINE,0); lcdPuts((char*)aShowTime); // перевіримо чи настав інший день if(!checkNewDay(RTC_BKP_DR1)) { // як так, то запишемо до backup регістрів нову дату writeDateToBackup(RTC_BKP_DR1,RTC_BKP_DR2); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
Це тільки приклад для демонстрації роботи RTC. Функції можна вдосконалити чи переробити на свій лад. Показав сам принцип.
Висновок: Повноцінно користуватись можна. Недолік - займання аж двох BACKUP регістрів, які можуть знадобитись для більш важливіших цілей. Дата зберігається, але дні коли пристрій був у "відключці" - втрачаються. Краще реалізувати свою бібліотеку реалізації RTC з колодаром.
Налаштування і запуск RTC за допомоги CMSIS драйверу
Щоб обійтись без HAL для RTC можна скористатись стандартним драйвером CMSIS. Це стандартна бібліотека, у ній визначено/оголошено всі імена регістрів мікроконтролера. Тому немає потреби задавати їх адреси в пам'яті, постійно зазираючи до технічної документації по мікроконтролеру. Не містить програмного коду як такого.
Ось варіант ініціалізації на CMSIS:
Текст функції докладно доповнений коментарями, додаткового пояснення не потребує.
Після виклику функції mRTCInit, яку ми самі створили, годинник реального часу почне додавати одиницю в 32-бітний регістр RTC раз на секунду.
Ось варіант ініціалізації на CMSIS:
static void mRTCInit(void) { if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN) // Перевірка роботи годинника, якщо не увімкнені, то ініціалізувати { RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; // Увімкнути тактування PWR та Backup PWR->CR |= PWR_CR_DBP; // Дозволити доступ до Backup області RCC->BDCR |= RCC_BDCR_BDRST; // Скинути Backup область RCC->BDCR &= ~RCC_BDCR_BDRST; RCC->BDCR |= RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_LSE; // Обрати LSE джерело (кварц 32768) і подати тактування RCC->BDCR |= RCC_BDCR_LSEON; // Увімкнути LSE while ((RCC->BDCR & RCC_BDCR_LSEON) != RCC_BDCR_LSEON){}// Дочекатись увімкнення BKP->RTCCR |= 0; // Калібрування RTC значення від 0 до 0x7F while (!(RTC->CRL & RTC_CRL_RTOFF)); // Перевірити чи закінчились зміни регістрів RTC RTC->CRL |= RTC_CRL_CNF; // Дозволити запис до RTC RTC->PRLL = 0x7FFF; // Налаштувати дільник на 32768 (32767+1) RTC->CRL &= ~RTC_CRL_CNF; // Заборонити запис до RTC while (!(RTC->CRL & RTC_CRL_RTOFF)); // Дочекатись кінця запису RTC->CRL &= (uint16_t)~RTC_CRL_RSF; // Синхронизувати RTC while((RTC->CRL & RTC_CRL_RSF) != RTC_CRL_RSF){} // Дочекатись синхронізації PWR->CR &= ~PWR_CR_DBP; // Заборонити доступ до Backup області } }
Після виклику функції mRTCInit, яку ми самі створили, годинник реального часу почне додавати одиницю в 32-бітний регістр RTC раз на секунду.
Реалізація колодару
Напишемо всі необхідні функції для реалізації колодару.
Задавання і отримання значення 32-бітного лічильника RTC
Функція отримання значення 32-бітного лічильника RTC:
static uint32_t mRTCGetCounter(void) // Отримати значення лічильника { return (uint32_t)((RTC->CNTH << 16) | RTC->CNTL); }
Функція встановлення нового значення 32-бітного лічильника RTC:
static void mRTCSetCounter(uint32_t count) // Записати нове значення лічильника { RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; // Увімкнути тактування PWR і Backup PWR->CR |= PWR_CR_DBP; // Дозволити доступ до Backup області while (!(RTC->CRL & RTC_CRL_RTOFF)); // Перевірити чи закінчились зміни регистрів RTC RTC->CRL |= RTC_CRL_CNF; // Дозволити запис в регістри RTC RTC->CNTH = count>>16; // Записати нове значення годинникового лічильника RTC->CNTL = count; RTC->CRL &= ~RTC_CRL_CNF; // Заборонити запис в регістри RTC while (!(RTC->CRL & RTC_CRL_RTOFF)); // Дочекатись кінця запису PWR->CR &= ~PWR_CR_DBP; // Заборонити доступ до Backup області }
Отримувати і задавати значення RTC лічильника ми вже можемо, залишилось ще перетворити значення лічильника в дату і час, та перетворення з дати і часу в значення лічильника.
Тут є два варіанти. Скористатись Unix Time, початок відліку часу ведеться від 01.01.1970 року і 32-бітного лічильника вистачить аж до 19 січня 2038 року, 03:14:07 UTC. Або вести початок відліку з 01.01.2001 року, що значно відтермінує час до виникнення переповнення лічильника.
Далі покажу дві реалізації перетворення значення лічильника в дату і час та навпаки. Одна реалізація колодару буде рахувати секунди від 01.01.1970. Інша реалізація колодару початок відліку почне від 01.01.2001. Обидва колодари реалізуємо у вигляді окремих бібліотек.
Рахуємо від 1 січня 1970 року
Взяв тут цю готову реалізацію, але алгоритми перетворення загальновідомі, на основі них і реалізовані перетворення.
Файл "UnixTime.h" де оголошена структура зберігання дати і часу та оголошені прототипи функцій:
/*************************************************************************** * Ця бібліотека призначена для роботи з RTC STM32F1xx за допомоги UnixTime* * дозволяє перетворювати з лічильника в колодар (день, місяць, рік) * * і час (години, хвилини, секунди), і навпаки * ***************************************************************************/ #ifndef __UNIXTIME_H #define __UNIXTIME_H #define SECOND_A_DAY 86400 typedef struct { int year; char mon; char mday; char hour; char min; char sec; char wday; } unixColodar; void counterToColodar (unsigned long counter, unixColodar * unixTime); unsigned long colodarToCounter (unixColodar * unixTime); #endif
Файл "UnixTime.c" де розташовані самі функції перетворення:
#include "UnixTime.h" void counterToColodar (unsigned long counter, unixColodar * unixTime) { unsigned long a; char b; char c; char d; unsigned long time; time = counter%SECOND_A_DAY; a = ((counter+43200)/(SECOND_A_DAY>>1)) + (2440587<<1) + 1; a>>=1; unixTime->wday = a%7; a+=32044; b=(4*a+3)/146097; a=a-(146097*b)/4; c=(4*a+3)/1461; a=a-(1461*c)/4; d=(5*a+2)/153; unixTime->mday=a-(153*d+2)/5+1; unixTime->mon=d+3-12*(d/10); unixTime->year=100*b+c-4800+(d/10); unixTime->hour=time/3600; unixTime->min=(time%3600)/60; unixTime->sec=(time%3600)%60; } unsigned long colodarToCounter (unixColodar * unixTime) { char a; int y; char m; unsigned long Uday; unsigned long time; a=((14-unixTime->mon)/12); y=unixTime->year+4800-a; m=unixTime->mon+(12*a)-3; Uday=(unixTime->mday+((153*m+2)/5)+365*y+(y/4)-(y/100)+(y/400)-32045)-2440588; time=Uday*SECOND_A_DAY; time+=unixTime->sec+unixTime->min*60+unixTime->hour*3600; return time; }
Функція colodarToCounter, перетворює дату і час в значення лічильника. На вхід параметри:
вказівник на структуру дати і часу. Повертає значення лічильника.
Завантажити архів бібліотеки UnixTime.rar
Як користуватись
Додайте файли "UnixTime.h" та "UnixTime.c" до свого проекту. На початку головного файлу "main.c" приєднайте бібліотеку:
#include "UnixTime.h"
unixColodar unixTime;
static uint32_t colodarSetting(int year, char month, char day, char hour, char min, char sec) { unixTime.year=year; unixTime.mon=month; unixTime.mday=day; unixTime.hour=hour; unixTime.min=min; unixTime.sec=sec; return colodarToCounter(&unixTime); }
Наприклад, щоб встановити дату 23 грудня 2016 року, а час 21 годину 15 хвилин 00 секунд, треба зробити так:
mRTCSetCounter(colodarSetting(2016,12,23,21,15,0));
Щоб отримати поточну дату і час треба викликати функцію counterToColodar:
counterToColodar(mRTCGetCounter(), &unixTime);
Структура даних дати і часу заповнені, тепер можна отриману дату і час показати на дисплей. Це вже залежить на який дисплей ви будете виводити інформацію. Наприклад, якщо на дисплей типу WH1602 з бібліотекою hd44780. Підключення і робота з бібліотекою в статті: "STM32: Бібліотека для роботи з LCD WH1602(4) або іншим сумісним дисплеєм з контролером HD44780", то вивід дати і часу на дисплей можуть виглядати так:
counterToColodar(mRTCGetCounter(), &unixTime); lcdGoto(LCD_1st_LINE,0); lcdNtos((uint32_t) unixTime.mday, 2); lcdGoto(LCD_1st_LINE,3); lcdNtos((uint32_t) unixTime.mon, 2); lcdGoto(LCD_1st_LINE,6); lcdNtos((uint32_t) unixTime.year, 4); lcdGoto(LCD_2nd_LINE,0); lcdNtos((uint32_t) unixTime.hour, 2); lcdGoto(LCD_2nd_LINE,3); lcdNtos((uint32_t) unixTime.min, 2); lcdGoto(LCD_2nd_LINE,6); lcdNtos((uint32_t) unixTime.sec, 2);
Рахуємо від 1 січня 2001 року
Це просто ще одна реалізація колодару, як приклад. Взято тут. В цьому варіанті ще додано віднімання днів, що минули з 1 січня 1970 року по 1 січня 2001 року.
Файл "fTimeJD.h" де оголошено константу, структуру і прототипи функцій.
#ifndef FTIMEJD_H_ #define FTIMEJD_H_ #include "stm32f1xx.h" #define JD0 2451911 // днів до 01 січня 2001 ПН typedef struct{ uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; } ftime_t; // функція перетворення григоріанської дати і часу в значення лічильника uint32_t FtimeToCounter(ftime_t * ftime); // функція перетворення значення лічильника в григоріанську дату і час void CounterToFtime(uint32_t counter,ftime_t * ftime); #endif /* FTIMEJD_H_ */
Файл "fTimeJD.c" з функціями бібліотеки.
#include "fTimeJD.h" // функція перетворення григоріанської дати і часу в значення лічильника uint32_t FtimeToCounter(ftime_t * ftime) { uint8_t a; uint16_t y; uint8_t m; uint32_t JDN; // Обчислення необхідних коефіцієнтів a=(14-ftime->month)/12; y=ftime->year+4800-a; m=ftime->month+(12*a)-3; // Обчислюємо значення поточного Юліанського дня JDN=ftime->day; JDN+=(153*m+2)/5; JDN+=365*y; JDN+=y/4; JDN+=-y/100; JDN+=y/400; JDN+=-32045; JDN+=-JD0; // приберемо дні які минули до 1 січня 2001 року JDN*=86400; // перетворимо дні в секунди JDN+=(ftime->hour*3600); // і додамо секунди поточного дня JDN+=(ftime->minute*60); JDN+=(ftime->second); // повертаємо кількість секунд з опівночі 1 січня 2001 року return JDN; } // функція перетворення значення лічильника в григоріанску дату і час void CounterToFtime(uint32_t counter,ftime_t * ftime) { uint32_t ace; uint8_t b; uint8_t d; uint8_t m; ace=(counter/86400)+32044+JD0; b=(4*ace+3)/146097; ace=ace-((146097*b)/4); d=(4*ace+3)/1461; ace=ace-((1461*d)/4); m=(5*ace+2)/153; ftime->day=ace-((153*m+2)/5)+1; ftime->month=m+3-(12*(m/10)); ftime->year=100*b+d-4800+(m/10); ftime->hour=(counter/3600)%24; ftime->minute=(counter/60)%60; ftime->second=(counter%60); }
Функція FtimeToCounter, перетворює дату і час в значення лічильника. На вхід параметри:
вказівник на структуру дати і часу. Повертає значення лічильника.
Завантажити архів бібліотеки fTimeJD.rar
Як користуватись
Додайте файли "fTimeJD.h" та "fTimeJD.c" до свого проекту. На початку головного файлу "main.c" приєднайте бібліотеку:
#include "fTimeJD.h"
ftime_t jdTime;
static uint32_t colodarSetting(int year, char month, char day, char hour, char min, char sec) { jdTime.year = year; jdTime.month = month; jdTime.day = day; jdTime.hour = hour; jdTime.minute = min; jdTime.second = sec; return FtimeToCounter(&jdTime); }
Наприклад, щоб встановити дату 23 грудня 2016 року, а час 21 годину 15 хвилин 00 секунд, треба зробити так:
mRTCSetCounter(colodarSetting(2016,12,23,21,15,0));
CounterToFtime(mRTCGetCounter(), &jdTime);
Структура даних дати і часу заповнені, тепер можна отриману дату і час показати на дисплей. З дисплеєм і бібліотекою, як в попередньому прикладі, вивід дати і часу на дисплей можуть виглядати ще й так:
uint8_t aShowTime[20] = {0};
CounterToFtime(mRTCGetCounter(), &jdTime);sprintf((char*)aShowTime,"%02d:%02d:%02d",jdTime.hour, jdTime.minute, jdTime.second); lcdGoto(LCD_2nd_LINE,0); lcdPuts((char*)aShowTime); sprintf((char*)aShowTime,"%02d/%02d/%02d",jdTime.day,jdTime.month,jdTime.year); lcdGoto(LCD_1st_LINE,0); lcdPuts((char*)aShowTime);
Деякі зауваження
- Перший запуск годинника реального часу може тривати досить довго, аж до 1 секунди. Подальші увімкнення, як годинник запущений, будуть швидкими.
- Хоч і в функціях перетворення часу і дати в секунди та навпаки, багато операцій ділення і множення, та на STM32 ці операції хардварні і виконуються дуже швидко. Перетворення з лічильника секунд в дату і час триває менш ніж одна мілісекунда.
- Інформацію про будильник не надавав. Поки не було необхідності. Можливо згодом додам.
- плата розробника mini board STM32F103C8T6 продається з роз'ємами які не впаяні до плати. Треба їх самостійно впаяти. Будьте обережні і добре промийте спиртом плату після паяння. Часто-густо по причині забрудненості плати годинник реального часу RTC просто не запускається, або плин секунд мікроконтролера не відповідає плину секунд в реальності. Похибка може бути дуже значною. Можливо буде потреба пропаяти годинникові кварц і конденсатори. Або навіть підібрати конденсатори що в обв'язці годинникового кварцу.
- щодо вживання слова Колодар замість Календар. Слово календар походить від латинського calendarium - боргова книжка. Я з вами не боргову книжку налаштовував і запускав, а відлік часу. Коло-дар - це коло, яке дарує нам наше Сонце і по ньому ведеться відлік часу. Так буде правильно.
Позвольте не согласиться с Вашим комментарием от ноября 2018 года об отсутствии необходимости дополнительных схемных решений при подключении батарейки. При напряжении Vb ниже Vdd она будет заряжаться, а для нее это противопоказано. На мой взгляд Ваша "Схема підключення резервного живлення VBAT" - абсолютно верное решение
ВідповістиВидалитиТак, згоден. Ввели мене в оману. Повівся на авторитет. На перевірку виявилось, що батарейку таки треба підключати через діод. Дякую за зауваження. Зараз виправлю.
ВидалитиКак быть с МК STM32L100crt6? На нем нету CNTH & CNTL регистров. Есть какие-то альтернативы?
ВідповістиВидалития не знаю. якщо ви використовуєте HAL бібліотеки, то може підійде невеличка альтернативна бібліотека https://stm32withoutfear.blogspot.com/2018/05/rtc-for-stm32f1xx-part-Two.html
ВидалитиАвтор видалив цей коментар.
ВідповістиВидалитиДякую за роботу! Окрема подяка)
ВідповістиВидалитиБудь ласка. Позитивні відгуки - надихають. Дякую за цікавість до моєї роботи.
Видалитистрашный язык украинский, даже переводчик его плохо понимает
ВідповістиВидалитиБратіку! Щиро вам вдячний за вашу роботу! Все описано доступно, все зрозуміло!
ВідповістиВидалитиЩиро дякую за позитивний відгук.
Видалити