Исправить TESTLAMP, запуск CAN и расчёт фазных токов
Сохранить состояние TESTLAMP при кратких провалах входа и исключить повторный вход Timer1 во время передачи индикации. Явно сбрасывать служебный обмен CAN при запуске и выбирать соседнюю фазу по локальному индексу канала АЦП. Сохранить текущую последовательность DATA/CLK и проверочные варианты таймингов, добавить поведенческие тесты и обновить образ прошивки с картой памяти.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
#include <stdint.h> /* Задаёт точные размеры регистров и слов протокола при исполнении модели на ПК. */
|
||||
#include "ump_tms28335.h" /* Подключает настоящий интерфейс проверки запросов и упаковки фрагментов CAN. */
|
||||
#define UMP_CAN_MAX_WORDS UMP_MAX_WORDS /* Повторяет максимальную длину ответа из рабочего заголовка адаптера. */
|
||||
#define UMP_CAN_MARKER UMP_MARKER /* Использует каноническую метку отличия служебного кадра от технологических данных. */
|
||||
#define ump_can_prepare ump_tms28335_prepare /* Сохраняет штатное соответствие имени адаптера канонической функции порта. */
|
||||
#define ANSWER_LEN 128U /* Моделирует границу штатных технологических регистров для компиляции настоящего Handlai. */
|
||||
#define dsk_LOAD 6 /* Повторяет номер платы нагрузки из package.h для компиляции технологической ветви Handlai; сценарии используют служебные кадры. */
|
||||
#define CHECK(condition) do { if (!(condition)) return __LINE__; } while (0) /* Сохраняет проверки при O3 и возвращает точное место регрессии вызывающему Python. */
|
||||
|
||||
struct MODEL_REG { uint32_t all; }; /* Моделирует атомарное 32-битное чтение и запись слова аппаратного регистра. */
|
||||
struct MODEL_MAILBOX { struct MODEL_REG MDL, MDH; struct { struct { unsigned int DLC; } bit; } MSGCTRL; }; /* Предоставляет реальные имена полей данных и длины кадра, читаемые рабочим кодом. */
|
||||
struct ECAN_MBOXES { struct MODEL_MAILBOX MBOX1, MBOX2; }; /* Отделяет входящий запрос в mailbox 1 от служебного ответа в mailbox 2. */
|
||||
static struct { struct MODEL_REG CANTA, CANAA, CANTRR, CANTRS; } ECanaRegs; /* Хранит подтверждения, отмену и факт запуска передачи для наблюдения сценарием. */
|
||||
static struct ECAN_MBOXES ECanaMboxes; /* Предоставляет обработчикам адресуемую память вместо физической периферии. */
|
||||
static struct { unsigned long InterruptCount; } CpuTimer1; /* Задаёт миллисекундное время, от которого сервис считает тайм-аут ожидания ответа. */
|
||||
static int modbus[ANSWER_LEN], Desk, Lonely; /* Обеспечивает зависимости технологической ветви Handlai, не исполняемой при служебном запросе. */
|
||||
static unsigned int model_reads, model_writes, model_unexpected, model_irq_mask; /* Позволяет отличить ложную команду, корректное чтение и несбалансированную блокировку IRQ. */
|
||||
|
||||
static unsigned int __disable_interrupts(void) { unsigned int saved = model_irq_mask; model_irq_mask = 1; return saved; } /* Имитирует сохранение предыдущей маски, чтобы вложенные вызовы оставались проверяемыми. */
|
||||
static void __restore_interrupts(unsigned int saved) { model_irq_mask = saved; } /* Возвращает исходную маску и позволяет сценарию обнаружить оставленную блокировку. */
|
||||
static void Load_voltage_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) { (void)header; (void)a; (void)b; (void)c; model_unexpected++; } /* Считает ошибочным попадание служебного запроса в обработку технологического напряжения. */
|
||||
static void ump_log_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) { (void)header; (void)a; (void)b; (void)c; model_unexpected++; } /* Обнаруживает лишнюю регистрацию служебного опроса как управляющего CAN-события. */
|
||||
static void led2_toggle(void) { model_unexpected++; } /* Отмечает проход по обычной ветви CAN вместо раннего возврата служебного запроса. */
|
||||
static unsigned int Load_protection_prepare(unsigned int fn, uint16_t address, uint16_t value, uint16_t *reply, unsigned int *error) { (void)fn; (void)address; (void)value; (void)reply; *error = 2; model_unexpected++; return 0; } /* Запросы теста адресуют журнал, поэтому попадание в карту защиты является ошибкой маршрутизации. */
|
||||
#if EEPROM_M95M02 /* Компилирует зависимости дополнительной ветви так же, как прошивка профиля M95M02. */
|
||||
static int gas167_contains(uint16_t address) { return address >= 0x5000U && address < 0x5080U; } /* Отделяет область GAS от проверяемого журнала, не копируя реализацию профиля. */
|
||||
static unsigned int gas167_prepare(unsigned int fn, uint16_t address, uint16_t value, int can, uint16_t *reply, unsigned int *error) { (void)fn; (void)address; (void)value; (void)can; (void)reply; *error = 2; model_unexpected++; return 0; } /* Делает неожиданное обращение к GAS наблюдаемой ошибкой в тесте запуска журнала. */
|
||||
#endif /* Завершает минимальные зависимости профиля, не подменяя проверку общей CAN-маршрутизации. */
|
||||
unsigned int ump_log_read(unsigned int address) { model_reads++; return 0x5500U + address - UMP_BASE; } /* Возвращает узнаваемые слова backend, чтобы ответ нельзя было спутать со старым содержимым RAM. */
|
||||
int ump_log_write(unsigned int address, unsigned int value) { (void)address; (void)value; model_writes++; return 1; } /* Регистрирует любую нежелательную запись, случайно возникшую из остаточного запроса. */
|
||||
|
||||
/* PRODUCTION_CAN */
|
||||
|
||||
static void model_reset_hardware(void) /* Очищает только модель периферии; управляющую RAM оставляет сценарию для воспроизведения холодного старта. */
|
||||
{ /* Начинает воспроизводимое состояние аппаратных mailbox после сброса контроллера. */
|
||||
ECanaRegs.CANTA.all = 0; ECanaRegs.CANAA.all = 0; ECanaRegs.CANTRR.all = 0; ECanaRegs.CANTRS.all = 0; /* Убирает подтверждения и запросы, которые аппаратный сброс не сохраняет вместе с RAM. */
|
||||
ECanaMboxes.MBOX2.MDL.all = 0; ECanaMboxes.MBOX2.MDH.all = 0; /* Обнуляет наблюдаемые данные ответа для обнаружения каждой новой передачи. */
|
||||
CpuTimer1.InterruptCount = 1000; model_reads = 0; model_writes = 0; model_unexpected = 0; model_irq_mask = 0; /* Задаёт миллисекундный такт и независимые счётчики нового сценария. */
|
||||
} /* Завершает аппаратный сброс без скрытой инициализации проверяемых переменных прошивки. */
|
||||
|
||||
static void model_poison_state(void) /* Воссоздаёт допустимые остатки RAM, ранее приводившие к ответу без входного запроса. */
|
||||
{ /* Начинает заполнение всех управляющих полей ненулевыми либо специально подобранными значениями. */
|
||||
ump_can_pending = 0; ump_can_tag = 0xA506U; ump_can_address = UMP_BASE; ump_can_value = 1; /* Сохраняет след прежней команды записи, но отмечает отсутствие нового принятого запроса. */
|
||||
ump_can_length = 2; ump_can_index = 0; ump_can_reply_tag = 0x0100U; ump_can_waiting = 0; ump_can_sent_at = 777; /* Представляет старый двухсловный ответ с token 1, который сервис ошибочно считает готовым. */
|
||||
ump_can_reply[0] = 0x1234U; ump_can_reply[1] = 0x5678U; /* Создаёт отличимый от backend payload для доказательства отправки именно остаточного буфера. */
|
||||
} /* Завершает заполнение состояния, которое настоящий сброс обязан обезвредить. */
|
||||
|
||||
static int model_first_request(unsigned int token) /* Проверяет весь первый ответ после сброса через настоящий приёмник, валидатор и упаковщик. */
|
||||
{ /* Начинает независимую транзакцию чтения 16 слов заголовка журнала. */
|
||||
unsigned int fragment; /* Перебирает восемь CAN-фрагментов, каждый из которых содержит два 16-битных слова. */
|
||||
ECanaMboxes.MBOX1.MDH.all = ((uint32_t)UMP_CAN_MARKER << 16) | ((uint32_t)token << 8) | 3U; /* Формирует служебную метку, новый token и штатную функцию чтения FC03. */
|
||||
ECanaMboxes.MBOX1.MDL.all = ((uint32_t)UMP_BASE << 16) | 16U; ECanaMboxes.MBOX1.MSGCTRL.bit.DLC = 8; /* Запрашивает допустимый восьмибайтный кадр чтения полного 16-словного заголовка. */
|
||||
Handlai(&ECanaMboxes); /* Передаёт запрос настоящему разборщику mailbox без прямой установки поля pending. */
|
||||
CHECK(ump_can_pending == 1 && model_reads == 0 && model_writes == 0); /* Проверяет, что ISR только опубликовал запрос и не исполнил его вместо главного цикла. */
|
||||
for (fragment = 0; fragment < 8; fragment++) { /* Проверяет все фрагменты, включая последний, чтобы сброс счётчиков не скрывал укороченный ответ. */
|
||||
UMP_CAN_service(); /* Готовит снимок при первом вызове и далее отправляет по одному настоящему фрагменту. */
|
||||
CHECK(ECanaRegs.CANTRS.all == 4 && ump_can_waiting == 1); /* Требует запуска mailbox 2 и ожидания подтверждения именно отправленного кадра. */
|
||||
CHECK(ECanaMboxes.MBOX2.MDL.all == (((uint32_t)(0x5500U + fragment * 2U) << 16) | (0x5501U + fragment * 2U))); /* Сравнивает оба слова кадра с содержимым нового снимка, исключая остатки прежнего ответа. */
|
||||
CHECK(ECanaMboxes.MBOX2.MDH.all == (((uint32_t)(UMP_CAN_MARKER + fragment) << 16) | ((uint32_t)token << 8))); /* Проверяет порядок фрагментов, token текущего запроса и отсутствие кода ошибки. */
|
||||
ECanaRegs.CANTRS.all = 0; ECanaRegs.CANTA.all = 4; /* Имитирует окончание передачи и подтверждение только проверенного фрагмента аппаратурой CAN. */
|
||||
} /* Завершает передачу всех 16 запрошенных слов. */
|
||||
UMP_CAN_service(); ECanaRegs.CANTA.all = 0; /* Позволяет сервису принять последнее подтверждение; затем моделирует очистку W1C-регистра аппаратурой. */
|
||||
CHECK(ECanaRegs.CANTRS.all == 0 && ump_can_waiting == 0 && ump_can_pending == 0); /* Убеждается, что после последнего ACK не отправляется лишний либо старый фрагмент. */
|
||||
CHECK(model_reads == 16 && model_writes == 0 && model_unexpected == 0 && model_irq_mask == 0); /* Требует единственного согласованного снимка, отсутствия ложных команд и восстановленной маски IRQ. */
|
||||
return 0; /* Сообщает вызывающему сценарию о полном успешном ответе после инициализации. */
|
||||
} /* Завершает поведенческую проверку первой транзакции. */
|
||||
|
||||
#ifdef _WIN32 /* Экспортирует тест явно для загрузки MinGW DLL через ctypes в Windows. */
|
||||
__declspec(dllexport) /* Делает run_tests видимой снаружи библиотеки без таблицы экспорта проекта прошивки. */
|
||||
#endif /* На остальных платформах используется обычная видимость глобальной функции. */
|
||||
int run_tests(void) /* Возвращает ноль только при выполнении всех сценариев запуска и повторного сброса. */
|
||||
{ /* Начинает проверку реального дефекта до сброса и его устранения рабочим helper. */
|
||||
int result; /* Передаёт точную строку сбоя из сценария полной CAN-транзакции. */
|
||||
model_reset_hardware(); model_poison_state(); UMP_CAN_service(); /* Воспроизводит старое поведение на грязной RAM без вызова добавленного сброса. */
|
||||
CHECK(ECanaRegs.CANTRS.all == 4 && ECanaMboxes.MBOX2.MDL.all == 0x12345678UL && ump_can_pending == 0); /* Доказывает самопроизвольный ответ, чтобы тест не оказался нечувствителен к исходной регрессии. */
|
||||
model_reset_hardware(); model_poison_state(); ump_can_init(); /* Вызывает настоящий новый сброс при тех же остатках RAM и чистой периферии. */
|
||||
CHECK(ump_can_pending == 0 && ump_can_tag == 0 && ump_can_address == 0 && ump_can_value == 0); /* Проверяет, что старые параметры не смогут превратиться в ложную принятую команду. */
|
||||
CHECK(ump_can_length == 0 && ump_can_index == 0 && ump_can_reply_tag == 0 && ump_can_waiting == 0 && ump_can_sent_at == 0); /* Требует очистки всех управляющих полей незавершённой передачи и тайм-аута. */
|
||||
UMP_CAN_service(); UMP_CAN_service(); /* Даёт главному циклу повторно обработать состояние до появления первого запроса. */
|
||||
CHECK(ECanaRegs.CANTRS.all == 0 && ECanaRegs.CANTRR.all == 0 && model_reads == 0 && model_writes == 0); /* Запрещает ложную передачу, отмену несуществующего кадра и исполнение остаточной команды. */
|
||||
result = model_first_request(0x37U); if (result) return result; /* Проверяет приём и все фрагменты первого запроса после холодного запуска. */
|
||||
model_reset_hardware(); model_poison_state(); ump_can_waiting = 1; ump_can_pending = 1; /* Воспроизводит перезапуск во время передачи с ещё не обработанным старым запросом в RAM. */
|
||||
ump_can_init(); CpuTimer1.InterruptCount += 1000; UMP_CAN_service(); /* Проверяет сброс ожидания даже после истечения прежнего тайм-аута в 250 мс. */
|
||||
CHECK(ECanaRegs.CANTRS.all == 0 && ECanaRegs.CANTRR.all == 0 && ump_can_pending == 0 && ump_can_waiting == 0); /* Не допускает зависания в отмене старого mailbox либо исполнения устаревшего pending. */
|
||||
result = model_first_request(0x59U); if (result) return result; /* Доказывает, что после такого перезапуска новый token обслуживается полной последовательностью фрагментов. */
|
||||
return 0; /* Подтверждает исправление холодного старта без изменения алгоритма штатного CAN-ответа. */
|
||||
} /* Завершает экспортированную проверку регрессии для выбранного профиля EEPROM. */
|
||||
@@ -0,0 +1,56 @@
|
||||
# Проверяет запуск служебного CAN на грязной RAM: использует рабочие обработчики и канонический упаковщик протокола.
|
||||
import os # Читает пути общей библиотеки и компилятора, не меняя настройки проекта.
|
||||
from pathlib import Path # Строит абсолютные пути независимо от каталога запуска проверки.
|
||||
import re # Извлекает рабочие функции, не заменяя их алгоритмы копиями в модели.
|
||||
import shutil # Находит доступный компилятор GCC по пользовательскому PATH.
|
||||
import subprocess # Запускает компиляцию и отдельный процесс проверки с контролем кода завершения.
|
||||
import sys # Повторно использует текущий Python для загрузки тестовой библиотеки.
|
||||
import tempfile # Изолирует сгенерированный код и библиотеку от исходников и образов прошивки.
|
||||
|
||||
root = Path(__file__).resolve().parents[2] # Находит корень репозитория по расположению постоянного теста.
|
||||
templates = Path(os.environ.get("TEMPLATES_ROOT", root.parent / "newProject/SETGUI/third_party/templates")) # Повторяет настройку общей библиотеки существующих тестов УМП.
|
||||
protocol = templates / "c/set-protocol" # Выбирает канонические проверку запросов и формат фрагментов CAN.
|
||||
compiler = os.environ.get("CC") or shutil.which("gcc") or "C:/msys64/mingw64/bin/gcc.exe" # Поддерживает заданный CC и установленный в рабочем окружении MinGW.
|
||||
environment = os.environ.copy() # Ограничивает изменение PATH процессами компилятора и модели.
|
||||
environment["PATH"] = str(Path(compiler).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает доступными вспомогательные DLL выбранного компилятора в Windows.
|
||||
|
||||
def function(text, name): # Возвращает полное тело указанной функции вместе с её рабочими комментариями.
|
||||
match = re.search(r"^(?:static\s+)?(?:void|int)\s+" + re.escape(name) + r"\([^;{}]*\)", text, re.M) # Отличает определение функции от вызова и прототипа.
|
||||
if not match: # Не позволяет тесту незаметно проверять устаревшую копию после переименования функции.
|
||||
raise AssertionError(f"Не найдена рабочая функция {name}") # Завершает проверку с названием отсутствующей точки интеграции.
|
||||
end = re.search(r"^}", text[match.end():], re.M) # Использует принятое в модуле размещение закрывающей скобки функции в первом столбце.
|
||||
if not end: # Отвергает неполный фрагмент вместо генерации частично работающей модели.
|
||||
raise AssertionError(f"Не найден конец функции {name}") # Указывает причину невозможности извлечения рабочего алгоритма.
|
||||
return text[match.start():match.end() + end.end()] + "\n" # Сохраняет исходный порядок операторов для компиляции под O3.
|
||||
|
||||
source = (root / "Source/Internal/ecan.c").read_text(encoding="utf-8") # Читает именно текущие исходники транспорта, включая новый сброс состояния.
|
||||
startup = function(source, "InitCan") # Проверяет реальную связь сброса с аппаратной инициализацией, а не только отдельный helper.
|
||||
startup_code = re.sub(r"/\*.*?\*/|//[^\n]*", "", startup, flags=re.S) # Удаляет комментарии, чтобы упоминания helper или регистров не выдавались за вызов.
|
||||
assert len(re.findall(r"\bump_can_init\s*\(\s*\)\s*;", startup_code)) == 1, "InitCan должен один раз вызывать ump_can_init" # Требует выполнения нового сброса при каждом аппаратном запуске.
|
||||
hardware_start = re.search(r"\b(?:EALLOW|GpioCtrlRegs|ECanaRegs|PieCtrlRegs|IER)\b", startup_code) # Находит первую настройку периферии или разрешение CAN-прерываний.
|
||||
assert hardware_start and startup_code.index("ump_can_init()") < hardware_start.start(), "Сброс CAN должен предшествовать настройке периферии" # Не допускает окна прерывания с неочищенными полями обмена.
|
||||
state_start = source.index("static volatile unsigned int ump_can_pending;") # Начинает извлечение общих между ISR и главным циклом переменных.
|
||||
state_end = source.index("static unsigned long ump_can_sent_at;") + len("static unsigned long ump_can_sent_at;") # Ограничивает фрагмент управляющими полями и штатным буфером ответа.
|
||||
state = source[state_start:state_end] # Использует объявления из рабочего модуля, чтобы тест замечал изменение состава состояния.
|
||||
state = state.replace("static unsigned int ump_can_reply[", "static uint16_t ump_can_reply[") # Воспроизводит на ПК 16-битный unsigned int C28x именно в буфере слов CAN.
|
||||
model = (Path(__file__).with_name("model.inc")).read_text(encoding="utf-8") # Читает аппаратную модель и поведенческие сценарии, сохраняемые рядом с запускателем.
|
||||
body = state + "\n" + "".join(function(source, name) for name in ("ump_can_init", "Handlai", "UMP_CAN_service")) # Подставляет сброс, настоящий приём запроса и сервис отправки без изменений их логики.
|
||||
assert model.count("/* PRODUCTION_CAN */") == 1, "Нужна единственная точка подключения рабочего кода" # Исключает пропуск или двойную компиляцию проверяемых функций.
|
||||
model = model.replace("/* PRODUCTION_CAN */", body) # Встраивает текущие обработчики между аппаратной моделью и сценариями проверки.
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="ump-can-init-") as directory: # Автоматически удаляет только временные результаты этой проверки.
|
||||
build = Path(directory) # Фиксирует каталог файлов, не затрагивающих бинарные артефакты репозитория.
|
||||
source_file = build / "can_init_test.c" # Выделяет общий сгенерированный исходник для обоих профилей EEPROM.
|
||||
source_file.write_text(model, encoding="utf-8") # Сохраняет русские комментарии и извлечённые исходники без потери кодировки.
|
||||
for profile in (0, 1): # Проверяет обе ветви маршрутизации CAN: AT25256B и M95M02.
|
||||
library = build / (f"can_init_{profile}.dll" if os.name == "nt" else f"can_init_{profile}.so") # Разделяет библиотеки, чтобы профиль не подменял результат соседней сборки.
|
||||
command = [compiler, "-O3", "-shared", "-Wall", "-Wextra", "-Werror", f"-DEEPROM_M95M02={profile}"] # Проверяет рабочий код с оптимизацией O3 и запрещает скрывать предупреждения модели.
|
||||
command.append("-Wno-misleading-indentation") # Допускает существующие смешанные отступы Handlai без изменения проверяемого исходника TI.
|
||||
if os.name != "nt": # Учитывает требования загрузки разделяемых библиотек ELF на Linux.
|
||||
command.append("-fPIC") # Генерирует переносимые адреса только там, где они нужны формату библиотеки.
|
||||
command += ["-I" + str(protocol / "ports/tms320f28335"), str(source_file)] # Передаёт модели официальные объявления порта C28x и текущий извлечённый код.
|
||||
command += [str(protocol / "src/ump_protocol.c"), str(protocol / "ports/tms320f28335/ump_tms28335.c"), "-o", str(library)] # Линкует настоящие валидатор и упаковщик фрагментов вместо их заглушек.
|
||||
subprocess.run(command, cwd=root, env=environment, check=True) # Прерывает проверку при ошибке любого профиля до объявления успешного результата.
|
||||
runner = "import ctypes,sys; result=ctypes.CDLL(sys.argv[1]).run_tests(); print('PASS' if result==0 else f'FAIL: C line {result}'); sys.exit(bool(result))" # Возвращает строку неуспешной проверки C без зависимости от assert и режима NDEBUG.
|
||||
subprocess.run([sys.executable, "-c", runner, str(library)], cwd=root, env=environment, check=True) # Освобождает DLL завершением дочернего процесса перед очисткой временного каталога Windows.
|
||||
print(f"EEPROM_M95M02={profile}: холодный старт и первый запрос CAN проверены при -O3") # Показывает независимо подтверждённый результат каждого аппаратного профиля.
|
||||
Reference in New Issue
Block a user