Перенести исправления строба индикации из 167

Перенести настройки таймингов из 13e5bfe373
и относящуюся к индикации часть 04a9d989fb.
Сохранить текущую последовательность DATA/CLOCK ветки 167, удерживать
TESTLAMP при кратких провалах входа и исключить повторный вход Timer1.

Адаптировать тесты TESTLAMP к типу накопителя и кодировкам проекта 166.
Сохранить изменённые исходники и русские комментарии в UTF-8.

Проверка: поведенческие и мутационные тесты TESTLAMP, полная сборка
27 модулей TI C2000 21.12.0.STS и линковка, образ SCI8 30174 байта,
компиляция канала индикации с общей оптимизацией -O3.
This commit is contained in:
2026-10-13 19:18:10 +03:00
parent eb2ff79932
commit b25e026dec
5 changed files with 224 additions and 27 deletions

View File

@@ -1,7 +1,7 @@
/* Программный канал индикации. digits/readr задают символы и выбор
* элементов, DCLK/DOUT и kanal_Send формируют последовательность GPIO.
* Это другой интерфейс, чем dCLK/dCS в DAC.c. Похожие имена не
* означают общий ЦАП или общий выход запуска нагрузки.
/* Программный канал индикации. digits/readr задают символы и выбор
* элементов, DCLK/DOUT и kanal_Send формируют последовательность GPIO.
* Это другой интерфейс, чем dCLK/dCS в DAC.c. Похожие имена не
* означают общий ЦАП или общий выход запуска нагрузки.
*/
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -20,6 +20,27 @@
int digits[16] = {63,6,91,79,102,109,125,7,127,111,64,0,0,0,121,0};
int readr[5] = {0x08,0x0C,0x10,0x00,0x04};
/* Ограничивает настройки таймингов компилятором TI C2000; модель на ПК не использует эти директивы. */
#if defined(__TI_COMPILER_VERSION__)
/* Сохраняет отдельный вызов переключения CLOCK: встраивание при -O3 сокращало удержание DATA после фронта. */
#pragma FUNC_CANNOT_INLINE(DCLK)
/* Возвращает генерацию DCLK как в образе от 06.10.2026: уровень 1 и приоритет скорости 3 сохраняют прежнюю последовательность инструкций GPIO. */
#pragma FUNCTION_OPTIONS(DCLK, "--opt_level=1 --opt_for_speed=3")
/* Сохраняет отдельный вызов переключения DATA, чтобы длительность импульсов не зависела от встраивания в передатчик. */
#pragma FUNC_CANNOT_INLINE(DOUT)
/* Возвращает генерацию DOUT с прежними параметрами -O1 и --opt_for_speed=3; значения взяты из старой конфигурации CCS. */
#pragma FUNCTION_OPTIONS(DOUT, "--opt_level=1 --opt_for_speed=3")
/* Не позволяет встроить разделитель пакетов в цикл передачи и изменить интервалы между переключениями линий. */
#pragma FUNC_CANNOT_INLINE(RESET)
/* Сохраняет прежнюю последовательность вызовов GPIO и задержек разделителя, используя настройки старого образа. */
#pragma FUNCTION_OPTIONS(RESET, "--opt_level=1 --opt_for_speed=3")
/* Сохраняет отдельный вызов передачи бита; протокол использует порядок и интервалы фронтов CLOCK и DATA. */
#pragma FUNC_CANNOT_INLINE(SENDBIT)
/* Закрепляет для передачи бита настройки из 167: -O1 и --opt_for_speed=3 сохраняют интервалы GPIO независимо от общего уровня оптимизации проекта. */
#pragma FUNCTION_OPTIONS(SENDBIT, "--opt_level=1 --opt_for_speed=3")
/* Завершает блок настроек TI, не изменяя формат пакетов и существующие счётчики задержек. */
#endif
void DCLK(int x)
{
if(x) GpioDataRegs.GPASET.bit.GPIO6=1;
@@ -88,7 +109,7 @@ void kanal_Send(int adr, long dat, int dot)
long Word,data,aliq_part,dg[4];
int i,j,bit,byt,addr,sgn=0,punkt=0,aliq_len=0,full_len, isdot=0;
if(adr>1) // Лампочки
if(adr>1) // Адреса каналов выше 1 передают готовую битовую маску ламп; каналы 0 и 1 требуют преобразования числа в сегменты.
{
Word =dat;
}
@@ -139,7 +160,7 @@ void kanal_Send(int adr, long dat, int dot)
for(i=3;i>0;i--)
{
if((dg[i]==0)&&(i!=dot))
dg[i]=0xF; // Это значит пусто
dg[i]=0xF; // Индекс 0xF таблицы digits задаёт пустой символ, скрывая ведущий ноль вне позиции десятичной точки.
else break;
}
@@ -148,7 +169,7 @@ void kanal_Send(int adr, long dat, int dot)
{
if( (dg[i]==0xF)||(i==3))
{
dg[i]=0xA; // Это значит минус
dg[i]=0xA; // Индекс 0xA таблицы digits задаёт знак минуса в свободном разряде отрицательного числа.
break;
} }

View File

@@ -1,8 +1,8 @@
/* Выбор типа платы, направления GPIO и чтение входов. get_Mode
* читает аппаратный адрес и выбирает Desk. Один вывод в разных
* режимах может иметь разное назначение. Для нагрузки START=GPIO62,
* STOP=GPIO58, RES_OUT_2=GPIO3 (активный ноль). Направления заданы
* в GPIO_table.h; изменение распиновки требует сверки со схемой.
/* Выбор типа платы, направления GPIO и чтение входов. get_Mode
* читает аппаратный адрес и выбирает Desk. Один вывод в разных
* режимах может иметь разное назначение. Для нагрузки START=GPIO62,
* STOP=GPIO58, RES_OUT_2=GPIO3 (активный ноль). Направления заданы
* в GPIO_table.h; изменение распиновки требует сверки со схемой.
*/
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "filter_bat2.h"
@@ -51,10 +51,10 @@ void get_Mode()
{
EALLOW;
GpioCtrlRegs.GPAMUX1.all &= 0xFF000000; // 00—11
GpioCtrlRegs.GPAMUX2.all &= 0xFF00003F; // 19—27
GpioCtrlRegs.GPBMUX1.all &= 0xFFFFFFC0; // 32—34
GpioCtrlRegs.GPBMUX2.all &= 0x000FF000; // 48—53, 58—63
GpioCtrlRegs.GPAMUX1.all &= 0xFF000000; // Обнуляет двухбитовые поля MUX для GPIO0–11 по карте регистров TMS320F28335, выбирая обычный GPIO и сохраняя назначение GPIO12–15.
GpioCtrlRegs.GPAMUX2.all &= 0xFF00003F; // Выбирает обычный GPIO для выводов 19–27: маска сохраняет поля GPIO16–18 и GPIO28–31 в двухбитовой таблице MUX.
GpioCtrlRegs.GPBMUX1.all &= 0xFFFFFFC0; // Обнуляет младшие шесть битов MUX, переводя GPIO32–34 в режим обычных входов или выходов без изменения остальных выводов банка.
GpioCtrlRegs.GPBMUX2.all &= 0x000FF000; // Сохраняет поля GPIO54–57 и переводит GPIO48–53 и GPIO58–63 в обычный GPIO согласно двухбитовой раскладке GPBMUX2.
GpioCtrlRegs.GPADIR.bit.GPIO20 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO21 = 0;
@@ -187,7 +187,7 @@ void get_Buttons()
Inputs.all = butt;
}
/* Ничего такого в действительности нет
/* Ничего такого в действительности нет
if(Desk==dsk_BKST)
{ bSecretBt = !GpioDataRegs.GPBDAT.bit.GPIO63;
InputRep1 = bSecretBt;
@@ -205,7 +205,8 @@ void get_Buttons()
if(Desk==dsk_EPLT)
{
bTestLamp = er_anal(GpioDataRegs.GPADAT.bit.GPIO26, &INcount0, 1000, 0);
/* Сохраняет TESTLAMP до достижения порога фильтра: 1000 означает число опросов главного цикла, а bTestLamp удерживает прежний режим при кратком провале GPIO26, чтобы рабочие маски не прерывали тест индикации. */
bTestLamp = er_anal(GpioDataRegs.GPADAT.bit.GPIO26, &INcount0, 1000, bTestLamp);
if(GpioDataRegs.GPADAT.bit.GPIO27) butt +=0x0001;
if(GpioDataRegs.GPADAT.bit.GPIO24) butt +=0x0002;
@@ -223,7 +224,7 @@ void get_Buttons()
if(GpioDataRegs.GPADAT.bit.GPIO5 ) butt +=0x0800;
if(GpioDataRegs.GPBDAT.bit.GPIO48) butt +=0x1000;
if(GpioDataRegs.GPADAT.bit.GPIO10) butt +=0x2000; // Секретный вход
if(GpioDataRegs.GPADAT.bit.GPIO10) butt +=0x2000; // Публикует активный секретный вход GPIO10 в бите 13 слова Inputs; маска 0x2000 задаёт назначенную ему позицию протокола.
Inputs.all = butt;
}
@@ -267,4 +268,4 @@ void get_Buttons()
butt += 8 * !GpioDataRegs.GPBDAT.bit.GPIO51;
Jumpers = butt;
}
}

