feat(rtc-service): служба RTC с резервированным backup-томом
Перенесена из k1921vk028/1921vk028/lib/rtc_service. Ядро rtc_service_core.c зависит только от stddef, string и собственных заголовков; доступ к часам и backup-памяти вынесен в rtc_service_port.h. Порт для K1921VK028 идёт в комплекте и служит образцом для следующего МК.
This commit is contained in:
68
c/rtc-service/HELP.md
Normal file
68
c/rtc-service/HELP.md
Normal file
@@ -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-подхода.
|
||||||
45
c/rtc-service/PORTING.md
Normal file
45
c/rtc-service/PORTING.md
Normal file
@@ -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.
|
||||||
39
c/rtc-service/PROJECT_RELATIONS.md
Normal file
39
c/rtc-service/PROJECT_RELATIONS.md
Normal file
@@ -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.
|
||||||
53
c/rtc-service/README.md
Normal file
53
c/rtc-service/README.md
Normal file
@@ -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)
|
||||||
21
c/rtc-service/rtc_service.h
Normal file
21
c/rtc-service/rtc_service.h
Normal file
@@ -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 */
|
||||||
330
c/rtc-service/rtc_service_core.c
Normal file
330
c/rtc-service/rtc_service_core.c
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
/**
|
||||||
|
****************************************************************************
|
||||||
|
* @file rtc_service_core.c
|
||||||
|
* @brief Ядро RTC Service. Портируемая логика и валидация.
|
||||||
|
****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rtc_service.h"
|
||||||
|
#include "rtc_service_port.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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";
|
||||||
|
}
|
||||||
|
}
|
||||||
40
c/rtc-service/rtc_service_port.h
Normal file
40
c/rtc-service/rtc_service_port.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
****************************************************************************
|
||||||
|
* @file rtc_service_port.h
|
||||||
|
* @brief Адаптерная прослойка для RTC Service.
|
||||||
|
****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __RTC_SERVICE_PORT_H
|
||||||
|
#define __RTC_SERVICE_PORT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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 */
|
||||||
127
c/rtc-service/rtc_service_port_k1921vk028.c
Normal file
127
c/rtc-service/rtc_service_port_k1921vk028.c
Normal file
@@ -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 <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Вывод: в данном 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;
|
||||||
|
}
|
||||||
51
c/rtc-service/rtc_service_types.h
Normal file
51
c/rtc-service/rtc_service_types.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
****************************************************************************
|
||||||
|
* @file rtc_service_types.h
|
||||||
|
* @brief Общие типы для модуля RTC Service.
|
||||||
|
****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __RTC_SERVICE_TYPES_H
|
||||||
|
#define __RTC_SERVICE_TYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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 */
|
||||||
Reference in New Issue
Block a user