Для всех Mode передавать пакеты при обновлении данных и повторять каждый через 1300 мс от последнего CAN ACK. Отдельно отправлять heartbeat раз в секунду. Сохранить маски рассылки, отключение потоков нулевым периодом и неблокирующее восстановление после ошибок TX. Проверены границы периодов, все Mode, занятый mailbox, отмена без CANAA, отсутствие ACK, переполнение Timer1, первый снимок и справедливость потоков. Изменённые модули ecan.c и main.c скомпилированы компилятором TI C2000.
108 lines
23 KiB
C
108 lines
23 KiB
C
#include <stdint.h> /* Фиксирует ширину таймера и аппаратных полей eCAN независимо от разрядности host-компилятора. */
|
|
#include <stdio.h> /* Выводит точную строку нарушенного сценария вместо молчаливого неверного результата. */
|
|
#include <string.h> /* Изолирует независимые случаи очисткой модели аппаратуры и данных. */
|
|
typedef uint16_t Uint16; typedef uint32_t Uint32; /* Повторяет 16- и 32-битные аппаратные типы TI C28x. */
|
|
#define READY_FREQ 1000U /* Один такт Timer1 равен одной миллисекунде, как в действующей прошивке 166. */
|
|
#define CANPOWSE 20U /* Прежний базовый интервал обнаружения обновлений данных составляет 20 мс. */
|
|
#define ANSWER_LEN 128 /* Как в package.h, технологическая карта содержит ровно R0–R127 без дополнительного запаса для ошибочного чтения. */
|
|
#define interrupt /* Host-модель вызывает настоящий ISR как обычную функцию без атрибутов процессора TI. */
|
|
#define EALLOW ((void)0) /* Защищённый доступ аппаратных регистров отдельно проверяется настоящим компилятором TI. */
|
|
#define EDIS ((void)0) /* Модель не подменяет логику изменения битов тайм-аута mailbox. */
|
|
#define EINT ((void)0) /* Тесты явно выбирают момент аппаратного прерывания, исключая недетерминированные потоки. */
|
|
#define DINT ((void)0) /* Сценарий проверяет восстановление маски, не симулируя планировщик ОС. */
|
|
#define M_INT9 0x0100U /* Девятая группа PIE обслуживает eCAN в production-обработчике. */
|
|
#define MINT9 0x0100U /* Сохраняет аппаратную маску CPU INT9 при обработке ошибки передачи. */
|
|
#define MG96 0x00DFU /* Маска соответствует подавлению вложенного INTx6 в старом ISR проекта. */
|
|
struct REG { Uint32 all; }; /* Даёт production-коду настоящие 32-битные поля регистров eCAN. */
|
|
struct ECAN_REGS { struct REG CANTA, CANAA, CANTRR, CANTRS, CANTOC, CANTOS; Uint32 CANTSC; }; /* Моделирует только используемый жизненный цикл штатного mailbox. */
|
|
static volatile struct ECAN_REGS ECanaRegs; /* Volatile сохраняет все наблюдаемые записи production-функций при оптимизации O3. */
|
|
static struct { struct { struct REG MDH, MDL; } MBOX0; } ECanaMboxes; /* Восемь октетов передаваемого кадра наблюдаются независимо от кэша алгоритма. */
|
|
static struct { Uint32 InterruptCount; } CpuTimer1; /* Счётчик переполняется в точности через 2^32 тактов. */
|
|
static struct { struct { Uint16 all; } PIEIER9; union { unsigned all; struct { unsigned reserved:8, ACK9:1; } bit; } PIEACK; } PieCtrlRegs; /* Сохраняет реальное положение PIEACK9 в маске подтверждения IRQ. */
|
|
static Uint16 IER; static unsigned int irq_mask; /* Позволяет проверить возврат исходного состояния прерываний после короткого снимка данных. */
|
|
static int wait, CanTimeOutErrorTR, Mode, Desk, modbus[ANSWER_LEN]; /* Использует настоящий размер массива технологических слов и наблюдаемые параметры узла. */
|
|
static unsigned int Maska[2][8]; /* Каждому потоку соответствует восемь 16-битных масок разрешённых регистров. */
|
|
#define Cancount (modbus + 0x60) /* Как в package.h, R96 и R97 определяют включение двух потоков данных. */
|
|
static unsigned int __disable_interrupts(void) { unsigned int previous = irq_mask; irq_mask = 1U; return previous; } /* Имитирует сохранение исходной маски IRQ даже при вложенном чтении Timer1. */
|
|
static void __restore_interrupts(unsigned int previous) { irq_mask = previous; } /* Выявляет ошибочное безусловное разрешение прерываний отправителем. */
|
|
static void led1_toggle(void) {} /* Индикация не влияет на подтверждение доставки или период таймера. */
|
|
#include "can_telemetry_impl.h" /* Компилирует точный production-алгоритм, а не отдельную копию реализации в тесте. */
|
|
/* TIMEOUT_ISR */
|
|
#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Проверка остаётся активной при оптимизации и показывает нарушенное ожидание. */
|
|
static Uint32 frames_hi[2048], frames_lo[2048], frames_at[2048]; static unsigned int count; /* Журнал хранит только действительно запущенные аппаратные кадры и момент их передачи. */
|
|
static void hardware_ack(void) { ECanaRegs.CANTRS.all &= ~1UL; ECanaRegs.CANTRR.all &= ~1UL; ECanaRegs.CANTA.all = 1UL; ECanaRegs.CANAA.all = 0UL; } /* Завершает именно mailbox 0 успешным аппаратным ACK. */
|
|
static void prepare(unsigned int mode, unsigned int desk) { /* Начинает независимый сценарий, включая имитацию повторного запуска программы. */
|
|
memset((void *)&ECanaRegs, 0, sizeof(ECanaRegs)); memset(&ECanaMboxes, 0, sizeof(ECanaMboxes)); memset(&PieCtrlRegs, 0, sizeof(PieCtrlRegs)); /* Сбрасывает только модель периферии между испытаниями. */
|
|
memset(modbus, 0, sizeof(modbus)); memset(Maska, 0, sizeof(Maska)); CpuTimer1.InterruptCount = 0U; count = 0U; irq_mask = 0U; /* Устанавливает известное начальное содержимое карты и таймера. */
|
|
Mode = mode; Desk = desk; Cancount[0] = 10; Cancount[1] = 101; IER = M_INT9; PieCtrlRegs.PIEIER9.all = 0x0030U; CanTimeOutErrorTR = 0; /* Периоды 10 и 101 проверяют независимость нового повтора от старых делителей. */
|
|
CAN_telemetry_init(); /* Первый снимок после каждого запуска должен отправляться независимо от содержимого старых кэш-массивов. */
|
|
} /* Аппаратный ACK в каждом сценарии задаётся явно. */
|
|
static int pump(void) { /* Выдаёт всю уже готовую очередь с ACK между кадрами, не продвигая тестовое время. */
|
|
unsigned int limit; /* Ограничивает итерации для обнаружения зависания на пустой маске или неизменном пакете. */
|
|
for (limit = 0U; limit < 512U; ++limit) { /* Полная карта из двух потоков требует значительно меньше 512 аппаратных кадров. */
|
|
CAN_telemetry_service(); /* Обслуживает реальную очередь, heartbeat и индивидуальные периоды подтверждённых данных. */
|
|
CHECK(irq_mask == 0U); /* После любого пути отправки прерывания должны иметь первоначальное состояние. */
|
|
if (!(ECanaRegs.CANTRS.all & 1UL)) return 0; /* Отсутствие нового TX означает завершённую выдачу готовых кадров. */
|
|
CHECK(count < 2048U); /* Исключает скрытое переполнение тестового журнала. */
|
|
frames_hi[count] = ECanaMboxes.MBOX0.MDH.all; frames_lo[count] = ECanaMboxes.MBOX0.MDL.all; frames_at[count++] = CpuTimer1.InterruptCount; /* Запоминает точные данные mailbox, не используя внутренний кэш production-кода как эталон. */
|
|
hardware_ack(); /* Только успешное подтверждение разрешает алгоритму подавить следующий неизменный пакет. */
|
|
} /* Бесконечное повторение выявляется как ошибка конечной модели. */
|
|
CHECK(0); /* Даже при пустой маске или неподтверждённом адресе сервис обязан возвращаться без блокировки. */
|
|
return 1; /* Сохраняет однозначный код ошибки для компилятора C. */
|
|
} /* Все вызовы pump выполняются с фиксированным временем, поэтому сравнения периодов точны. */
|
|
static unsigned int address_at(unsigned int index) { return (unsigned int)((frames_hi[index] >> 16) & 0x1FFFU); } /* Декодирует существующий маркер BALZAM независимо от внутренней реализации отправителя. */
|
|
static unsigned int count_address(unsigned int address) { unsigned int index, found = 0U; for (index = 0U; index < count; ++index) if (address_at(index) == address) ++found; return found; } /* Считает реальные кадры выбранного технологического или heartbeat-адреса. */
|
|
int main(void) { /* Проверяет требования пользователя, границы карты и ошибки аппаратного TX на настоящем коде прошивки. */
|
|
unsigned int mode, before, index; Uint32 started; /* Сценарии используют независимые ожидаемые периоды в миллисекундах. */
|
|
for (mode = 1U; mode <= 9U; ++mode) { /* Сравнение данных и повтор 1,3 с применяются ко всем аппаратным Mode, включая задатчик. */
|
|
prepare(mode, mode == 8U ? 4U : 0U); Maska[0][0] = 1U; modbus[0] = 9; modbus[1] = 12; modbus[2] = -1; CHECK(pump() == 0); /* Начальный пакет должен передаться сразу в прежнем порядке трёх слов. */
|
|
CHECK(count == 1U && frames_hi[0] == 0xE0000009UL && frames_lo[0] == 0x000CFFFFUL); /* Отрицательное 16-битное значение не портит упаковку и старший регистр. */
|
|
CpuTimer1.InterruptCount = 999U; CHECK(pump() == 0 && count == 1U); /* Неизменившиеся данные не создают лишних кадров до секундного heartbeat. */
|
|
CpuTimer1.InterruptCount = 1000U; CHECK(pump() == 0 && count == 2U && address_at(1U) == 0x100U); /* Heartbeat появляется ровно через одну секунду, независимо от обоих Cancount. */
|
|
CHECK((frames_hi[1] & 0xFFFFUL) == 1U && frames_lo[1] == ((Uint32)Mode << 16 | (unsigned int)Desk)); /* Отдельный heartbeat содержит счётчик секунд, Mode и Desk. */
|
|
CpuTimer1.InterruptCount = 1299U; CHECK(pump() == 0 && count_address(0U) == 1U); /* Технологический пакет не повторяется раньше индивидуального срока. */
|
|
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(0U) == 2U); /* Неизменные данные повторяются на границе 1,3 с без искусственного изменения состояния планировщика тестом. */
|
|
CHECK(frames_at[count - 1U] == 1300U && frames_hi[count - 1U] == frames_hi[0]); /* Повторяет сами данные, а не только отдельный heartbeat. */
|
|
} /* Завершает проверку одинакового поведения всех Mode. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; Maska[1][0] = 8U; modbus[0] = 1; modbus[3] = 2; CHECK(pump() == 0 && count == 2U); /* Два потока получают отдельные пакеты и подтверждения без ожидания старых делителей. */
|
|
CpuTimer1.InterruptCount = 40U; modbus[1] = 77; CHECK(pump() == 0 && count == 3U && address_at(2U) == 0U); /* Изменение второго слова пакета отправляется до истечения 1,3 с. */
|
|
CpuTimer1.InterruptCount = 60U; modbus[2] = 88; CHECK(pump() == 0 && count == 4U && (frames_lo[3] & 0xFFFFUL) == 88U); /* Изменение третьего слова тоже обнаруживается и не подавляется кэшем первого регистра. */
|
|
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(3U) == 2U && count_address(0U) == 3U); /* Независимый пакет повторяется через 1,3 с, а обновлённый пакет R0 ещё не достиг своего нового срока. */
|
|
CpuTimer1.InterruptCount = 1360U; CHECK(pump() == 0 && count_address(0U) == 4U); /* Период R0 отсчитывается заново от ACK последнего изменённого снимка. */
|
|
prepare(8U, 4U); Maska[0][1] = 1U; Maska[1][0] = 15U; ECanaRegs.CANTRS.all = 1U; ECanaMboxes.MBOX0.MDH.all = 0x12345678UL; CAN_telemetry_service(); /* Занятый внешним запросом mailbox нельзя менять или считать доставленным. */
|
|
CHECK(ECanaMboxes.MBOX0.MDH.all == 0x12345678UL && can_scan_due == 1U); /* Оба потока сохраняют готовность до физического освобождения передатчика. */
|
|
ECanaRegs.CANTRS.all = 0U; CHECK(pump() == 0 && count_address(16U) == 1U && count_address(0U) == 1U && count_address(3U) == 1U); /* После занятости не теряются R0–R2 и R3–R5 медленного потока. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); CHECK(wait && !(can_valid[0] & 1U)); /* Постановка первого пакета в TX ещё не означает успешную доставку. */
|
|
ECanaRegs.CANTA.all = 0U; ECanaRegs.CANTOS.all = 1U; ECanaRegs.CANTOC.all = 5U; CANa_reset_err(); /* Воспроизводит аппаратный timeout, пока отдельный контроль служебного mailbox тоже включён. */
|
|
CHECK(can_tx_failed && CanTimeOutErrorTR == 1 && ECanaRegs.CANTRR.all == 1U && ECanaRegs.CANTOC.all == 4U); /* ISR помечает отказ и отменяет только штатный TX без выключения служебного контроля. */
|
|
ECanaRegs.CANTOS.all &= ECanaRegs.CANTOC.all; ECanaRegs.CANTRS.all = ECanaRegs.CANTRR.all = 0U; ECanaRegs.CANAA.all = 0U; CpuTimer1.InterruptCount = 10U; CAN_telemetry_service(); /* Отмена завершена без CANAA: отсутствие этого флага не должно навсегда заблокировать очередь. */
|
|
CHECK(!wait && can_backoff && !(can_valid[0] & 1U)); /* Неудачный снимок не становится подтверждённым и остаётся кандидатом следующей попытки. */
|
|
CpuTimer1.InterruptCount = 1009U; modbus[0] = 42; CAN_telemetry_service(); CHECK(!(ECanaRegs.CANTRS.all & 1U)); /* До окончания секундной паузы после ошибки повтор не засоряет CAN. */
|
|
CpuTimer1.InterruptCount = 1010U; CHECK(pump() == 0 && count_address(0U) == 1U && (frames_hi[count - 1U] & 0xFFFFUL) == 42U); /* После паузы повторяет актуальное изменение и отдельно ожидающий heartbeat. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); ECanaRegs.CANTA.all = 0U; CpuTimer1.InterruptCount = 100U; CAN_telemetry_service(); /* Потерянный timeout IRQ страхуется реальным временем Timer1 через 100 мс. */
|
|
CHECK(can_tx_failed && ECanaRegs.CANTRR.all == 1U && CanTimeOutErrorTR == 1); /* Программная отмена не ждёт аппаратного CANAA в блокирующем цикле. */
|
|
CAN_telemetry_service(); CHECK(CanTimeOutErrorTR == 1); /* Длительная аппаратная отмена не увеличивает счётчик ошибки на каждой итерации main. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; CHECK(pump() == 0); CpuTimer1.InterruptCount = 20U; modbus[0] = 2; CAN_telemetry_service(); ECanaRegs.CANTA.all = 0U; ECanaRegs.CANTRS.all = 0U; ECanaRegs.CANAA.all = 1U; CAN_telemetry_service(); /* Изменение, отменённое после старого успешного снимка, не должно затереть подтверждённый кэш. */
|
|
CHECK(can_last_hi[0] == 0xE0000000UL && can_backoff); /* Только реально доставленный прежний пакет остаётся подтверждённым. */
|
|
prepare(5U, 0U); CHECK(pump() == 0 && count == 0U); CpuTimer1.InterruptCount = 3500U; CHECK(pump() == 0 && count == 1U && address_at(0U) == 0x100U && (frames_hi[0] & 0xFFFFU) == 3U); /* Пустые маски не зависают; после задержки main отправляется последний heartbeat без пачки пропущенных секунд. */
|
|
CHECK(pump() == 0 && count == 1U); /* Повторная итерация main не дублирует уже поставленный heartbeat. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; Cancount[0] = 0; Cancount[1] = 0; CHECK(pump() == 0 && count == 0U); CpuTimer1.InterruptCount = 1000U; CHECK(pump() == 0 && count == 1U && address_at(0U) == 0x100U); /* Отключённые потоки не отключают независимый heartbeat узла. */
|
|
prepare(5U, 0U); Maska[0][7] = 0x8000U; modbus[127] = 0x55AA; CHECK(pump() == 0 && count == 1U && frames_hi[0] == 0xE07F55AAUL && frames_lo[0] == 0U); /* Конец карты R127 дополняется нулями без чтения data[128] и data[129]. */
|
|
prepare(5U, 0U); for (index = 0U; index < 8U; ++index) Maska[0][index] = 0xFFFFU; CHECK(pump() == 0 && count == 43U); /* Плотная маска сохраняет прежние 43 трёхсловных пакета вместо лишних 128 перекрывающихся кадров. */
|
|
for (index = 0U; index < count; ++index) CHECK(address_at(index) == index * 3U); /* Первый полный снимок покрывает ровно R0, R3, ... R126 в установленном формате. */
|
|
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(0U) == 2U && count_address(126U) == 2U && count == 87U); /* Готовая очередь выдаёт всю карту за период, а не один кадр маски каждые 1,3 с. */
|
|
prepare(5U, 0U); CpuTimer1.InterruptCount = 0xFFFFFF00UL; started = CpuTimer1.InterruptCount; Maska[0][0] = 1U; CHECK(pump() == 0); CpuTimer1.InterruptCount = started + 1000U; CHECK(pump() == 0 && count_address(0x100U) == 1U); /* Секундный heartbeat переживает переполнение Timer1. */
|
|
CpuTimer1.InterruptCount = started + 1300U; CHECK(pump() == 0 && count_address(0U) == 2U); /* Период повторения данных тоже корректен через 32-битное переполнение. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; CHECK(pump() == 0); before = count; can_valid[7] = 0xFFFFU; can_backoff = can_heartbeat_due = 1U; wait = 1; CAN_telemetry_init(); CHECK(pump() == 0 && count == before + 1U); /* Повторная инициализация отбрасывает грязное состояние и заново отправляет первый снимок. */
|
|
prepare(5U, 0U); Maska[0][1] = 1U; Maska[1][0] = 1U; for (index = 0U; index <= 250U; ++index) { CpuTimer1.InterruptCount = index * 20U; modbus[16] = index; CHECK(pump() == 0); } /* Непрерывное изменение быстрого потока не должно вытеснить heartbeat и неизменный пакет другого потока. */
|
|
CHECK(count_address(16U) == 251U && count_address(0U) == 4U && count_address(0x100U) == 5U); /* За пять секунд доставляются все изменения, четыре состояния R0 и пять секундных heartbeat. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); CpuTimer1.InterruptCount = 50U; hardware_ack(); CAN_telemetry_service(); /* Подтверждает первый пакет через 50 мс после запуска, а не в нулевой момент передачи. */
|
|
CpuTimer1.InterruptCount = 1349U; CHECK(CAN_send(0, modbus, 0) == 2); CpuTimer1.InterruptCount = 1350U; CHECK(CAN_send(0, modbus, 0) == 1); /* Индивидуальные 1300 мс отсчитываются от ACK, поэтому ранний повтор через 1300 мс от запуска запрещён. */
|
|
prepare(5U, 0U); ECanaRegs.CANTOC.all = 1U; ECanaRegs.CANTRS.all = 1U; CANa_reset_err(); /* Вызывает фантомный ISR без аппаратного CANTOS0 во время исправной передачи. */
|
|
CHECK(!can_tx_failed && !CanTimeOutErrorTR && ECanaRegs.CANTRR.all == 0U && ECanaRegs.CANTOC.all == 1U && PieCtrlRegs.PIEIER9.all == 0x0030U); /* Не отменяет исправный кадр и восстанавливает прежние разрешения обоих CAN IRQ. */
|
|
prepare(5U, 0U); Maska[0][0] = 1U; ECanaRegs.CANTRR.all = 1U; ECanaMboxes.MBOX0.MDH.all = 0x11223344UL; CAN_telemetry_service(); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x11223344UL && !wait); /* Даже без CANTRS незавершённая аппаратная отмена запрещает перезапись mailbox. */
|
|
prepare(5U, 0U); irq_mask = 1U; CHECK(CAN_send(0, modbus, 0) == 1 && irq_mask == 1U); /* Передатчик сохраняет уже запрещённые IRQ вызывающего кода вместо безусловного разрешения. */
|
|
puts("PASS: heartbeat 1 Hz; changed data; per-packet refresh 1300 ms; all modes; masks; ACK, timeout and backoff; wraparound; dirty RAM; bounds"); /* Подтверждает проверку реального production-кода без утверждения о запуске на физической плате. */
|
|
return 0; /* Все независимые поведенческие проверки завершились успешно. */
|
|
} /* Модель не требует подключения CAN-адаптера и не отправляет команды реальному оборудованию. */
|