View File

@@ -1,7 +1,7 @@
/* Логика пульта и индикации. cpu_timer1_isr_PULT выбирается
* только для dsk_EPLT; для нагрузки работает обработчик из measure.c.
* Lonely относится к активности обмена, не к задержке пуска СИФУ.
* Не смешивать функции двух режимов при изменении общего таймера.
/* Логика пульта и индикации. cpu_timer1_isr_PULT выбирается
* только для dsk_EPLT; для нагрузки работает обработчик из measure.c.
* Lonely относится к активности обмена, не к задержке пуска СИФУ.
* Не смешивать функции двух режимов при изменении общего таймера.
*/
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -46,7 +46,8 @@ interrupt void cpu_timer1_isr_PULT(void)
EALLOW;
CpuTimer1.InterruptCount++;
IER |= MINT13; // Set "global" priority
/* Оставляет только ранее разрешённые источники по таблице приоритетов TI MINT13 и сбрасывает M_INT13 — бит 12 регистра IER, запрещая повторный Timer1 и перемешивание стробов DATA/CLOCK; исходную маску восстановит IRET. */
IER &= (MINT13 & ~M_INT13);
EINT;
EDIS; // This is needed to disable write to EALLOW protected registers
@@ -60,8 +61,8 @@ interrupt void cpu_timer1_isr_PULT(void)
toggle_ONLINE();
toggle_RES_OUT_1(); // типа это готовность
toggle_RES_OUT_2(); // на всакий
toggle_RES_OUT_1(); // Переключает резервный выход RES_OUT_1 в каждом такте пульта, сохраняя существующую выдачу признака активности обработчика.
toggle_RES_OUT_2(); // Переключает второй резервный выход RES_OUT_2 синхронно с тактом пульта, сохраняя существующее поведение этого режима платы.
if(++Lonely > Alone) Lonely = Alone;

