Перенесена из k1921vk028/1921vk028/lib/rtc_service. Ядро rtc_service_core.c зависит только от stddef, string и собственных заголовков; доступ к часам и backup-памяти вынесен в rtc_service_port.h. Порт для K1921VK028 идёт в комплекте и служит образцом для следующего МК.
331 lines
10 KiB
C
331 lines
10 KiB
C
/**
|
|
****************************************************************************
|
|
* @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";
|
|
}
|
|
}
|