diff --git a/Source/Internal/ecan.c b/Source/Internal/ecan.c index f3aa798..bae99d6 100644 --- a/Source/Internal/ecan.c +++ b/Source/Internal/ecan.c @@ -1,8 +1,8 @@ -/* Êîìàíäû è òåëåìåòðèÿ eCAN. MBOX1 ïðèíèìàåò êàäðû, MBOX0 ïåðåäà¸ò - * òåõíîëîãè÷åñêèå ðåãèñòðû, MBOX2 âûäåëåí ïîä îòâåòû ëîããåðà. - * Ñëîâî 127 ïðîõîäèò UMP_commands_write íåïîñðåäñòâåííî â CAN ISR. - * Çàïðîñ àðõèâà ISR ëèøü ñòàâèò íà îáñëóæèâàíèå; ôðàãìåíòàöèÿ - * îòâåòîâ âûïîëíÿåòñÿ UMP_CAN_service èç ôîíîâîãî öèêëà. +/* Команды и телеметрия eCAN. MBOX1 принимает кадры, MBOX0 передаёт + * технологические регистры, MBOX2 выделен под ответы логгера. + * Слово 127 проходит UMP_commands_write непосредственно в CAN ISR. + * Запрос архива ISR лишь ставит на обслуживание; фрагментация + * ответов выполняется UMP_CAN_service из фонового цикла. */ #include "DSP2833x_Device.h" // DSP2833x Headerfile Include File #include "DSP2833x_SWPrioritizedIsrLevels.h" @@ -39,9 +39,9 @@ int CanTimeOutErrorTR = 0; int wait=0; -/* MBOX2 âûäåëåí ïîä îòâåòû ðåãèñòðàòîðà; øòàòíûé MBOX0 ïðîäîëæàåò - * ïåðåäàâàòü òåõíîëîãè÷åñêèå äàííûå íåçàâèñèìî. ISR òîëüêî ñòàâèò çàïðîñ, - * à ñíèìîê è îòïðàâêà êîðîòêèìè øàãàìè âûïîëíÿþòñÿ â îñíîâíîì öèêëå. */ +/* MBOX2 выделен под ответы регистратора; штатный MBOX0 продолжает + * передавать технологические данные независимо. ISR только ставит запрос, + * а снимок и отправка короткими шагами выполняются в основном цикле. */ static volatile unsigned int ump_can_pending; static volatile unsigned int ump_can_tag, ump_can_address, ump_can_value; static unsigned int ump_can_reply[UMP_CAN_MAX_WORDS]; @@ -86,17 +86,17 @@ void InitCan(int Port, int DevNum) // Required before writing the MSGIDs ECanRegs->CANME.all = 0; -// çàäàåì àäðåñ 0 àùèêa íà ïåðåäà÷ó +// задаем адрес 0 ащикa на передачу ECanMboxes->MBOX0.MSGID.all = id + 0x10 + DevNum; -// çàäàåì àäðåñ 1 àùèêa íà ïðèåì - ECanMboxes->MBOX1.MSGID.all = id + DevNum; //ïîìåíàòü!!!! 1 è 0!!! +// задаем адрес 1 ащикa на прием + ECanMboxes->MBOX1.MSGID.all = id + DevNum; //поменать!!!! 1 и 0!!! -// MBOX0 è MBOX2 ïåðåäàþò äàííûå; MBOX1 ïðèíèìàåò êîìàíäû. +// MBOX0 и MBOX2 передают данные; MBOX1 принимает команды. ECanMboxes->MBOX2.MSGID.all = id + 0x20 + DevNum; ECanRegs->CANMD.all = 0xFFFFFFFA; -// Âêëþ÷àåì òðè mailbox: òåëåìåòðèÿ, êîìàíäû è îòâåòû ëîããåðà. +// Включаем три mailbox: телеметрия, команды и ответы логгера. ECanRegs->CANME.all = 0x00000007; // Clear all TAn bits @@ -121,7 +121,7 @@ void InitCan(int Port, int DevNum) ECanRegs->CANMC.all = ECanShadow.CANMC.all; while(!ECanRegs->CANES.bit.CCE); // Wait for CCE bit to be set.. -// íàñòðèâàåì ñêîðîñòü CAN +// настриваем скорость CAN ECanShadow.CANBTC.all = ECanRegs->CANBTC.all; ECanShadow.CANBTC.bit.SJWREG=1; @@ -134,12 +134,12 @@ void InitCan(int Port, int DevNum) ECanRegs->CANMC.all = ECanShadow.CANMC.all; while(ECanRegs->CANES.bit.CCE); // Wait for CCE bit to be cleared.. -// çàäàåì òàéìàóòû äëà îæèäàíèà îòïðàâêè ïîëó÷åíèà ïîñûëêè +// задаем таймауты дла ожиданиа отправки получениа посылки ECanMOTORegs->MOTO0 = 550000; ECanMOTORegs->MOTO1 = 550000; - ECanRegs->CANTOC.all = 1; - ECanRegs->CANTOS.all = 0; // clear all time-out flags + /* До первой передачи отключает тайм-ауты всех mailbox; по SPRUI07, 13.8.18.3, сброс TOC снимает TOS и MTOF. */ + ECanRegs->CANTOC.all = 0; ECanRegs->CANTSC = 0; // clear time-out counter ECanShadow.CANGIM.all = 0; @@ -161,7 +161,7 @@ void InitCan(int Port, int DevNum) IER |= M_INT9; // Enable CPU INT EDIS; -// çàâåðøèëè íàñòðîéêó CAN àùèêîâ +// завершили настройку CAN ащиков MessageReceivedCount = 0; ErrorCount = 0; @@ -169,8 +169,8 @@ void InitCan(int Port, int DevNum) MessageTransivedCount=0; } -/* Ïðè çàíÿòîì MBOX0 âîçâðàùàåòñÿ áåç îæèäàíèÿ ACK â öèêëå. - * Ýòî îòëè÷àåòñÿ îò áëîêèðóþùèõ UART/EEPROM îïåðàöèé main. +/* При занятом MBOX0 возвращается без ожидания ACK в цикле. + * Это отличается от блокирующих UART/EEPROM операций main. */ void CAN_send(int Port, int data[], int Addr) { @@ -181,6 +181,10 @@ void CAN_send(int Port, int data[], int Addr) ECanRegs = &ECanaRegs; ECanMboxes = &ECanaMboxes; + /* Не меняет данные mailbox 0, пока передача либо её аппаратная отмена ещё выполняется; бит 0 соответствует штатному TX. */ + if ((ECanRegs->CANTRS.all | ECanRegs->CANTRR.all) & 1UL) + return; /* Сохраняет текущий кадр до завершения CANTRS/CANTRR, не ожидая аппаратного ответа в главном цикле. */ + if(wait) if(!(ECanRegs->CANTA.all & 1)) if(!(ECanRegs->CANAA.all & 1)) @@ -198,10 +202,15 @@ void CAN_send(int Port, int data[], int Addr) ECanMboxes->MBOX0.MDL.all = loword; EALLOW; + /* Отключает старый контроль mailbox 0 перед перезапуском счётчика; остальные mailbox используют собственный контроль. */ + ECanRegs->CANTOC.all = 0; + /* Записывает ECanRegs->CANTSC значением 0; E CAN Regs CANTSC. */ ECanRegs->CANTSC = 0; // clear time-out counter + /* Заново разрешает тайм-аут штатного TX с порогом MOTO0, установленным в InitCan, только для следующего кадра. */ + ECanRegs->CANTOC.all = 1UL; EDIS; - ECanRegs->CANTRS.all = 1; // çàïóñòèòü ïåðåäà÷ó + ECanRegs->CANTRS.all = 1; // запустить передачу wait=1; led1_toggle(); @@ -220,9 +229,9 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes) hiword = ECanMboxes->MBOX1.MDH.all; loword = ECanMboxes->MBOX1.MDL.all; - /* Ñëóæåáíûé çàïðîñ ëîããåðà íå ÿâëÿåòñÿ êîìàíäîé ÓÌÏ è ñàì íå äîëæåí - * çàïîëíÿòü æóðíàë CAN-ñîáûòèÿìè ïðè îíëàéí-÷òåíèè. Êîðîòêèå êàäðû - * îòáðàñûâàþòñÿ: íåçàïîëíåííûå áàéòû mailbox ìîãëè îñòàòüñÿ îò ïðîøëîãî. */ + /* Служебный запрос логгера не является командой УМП и сам не должен + * заполнять журнал CAN-событиями при онлайн-чтении. Короткие кадры + * отбрасываются: незаполненные байты mailbox могли остаться от прошлого. */ if ((unsigned int)(hiword >> 16) == UMP_CAN_MARKER) { if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8) { ump_can_tag = (unsigned int)hiword; @@ -244,10 +253,10 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes) Data[1] = (loword>>16) & 0xffff; Data[2] = (loword ) & 0xffff; - /* Êàæäîå ðàçðåø¸ííîå ñëîâî èìååò ñîáñòâåííûé àäðåñ. Íå îïðàøèâàåì - * Commands ïîñëå êàäðà áåçóñëîâíî: ïàêåò òåëåìåòðèè íå äîëæåí - * ïðåâðàùàòü âíóòðåííèé ñáðîñ ïàðàìåòðîâ â êîìàíäíûé ôðîíò. - * Àäðåñ 127 ïðîõîäèò ÷åðåç åäèíûé îáðàáîò÷èê ïîëíîãî ñëîâà. */ + /* Каждое разрешённое слово имеет собственный адрес. Не опрашиваем + * Commands после кадра безусловно: пакет телеметрии не должен + * превращать внутренний сброс параметров в командный фронт. + * Адрес 127 проходит через единый обработчик полного слова. */ { unsigned int i; for (i = 0; i < 3; i++, adr++) { @@ -258,7 +267,7 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes) } } - /* Ôèêñèðóåì ïðèìåí¸ííóþ êîìàíäó äî âûõîäà èç îáðàáîò÷èêà CAN. */ + /* Фиксируем применённую команду до выхода из обработчика CAN. */ ump_log_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]); led2_toggle(); } @@ -292,8 +301,16 @@ interrupt void CANa_reset_err(void) PieCtrlRegs.PIEACK.all = 0xFFFF; // Enable PIE interrupts EINT; - ECanaRegs.CANTRR.all = 1; - CanTimeOutErrorTR++; + /* Проверяет реальный тайм-аут mailbox 0: отложенное PIE-прерывание после успешного TX уже может не иметь причины. */ + if (ECanaRegs.CANTOS.all & 1UL) + { /* Обрабатывает только штатный передатчик; служебный mailbox 2 контролируется отдельно в UMP_CAN_service. */ + EALLOW; /* Разрешает изменение управляющего регистра eCAN перед снятием источника прерывания. */ + ECanaRegs.CANTOC.all = 0; /* Сброс TOC снимает TOS и MTOF согласно SPRUI07, 13.8.18.3; одной отмены TX недостаточно. */ + EDIS; /* Восстанавливает защиту регистров до выхода из ветви обработки тайм-аута. */ + if (ECanaRegs.CANTRS.all & 1UL) /* Передача могла завершиться между чтением TOS и снятием контроля. */ + ECanaRegs.CANTRR.all = 1UL; /* Запрашивает отмену только ещё занятого mailbox 0, не задерживая прерывание ожиданием CANAA. */ + CanTimeOutErrorTR++; /* Считает обнаруженный тайм-аут один раз, исключая фантомные повторные входы PIE. */ + } /* Следующая штатная передача сама заново включит TOC после сброса временного счётчика. */ PieCtrlRegs.PIEACK.bit.ACK9 |= 1; @@ -307,10 +324,10 @@ interrupt void CANa_reset_err(void) //=========================================================================== -/* Îäèí âûçîâ îáñëóæèâàåò ìàêñèìóì îäèí êàäð è íèêîãäà íå æä¸ò ACK â öèêëå. - * Ñíèìîê ôèêñèðóåòñÿ îäèí ðàç íà çàïðîñ: CAN-ôðàãìåíòû íå ñìåøèâàþò - * çíà÷åíèÿ äî/ïîñëå ïåðåêëþ÷åíèÿ êîëüöåâîãî áàíêà. Íîâûå çàïðîñû çàáèðàåì - * ïîñëå çàâåðøåíèÿ ïðåäûäóùåãî îòâåòà. GUI äåðæèò òîëüêî îäèí çàïðîñ. */ +/* Один вызов обслуживает максимум один кадр и никогда не ждёт ACK в цикле. + * Снимок фиксируется один раз на запрос: CAN-фрагменты не смешивают + * значения до/после переключения кольцевого банка. Новые запросы забираем + * после завершения предыдущего ответа. GUI держит только один запрос. */ void UMP_CAN_service(void) { unsigned int saved, tag, address, value, error; @@ -321,11 +338,11 @@ void UMP_CAN_service(void) ump_can_waiting = 0; } else if ((ECanaRegs.CANAA.all & 4UL) || ((unsigned long)CpuTimer1.InterruptCount - ump_can_sent_at > 250UL)) { - /* Íåò ïîäòâåðæäåíèÿ øèíû: îòìåíÿåì òîëüêî ñëóæåáíûé mailbox. - * GUI îáíàðóæèò íåïîëíûé îòâåò ïî òàéì-àóòó; óïðàâëåíèå MBOX0 - * è ïðè¸ì êîìàíä MBOX1 ïðîäîëæàþòñÿ. */ + /* Нет подтверждения шины: отменяем только служебный mailbox. + * GUI обнаружит неполный ответ по тайм-ауту; управление MBOX0 + * и приём команд MBOX1 продолжаются. */ ECanaRegs.CANTRR.all = 4UL; - /* Äî ôàêòè÷åñêîãî çàâåðøåíèÿ îòìåíû mailbox íå ïåðåèñïîëüçóåì. */ + /* До фактического завершения отмены mailbox не переиспользуем. */ if (!(ECanaRegs.CANAA.all & 4UL)) return; ECanaRegs.CANAA.all = 4UL; ump_can_waiting = 0; @@ -342,7 +359,7 @@ void UMP_CAN_service(void) __restore_interrupts(saved); } if (ump_can_index >= ump_can_length) return; - /* Ïåðåä ñëåäóþùèì ôðàãìåíòîì óñòóïàåì øòàòíîé òåëåìåòðèè MBOX0. */ + /* Перед следующим фрагментом уступаем штатной телеметрии MBOX0. */ if (ECanaRegs.CANTRS.all & 1UL) return; hiword = ((unsigned long)(UMP_CAN_MARKER + ump_can_index/2) << 16) | ump_can_reply_tag; loword = ((unsigned long)ump_can_reply[ump_can_index] << 16) | ump_can_reply[ump_can_index+1]; diff --git a/tests/can_timeout/model.inc b/tests/can_timeout/model.inc new file mode 100644 index 0000000..1291628 --- /dev/null +++ b/tests/can_timeout/model.inc @@ -0,0 +1,58 @@ +#include /* Задаёт размеры 16- и 32-битных типов C28x в хостовой модели. */ +#include /* Печатает конкретное нарушенное условие и итог сценариев. */ +#include /* Обнуляет модель между независимыми сценариями. */ +typedef uint16_t Uint16; /* Воспроизводит тип масок PIE, используемый обработчиком TI. */ +typedef uint32_t Uint32; /* Воспроизводит упаковку двух технологических слов в MDH/MDL. */ +#define interrupt /* Убирает только аппаратный атрибут функции при компиляции на ПК. */ +#define EALLOW ((void)0) /* Модель не воспроизводит механизм защиты регистров ядра. */ +#define EDIS ((void)0) /* Защита EALLOW проверяется сборкой настоящего модуля под TI. */ +#define EINT ((void)0) /* Входы ISR вызывает сценарий, не поток операционной системы. */ +#define DINT ((void)0) /* Отключение прерываний не требует имитации планировщика на ПК. */ +#define M_INT9 0x0100U /* Маска CPU INT9 соответствует девятой группе PIE. */ +#define MINT9 0x0100U /* Для проверки жизненного цикла сохраняет разрешение группы CAN. */ +#define MG96 0x00DFU /* Исключает повторную вложенную обработку INTx6 той же группы. */ +struct REG { uint32_t all; }; /* Представляет 32-битные регистры eCAN без зависимости от размеров long на хосте. */ +struct ECAN_REGS { struct REG CANTA, CANAA, CANTRR, CANTRS, CANTOC, CANTOS; uint32_t CANTSC; }; /* Даёт рабочим функциям реальные имена используемых регистров. */ +static volatile struct ECAN_REGS ECanaRegs; /* Позволяет наблюдать все записи production-функций при оптимизации O3. */ +struct ECAN_MBOXES { struct { struct REG MDH, MDL; } MBOX0; }; /* Моделирует только штатный передатчик, который проверяет этот тест. */ +static volatile struct ECAN_MBOXES ECanaMboxes; /* Содержимое mailbox остаётся доступным для проверок неизменности занятого кадра. */ +static struct { struct { Uint16 all; } PIEIER9; union { unsigned all; struct { unsigned reserved:8, ACK9:1; } bit; } PIEACK; } PieCtrlRegs; /* Связывает запись всей маски PIEACK с битом подтверждения девятой группы в позиции 8. */ +static Uint16 IER; /* Представляет маску разрешённых прерываний ядра. */ +static int wait, CanTimeOutErrorTR; /* Повторяет состояние штатного передатчика и счётчик обнаруженных тайм-аутов. */ +static void led1_toggle(void) {} /* Светодиод передачи не влияет на завершение аппаратной транзакции. */ +/* PRODUCTION */ +#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Не отключает проверку условий при оптимизации или NDEBUG. */ +static void reset_model(void) { /* Начинает сценарий без остаточных запросов и подтверждений предыдущего теста. */ + memset((void *)&ECanaRegs, 0, sizeof(ECanaRegs)); /* Сбрасывает моделируемые аппаратные регистры CAN. */ + memset((void *)&ECanaMboxes, 0, sizeof(ECanaMboxes)); /* Убирает ранее переданное содержимое mailbox. */ + memset(&PieCtrlRegs, 0, sizeof(PieCtrlRegs)); /* Исключает ложное подтверждение PIE из предыдущего сценария. */ + PieCtrlRegs.PIEIER9.all = 0x0030U; IER = M_INT9; /* Разрешает оба CAN-прерывания для проверки восстановления маски ISR. */ + wait = 0; CanTimeOutErrorTR = 0; /* Повторяет начальные значения production-переменных. */ +} /* Завершает начальную установку независимого сценария. */ +int main(void) { /* Проверяет потерю ACK, фантомное прерывание, занятый mailbox и восстановление отправки. */ + int words[128] = {0}; unsigned calls; /* Хранит данные кнопок и ограничивает имитацию повторных входов ISR. */ + reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTOS.all = 1; ECanaRegs.CANTRS.all = 5; wait = 1; /* Тайм-аут относится к mailbox 0, пока служебный mailbox 2 тоже занят. */ + for (calls = 0; calls < 4 && ECanaRegs.CANTOS.all; calls++) { /* Повторяет IRQ, пока источник не снят, с явной защитой от зависания старого кода. */ + CANa_reset_err(); /* Выполняет настоящий обработчик проекта, включая отмену TX и подтверждение PIE. */ + ECanaRegs.CANTOS.all &= ECanaRegs.CANTOC.all; /* По SPRUI07 13.8.18.3 сброс TOC аппаратно снимает TOS и MTOF; одна запись CANTRR этого не гарантирует. */ + } /* Проверяет, что обработчик освобождает процессор после первого тайм-аута. */ + CHECK(calls == 1 && ECanaRegs.CANTOS.all == 0 && CanTimeOutErrorTR == 1); /* Повторные входы без снятия источника означают блокирование основного цикла. */ + CHECK(ECanaRegs.CANTRR.all == 1 && ECanaRegs.CANTRS.all == 5); /* Отмена запрошена только для штатного TX; модель намеренно ещё не завершила аппаратную отмену. */ + CHECK(PieCtrlRegs.PIEIER9.all == 0x0030U && PieCtrlRegs.PIEACK.bit.ACK9 == 1); /* ISR сохраняет прежние разрешения обоих каналов CAN и подтверждает свою группу. */ + words[16] = 8; ECanaMboxes.MBOX0.MDH.all = 0x12345678UL; /* Подготавливает новое состояние кнопки при ещё занятом предыдущем кадре. */ + CAN_send(0, words, 16); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x12345678UL); /* Не допускает перезаписи данных до завершения отмены. */ + ECanaRegs.CANTRS.all = 0; ECanaRegs.CANTRR.all = 0; ECanaRegs.CANAA.all = 1; ECanaRegs.CANTSC = 999; /* Имитирует аппаратное завершение отмены и подтверждение CANAA0. */ + CAN_send(0, words, 16); /* Проверяет повторный запуск тем же реальным передатчиком после исчезновения тайм-аута. */ + CHECK(ECanaRegs.CANTOC.all == 1 && ECanaRegs.CANTSC == 0 && ECanaRegs.CANTRS.all == 1); /* Новая передача вновь получает собственный отсчёт тайм-аута. */ + CHECK(ECanaMboxes.MBOX0.MDH.all == 0xE0100008UL && ECanaMboxes.MBOX0.MDL.all == 0); /* После восстановления передаются свежие данные R16, а не старый нулевой кадр. */ + reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTRS.all = 1; /* Фантомное PIE-прерывание может прийти, когда начат следующий кадр без тайм-аута. */ + CANa_reset_err(); CHECK(ECanaRegs.CANTRR.all == 0 && CanTimeOutErrorTR == 0 && ECanaRegs.CANTOC.all == 1); /* Не отменяет исправную передачу при уже снятом TOS0. */ + reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTOS.all = 1; /* Передача успела завершиться, но сценарий сохраняет зафиксированный флаг тайм-аута. */ + CANa_reset_err(); CHECK(ECanaRegs.CANTRR.all == 0 && ECanaRegs.CANTOC.all == 0); /* Снимает контроль, не отменяя уже свободный mailbox. */ + reset_model(); ECanaRegs.CANTRR.all = 1; ECanaMboxes.MBOX0.MDH.all = 0x11223344UL; /* Аппаратная отмена ещё активна, даже если CANTRS уже снят. */ + CAN_send(0, words, 16); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x11223344UL && ECanaRegs.CANTRS.all == 0); /* Не запускает повторный кадр до снятия CANTRR0. */ + reset_model(); wait = 1; ECanaRegs.CANTA.all = 1; /* Проверяет обычную передачу после успешного подтверждения предыдущего кадра. */ + CAN_send(0, words, 16); CHECK(ECanaRegs.CANTRS.all == 1 && ECanaRegs.CANTOC.all == 1); /* Исправление не блокирует штатный обмен при наличии CAN ACK. */ + puts("PASS CAN timeout: IRQ released, phantom ignored, busy mailbox preserved, TX recovered"); /* Сообщает только о проверенных сценариях программной модели. */ + return 0; /* Подтверждает прохождение всех проверок оптимизированного рабочего кода. */ +} /* Завершает тест без доступа к реальному USB-CAN или питанию платы. */ diff --git a/tests/can_timeout/run_test.py b/tests/can_timeout/run_test.py new file mode 100644 index 0000000..b8e34d3 --- /dev/null +++ b/tests/can_timeout/run_test.py @@ -0,0 +1,31 @@ +"""Проверяет тайм-аут штатного CAN на настоящих функциях проекта 166.""" # Модель не заменяет испытание аппаратного eCAN. +import os # Передаёт пути библиотек GCC только дочерним процессам. +from pathlib import Path # Находит исходники независимо от рабочего каталога запуска. +import re # Удаляет комментарии и выделяет тела проверяемых функций. +import subprocess # Запускает компилятор и проверяет поведенческий результат модели. +import tempfile # Изолирует сгенерированные файлы от образов прошивки пользователя. +import sys # Позволяет проверить отдельно сохранённую старую версию ecan.c. + +root = Path(__file__).resolve().parents[2] # Корень проекта содержит рабочую реализацию eCAN. +source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "Source/Internal/ecan.c" # Дополнительный путь нужен для проверки чувствительности к старой ошибке. +source = re.sub(r"/\*.*?\*/|//[^\n]*", "", source_path.read_text(encoding="utf-8"), flags=re.S) # Комментарии со скобками не участвуют в выделении функций. + +def function(name): # Извлекает текущий алгоритм вместо поддержания отдельной копии в тесте. + match = re.search(r"(?:interrupt\s+)?void\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от вызова и объявления. + assert match is not None, name # Исчезновение функции должно останавливать проверку. + end, depth = match.end(), 1 # Начинает поиск после открывающей скобки тела. + while depth: # Учитывает вложенные условия до закрытия внешнего блока. + depth += (source[end] == "{") - (source[end] == "}") # Считает только скобки кода без комментариев. + end += 1 # Включает найденную закрывающую скобку в результат. + return source[match.start():end] # Сохраняет все реальные обращения к регистрам и условия. + +model = Path(__file__).with_name("model.inc").read_text(encoding="utf-8") # Загружает минимальное аппаратное окружение и сценарии потери ACK. +model = model.replace("/* PRODUCTION */", function("CAN_send") + "\n" + function("CANa_reset_err")) # Проверяет передачу и прерывание в одном жизненном цикле. +compiler = os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe") # Использует установленный GCC, сохраняя возможность явно выбрать другой. +environment = dict(os.environ) # Не меняет окружение приложения и проекта CCS. +environment["PATH"] = str(Path(compiler).parent) + os.pathsep + environment.get("PATH", "") # Делает библиотеки MinGW доступными тестовой программе. +with tempfile.TemporaryDirectory(prefix="can-timeout-") as directory: # Удаляет только собственные временные файлы после завершения проверки. + c_path, exe_path = Path(directory) / "test.c", Path(directory) / "test.exe" # Разделяет исходник модели и исполняемый результат. + c_path.write_text(model, encoding="utf-8") # Сохраняет русские пояснения модели в требуемой кодировке. + subprocess.run([compiler, "-std=c99", "-O3", "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", str(c_path), "-o", str(exe_path)], env=environment, check=True) # Проверяет реальные функции под оптимизацией и без скрытых предупреждений. + subprocess.run([str(exe_path)], env=environment, check=True, timeout=10) # Зависание и нарушенное условие делают сценарий неуспешным.