123
tests/testlamp/model.inc Normal file
View File

@@ -0,0 +1,123 @@
/* Проверяет удержание TESTLAMP и выбор пакетов при уже включённых рабочих лампах. */
#include <stdio.h> /* Выводит конкретные нарушенные условия и итог сценария. */
#include <string.h> /* Обнуляет регистровое пространство перед началом проверки. */
#define interrupt /* Убирает аппаратный атрибут ISR при компиляции модели на ПК. */
typedef float FILTERBAT; /* Позволяет подключить production-заголовок с интерфейсами фильтров. */
#include "measure.h" /* Получает настоящие READY_FREQ, BLINK_TIME, CANPOWSE и maximum_bright. */
#include "package.h" /* Получает настоящие адреса TESTLAMP, яркости, ламп и типов плат. */
#include "DSP2833x_SWPrioritizedIsrLevels.h" /* Использует реальную вычисляемую маску приоритетов TI MINT13. */
typedef union { /* Моделирует слово Modbus размером хостового int, сохраняя номера битов production-псевдонимов. */
unsigned int all; /* Обеспечивает запись целого слова по тому же смещению массива. */
struct { /* Размещает шестнадцать технологических битов в младшей части слова. */
unsigned int bit0:1, bit1:1, bit2:1, bit3:1, bit4:1, bit5:1, bit6:1, bit7:1; /* Сохраняет позиции кнопок и команд 0..7. */
unsigned int bit8:1, bit9:1, bitA:1, bitB:1, bitC:1, bitD:1, bitE:1, bitF:1; /* Сохраняет позиции повторителей входов и старших команд 8..15. */
} bit; /* Предоставляет production-псевдонимам доступ к полям регистра. */
} WORDE; /* Завершает хостовое представление одного регистра Modbus. */
struct gpio_bits { /* Предоставляет входы всех ветвей настоящей get_Buttons, без моделирования электрических свойств. */
unsigned GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO8, GPIO9, GPIO10, GPIO11; /* Представляет дискретные входы нескольких типов плат. */
unsigned GPIO20, GPIO21, GPIO22, GPIO23, GPIO24, GPIO25, GPIO26, GPIO27; /* Представляет перемычки и вход TESTLAMP GPIO26 пульта. */
unsigned GPIO48, GPIO49, GPIO50, GPIO51, GPIO52, GPIO53; /* Представляет входы банка B, читаемые get_Buttons. */
}; /* Завершает перечень моделируемых входов. */
struct gpio_reg { struct gpio_bits bit; }; /* Даёт регистрам GPADAT и GPBDAT production-форму обращения. */
static struct { struct gpio_reg GPADAT, GPBDAT; } GpioDataRegs; /* Хранит управляемые сценарием уровни входов. */
static struct { unsigned long all; } DigErr; /* Принимает диагностическую маску посторонней ветви SHKF. */
static struct { unsigned long InterruptCount; } CpuTimer1; /* Учитывает вызовы настоящего обработчика пульта. */
int modbus[129], Desk; /* Предоставляет технологические регистры и выбирает ветвь пульта. */
long INcount0; /* Повторяет тип накопителя в peripher.c проекта 166 и параметр long * функции er_anal, чтобы модель проверяла настоящий фильтр без несовместимых указателей. */
unsigned int CanPowse, CanGO; /* Хранит счётчик тактов выдачи CAN и признак готовности очередной передачи. */
unsigned long isMask, Lonely; /* Хранит тестовую маску и возраст последней связи. */
int isNumb, quaLamp = 6; /* Сохраняет тестовые цифры и число ламп из pulto.c. */
static unsigned int IER, mask_at_eint, entry_mask, failures, sends, nested_entries, timer_pending; /* Отслеживает разрешения прерываний, пакетные вызовы и возможность вложенного Timer1. */
static unsigned long lamp_word; /* Запоминает последний пакет шести сигнальных ламп канала 2. */
static long digit_word; /* Сохраняет последнее значение одного из двух семисегментных индикаторов в текущем такте. */
static int digit_dot, digit_sends; /* Запоминает режим форматирования цифр и наличие числового пакета в текущем такте. */
#define M_INT13 0x1000U /* Повторяет бит 12 INT13 из DSP2833x_Device.h для модели регистра IER. */
#define EALLOW ((void)0) /* Не моделирует защиту аппаратных регистров, поскольку она не влияет на рассматриваемые ветви. */
#define EDIS ((void)0) /* Завершает заглушку защищённой записи. */
#define EINT (mask_at_eint = IER) /* Снимает реально применённую маску в момент разрешения вложенных прерываний. */
#define ServiceDog() ((void)0) /* Исключает аппаратный сторожевой таймер из проверки индикации. */
#define toggle_ONLINE() ((void)0) /* Не моделирует электрический сигнал готовности ПЛИС. */
#define toggle_RES_OUT_1() ((void)0) /* Исключает резервный GPIO из логики выбора TESTLAMP. */
#define toggle_RES_OUT_2() ((void)0) /* Исключает второй резервный GPIO из пакетной проверки. */
void cpu_timer1_isr_PULT(void); /* Позволяет заглушке передачи смоделировать повторное аппаратное событие Timer1. */
static void kanal_Send(int adr, long dat, int dot) /* Записывает передаваемые данные, не подменяя проверяемый выбор режима. */
{ /* Начинает обработку одного production-вызова передачи. */
sends++; /* Позволяет обнаружить дополнительные вызовы передачи при повторном входе в ISR. */
if (timer_pending && (IER & M_INT13)) { /* Моделирует новый такт Timer1 во время незаконченной передачи, только когда собственное прерывание ошибочно разрешено. */
timer_pending = 0; /* Ограничивает искусственное событие одним вложенным вызовом. */
nested_entries++; /* Регистрирует перемешивание обработки двух тактов. */
cpu_timer1_isr_PULT(); /* Запускает настоящую production-функцию повторно при неверной маске. */
} /* Завершает моделирование потенциального повторного входа. */
if (adr == 2) lamp_word = (unsigned long)dat; /* Сохраняет именно канал сигнальных ламп, обновляемый в каждом такте. */
if (adr < 2) { /* Записывает также передачу цифр, чтобы обнаружить переход семисегментного индикатора в рабочий режим при провале кнопки. */
digit_word = dat; /* Сохраняет переданное production-функцией числовое значение. */
digit_dot = dot; /* Различает тест четырёх одинаковых цифр и обычное форматирование измерений. */
digit_sends++; /* Отмечает присутствие числового пакета в текущем такте. */
} /* Завершает фиксацию передачи на семисегментный индикатор. */
} /* Завершает заглушку аппаратной передачи. */
/* HANDLERS */
static void check(int condition, const char *name) /* Накапливает ошибки, позволяя проверить все последствия одного дефекта. */
{ /* Начинает регистрацию одного требования. */
if (!condition) { /* Выводит только нарушенные условия. */
printf("FAIL %s\n", name); /* Даёт мутационному тесту устойчивое имя обнаруженного дефекта. */
failures++; /* Делает код завершения ненулевым при любой регрессии. */
} /* Завершает обработку ошибки. */
} /* Завершает проверку одного требования. */
static void tick(void) /* Моделирует сохранение и восстановление IER на границе аппаратного ISR. */
{ /* Начинает один такт индикации. */
IER = entry_mask; /* Возвращает исходные разрешения источников перед новым обработчиком. */
sends = 0; /* Отделяет вызовы передачи текущего такта от предыдущих. */
digit_sends = 0; /* Не принимает пакет предыдущего такта за обновление цифр в текущем. */
cpu_timer1_isr_PULT(); /* Выполняет настоящий алгоритм выбора режима, PWM и пакетов. */
check(mask_at_eint == (entry_mask & MINT13 & ~M_INT13), "interrupt mask"); /* Проверяет отсутствие собственного Timer1 и запрет включения ранее выключенных источников. */
check(sends >= 1 && sends <= 2, "packet count"); /* Требует штатный объём одного такта: пакет ламп и не более одного пакета очередного индикатора. */
} /* Завершает один такт индикации. */
int main(void) /* Запускает на настоящих функциях сценарий рабочих ламп, теста с помехами и возврата в работу. */
{ /* Начинает общий поведенческий сценарий. */
int i, pwm_on = 0; /* Перебирает опросы и такты; считает включённые такты рабочего PWM за один полный период. */
memset(modbus, 0, sizeof(modbus)); /* Убирает старые команды и состояния перед сценарием. */
check(sizeof(WORDE) == sizeof(modbus[0]), "register layout"); /* Подтверждает корректность production-псевдонимов в хостовой модели. */
Desk = dsk_EPLT; /* Выбирает реальную ветвь чтения TESTLAMP пульта. */
modbus[2] = 1; /* Включает рабочую лампу до нажатия TESTLAMP, как в сообщении пользователя. */
Bright[0] = maximum_bright; /* Задаёт уже включённой лампе постоянную яркость для обнаружения случайных провалов. */
entry_mask = M_INT13 | (1U << 0) | (1U << 13); /* Разрешает Timer1, группу ADC и Timer2, проверяя также маскирование равного приоритета. */
tick(); /* Получает первый рабочий пакет ламп. */
check(lamp_word == 1, "normal lamp before test"); /* Подтверждает исходное постоянное свечение до нажатия. */
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Нажимает физическую кнопку активным высоким уровнем. */
for (i = 0; i < 1001; i++) get_Buttons(); /* Проходит реальные 1000 накопительных опросов и следующий опрос фиксации порога. */
check(bTestLamp == 1, "test activation"); /* Требует включения теста после полной выдержки. */
for (i = 0; i < 600; i++) { /* Проверяет удержание более BLINK_TIME=500 тактов без подавления штатного медленного мигания. */
unsigned long expected_mask; /* Сохраняет фазу теста перед очередным возмущением кнопки. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; /* Вводит короткий провал входа на один опрос главного цикла. */
get_Buttons(); /* Выполняет настоящий накопительный фильтр и публикацию кнопки. */
check(bTestLamp == 1, "held test"); /* Проверяет сохранение TESTLAMP вместо мгновенного возврата рабочих масок. */
timer_pending = 1; /* Допускает новый аппаратный такт во время передачи для проверки запрета повторного входа. */
tick(); /* Формирует тестовый пакет при кратком провале физического входа. */
expected_mask = isMask; /* Получает ожидаемую медленную фазу из настоящей what_is. */
check(lamp_word == expected_mask, "test packet during input glitch"); /* Исключает рабочую маску уже горящих ламп из тестового потока. */
check(!digit_sends || (digit_word == isNumb && digit_dot == 4), "test digit packet during input glitch"); /* Требует сохранения тестового значения и формата цифр во время помехи кнопки. */
check(nested_entries == 0, "nested transmission"); /* Требует единственного владельца DATA/CLK на протяжении передачи. */
timer_pending = 0; /* Убирает смоделированное событие после завершения текущего такта. */
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Возвращает уровень удерживаемой кнопки. */
get_Buttons(); /* Восстанавливает насыщенный накопитель без смены тестового режима. */
} /* Завершает серию коротких помех при удержании. */
check(isNumb == 1111 && isMask == 0xFFFFFFFFUL, "slow test sequence"); /* После 600 миллисекунд требует единственного штатного шага what_is с периодом 500 тактов, а не ускоренного строба. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; /* Отпускает физическую кнопку на длительное время. */
for (i = 0; i < 1001; i++) get_Buttons(); /* Даёт накопителю полностью разрядиться и зафиксировать отпускание. */
check(bTestLamp == 0, "test release"); /* Проверяет, что сохранение состояния не залипает в TESTLAMP. */
tick(); /* Возвращает передачу рабочей маски после отпускания. */
check(lamp_word == 1, "normal lamp after test"); /* Восстанавливает уже включённую до теста лампу. */
cTestLamp = 1; /* Включает независимую удалённую команду теста через production-регистр. */
tick(); /* Проверяет приоритет удалённого TESTLAMP при отпущенной физической кнопке. */
check(lamp_word == isMask, "remote test"); /* Требует тестовую маску для обоих источников команды. */
cTestLamp = 0; /* Завершает удалённый тест перед проверкой рабочего PWM. */
Bright[0] = 6; /* Устанавливает шесть включённых тактов из десяти в production-периоде яркости. */
for (i = 0; i < 10; i++) { /* Проверяет один полный период PWM после выхода из теста. */
tick(); /* Даёт настоящей функции вычислить текущую фазу яркости. */
check(lamp_word == 0 || lamp_word == 1, "normal PWM packet"); /* Исключает оставшиеся тестовые биты из рабочей маски. */
pwm_on += lamp_word == 1; /* Измеряет реальное число включённых тактов production-PWM. */
} /* Завершает период обычной яркости. */
check(pwm_on == 6, "normal PWM duty"); /* Подтверждает сохранение заданной яркости 6/10 после выхода из теста. */
printf("%s TESTLAMP: held input glitches, single ISR, normal lamps, release, remote command\n", failures ? "FAIL" : "PASS"); /* Выводит проверенные свойства без заявления о физическом испытании платы. */
return failures ? 1 : 0; /* Возвращает результат всех поведенческих требований. */
} /* Завершает хостовую проверку. */

