В микроконтроллерах серии STM32F10x (например, STM32F103C8T6 из популярной BluePill) есть часы реального времени, они "тикают" от батарейки после того, как пропадает основное питание. Но в этих RTC нет аппаратного календаря. Это значит, что дата "не идёт". Даже если вы в своем HAL проекте установили галочку "activate calendar", это значит, что дата будет "идти" при наличии основного питания, но после прекращения питания ДАТА СОБЬЕТСЯ до 01.01.00. Это плохая новость... Хорошая новость в том, что на HAL'е мир не сошелся клином. И мы сделаем полноценные часы реально времени с датой на CMSIS.
(обновление статьи 03.05.2024)
В современных микроконтроллерах STM32 (например серии STM32F4) применены современные часы реального времени, версии V2. В них идет счет и даты и времени. В микроконтроллерах STM32F10x используется старый модуль часов реального времени версии V1. При работе от часовой батарейки приращивается один 32-битный регистр RTC->CNTx. Этот регистр по своему алгоритму, HAL разбивает на часы, минуты, секунды и миллисекунды. Этот регистр мы и будем использовать для хранения штампа времени! Мы будем накапливать в него не миллисекунды, а секунды. В 32-битный регистр можно уместить секунд на 136 лет. Даже если вести отсчет от 2024-го года, то часы теоретически могут идти до 2160-го года. Плата сгниет быстрее, чем "кончится время".
На просторах интернета я нашел примеры кода инициализации часов реального времени, облагородил их таймаутами и написал свой алгоритм преобразования счетчика в год, месяц, день, часы, минуты, секунды.. и обратно.
Готовую библиотеку вы сможете скачать отсюда https://cloud.as.life/s/BGHtZGf5F9PXpcS или из обсуждений на канале https://t.me/ipasoftel/15
Работа со штампами времени
static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// Получить штамп времени в секундах с 0-го по 136-й года.
uint32_t TS_GetSeconds(uint8_t year, uint8_t month, uint8_t day, uint8_t hours, uint8_t minutes, uint8_t seconds) {
uint8_t i;
uint32_t result = 0;
if ((year==0)&&(month==1)&&(day==1)&&(hours==0)&&(minutes==0)&&(seconds==0)) return 0;
for (i = 0; i < year; i++) { // Добавлять секунды по прошедшим годам
if ((i % 4) == 0) result += 366 * 24 * 60 * 60; // Високосный
else result += 365 * 24 * 60 * 60; // Невисокосный
}
for (i = 1; i < month; i++) { // Добавлять секунды по прошедшим месяцам указанного года
if (((year % 4) == 0) && (i == 2)) result += 29 * 24 * 60 * 60; // Високосный год февраль
else result += (uint32_t)daysInMonth[i - 1] * 24 * 60 * 60; // Другие года и/или месяцы
}
result += (uint32_t)(day - 1) * 24 * 60 * 60; // Добавить секунды по прошедшим дням
result += (uint32_t)hours * 60 * 60; // Добавить секунды по прошедшим часам
result += (uint32_t)minutes * 60; // Добавить секунды по прошедшим минутам
result += seconds+1;
return result;
}
// Разбить секунды штампа времени на дату и время
void TS_GetDateTime(uint32_t stamp, uint8_t *year, uint8_t *month, uint8_t *day, uint8_t *hours, uint8_t *minutes, uint8_t *seconds) {
uint32_t tempStamp = stamp;
*year = 0; *month = 1; *day = 1; *hours = 0; *minutes = 0; *seconds = 0;
if (stamp == 0) return;
while(1) { // Разбить секунды на года
if ((*year % 4) == 0) { // Если висоскосный год обрабатывается
if (tempStamp > 366 * 24 * 60 * 60) { // Год тут есть точно
tempStamp -= 366 * 24 * 60 * 60;
*year = *year + 1; // Прирастить год и убавить из штампа кол-во секунд в этом году
}
else break; // Года не наберется
}
else { // Если обрабатывается невисокосный год
if (tempStamp > 365 * 24 * 60 * 60) { // Год тут есть точно
tempStamp -= 365 * 24 * 60 * 60;
*year = *year + 1; // Прирастить год и убавить из штампа кол-во секунд в этом году
}
else break; // Года не наберется
}
}
while(1) { // Разбить секунды на месяцы
if (((*year % 4) == 0) && *month == 2) { // Если февраль високосного года
if (tempStamp > 29 * 24 * 60 * 60) { // Месяц тут точно есть
tempStamp -= 29 * 24 * 60 * 60;
*month = *month + 1;
}
else break; // Нет месяца
}
else { // Другие месяцы високосных и невисокосных лет
if (tempStamp > (uint32_t)daysInMonth[*month - 1] * 24 * 60 * 60) { // Есть месяц
tempStamp -= (uint32_t)daysInMonth[*month - 1] * 24 * 60 * 60;
*month = *month + 1;
}
else break; // Не будет месяца
}
}
// Разбить секунды на дни
while(1) {
if (tempStamp > 24 * 60 * 60) { // секунд хватает на день
*day = *day + 1;
tempStamp -= 24 * 60 * 60;
}
else break; // Секунд меньше чем в сутках
}
// Разбить секунды на часы
while(1) {
if (tempStamp > 60 * 60) {
*hours = *hours + 1;
tempStamp -= 60 * 60;
}
else break;
}
// Разбить секунды на минуты
while(1) {
if (tempStamp > 60) {
tempStamp -= 60;
*minutes = *minutes + 1;
}
else break;
}
// Оставшиеся секунды
if (tempStamp == 0) *seconds = 0;
else *seconds = tempStamp-1;
}
// Вычисляет день недели любой даты начиная с 2023. Дни 1 - 31, месяцы 1 - 12, год 23 - 99
// Вернёт значение с 0 по 6
uint8_t TS_calcDayOfWeek(uint8_t day, uint8_t month, uint8_t year) {
uint16_t firstDayOfWeekInYear = 6; // Первый день недели в текущем году. 0 - понедельник, 1 - втор..
uint8_t i;
uint8_t leapYear = 0; // Признак високосного года
// Вычислить первый день недели в указанном году.
// 2023 году 1 января - воскресенье, день 6
for (i = 23; i < year; i++) {
firstDayOfWeekInYear++;
if (((i - 1) % 4) == 0) { // Если предыдущий год был високосным то
firstDayOfWeekInYear++;
}
}
if (((year - 1) % 4) == 0) { // Если предыдущий год был високосным то
firstDayOfWeekInYear++;
}
firstDayOfWeekInYear = firstDayOfWeekInYear % 7;
// Понять, високосный ли год текущий
if ((year % 4) == 0) leapYear = 1;
// Сложить дни прошедших месяцев
for (i = 0; i < (month - 1); i++) {
firstDayOfWeekInYear += daysInMonth[i];
if (i == 1) if (leapYear) firstDayOfWeekInYear++; // Если февраль високосного года
}
// Прибавить искомый день
firstDayOfWeekInYear += (day - 1);
firstDayOfWeekInYear = firstDayOfWeekInYear % 7;
return (uint8_t)(firstDayOfWeekInYear);
}
// Вернет количество дней в месяце в зависисмости от номера месяца (1-12) и года (0-99)
uint8_t TS_GetDaysInMonth(uint8_t month, uint8_t year) {
if ((!month) || (month > 12)) return 0;
uint8_t tempDaysInMonth;
tempDaysInMonth = daysInMonth[month-1];
if ((month == 2) && (!(year % 4))) tempDaysInMonth++;
return tempDaysInMonth;
}
В этой статье я не буду расписывать как что работает, вы это все прочтете в комментариях в коде. Хочу лишь показать как использовать библиотеку.
Сперва необходимо ее разместить в каталоге с проектом, указать компилятору путь к header'ам, подключить #include "stm32f1_rtc.h". Проведите инициализацию сразу после всех сгенеренных инициализаций HAL'а.
Объявите переменную структурного типа для хранения и передачи даты/времени
RTC_UNIT datetime;
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
rtc_Init();
Теперь вы можете работать с датой и временем через функции
RTC_UNIT rtc_GetTime(void);
void rtc_SetTime(RTC_UNIT dt);
Пример (внутри main() {})
datetime = rtc_GetTime();
printf("%02i:%02i:%02i\r\n", datetime.hours, datetime.minutes, datetime.seconds);
Копируйте, пользуйтесь свободно. Вы можете обсудить эту статью в нашем Telegram канале. Не забудьте присоединиться, чтобы не пропустить новые статьи.