From 34cbd92808659d5d55d9bc8f706d224576b21da3 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Sun, 23 Aug 2026 01:15:36 +0300 Subject: [PATCH] =?UTF-8?q?feat(rtc-service):=20=D1=81=D0=BB=D1=83=D0=B6?= =?UTF-8?q?=D0=B1=D0=B0=20RTC=20=D1=81=20=D1=80=D0=B5=D0=B7=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=BC=20?= =?UTF-8?q?backup-=D1=82=D0=BE=D0=BC=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Перенесена из k1921vk028/1921vk028/lib/rtc_service. Ядро rtc_service_core.c зависит только от stddef, string и собственных заголовков; доступ к часам и backup-памяти вынесен в rtc_service_port.h. Порт для K1921VK028 идёт в комплекте и служит образцом для следующего МК. --- c/rtc-service/HELP.md | 68 ++++ c/rtc-service/PORTING.md | 45 +++ c/rtc-service/PROJECT_RELATIONS.md | 39 +++ c/rtc-service/README.md | 53 ++++ c/rtc-service/rtc_service.h | 21 ++ c/rtc-service/rtc_service_core.c | 330 ++++++++++++++++++++ c/rtc-service/rtc_service_port.h | 40 +++ c/rtc-service/rtc_service_port_k1921vk028.c | 127 ++++++++ c/rtc-service/rtc_service_types.h | 51 +++ 9 files changed, 774 insertions(+) create mode 100644 c/rtc-service/HELP.md create mode 100644 c/rtc-service/PORTING.md create mode 100644 c/rtc-service/PROJECT_RELATIONS.md create mode 100644 c/rtc-service/README.md create mode 100644 c/rtc-service/rtc_service.h create mode 100644 c/rtc-service/rtc_service_core.c create mode 100644 c/rtc-service/rtc_service_port.h create mode 100644 c/rtc-service/rtc_service_port_k1921vk028.c create mode 100644 c/rtc-service/rtc_service_types.h diff --git a/c/rtc-service/HELP.md b/c/rtc-service/HELP.md new file mode 100644 index 0000000..4db1726 --- /dev/null +++ b/c/rtc-service/HELP.md @@ -0,0 +1,68 @@ +# HELP — RTC Service + +## Что делает модуль + +Поддерживает: +- инициализацию RTC; +- валидацию даты/времени; +- сохранение последнего корректного времени в резервной области NVR; +- восстановление времени после перезагрузки по backup-данным. + +## API + +```c +RtcService_Result RtcService_Init(const RtcService_InitConfig* cfg, RtcService_Status* status); +RtcService_Result RtcService_GetDateTime(RtcService_DateTime* dt); +RtcService_Result RtcService_SetDateTime(const RtcService_DateTime* dt); +const char* RtcService_ResultToText(RtcService_Result res); +``` + +## Пример использования + +```c +RtcService_InitConfig cfg = { + .preferred_clock_source = RTC_SERVICE_CLOCK_SOURCE_AUTO, + .fallback_datetime = { + .year = 25, .month = 1, .day = 1, + .weekday = 2, .hour = 12, .minute = 0, .second = 0 + } +}; + +RtcService_Status status = {0}; +RtcService_Result res = RtcService_Init(&cfg, &status); +``` + +```c +RtcService_DateTime now; +if (RtcService_GetDateTime(&now) == RTC_SERVICE_RESULT_OK) { + // now готов для чтения полей +} +``` + +## Конфигурация + +1. Выберите `RtcService_InitConfig`: + - `preferred_clock_source` — пока рекомендуется `RTC_SERVICE_CLOCK_SOURCE_AUTO`; + - `fallback_datetime` — базовая дата на случай первого старта/коррупции. +2. Передайте структуру в `RtcService_Init`. +3. Используйте `RtcService_GetDateTime` для получения времени. +4. Для установки времени — `RtcService_SetDateTime`. + +## Коды ошибок + +`RtcService_ResultToText` возвращает строку: +- `OK` +- `INVALID_ARGUMENT` +- `INVALID_DATETIME` +- `HW_NOT_SUPPORTED` +- `BACKUP_CORRUPT` +- `BACKUP_UNAVAILABLE` +- `FAILED` + +## Ограничения + +- Проверка флага power-on reset в текущей платформенной части пока заглушка; + если нужно строгое поведение, в `rtc_service_port_k1921vk028.c` добавить чтение + реестра причины сброса/статуса. +- Выбор LSE/LSI на текущий момент реализован как `UNSUPPORTED` до появления + подходящего HAL-подхода. diff --git a/c/rtc-service/PORTING.md b/c/rtc-service/PORTING.md new file mode 100644 index 0000000..5e129af --- /dev/null +++ b/c/rtc-service/PORTING.md @@ -0,0 +1,45 @@ +# PORTING — RTC Service + +## Что переносить + +Библиотека строится как: +- переносимое ядро (`rtc_service_core.c`) — без зависимостей от HAL/реестров; +- порт (`rtc_service_port_k1921vk028.c`) — конкретные вызовы `plib028`. + +## Шаги переноса на другой MCU + +1. Создать новый `rtc_service_port_*.c` (и при необходимости `_port.h`). +2. Реализовать все функции из `rtc_service_port.h`: + - `RtcService_Port_InitClock` + - `RtcService_Port_EnableRtcClock` + - `RtcService_Port_IsPowerOnReset` + - `RtcService_Port_ReadRtc` + - `RtcService_Port_WriteRtc` + - `RtcService_Port_ReadBackup` + - `RtcService_Port_WriteBackup` + - `RtcService_Port_EraseBackup` +3. Собрать таблицу соответствий: + - форматы времени; + - единицы дня/месяца/года; + - адреса backup-области и ограничения размера. +4. Пройти чеклист: + - `RtcService_Init` с cold-reset и warm-reset; + - сценарий power-on без сохранённого backup; + - проверка CRC/валидности; + - задание корректной даты при запуске; + - корректная работа `RtcService_SetDateTime`. + +## Что считать обязательным + +- Выделить RAM/flash под backup-blob как минимум на 16 слов. +- Обеспечить атомарность записи backup (erase+write/защита от разрыва). +- Поддержать код возврата: + - ошибки портов; + - недоступность storage; + - отсутствие флага инициализации. + +## Рекомендуемые расширения + +- Реальное определение режима источника (`LSE`/`LSI`/`AUTO`). +- Реальная детекция `power-on reset`. +- Таймауты доступа к Flash. diff --git a/c/rtc-service/PROJECT_RELATIONS.md b/c/rtc-service/PROJECT_RELATIONS.md new file mode 100644 index 0000000..763c263 --- /dev/null +++ b/c/rtc-service/PROJECT_RELATIONS.md @@ -0,0 +1,39 @@ +# PROJECT_RELATIONS.md — RTC Service + +## Роль в проекте + +RTC Service является отдельной переносимой библиотекой времени и восстановления +состояния через NVR (backup) памяти. + +- Переносимое ядро: `rtc_service_core.c` +- Порт к текущему MCU: `rtc_service_port_k1921vk028.c` +- Публичный API: `rtc_service.h`, `rtc_service_types.h` + +## Поток данных + +`main.c` -> `RtcService_Init` -> `rtc_service_core.c` -> `rtc_service_port_*` + +`RtcService_SetDateTime` -> `RtcService_Port_WriteRtc` -> `plib028_rtc` +`RtcService_SetDateTime` -> `RtcService_Port_WriteBackup` -> `plib028_bflash` + +`RtcService_Init` -> `RtcService_Port_ReadBackup` -> `plib028_bflash` +`RtcService_Init` -> `RtcService_Port_ReadRtc` -> `plib028_rtc` + +## Входные/выходные границы + +- `rtc_service_core.c` (переносимый): только через порт-функции. +- `rtc_service_port_k1921vk028.c` (адаптер): напрямую зависит от `plib028_*`. +- `main.c` (приложение): использует только публичный API `rtc_service.h`. + +## Данные и владение + +- `RtcService_Status`, `RtcService_DateTime` и статусы ошибок — во владении + библиотечного API и хранятся во внешнем приложении. +- Backup-blob в NVR (`16` слов) — владелец `rtc_service_core.c`. +- Доступ к RTC/flash только через порт-функции. + +## Важные ограничения + +- `RtcService_Port_IsPowerOnReset` в текущей версии возвращает безопасное + значение по умолчанию (`0`), пока не подтверждена схема чтения флага сброса. +- Автовыбор источника RTC (`AUTO`) выбран из-за ограничений текущего PLIB API. diff --git a/c/rtc-service/README.md b/c/rtc-service/README.md new file mode 100644 index 0000000..664a30a --- /dev/null +++ b/c/rtc-service/README.md @@ -0,0 +1,53 @@ +# RTC Service + +Независимый модуль для управления RTC с резервированным backup-томом на `K1921VK028`. + +- Ядро: `rtc_service_core.c` +- Порт HAL: `rtc_service_port_k1921vk028.c` +- Публичный API: `rtc_service.h`, `rtc_service_types.h`, `rtc_service_port.h` + +## Быстрый старт + +```c +#include "rtc_service.h" + +RtcService_InitConfig cfg = { + .preferred_clock_source = RTC_SERVICE_CLOCK_SOURCE_AUTO, + .fallback_datetime = { + .year = 25, .month = 1, .day = 1, + .weekday = 3, .hour = 0, .minute = 0, .second = 0 + } +}; +RtcService_Status status; +RtcService_Init(&cfg, &status); +``` + +Структура файлов: + +``` +lib/rtc_service/ + ├─ rtc_service_types.h + ├─ rtc_service.h + ├─ rtc_service_port.h + ├─ rtc_service_port_k1921vk028.c + ├─ rtc_service_core.c + ├─ README.md + ├─ PROJECT_RELATIONS.md + ├─ HELP.md + └─ PORTING.md +``` + +## Интеграция + +Для текущего проекта: + +1. В `main/app/main.c` вызвать `RtcService_Init(...)` из `periph_init`. +2. Считать время `RtcService_GetDateTime(...)` где нужно. +3. Для изменения времени использовать `RtcService_SetDateTime(...)`. + +## Полезные ссылки + +- Обязательные правила: [TASK_RULES.md](../../TASK_RULES.md) +- Детальные связи: [PROJECT_RELATIONS.md](./PROJECT_RELATIONS.md) +- Пользовательская документация: [HELP.md](./HELP.md) +- Переход на другую платформу: [PORTING.md](./PORTING.md) diff --git a/c/rtc-service/rtc_service.h b/c/rtc-service/rtc_service.h new file mode 100644 index 0000000..3edf7b5 --- /dev/null +++ b/c/rtc-service/rtc_service.h @@ -0,0 +1,21 @@ +/** + **************************************************************************** + * @file rtc_service.h + * @brief Портируемый модуль RTC Service. + **************************************************************************** + */ + +#ifndef __RTC_SERVICE_H +#define __RTC_SERVICE_H + +#include "rtc_service_types.h" + +RtcService_Result RtcService_Init(const RtcService_InitConfig* cfg, RtcService_Status* status); + +RtcService_Result RtcService_GetDateTime(RtcService_DateTime* dt); + +RtcService_Result RtcService_SetDateTime(const RtcService_DateTime* dt); + +const char* RtcService_ResultToText(RtcService_Result res); + +#endif /* __RTC_SERVICE_H */ diff --git a/c/rtc-service/rtc_service_core.c b/c/rtc-service/rtc_service_core.c new file mode 100644 index 0000000..d204a8c --- /dev/null +++ b/c/rtc-service/rtc_service_core.c @@ -0,0 +1,330 @@ +/** + **************************************************************************** + * @file rtc_service_core.c + * @brief Ядро RTC Service. Портируемая логика и валидация. + **************************************************************************** + */ + +#include "rtc_service.h" +#include "rtc_service_port.h" + +#include +#include + +#define RTC_SERVICE_MAGIC 0x52544353UL /* "RTCS" */ +#define RTC_SERVICE_RECORD_VERSION 0x01UL +#define RTC_SERVICE_BACKUP_WORDS 16UL +#define RTC_SERVICE_BACKUP_DATA_WORDS 11UL + +typedef struct { + uint32_t data[RTC_SERVICE_BACKUP_DATA_WORDS]; +} RtcService_BackupPayload; + +typedef union { + uint32_t raw[RTC_SERVICE_BACKUP_WORDS]; + struct { + uint32_t magic; + uint32_t version; + uint32_t crc32; + uint32_t payload[RTC_SERVICE_BACKUP_DATA_WORDS]; + } f; +} RtcService_BackupRecord; + +static uint32_t s_last_backup_seq = 0U; + +static uint8_t is_leap_year(uint16_t year) +{ + /* year in range [0..99] interpreted as 2000..2099 */ + uint16_t full_year = (uint16_t)(2000U + year); + if ((full_year % 4U) != 0U) + return 0U; + if ((full_year % 100U) == 0U) + return (full_year % 400U) == 0U; + return 1U; +} + +static uint8_t days_in_month(uint16_t year, uint16_t month) +{ + static const uint8_t dim[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (month < 1U || month > 12U) + return 0U; + if (month != 2U) + return dim[month - 1U]; + return is_leap_year(year) ? 29U : 28U; +} + +static uint8_t rtc_service_is_datetime_valid(const RtcService_DateTime* dt) +{ + if (dt == NULL) + return 0U; + + if (dt->year > 99U || dt->month < 1U || dt->month > 12U) + return 0U; + + { + uint8_t dim = days_in_month(dt->year, dt->month); + if (dt->day == 0U || dt->day > dim) + return 0U; + } + + if (dt->hour > 23U || dt->minute > 59U || dt->second > 59U) + return 0U; + + if (dt->weekday < 1U || dt->weekday > 7U) + return 0U; + + return 1U; +} + +static uint32_t crc32_fletcher(const uint32_t* data, size_t words) +{ + uint32_t a = 0xFFFFU; + uint32_t b = 0xFFFFU; + for (size_t i = 0U; i < words; i++) { + uint32_t w = data[i]; + a = (a + ((w >> 0U) & 0xFFU)) & 0xFFFFU; + b = (b + a) & 0xFFFFU; + a = (a + ((w >> 8U) & 0xFFU)) & 0xFFFFU; + b = (b + a) & 0xFFFFU; + a = (a + ((w >> 16U) & 0xFFU)) & 0xFFFFU; + b = (b + a) & 0xFFFFU; + a = (a + ((w >> 24U) & 0xFFU)) & 0xFFFFU; + b = (b + a) & 0xFFFFU; + } + return (b << 16U) | a; +} + +static void rtc_service_pack_payload(const RtcService_DateTime* dt, uint32_t seq, RtcService_BackupPayload* payload) +{ + memset(payload, 0, sizeof(*payload)); + payload->data[0U] = RTC_SERVICE_MAGIC; + payload->data[1U] = seq; + payload->data[2U] = RTC_SERVICE_RECORD_VERSION; + payload->data[3U] = (uint32_t)dt->year; + payload->data[4U] = ((uint32_t)dt->month << 16U) | dt->day; + payload->data[5U] = ((uint32_t)dt->hour << 16U) | dt->minute; + payload->data[6U] = ((uint32_t)dt->weekday << 16U) | dt->second; +} + +static void rtc_service_unpack_payload(const RtcService_BackupPayload* payload, RtcService_DateTime* dt, uint32_t* seq) +{ + if (dt != NULL) { + dt->year = (uint16_t)(payload->data[3U] & 0xFFFFU); + dt->month = (uint16_t)((payload->data[4U] >> 16U) & 0xFFFFU); + dt->day = (uint16_t)(payload->data[4U] & 0xFFFFU); + dt->hour = (uint16_t)((payload->data[5U] >> 16U) & 0xFFFFU); + dt->minute = (uint16_t)(payload->data[5U] & 0xFFFFU); + dt->weekday = (uint16_t)((payload->data[6U] >> 16U) & 0xFFFFU); + dt->second = (uint16_t)(payload->data[6U] & 0xFFFFU); + } + if (seq != NULL) + *seq = payload->data[1U]; +} + +static RtcService_Result rtc_service_load_backup(RtcService_BackupPayload* payload, uint32_t* seq) +{ + RtcService_BackupRecord rec; + if (payload == NULL) + return RTC_SERVICE_RESULT_INVALID_ARGUMENT; + + if (RtcService_Port_ReadBackup(rec.raw, RTC_SERVICE_BACKUP_WORDS) != RTC_SERVICE_PORT_OK) + return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; + + if (rec.f.magic != RTC_SERVICE_MAGIC) + return RTC_SERVICE_RESULT_BACKUP_CORRUPT; + + if (rec.f.version != RTC_SERVICE_RECORD_VERSION) + return RTC_SERVICE_RESULT_BACKUP_CORRUPT; + + { + uint32_t calc_crc = crc32_fletcher(rec.f.payload, RTC_SERVICE_BACKUP_DATA_WORDS); + if (calc_crc != rec.f.crc32) + return RTC_SERVICE_RESULT_BACKUP_CORRUPT; + } + + memcpy(payload->data, rec.f.payload, sizeof(rec.f.payload)); + if (seq != NULL) + *seq = payload->data[1U]; + + return RTC_SERVICE_RESULT_OK; +} + +static RtcService_Result rtc_service_store_backup(const RtcService_DateTime* dt, uint32_t seq) +{ + RtcService_BackupRecord rec; + RtcService_BackupPayload payload; + memset(&rec, 0, sizeof(rec)); + + rtc_service_pack_payload(dt, seq, &payload); + + rec.f.magic = RTC_SERVICE_MAGIC; + rec.f.version = RTC_SERVICE_RECORD_VERSION; + rec.f.crc32 = crc32_fletcher(payload.data, RTC_SERVICE_BACKUP_DATA_WORDS); + memcpy(rec.f.payload, payload.data, sizeof(payload.data)); + + if (RtcService_Port_EraseBackup() != RTC_SERVICE_PORT_OK) + return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; + + if (RtcService_Port_WriteBackup(rec.raw, RTC_SERVICE_BACKUP_WORDS) != RTC_SERVICE_PORT_OK) + return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; + + return RTC_SERVICE_RESULT_OK; +} + +static RtcService_Result rtc_service_sanitize_weekday(RtcService_DateTime* dt) +{ + if (dt == NULL) + return RTC_SERVICE_RESULT_INVALID_ARGUMENT; + + if (!rtc_service_is_datetime_valid(dt)) + return RTC_SERVICE_RESULT_INVALID_DATETIME; + + /* Если weekday некорректен, корректно пересчитываем по Zeller (для 2000..2099). */ + if (dt->weekday < 1U || dt->weekday > 7U) { + int16_t q = (int16_t)dt->day; + int16_t m = (int16_t)dt->month; + int16_t Y = 2000 + (int16_t)dt->year; + if (m < 3) { + m += 12; + Y -= 1; + } + { + int16_t K = Y % 100; + int16_t J = Y / 100; + int16_t h = (q + (13 * (m + 1)) / 5 + K + K / 4 + J / 4 + 5 * J) % 7; + dt->weekday = (uint16_t)((h + 5U) % 7U + 1U); + } + } + + return RTC_SERVICE_RESULT_OK; +} + +RtcService_Result RtcService_Init(const RtcService_InitConfig* cfg, RtcService_Status* status) +{ + if (cfg == NULL || status == NULL) + return RTC_SERVICE_RESULT_INVALID_ARGUMENT; + + memset(status, 0, sizeof(*status)); + s_last_backup_seq = 0U; + + { + RtcService_PortResult pres = RtcService_Port_InitClock(cfg->preferred_clock_source, + &status->active_clock_source); + if (pres != RTC_SERVICE_PORT_OK) { + if (pres == RTC_SERVICE_PORT_RESULT_UNSUPPORTED_CLOCK) + return RTC_SERVICE_RESULT_HW_NOT_SUPPORTED; + return RTC_SERVICE_RESULT_FAILED; + } + } + + RtcService_Port_EnableRtcClock(); + + { + RtcService_DateTime current = {0}; + RtcService_PortResult pres = RtcService_Port_ReadRtc(¤t); + if (pres == RTC_SERVICE_PORT_OK) { + status->initialized = rtc_service_is_datetime_valid(¤t); + } + } + + status->valid_reset = RtcService_Port_IsPowerOnReset(); + + RtcService_BackupPayload payload; + memset(&payload, 0, sizeof(payload)); + RtcService_DateTime backup_dt = {0}; + uint32_t seq = 0U; + RtcService_Result bres = rtc_service_load_backup(&payload, &seq); + uint8_t has_valid_backup = 0U; + + if ((bres == RTC_SERVICE_RESULT_OK)) { + RtcService_DateTime unpacked = {0}; + rtc_service_unpack_payload(&payload, &unpacked, NULL); + if (rtc_service_is_datetime_valid(&unpacked)) { + backup_dt = unpacked; + has_valid_backup = 1U; + } + } + + s_last_backup_seq = seq; + + if (has_valid_backup != 0U) { + if ((status->valid_reset != 0U) || (status->initialized == 0U)) { + status->restored_from_backup = 1U; + if (RtcService_SetDateTime(&backup_dt) != RTC_SERVICE_RESULT_OK) + return RTC_SERVICE_RESULT_FAILED; + return RTC_SERVICE_RESULT_OK; + } + return RTC_SERVICE_RESULT_OK; + } + + if (RtcService_SetDateTime(&cfg->fallback_datetime) != RTC_SERVICE_RESULT_OK) + return RTC_SERVICE_RESULT_INVALID_DATETIME; + + if (rtc_service_store_backup(&cfg->fallback_datetime, s_last_backup_seq + 1U) != RTC_SERVICE_RESULT_OK) { + return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; + } + s_last_backup_seq += 1U; + status->initialized = 1U; + + return RTC_SERVICE_RESULT_OK; +} + +RtcService_Result RtcService_GetDateTime(RtcService_DateTime* dt) +{ + if (dt == NULL) + return RTC_SERVICE_RESULT_INVALID_ARGUMENT; + + if (RtcService_Port_ReadRtc(dt) != RTC_SERVICE_PORT_OK) + return RTC_SERVICE_RESULT_FAILED; + + if (!rtc_service_is_datetime_valid(dt)) + return RTC_SERVICE_RESULT_INVALID_DATETIME; + + return RTC_SERVICE_RESULT_OK; +} + +RtcService_Result RtcService_SetDateTime(const RtcService_DateTime* dt) +{ + RtcService_Result res; + RtcService_DateTime normalized = {0}; + + if (dt == NULL) + return RTC_SERVICE_RESULT_INVALID_ARGUMENT; + + memcpy(&normalized, dt, sizeof(normalized)); + res = rtc_service_sanitize_weekday(&normalized); + if (res != RTC_SERVICE_RESULT_OK) + return res; + + if (!rtc_service_is_datetime_valid(&normalized)) + return RTC_SERVICE_RESULT_INVALID_DATETIME; + + if (RtcService_Port_WriteRtc(&normalized) != RTC_SERVICE_PORT_OK) + return RTC_SERVICE_RESULT_FAILED; + + if (rtc_service_store_backup(&normalized, s_last_backup_seq + 1U) == RTC_SERVICE_RESULT_OK) { + s_last_backup_seq += 1U; + } + + return RTC_SERVICE_RESULT_OK; +} + +const char* RtcService_ResultToText(RtcService_Result res) +{ + switch (res) { + case RTC_SERVICE_RESULT_OK: + return "OK"; + case RTC_SERVICE_RESULT_INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case RTC_SERVICE_RESULT_INVALID_DATETIME: + return "INVALID_DATETIME"; + case RTC_SERVICE_RESULT_HW_NOT_SUPPORTED: + return "HW_NOT_SUPPORTED"; + case RTC_SERVICE_RESULT_BACKUP_CORRUPT: + return "BACKUP_CORRUPT"; + case RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE: + return "BACKUP_UNAVAILABLE"; + default: + return "FAILED"; + } +} diff --git a/c/rtc-service/rtc_service_port.h b/c/rtc-service/rtc_service_port.h new file mode 100644 index 0000000..cf9465d --- /dev/null +++ b/c/rtc-service/rtc_service_port.h @@ -0,0 +1,40 @@ +/** + **************************************************************************** + * @file rtc_service_port.h + * @brief Адаптерная прослойка для RTC Service. + **************************************************************************** + */ + +#ifndef __RTC_SERVICE_PORT_H +#define __RTC_SERVICE_PORT_H + +#include +#include + +#include "rtc_service_types.h" + +typedef enum { + RTC_SERVICE_PORT_OK = 0, + RTC_SERVICE_PORT_RESULT_UNSUPPORTED_CLOCK, + RTC_SERVICE_PORT_RESULT_HW_ERROR, + RTC_SERVICE_PORT_RESULT_NO_STORAGE, +} RtcService_PortResult; + +RtcService_PortResult RtcService_Port_InitClock(RtcService_ClockSource preferred_source, + RtcService_ClockSource* actual_source); + +void RtcService_Port_EnableRtcClock(void); + +uint8_t RtcService_Port_IsPowerOnReset(void); + +RtcService_PortResult RtcService_Port_ReadRtc(RtcService_DateTime* dt); + +RtcService_PortResult RtcService_Port_WriteRtc(const RtcService_DateTime* dt); + +RtcService_PortResult RtcService_Port_ReadBackup(uint32_t* buffer, size_t words); + +RtcService_PortResult RtcService_Port_WriteBackup(const uint32_t* buffer, size_t words); + +RtcService_PortResult RtcService_Port_EraseBackup(void); + +#endif /* __RTC_SERVICE_PORT_H */ diff --git a/c/rtc-service/rtc_service_port_k1921vk028.c b/c/rtc-service/rtc_service_port_k1921vk028.c new file mode 100644 index 0000000..bafe9eb --- /dev/null +++ b/c/rtc-service/rtc_service_port_k1921vk028.c @@ -0,0 +1,127 @@ +/** + **************************************************************************** + * @file rtc_service_port_k1921vk028.c + * @brief Порт RTC Service для семейства K1921VK028 (plib028). + **************************************************************************** + */ + +#include "rtc_service_port.h" +#include "rtc_service_types.h" + +#include "plib028_bflash.h" +#include "plib028_rtc.h" +#include "plib028_rcu.h" + +#include +#include + +/* Вывод: в данном HAL API явного выбора источника LSE/LSI на уровне C API нет, + поэтому конфигурирование делается как AUTO с флагом активного источника AUTO. */ + +RtcService_PortResult RtcService_Port_InitClock(RtcService_ClockSource preferred_source, + RtcService_ClockSource* actual_source) +{ + if (actual_source != NULL) { + *actual_source = RTC_SERVICE_CLOCK_SOURCE_AUTO; + } + + if (preferred_source != RTC_SERVICE_CLOCK_SOURCE_AUTO) { + /* На текущей ревизии драйвер NIIЭТ не предоставляет стабильных API для ручного + * выбора LSE/LSI через публичные функции. Рекомендуется использовать AUTO. */ + return RTC_SERVICE_PORT_RESULT_UNSUPPORTED_CLOCK; + } + + /* Включение тактирования RTC в RCC */ + RCU_APBClk0Cmd(RCU_APBClk0_RTC, ENABLE); + + return RTC_SERVICE_PORT_OK; +} + +void RtcService_Port_EnableRtcClock(void) +{ + /* На K1921VK028 дополнительной явной подготовки источника после включения тактирования + * RTC/APB-блока, как правило, не требуется. Реализация оставлена явной для port API. */ + (void)0; +} + +uint8_t RtcService_Port_IsPowerOnReset(void) +{ + /* TODO: при наличии отдельного API/регистра статуса сброса заменить на реальную проверку. + * Безопасная дефолтная стратегия — считать, что это не cold reset. */ + return 0U; +} + +RtcService_PortResult RtcService_Port_ReadRtc(RtcService_DateTime* dt) +{ + if (dt == NULL) + return RTC_SERVICE_PORT_RESULT_HW_ERROR; + + RTC_Time_TypeDef t = {0}; + RTC_Date_TypeDef d = {0}; + + RTC_GetTime(RTC_Format_BIN, &t); + RTC_GetDate(RTC_Format_BIN, &d); + + dt->year = (uint16_t)d.Year; + dt->month = (uint16_t)d.Month; + dt->day = (uint16_t)d.Day; + dt->weekday = (uint16_t)d.Weekday; + dt->hour = (uint16_t)t.Hour; + dt->minute = (uint16_t)t.Minute; + dt->second = (uint16_t)t.Second; + + return RTC_SERVICE_PORT_OK; +} + +RtcService_PortResult RtcService_Port_WriteRtc(const RtcService_DateTime* dt) +{ + RTC_Time_TypeDef t = {0}; + RTC_Date_TypeDef d = {0}; + + if (dt == NULL) + return RTC_SERVICE_PORT_RESULT_HW_ERROR; + + t.PSecond = 0U; + t.Hour = dt->hour; + t.Minute = dt->minute; + t.Second = dt->second; + d.Year = dt->year; + d.Month = dt->month; + d.Day = dt->day; + d.Weekday = (RTC_Weekday_TypeDef)dt->weekday; + + RTC_SetTime(RTC_Format_BIN, &t); + RTC_SetDate(RTC_Format_BIN, &d); + RTC_ShadowCmd(DISABLE); + RTC_ShadowCmd(ENABLE); + + return RTC_SERVICE_PORT_OK; +} + +RtcService_PortResult RtcService_Port_ReadBackup(uint32_t* buffer, size_t words) +{ + if ((buffer == NULL) || (words == 0U)) + return RTC_SERVICE_PORT_RESULT_HW_ERROR; + + for (size_t i = 0U; i < words; ++i) { + BFLASH_ReadData((uint32_t)i, &buffer[i], BFLASH_Region_NVR); + } + return RTC_SERVICE_PORT_OK; +} + +RtcService_PortResult RtcService_Port_WriteBackup(const uint32_t* buffer, size_t words) +{ + if ((buffer == NULL) || (words == 0U)) + return RTC_SERVICE_PORT_RESULT_HW_ERROR; + + for (size_t i = 0U; i < words; ++i) { + BFLASH_WriteData((uint32_t)i, (uint32_t*)&buffer[i], BFLASH_Region_NVR); + } + return RTC_SERVICE_PORT_OK; +} + +RtcService_PortResult RtcService_Port_EraseBackup(void) +{ + BFLASH_ErasePage(0UL, BFLASH_Region_NVR); + return RTC_SERVICE_PORT_OK; +} diff --git a/c/rtc-service/rtc_service_types.h b/c/rtc-service/rtc_service_types.h new file mode 100644 index 0000000..358bf79 --- /dev/null +++ b/c/rtc-service/rtc_service_types.h @@ -0,0 +1,51 @@ +/** + **************************************************************************** + * @file rtc_service_types.h + * @brief Общие типы для модуля RTC Service. + **************************************************************************** + */ + +#ifndef __RTC_SERVICE_TYPES_H +#define __RTC_SERVICE_TYPES_H + +#include + +typedef enum { + RTC_SERVICE_CLOCK_SOURCE_AUTO = 0, + RTC_SERVICE_CLOCK_SOURCE_LSE, + RTC_SERVICE_CLOCK_SOURCE_LSI, +} RtcService_ClockSource; + +typedef enum { + RTC_SERVICE_RESULT_OK = 0, + RTC_SERVICE_RESULT_INVALID_ARGUMENT, + RTC_SERVICE_RESULT_INVALID_DATETIME, + RTC_SERVICE_RESULT_HW_NOT_SUPPORTED, + RTC_SERVICE_RESULT_BACKUP_CORRUPT, + RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE, + RTC_SERVICE_RESULT_FAILED, +} RtcService_Result; + +typedef struct { + uint16_t year; + uint16_t month; + uint16_t day; + uint16_t weekday; // 1..7, 1=понедельник + uint16_t hour; + uint16_t minute; + uint16_t second; +} RtcService_DateTime; + +typedef struct { + RtcService_ClockSource preferred_clock_source; + RtcService_DateTime fallback_datetime; +} RtcService_InitConfig; + +typedef struct { + uint8_t initialized; + uint8_t restored_from_backup; + uint8_t valid_reset; + RtcService_ClockSource active_clock_source; +} RtcService_Status; + +#endif /* __RTC_SERVICE_TYPES_H */