View File

@@ -0,0 +1,51 @@
"""Проверяет TESTLAMP на настоящих функциях прошивки без подключения платы."""
import argparse # Позволяет использовать доступный GCC для модели аппаратного окружения.
import os # Передаёт компилятору PATH с его вспомогательными программами и библиотеками.
from pathlib import Path # Разрешает исходники и модель относительно каталога этого теста.
import re # Извлекает функции, убирая комментарии, которые могут содержать примеры скобок.
import subprocess # Компилирует и запускает независимые проверки с контролем результата.
import tempfile # Удаляет только временные исходники и исполняемые файлы данного запуска.
ROOT = Path(__file__).resolve().parents[2] # Получает корень репозитория из расположения tests/testlamp.
def source(name): # Читает production-модуль без изменения исходного файла.
data = (ROOT / "Source/Internal" / name).read_bytes() # Читает исходник без изменения его кодировки: в 166 ещё есть модули Windows-1251.
try: # Сначала использует UTF-8 для перенесённых модулей индикации.
text = data.decode("utf-8") # Строго декодирует обновлённые исходники без потери русских комментариев.
except UnicodeDecodeError: # Поддерживает старый measure.c, не изменяя его ради хостового теста.
text = data.decode("cp1251") # Читает исходную Windows-1251 только при невозможности декодирования UTF-8.
return re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S) # Убирает комментарии перед разбором вложенных блоков.
def function(text, name): # Выбирает определение функции вместо копии её алгоритма в тесте.
match = re.search(r"(?:interrupt\s+)?(?:void|int)\s+" + name + r"\([^;{}]*\)\s*\{", text) # Отличает тело функции от прототипов и вызовов.
assert match is not None, name # Завершает проверку, если проверяемая production-функция исчезла.
end, depth = match.end(), 1 # Учитывает найденную открывающую скобку внешнего блока.
while depth: # Читает все вложенные ветви до конца определения.
depth += (text[end] == "{") - (text[end] == "}") # Находит парную закрывающую скобку.
end += 1 # Включает текущий символ в извлекаемый фрагмент.
return text[match.start():end] # Сохраняет порядок операторов настоящей функции.
parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение отдельного запуска проверки.
parser.add_argument("--cc", default="C:/msys64/mingw64/bin/gcc.exe", help="Путь к GCC") # Повторяет установленный в этом проекте хостовый компилятор.
args = parser.parse_args() # Разбирает параметры до формирования временных файлов.
button = function(source("peripher.c"), "get_Buttons") # Проверяет именно публикацию отфильтрованной кнопки в Modbus.
pult = source("pulto.c") # Берёт текущую обработку индикации, включая реальные ветви теста и яркости.
body = "\n".join((function(source("measure.c"), "er_anal"), button, function(pult, "what_is"), function(pult, "cpu_timer1_isr_PULT"))) # Собирает функции, участвующие в переключении режима и передаче масок.
before, after = Path(__file__).with_name("model.inc").read_text(encoding="utf-8").split("/* HANDLERS */") # Помещает production-функции между аппаратными заглушками и поведенческими сценариями.
old_button, count_button = re.subn(r"(er_anal\(GpioDataRegs\.GPADAT\.bit\.GPIO26,\s*&INcount0,\s*1000,\s*)bTestLamp(\))", r"\g<1>0\2", body) # Возвращает сброс фильтра при одном провале только во временную мутацию.
old_mask, count_mask = re.subn(r"IER\s*&=\s*\(MINT13\s*&\s*~M_INT13\);", "IER |= MINT13;", body) # Восстанавливает ошибочное разрешение прерываний в отдельном временном варианте.
assert count_button == count_mask == 1, "Не найдены единственные места исправлений TESTLAMP" # Не позволяет мутации незаметно проверять неизменённый код.
environment = dict(os.environ) # Сохраняет окружение основного процесса.
environment["PATH"] = str(Path(args.cc).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает DLL и вспомогательные программы GCC доступными дочерним процессам.
with tempfile.TemporaryDirectory(prefix="testlamp-") as directory: # Изолирует артефакты каждой проверки от дерева прошивки.
for name, functions, failure in (("production", body, None), ("old_button", old_button, "FAIL held test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет исправление и чувствительность сценариев к каждой из двух старых ошибок.
c_path, exe_path = Path(directory) / (name + ".c"), Path(directory) / (name + ".exe") # Разделяет временные исходники и программы трёх вариантов.
c_path.write_text(before + functions + after, encoding="utf-8") # Сохраняет модель и русские пояснения в UTF-8.
subprocess.run([args.cc, "-std=c99", "-O3", "-fno-strict-aliasing", "-Werror=implicit-function-declaration", "-I", str(ROOT / "Source/Internal/Include"), "-I", str(ROOT / "Source/External/v120/DSP2833x_common/include"), str(c_path), "-o", str(exe_path)], env=environment, check=True) # Сохраняет адреса production-псевдонимов регистров и использует реальную таблицу приоритетов TI.
result = subprocess.run([str(exe_path)], env=environment, capture_output=True, text=True, timeout=20) # Ограничивает зависание и сохраняет конкретные нарушенные условия.
if failure is None: # Рабочая версия обязана пройти все сценарии.
print(result.stdout, end="") # Показывает результат поведенческих проверок текущей прошивки.
assert result.returncode == 0, result.stdout + result.stderr # Возвращает ошибку при любой регрессии TESTLAMP или рабочего режима.
else: # Старая ошибка должна воспроизводимо нарушить соответствующее требование.
assert result.returncode == 1 and failure in result.stdout, result.stdout + result.stderr # Доказывает, что тест действительно обнаруживает возвращённый дефект.
print("PASS mutation:", name, "rejected") # Сообщает об ожидаемом провале временного варианта со старой ошибкой.