Files
templates/c/global-time/tests/test_global_time.c
T
Andrey 2da7e7b525 feat(global-time): Добавь общие часы и порт TMS320F28335
Вынеси программные часы, коррекцию по ProtoCAN RTCSETUP и атомарную метку логгера в переносимое ядро без глобального состояния.

Подключи eCAN-A и CPU Timer2 отдельным портом F28335; зафиксируй календарные границы, переполнение, задержку RX и защиту IRQ в хостовых тестах.
2026-10-23 14:16:21 +03:00

146 lines
22 KiB
C

#include "global_time.h" /* Проверяет настоящий переносимый код часов без подмены его реализации. */
#ifdef NDEBUG /* Проверки должны выполняться и при Release-сборке CMake. */
#undef NDEBUG /* Сохраняет assert активным независимо от выбранного уровня оптимизации. */
#endif /* Завершает принудительное включение проверяющих утверждений. */
#include <assert.h> /* Останавливает тест при нарушении временного или проводного контракта. */
#include <stdio.h> /* Выводит компактное подтверждение выполнения набора проверок. */
#include <string.h> /* Позволяет воспроизвести ненулевую RAM после тёплого старта. */
static uint16_t irq_mask, depth; /* Моделирует исходную маску IRQ и вложенность критических секций. */
static int accepted, sends; /* Управляет занятостью транспорта и считает попытки объявления готовности. */
static gt_frame sent; /* Сохраняет последний кадр для независимой проверки ID и payload. */
static uint16_t enter(void *user) /* Моделирует TI intrinsic с сохранением предыдущего INTM. */
{ /* Проверяет, что ядро пользуется единым механизмом защиты состояния. */
uint16_t previous = irq_mask; /* Сохраняет маску до входа в текущий уровень блокировки. */
assert(user == &sends); /* Подтверждает неизменную передачу пользовательского контекста. */
irq_mask = 1U; /* Запрещает модельным прерываниям менять данные до leave. */
++depth; /* Отмечает вложенный уровень для контроля парности входа и выхода. */
return previous; /* Возвращает исходное состояние, включая вызов из уже закрытого ISR. */
} /* Завершает модель входа в критическую секцию. */
static void leave(void *user, uint16_t previous) /* Проверяет восстановление предыдущей маски без безусловного разрешения. */
{ /* Делает нарушения вложенности видимыми в host-тесте. */
assert(user == &sends && depth > 0U && irq_mask == 1U); /* Выход допустим только из реально захваченной секции. */
--depth; /* Освобождает один уровень модельной блокировки. */
irq_mask = previous; /* Возвращает именно исходную маску вызывающего кода. */
} /* Завершает проверяемую модель выхода. */
static int send(void *user, const gt_frame *frame) /* Моделирует неблокирующую передачу и копирование временного payload. */
{ /* Callback не должен выполняться с длинной критической секцией ядра. */
assert(user == &sends && depth == 0U); /* Проверяет передачу контекста и отсутствие блокировки IRQ на время отправки. */
++sends; /* Считает и принятые, и отклонённые транспортом попытки. */
sent = *frame; /* Сохраняет независимую копию до возврата callback. */
return accepted; /* Имитирует занятость mailbox без ожидания. */
} /* Завершает модель транспорта. */
static void advance(gt_clock *clock, uint32_t milliseconds) /* Продвигает реальный алгоритм через границы календарных полей. */
{ /* Пошаговый вызов также проверяет миллисекундные переносы в ISR. */
while (milliseconds--) gt_tick_1ms(clock); /* Не подменяет поведение таймера прямой правкой абсолютного времени. */
} /* Завершает управляемое продвижение времени. */
static gt_frame rtc(uint16_t year, uint16_t month, uint16_t day, uint16_t hour, uint16_t minute, uint16_t second) /* Строит календарный эталон кодировщиком верхнего уровня. */
{ /* Ожидаемые значения времени в тестах проверяются отдельно от кодировщика. */
gt_datetime date = {year, month, day, 6U, hour, minute, second, 0U}; /* День недели сохраняется как допустимая фаза цикла, исходная суббота=6. */
gt_frame frame; /* Получает полноценный расширенный CAN кадр. */
assert(gt_make_rtc(&date, &frame)); /* Корректный календарь обязан кодироваться в существующие семь октетов. */
return frame; /* Возвращает независимый контейнер для изменения ошибочных полей. */
} /* Завершает построение тестовых опорных кадров. */
int main(void) /* Проверяет календарь, коррекции, задержки, переполнения и стартовый CAN-обмен. */
{ /* Использует только публичный API, кроме целевой установки границы uptime. */
gt_clock clock; /* Состояние заполняется мусором, имитируя startup без очистки .ebss. */
gt_port port = {&sends, enter, leave, send}; /* Подключает проверяющие блокировки и управляемый транспорт. */
gt_stamp stamp, before; /* Два атомарных снимка позволяют проверять отсутствие побочных изменений при ошибке. */
gt_datetime date; /* Календарный результат проверяется по известным границам дат. */
gt_frame frame, bad; /* Исходный корректный кадр и копия для отрицательных проверок. */
uint16_t type, id, index; /* Полученные адресные поля и индекс проверки каждого октета. */
const uint16_t invalid[7] = {24U, 60U, 60U, 100U, 0U, 0U, 7U}; /* Независимые граничные ошибки каждого календарного поля на линии. */
memset(&clock, 0xA5, sizeof(clock)); /* Воспроизводит остатки RAM вместо неявной инициализации нулями хоста. */
assert(!gt_init(&clock, &port, 8U, 0U)); /* Запрещает трёхбитному типу переполниться в направление Route. */
assert(!gt_init(&clock, &port, 1U, 16U)); /* Запрещает молчаливое обрезание экземпляра до адреса другой платы. */
assert(gt_init(&clock, &port, 1U, 5U)); /* Создаёт УМП1 по действующему реестру PERIPH. */
gt_get(&clock, &stamp); /* Проверяет результат явной инициализации всей временной базы. */
assert(stamp.seconds == 0UL && stamp.uptime_ms == 0UL && !stamp.synchronized && stamp.generation == 0UL); /* До синхронизации календарь не должен выглядеть достоверным. */
assert(!gt_calendar(&stamp, &date)); /* Не позволяет приложению выдавать локальную эпоху за полученную дату. */
advance(&clock, 1001UL); /* Пересекает первую секундную границу программного таймера. */
gt_get(&clock, &stamp); /* Снимает дробную часть после переноса секунды. */
assert(stamp.seconds == 1UL && stamp.millisecond == 1U && stamp.uptime_ms == 1001UL); /* Проверяет ровно 1000 тиков на секунду без ошибки на единицу. */
gt_poll(&clock); /* Пытается обслужить отправку до завершения инициализации платы. */
assert(sends == 0); /* Пакет готовности запрещён до явного ready. */
gt_ready(&clock); /* Разрешает стартовый пакет только после прикладной готовности. */
gt_poll(&clock); /* Имитирует занятую CAN передачу при accepted=0. */
assert(sends == 1 && !clock.announced); /* Занятость транспорта не теряет обязательный пакет инициализации. */
accepted = 1; /* Освобождает транспорт на следующем проходе main. */
gt_poll(&clock); /* Повторяет отклонённую попытку без задержки и блокировки IRQ. */
assert(sends == 2 && sent.id == 0x19513000UL && sent.dlc == 1U && sent.data[0] == 1U); /* Фиксирует проводной эталон READY узла Type=1, ID=5. */
assert(gt_parse_ready(&sent, &type, &id) && type == 1U && id == 5U); /* Мастер должен узнать физический адрес стартовавшей платы. */
bad = sent; /* Готовит отрицательный вариант стартового сообщения. */
bad.id ^= 0x08000000UL; /* Меняет направление на запрос мастера, сохранив payload. */
assert(!gt_parse_ready(&bad, 0, 0)); /* Исключает распознавание собственного запроса как готовности платы. */
advance(&clock, 999UL); /* Проверяет ограничение частоты повторов перед секундной границей. */
gt_poll(&clock); /* Обслуживает CAN на последней миллисекунде запрета повтора. */
assert(sends == 2); /* Готовность не должна засорять шину на каждом проходе main. */
advance(&clock, 1UL); /* Достигает точно секундного интервала повторной отправки. */
gt_poll(&clock); /* Повторяет событие старта при отсутствии ответа временем. */
assert(sends == 3); /* Потерянный ответ мастера должен приводить к повтору объявления готовности. */
frame = rtc(2024U, 2U, 28U, 23U, 59U, 59U); /* Задаёт известную границу високосного февраля. */
assert(frame.id == 0x30UL && frame.dlc == 7U && frame.data[3] == 24U && frame.data[4] == 2U); /* Проверяет совместимый ID и календарный wire format. */
assert(gt_receive(&clock, &frame, clock.stamp.uptime_ms)); /* Устанавливает первую реальную календарную дату. */
advance(&clock, 1001UL); /* Пересекает полночь на один миллисекундный тик. */
gt_get(&clock, &stamp); /* Снимает календарную метку после перехода даты. */
assert(gt_calendar(&stamp, &date) && date.year == 2024U && date.month == 2U && date.day == 29U && date.hour == 0U && date.millisecond == 1U); /* Проверяет 29 февраля и сброс часов/минут/секунд на полночь. */
assert(stamp.weekday == 0U && stamp.generation == 1UL); /* День недели циклически продвигается, а тики не создают новые поколения коррекции. */
assert(gt_timestamp_ms(&stamp) > UINT32_MAX); /* Календарные миллисекунды остаются широкими и не обрезаются до uptime. */
advance(&clock, 2000UL); /* Даёт мастеру время ответить; уже синхронизированный узел не повторяет READY. */
gt_poll(&clock); /* Проверяет прекращение стартовых повторов после RTCSETUP. */
assert(sends == 3); /* Получение календаря завершает повторный запрос времени. */
before = stamp; /* Сохраняет известную метку для проверки отрицательной коррекции. */
frame = rtc(2000U, 1U, 1U, 0U, 0U, 0U); /* Задаёт независимый эталон нулевой секунды эпохи библиотеки. */
assert(gt_receive(&clock, &frame, clock.stamp.uptime_ms)); /* Применяет разрешённый перевод глобальных часов назад. */
gt_get(&clock, &stamp); /* Снимает результат атомарной коррекции. */
assert(stamp.seconds == 0UL && stamp.millisecond == 0U && stamp.correction_ms < 0 && stamp.uptime_ms > before.uptime_ms && stamp.generation == 2UL); /* Проверяет независимость тайм-аутов от отрицательной календарной коррекции. */
assert(gt_timestamp_ms(&stamp) == 0ULL); /* Фиксирует точное начало принятой эпохи. */
before = stamp; /* Сохраняет состояние до серии ошибочных CAN кадров. */
for (index = 0U; index < 7U; ++index) { /* Проверяет наличие лишних старших битов в каждом слове, содержащем октет C28x. */
bad = frame; /* Восстанавливает исходный корректный RTCSETUP перед каждой ошибкой. */
bad.data[index] = 256U; /* Значение вне одного проводного октета не должно обрезаться до нуля. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Отвергает ошибку порта до изменения даты. */
bad = frame; /* Возвращает корректный исходный кадр для проверки допустимого диапазона поля. */
bad.data[index] = invalid[index]; /* Подставляет двоичное, но календарно недопустимое значение. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Исключает нормализацию ошибочного времени и выход за границы таблицы месяцев. */
} /* Завершает проверку всей широкой wire-границы. */
bad = frame; bad.extended = 0U; /* Подготавливает стандартный 11-битный кадр с похожим ID. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* ProtoCAN требует IDE=1 независимо от численного ID. */
bad = frame; bad.remote = 1U; /* Подготавливает RTR без реальных календарных данных. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Удалённый запрос не устанавливает глобальное время. */
bad = frame; bad.dlc = 8U; /* Добавляет неописанный восьмой октет без согласованного расширения протокола. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Длина существующего RTCSETUP должна быть ровно семь октетов. */
bad = frame; bad.id |= 0x01000000UL; /* Имитирует broadcast от чужого типа устройства вместо верхнего уровня. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Исключает коррекцию по произвольным сообщениям другой платы. */
bad = frame; bad.data[4] = 2U; bad.data[5] = 30U; /* Подготавливает несуществующее 30 февраля високосного года. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Календарные ошибки не нормализуются в другую дату. */
bad = frame; bad.data[3] = 23U; bad.data[4] = 2U; bad.data[5] = 29U; /* Подготавливает 29 февраля невисокосного года. */
assert(!gt_receive(&clock, &bad, before.uptime_ms)); /* Отвергает некорректную дату даже при допустимых диапазонах отдельных полей. */
gt_get(&clock, &stamp); /* Проверяет итоговое состояние после всех отклонённых кадров. */
assert(stamp.seconds == before.seconds && stamp.generation == before.generation && stamp.synchronized); /* Неверные пакеты не теряют последнюю достоверную синхронизацию. */
before.seconds = 3160857600UL; /* Независимый календарный эталон: ровно 36584 суток от 2000-01-01 до 2100-03-01. */
assert(gt_calendar(&before, &date) && date.year == 2100U && date.month == 3U && date.day == 1U); /* Проверяет исключение невисокосного 2100 года при продолжении локального хода часов. */
clock.stamp.uptime_ms = UINT32_MAX - 4UL; /* Целевым образом размещает монотонный счётчик около переполнения 49,7 суток. */
advance(&clock, 10UL); /* Пересекает modulo-границу до обработки уже полученного кадра. */
frame.id |= 0x10000000UL; /* Проверяет допустимость стандартного приоритета RTCSETUP наряду с критическим. */
assert(gt_receive(&clock, &frame, UINT32_MAX - 4UL)); /* Передаёт момент RX до переполнения, обработку — после. */
gt_get(&clock, &stamp); /* Снимает скорректированную фазу доли секунды. */
assert(stamp.seconds == 0UL && stamp.millisecond == 10U && stamp.uptime_ms == 5UL); /* Компенсация очереди должна быть 10 мс, независимо от переполнения uptime. */
frame = rtc(2023U, 12U, 31U, 23U, 59U, 59U); /* Проверяет смену календарного года при отложенной обработке. */
before = stamp; /* Запоминает момент RX непосредственно перед задержкой основного цикла. */
advance(&clock, 1250UL); /* Имитирует долгую прикладную операцию при продолжающемся аппаратном таймере. */
assert(gt_receive(&clock, &frame, before.uptime_ms)); /* Применяет время по моменту RX, а не по моменту выхода из очереди. */
gt_get(&clock, &stamp); /* Снимает дату после учёта задержки обработки. */
assert(gt_calendar(&stamp, &date) && date.year == 2024U && date.month == 1U && date.day == 1U && date.second == 0U && date.millisecond == 250U); /* Проверяет перенос секунды, суток и года с сохранением 250 мс. */
irq_mask = 1U; /* Имитирует вызов логгера из ISR с уже запрещёнными прерываниями. */
gt_get(&clock, &stamp); /* Проверяет чтение метки при вложенной критической секции. */
gt_tick_1ms(&clock); /* Проверяет продвижение часов при исходном INTM=1. */
assert(irq_mask == 1U && depth == 0U); /* Выходы не должны случайно разрешить IRQ вызывающего обработчика. */
irq_mask = 0U; /* Восстанавливает модельное окружение main перед новой инициализацией. */
assert(gt_init(&clock, &port, 2U, 15U)); /* Проверяет холодный reset качества и верхний адрес PERIPH31. */
assert(gt_receive(&clock, &frame, 0UL)); /* Имитирует broadcast ещё до прикладного ready. */
gt_ready(&clock); gt_ready(&clock); gt_poll(&clock); /* Повторный ready должен быть идемпотентным, но первая отправка всё равно обязательна. */
assert(sends == 4 && gt_parse_ready(&sent, &type, &id) && type == 2U && id == 15U); /* Ранняя синхронизация не подавляет сообщение об успешной инициализации платы. */
puts("global-time: calendar, CAN, correction, IRQ and rollover OK"); /* Возвращает краткое свидетельство пройденных смысловых сценариев. */
return 0; /* Завершает host-тест только после всех обязательных проверок. */
} /* Завершает независимую проверку переносимого ядра. */