Исправить восстановление CAN после тайм-аута передачи в 166

Перенесено из коммита 985804607d40b84f985eb04fc1f1b0a0dcfbe795 ветки 167.
Конфликты разрешены с сохранением обработчиков проекта 166.
This commit is contained in:
2026-10-13 19:17:43 +03:00
parent b25e026dec
commit 9ddea2d99a
3 changed files with 146 additions and 40 deletions

View File

@@ -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];

View File

@@ -0,0 +1,58 @@
#include <stdint.h> /* Задаёт размеры 16- и 32-битных типов C28x в хостовой модели. */
#include <stdio.h> /* Печатает конкретное нарушенное условие и итог сценариев. */
#include <string.h> /* Обнуляет модель между независимыми сценариями. */
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 или питанию платы. */

View File

@@ -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) # Зависание и нарушенное условие делают сценарий неуспешным.