Добавить heartbeat и передачу CAN по изменению и таймеру
Для всех Mode передавать пакеты при обновлении данных и повторять каждый через 1300 мс от последнего CAN ACK. Отдельно отправлять heartbeat раз в секунду. Сохранить маски рассылки, отключение потоков нулевым периодом и неблокирующее восстановление после ошибок TX. Проверены границы периодов, все Mode, занятый mailbox, отмена без CANAA, отсутствие ACK, переполнение Timer1, первый снимок и справедливость потоков. Изменённые модули ecan.c и main.c скомпилированы компилятором TI C2000.
This commit is contained in:
123
Source/Internal/Include/can_telemetry_impl.h
Normal file
123
Source/Internal/Include/can_telemetry_impl.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifndef CAN_TELEMETRY_IMPL_H /* Реализация включается только в ecan.c после аппаратных типов и переменных штатного передатчика. */
|
||||
#define CAN_TELEMETRY_IMPL_H /* Не допускает повторного определения состояния и функций в одной единице трансляции. */
|
||||
#define CAN_HEARTBEAT_ADDR 0x0100U /* Как в варианте 167, отдельный адрес heartbeat находится вне R0–R127 и служебной области логгера 0x1000. */
|
||||
#define CAN_HEARTBEAT_TICKS ((Uint32)READY_FREQ) /* READY_FREQ задаёт частоту Timer1 в герцах; это ровно одна секунда его тактов. */
|
||||
#define CAN_REFRESH_TICKS ((Uint32)READY_FREQ * 13UL / 10UL) /* Тринадцать десятых секунды дают период повторения данных 1,3 с независимо от числа циклов main. */
|
||||
#define CAN_SCAN_TICKS ((Uint32)READY_FREQ * CANPOWSE / 1000UL) /* Существующие CANPOWSE=20 мс ограничивают задержку обнаружения обновлений, но не скорость выдачи уже готовой очереди. */
|
||||
#define CAN_ACK_TICKS ((Uint32)READY_FREQ / 10UL) /* Программная страховка через 100 мс отменяет зависший TX, если аппаратный timeout IRQ не был обработан. */
|
||||
static Uint32 can_last_hi[ANSWER_LEN], can_last_lo[ANSWER_LEN], can_last_at[ANSWER_LEN]; /* Последний подтверждённый пакет и время ACK хранятся отдельно для каждого начального адреса R0–R127. */
|
||||
static unsigned int can_valid[8], can_cursor[2], can_stream; /* Восемь 16-битных масок покрывают 128 адресов; два курсора и очередность обеспечивают справедливость потоков. */
|
||||
static Uint32 can_tx_at, can_failed_at, can_heartbeat_at, can_scan_at; /* Беззнаковые отметки Timer1 поддерживают вычитание через переполнение 32-битного счётчика. */
|
||||
static unsigned int can_backoff, can_started, can_heartbeat_count, can_heartbeat_due, can_scan_due; /* Отделяет секундную паузу после ошибки, старт часов, heartbeat и незавершённое сканирование масок. */
|
||||
static int can_pending_addr; /* Значение -1 обозначает heartbeat, который не должен индексировать кэш технологических регистров. */
|
||||
static volatile unsigned int can_tx_failed; /* Timeout ISR отмечает ошибку текущего mailbox до обработки ACK в основном цикле. */
|
||||
static Uint32 CAN_millis(void) /* Возвращает согласованный счётчик Timer1; при READY_FREQ=1000 один такт соответствует миллисекунде. */
|
||||
{ /* Чтение 32 бит защищается отдельно на 16-битном C28x без ожидания аппаратуры. */
|
||||
unsigned int saved = __disable_interrupts(); /* Сохраняет исходное состояние IRQ, включая вызовы из уже защищённой отправки. */
|
||||
Uint32 now = CpuTimer1.InterruptCount; /* Счётчик обновляют оба существующих обработчика Timer1 для пульта и измерительной платы. */
|
||||
__restore_interrupts(saved); /* Немедленно возвращает прежнюю маску прерываний. */
|
||||
return now; /* Наружу передаётся один целостный снимок времени. */
|
||||
} /* Длительные расчёты масок выполняются с разрешёнными прерываниями. */
|
||||
static void CAN_telemetry_init(void) /* Сбрасывает программное состояние при каждом InitCan, в том числе после запуска с грязной RAM. */
|
||||
{ /* Крупные массивы не очищаются, потому что их элементы недоступны без valid-бита. */
|
||||
unsigned int i; /* Перебирает восемь масок достоверности кэша. */
|
||||
for (i = 0U; i < 8U; ++i) can_valid[i] = 0U; /* Первый пакет каждого разрешённого адреса обязательно передаётся после запуска. */
|
||||
can_cursor[0] = can_cursor[1] = can_stream = 0U; /* Обе маски начинают обход с R0; далее потоки чередуются после реально поставленного кадра. */
|
||||
can_backoff = can_started = can_heartbeat_count = can_heartbeat_due = 0U; /* Новые часы и секундный heartbeat начинают отсчёт при первом обслуживании main. */
|
||||
can_scan_due = 1U; /* Первый снимок данных выдаётся сразу, без ожидания периода 1,3 с. */
|
||||
can_pending_addr = -1; can_tx_failed = 0U; wait = 0; /* Исключает подтверждение кадра и ошибку из предыдущего запуска программы. */
|
||||
} /* Сброс не меняет аппаратные CAN ID, маски регистров и настройки EEPROM. */
|
||||
static int CAN_tx_ready(Uint32 now) /* Обслуживает завершение штатного mailbox 0; вызывающий код уже запретил IRQ на короткое время. */
|
||||
{ /* Возвращает готовность без ожидания ACK, CANAA или освобождения шины в цикле. */
|
||||
if ((ECanaRegs.CANTRS.all | ECanaRegs.CANTRR.all) & 1UL) { /* Бит 0 относится только к штатному TX; служебный mailbox 2 не перезаписывается. */
|
||||
if (wait && !can_tx_failed && (ECanaRegs.CANTRS.all & 1UL) && now - can_tx_at >= CAN_ACK_TICKS) { /* Программный timeout страхует потерянное аппаратное прерывание спустя 100 мс. */
|
||||
can_tx_failed = 1U; ++CanTimeOutErrorTR; /* Отмечает ошибку ровно один раз, пока этот запрос ещё ожидает завершения отмены. */
|
||||
EALLOW; ECanaRegs.CANTOC.all &= ~1UL; EDIS; /* Снимает источник аппаратного timeout только для mailbox 0, сохраняя остальные биты контроля. */
|
||||
ECanaRegs.CANTRR.all = 1UL; /* Запрашивает аппаратную отмену без блокирующего ожидания CANAA. */
|
||||
} /* Данные занятого mailbox остаются неизменными до аппаратного освобождения. */
|
||||
return 0; /* Текущий адрес и признак готовности сканирования сохраняются для повторного обслуживания. */
|
||||
} /* Только свободный mailbox позволяет обработать завершение предыдущей передачи. */
|
||||
if (wait) { /* ACK или ошибка относятся ровно к одному ранее запущенному кадру. */
|
||||
if ((ECanaRegs.CANTA.all & 1UL) && !can_tx_failed) { /* Только успешный ACK позволяет подавлять повтор неизменившихся данных. */
|
||||
if (can_pending_addr >= 0) { /* Heartbeat не занимает ограниченный технологический кэш. */
|
||||
can_last_hi[can_pending_addr] = ECanaMboxes.MBOX0.MDH.all; /* Запоминает действительно доставленные адрес и первое слово из неизменявшегося mailbox. */
|
||||
can_last_lo[can_pending_addr] = ECanaMboxes.MBOX0.MDL.all; /* Сохраняет второе и третье слова того же подтверждённого кадра. */
|
||||
can_last_at[can_pending_addr] = now; /* Отсчитывает повтор через 1,3 с от последнего подтверждения, а не от неудачной попытки. */
|
||||
can_valid[can_pending_addr >> 4] |= 1U << (can_pending_addr & 15); /* Устанавливает один бит достоверности начального адреса пакета. */
|
||||
} /* Перекрывающиеся пакеты разных масок имеют независимые начальные адреса. */
|
||||
} else { /* Отмена, ошибка или свободный TX без CANAA не подтверждают доставку текущих данных. */
|
||||
can_failed_at = now; can_backoff = 1U; /* Ограничивает новые попытки одной секундой при отсутствии получателя и CAN ACK. */
|
||||
} /* Старый подтверждённый кэш сохраняется, поэтому неудачная передача не теряет обновление. */
|
||||
wait = 0; can_tx_failed = 0U; can_pending_addr = -1; /* Завершение обрабатывается один раз даже без отдельного CANAA после отмены. */
|
||||
} /* Проверяет паузу после ошибки до подготовки следующего кадра. */
|
||||
if (can_backoff && now - can_failed_at < CAN_HEARTBEAT_TICKS) return 0; /* Во время секундной паузы данные и heartbeat остаются ожидающими. */
|
||||
can_backoff = 0U; /* Снятие паузы не требует изменения таймеров технологических данных. */
|
||||
return 1; /* Mailbox свободен и может принять один новый актуальный кадр. */
|
||||
} /* Чужие mailbox, аппаратный CAN ID и получаемые команды не меняются. */
|
||||
int CAN_send(int Port, int data[], int Addr) /* Возвращает 0 при занятости, 1 после запуска TX и 2 при неизменном пакете до срока повторения. */
|
||||
{ /* Снимок данных и запуск mailbox защищаются одним коротким запретом IRQ. */
|
||||
Uint32 hiword, loword, now; /* Ровно два 32-битных поля соответствуют восьми октетам существующего формата BALZAM. */
|
||||
unsigned int saved = __disable_interrupts(); /* Исключает разрыв трёхсловного снимка и гонку запуска с timeout ISR. */
|
||||
(void)Port; now = CAN_millis(); /* Как прежний передатчик, использует eCAN-A; время берётся из общего Timer1. */
|
||||
if (!CAN_tx_ready(now)) { __restore_interrupts(saved); return 0; } /* При занятости ничего не перезаписывает и не подтверждает данные программно. */
|
||||
if (Addr == CAN_HEARTBEAT_ADDR) { /* Отдельный пакет состояния узла не обращается к data=NULL или к R256. */
|
||||
hiword = 0xE0000000UL | ((Uint32)CAN_HEARTBEAT_ADDR << 16) | can_heartbeat_count; /* Сохраняет маркер BALZAM и передаёт циклический 16-битный счётчик секунд. */
|
||||
loword = ((Uint32)(unsigned int)Mode << 16) | (unsigned int)Desk; /* Два следующих слова сообщают аппаратный Mode и тип платы Desk. */
|
||||
can_pending_addr = -1; /* Успешный heartbeat не подтверждает ни один технологический пакет. */
|
||||
} else { /* Первый снимок, изменение любого слова либо истечение периода вызывают одинаковую отправку данных. */
|
||||
if (Addr < 0 || Addr >= ANSWER_LEN) { __restore_interrupts(saved); return 2; } /* Не допускает чтения за картой R0–R127 и позволяет планировщику пропустить неверный адрес. */
|
||||
hiword = 0xE0000000UL | ((Uint32)Addr << 16) | ((Uint32)data[Addr] & 0xFFFFUL); /* Первый регистр занимает младшие шестнадцать бит старшей половины CAN-данных. */
|
||||
loword = Addr + 1 < ANSWER_LEN ? ((Uint32)data[Addr + 1] & 0xFFFFUL) << 16 : 0UL; /* Отсутствующее второе слово у конца карты заменяется нулём. */
|
||||
if (Addr + 2 < ANSWER_LEN) loword |= (Uint32)data[Addr + 2] & 0xFFFFUL; /* Третье слово дополняется только в пределах реального массива регистров. */
|
||||
if ((can_valid[Addr >> 4] & (1U << (Addr & 15))) && can_last_hi[Addr] == hiword && can_last_lo[Addr] == loword && now - can_last_at[Addr] < CAN_REFRESH_TICKS) { /* Совпадение всех слов подавляет передачу только до индивидуального срока 1,3 с. */
|
||||
__restore_interrupts(saved); return 2; /* Не сдвигает время подтверждения: неизменные данные всё равно повторятся по таймеру. */
|
||||
} /* Изменение данных не ждёт окончания периода повторения. */
|
||||
can_pending_addr = Addr; /* Подготовленное состояние попадёт в кэш только после успешного аппаратного ACK. */
|
||||
} /* Для heartbeat и данных используется один и тот же безопасный жизненный цикл TX. */
|
||||
ECanaRegs.CANTA.all = 1UL; ECanaRegs.CANAA.all = 1UL; /* Очищает старые ACK и отмену mailbox 0 записью единиц в соответствующие биты eCAN. */
|
||||
ECanaMboxes.MBOX0.MDH.all = hiword; ECanaMboxes.MBOX0.MDL.all = loword; /* Записывает замороженный трёхсловный снимок без последующей подмены во время ожидания ACK. */
|
||||
EALLOW; ECanaRegs.CANTOC.all &= ~1UL; ECanaRegs.CANTSC = 0UL; ECanaRegs.CANTOC.all |= 1UL; EDIS; /* Перезапускает аппаратный timeout только для нового штатного запроса TX. */
|
||||
can_tx_at = now; can_tx_failed = 0U; wait = 1; /* Сохраняет момент запуска и принадлежность будущего ACK до разрешения прерываний. */
|
||||
ECanaRegs.CANTRS.all = 1UL; /* Однократно запускает аппаратную передачу в штатном mailbox 0. */
|
||||
led1_toggle(); __restore_interrupts(saved); /* Сохраняет индикацию передачи и возвращает исходную маску IRQ без ожидания шины. */
|
||||
return 1; /* Подтверждает только постановку кадра, оставляя окончательную доставку аппаратному ACK. */
|
||||
} /* Формат CAN ID, порядок слов и служебный mailbox логгера остаются прежними. */
|
||||
void CAN_heartbeat_service(void) /* Обслуживает независимый секундный heartbeat и завершение ранее запущенного TX. */
|
||||
{ /* Вызывается из main и не зависит от Cancount, масок данных или признака CanGO. */
|
||||
Uint32 now = CAN_millis(), elapsed, seconds; /* Время Timer1 учитывает задержки main без накопления дрейфа секундной сетки. */
|
||||
unsigned int saved = __disable_interrupts(); /* Короткая обработка ACK синхронизируется с timeout ISR. */
|
||||
CAN_tx_ready(now); __restore_interrupts(saved); /* Сохраняет успешное завершение данных даже тогда, когда новых пакетов пока нет. */
|
||||
if (!can_started) { can_started = 1U; can_heartbeat_at = can_scan_at = now; } /* Первый heartbeat появляется спустя полную секунду от начала обслуживания текущего запуска. */
|
||||
elapsed = now - can_heartbeat_at; /* Беззнаковое вычитание корректно при переполнении 32-битного счётчика. */
|
||||
if (elapsed >= CAN_HEARTBEAT_TICKS) { /* Формирует новую отметку на каждой секундной границе Timer1. */
|
||||
seconds = elapsed / CAN_HEARTBEAT_TICKS; can_heartbeat_at += seconds * CAN_HEARTBEAT_TICKS; /* После паузы main оставляет последнее значение без очереди устаревших кадров. */
|
||||
can_heartbeat_count = (unsigned int)((can_heartbeat_count + seconds) & 0xFFFFUL); can_heartbeat_due = 1U; /* Счётчик heartbeat циклически проходит диапазон 0..65535 секунд. */
|
||||
} /* Занятый передатчик не теряет ожидающий heartbeat. */
|
||||
if (can_heartbeat_due && CAN_send(0, 0, CAN_HEARTBEAT_ADDR) == 1) can_heartbeat_due = 0U; /* Приоритетно ставит один актуальный heartbeat после освобождения mailbox. */
|
||||
} /* Следующий повтор heartbeat планируется секундной сеткой, а не длительностью обхода масок. */
|
||||
void CAN_telemetry_service(void) /* Передаёт изменившиеся данные и повторяет каждый подтверждённый пакет через 1,3 с. */
|
||||
{ /* Фоновое обслуживание не ждёт CAN ACK и выдаёт готовую очередь по мере освобождения mailbox. */
|
||||
Uint32 now; unsigned int attempt, stream, pass, address, cursor; int result; /* Локальные счётчики ограничивают обход двумя потоками и конечной картой из 128 регистров. */
|
||||
CAN_heartbeat_service(); now = CAN_millis(); /* Секундный heartbeat получает приоритет перед обычными данными независимо от настройки потоков. */
|
||||
if (now - can_scan_at >= CAN_SCAN_TICKS) { can_scan_at += (now - can_scan_at) / CAN_SCAN_TICKS * CAN_SCAN_TICKS; can_scan_due = 1U; } /* Сохраняет сетку обнаружения изменений с интервалом 20 мс без накопления дрейфа после задержек main. */
|
||||
if (!can_scan_due) return; /* При неизменных данных main не повторяет полный обход масок на каждой итерации. */
|
||||
for (attempt = 0U; attempt < 2U; ++attempt) { /* После успешной постановки чередует потоки, исключая голодание медленной маски. */
|
||||
stream = (can_stream + attempt) & 1U; cursor = can_cursor[stream]; /* Начинает следующий поиск после последнего принятого кадра данного потока. */
|
||||
if (!Cancount[stream]) continue; /* Ноль сохраняет штатное отключение соответствующего потока; ненулевой период заменяется общим повтором 1,3 с. */
|
||||
for (pass = 0U; pass < 2U; ++pass) { /* Два конечных прохода реализуют циклический обход без зависания на пустой маске. */
|
||||
for (address = 0U; address < ANSWER_LEN;) { /* Формирует те же трёхсловные группы от первого разрешённого адреса маски. */
|
||||
if (!(Maska[stream][address >> 4] & (1U << (address & 15)))) { ++address; continue; } /* Пропускает запрещённые регистры и гарантированно продвигает поиск. */
|
||||
if ((pass == 0U && address >= cursor) || (pass == 1U && address < cursor)) { /* Сначала рассматривает хвост карты после курсора, затем оставшееся начало. */
|
||||
result = CAN_send(0, modbus, address); /* Сравнивает полный снимок с подтверждённым кэшем и индивидуальным временем повтора. */
|
||||
if (result != 2) { /* Только неизменный пакет до срока допускает переход к следующему кандидату в этом же вызове. */
|
||||
if (result == 1) { can_cursor[stream] = address + 3U < ANSWER_LEN ? address + 3U : 0U; can_stream = stream ^ 1U; } /* Продвигает курсор лишь после постановки кадра и отдаёт следующий свободный слот другому потоку. */
|
||||
return; /* При занятости сохраняет адрес; при постановке продолжит готовую очередь следующей итерацией main после ACK. */
|
||||
} /* Пропущенные неизменные пакеты не занимают физическую шину. */
|
||||
} /* Завершает проверку одного допустимого начального адреса. */
|
||||
address += 3U; /* Как прежний протокол, один кадр покрывает три последовательных регистра независимо от разреженности маски. */
|
||||
} /* Максимальный адрес ограничен размером реальной технологической карты. */
|
||||
} /* После обхода обеих частей все пакеты потока рассмотрены ровно один раз. */
|
||||
} /* Ни один разрешённый пакет не требует немедленной передачи. */
|
||||
can_scan_due = 0U; /* Следующее плановое сканирование произойдёт через 20 мс; выдача готовой очереди этим ожиданием не задерживается. */
|
||||
} /* Период 1,3 с относится к каждому пакету, а не к одному кадру всей маски за один период. */
|
||||
#endif /* Закрывает единственную реализацию фоновой телеметрии текущей прошивки 166. */
|
||||
@@ -1,9 +1,11 @@
|
||||
/* Телеметрия eCAN и фоновая выдача ответов логгера.
|
||||
* UMP_CAN_service вызывается из main, не из приёмного IRQ.
|
||||
/* Телеметрия eCAN и фоновая выдача ответов логгера.
|
||||
* UMP_CAN_service вызывается из main, не из приёмного IRQ.
|
||||
*/
|
||||
void InitCan(int Port, int DevNum);
|
||||
void CAN_send(int Port, int data[], int Addr);
|
||||
int CAN_send(int Port, int data[], int Addr); /* 0: занят, 1: кадр поставлен, 2: подтверждённые данные не изменились и 1,3 с ещё не прошли. */
|
||||
void CAN_heartbeat_service(void); /* Планирует отдельный heartbeat по Timer1 с периодом одна секунда. */
|
||||
void CAN_telemetry_service(void); /* В main обслуживает heartbeat, изменения данных и повтор каждого разрешённого пакета через 1,3 с. */
|
||||
extern int CAN_input_data[];
|
||||
|
||||
/* Неблокирующая передача ответов логгера отдельным CAN mailbox. */
|
||||
/* Неблокирующая передача ответов логгера отдельным CAN mailbox. */
|
||||
void UMP_CAN_service(void);
|
||||
|
||||
@@ -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];
|
||||
@@ -49,6 +49,8 @@ static unsigned int ump_can_length, ump_can_index, ump_can_reply_tag;
|
||||
static unsigned int ump_can_waiting;
|
||||
static unsigned long ump_can_sent_at;
|
||||
|
||||
#include "can_telemetry_impl.h" /* Определяет heartbeat, подтверждённый кэш и повтор данных через 1,3 с после объявления состояния штатного TX. */
|
||||
|
||||
void InitCan(int Port, int DevNum)
|
||||
{
|
||||
struct ECAN_REGS ECanShadow;
|
||||
@@ -59,6 +61,7 @@ void InitCan(int Port, int DevNum)
|
||||
|
||||
long id = 0x80BA0000;
|
||||
|
||||
CAN_telemetry_init(); /* Сбрасывает кэш, очередь и секундный heartbeat до разрешения аппаратных прерываний. */
|
||||
DevNum--;
|
||||
if(DevNum<0)DevNum=0;
|
||||
if(DevNum>15)DevNum=15;
|
||||
@@ -86,17 +89,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 +124,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,7 +137,7 @@ 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;
|
||||
|
||||
@@ -161,7 +164,7 @@ void InitCan(int Port, int DevNum)
|
||||
IER |= M_INT9; // Enable CPU INT
|
||||
EDIS;
|
||||
|
||||
// завершили настройку CAN ащиков
|
||||
// завершили настройку CAN ащиков
|
||||
|
||||
MessageReceivedCount = 0;
|
||||
ErrorCount = 0;
|
||||
@@ -169,43 +172,10 @@ 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)
|
||||
{
|
||||
unsigned long hiword,loword;
|
||||
volatile struct ECAN_REGS * ECanRegs;
|
||||
volatile struct ECAN_MBOXES * ECanMboxes;
|
||||
|
||||
ECanRegs = &ECanaRegs;
|
||||
ECanMboxes = &ECanaMboxes;
|
||||
|
||||
if(wait)
|
||||
if(!(ECanRegs->CANTA.all & 1))
|
||||
if(!(ECanRegs->CANAA.all & 1))
|
||||
return;
|
||||
|
||||
ECanRegs->CANTA.all = 1;
|
||||
ECanRegs->CANAA.all = 1;
|
||||
|
||||
hiword= ((((Uint32) Addr ) & 0xffff)<<16)| 0xE0000000 |
|
||||
((((Uint32)data[Addr ]) & 0xffff) );
|
||||
loword= ((((Uint32)data[Addr+1]) & 0xffff)<<16)|
|
||||
((((Uint32)data[Addr+2]) & 0xffff) );
|
||||
|
||||
ECanMboxes->MBOX0.MDH.all = hiword;
|
||||
ECanMboxes->MBOX0.MDL.all = loword;
|
||||
|
||||
EALLOW;
|
||||
ECanRegs->CANTSC = 0; // clear time-out counter
|
||||
EDIS;
|
||||
|
||||
ECanRegs->CANTRS.all = 1; // запустить передачу
|
||||
|
||||
wait=1;
|
||||
led1_toggle();
|
||||
}
|
||||
/* CAN_send определён общим для этой единицы трансляции блоком can_telemetry_impl.h выше. */
|
||||
|
||||
|
||||
void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
|
||||
@@ -220,9 +190,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 +214,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 +228,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 +262,17 @@ 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 &= ~1UL; /* Снимает timeout только mailbox 0, сохраняя контроль служебных передач. */ /* Сброс TOC снимает TOS и MTOF согласно SPRUI07, 13.8.18.3; одной отмены TX недостаточно. */
|
||||
EDIS; /* Восстанавливает защиту регистров до выхода из ветви обработки тайм-аута. */
|
||||
if (ECanaRegs.CANTRS.all & 1UL) /* Передача могла завершиться между чтением TOS и снятием контроля. */
|
||||
ECanaRegs.CANTRR.all = 1UL; /* Запрашивает отмену только ещё занятого mailbox 0, не задерживая прерывание ожиданием CANAA. */
|
||||
can_tx_failed = 1U; /* Не допускает записи повреждённого или отменённого кадра в подтверждённый кэш телеметрии. */
|
||||
CanTimeOutErrorTR++; /* Считает обнаруженный тайм-аут один раз, исключая фантомные повторные входы PIE. */
|
||||
} /* Следующая штатная передача сама заново включит TOC после сброса временного счётчика. */
|
||||
|
||||
PieCtrlRegs.PIEACK.bit.ACK9 |= 1;
|
||||
|
||||
@@ -307,10 +286,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 +300,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 +321,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];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* Инициализация и фоновый цикл платы. После настройки GPIO, обмена,
|
||||
* параметров и ЦАП UMP_pulse_init разрешает свежие команды СИФУ.
|
||||
* В main остаются телеметрия, EEPROM и разбор готовых RS-пакетов.
|
||||
* CAN-пуск и окончание импульса работают в IRQ, не ожидая main.
|
||||
* RS-команда ещё должна пройти проверку CRC в get_command из main.
|
||||
/* Инициализация и фоновый цикл платы. После настройки GPIO, обмена,
|
||||
* параметров и ЦАП UMP_pulse_init разрешает свежие команды СИФУ.
|
||||
* В main остаются телеметрия, EEPROM и разбор готовых RS-пакетов.
|
||||
* CAN-пуск и окончание импульса работают в IRQ, не ожидая main.
|
||||
* RS-команда ещё должна пройти проверку CRC в get_command из main.
|
||||
*/
|
||||
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
|
||||
|
||||
@@ -43,8 +43,7 @@ void timer_Init()
|
||||
|
||||
void main()
|
||||
{
|
||||
int i,j,mask;
|
||||
static unsigned int cancount[2], cancell[2];
|
||||
int i,j; /* Счётчики остались для прежних операций main; выбор CAN-пакетов перенесён в независимый сервис. */
|
||||
RS_DATA * rs;
|
||||
int presbyt=0;
|
||||
|
||||
@@ -100,7 +99,7 @@ void main()
|
||||
if(Desk==dsk_LOAD)
|
||||
Setup_DAC_time();
|
||||
|
||||
/* Завершена начальная настройка: только теперь разрешаем дискреты. */
|
||||
/* Завершена начальная настройка: только теперь разрешаем дискреты. */
|
||||
UMP_pulse_init();
|
||||
LastMode = Mode;
|
||||
|
||||
@@ -110,32 +109,9 @@ void main()
|
||||
|
||||
while(1)
|
||||
{
|
||||
/* Чтение логгера по CAN не блокирует цикл управления. */
|
||||
/* Чтение логгера по CAN не блокирует цикл управления. */
|
||||
UMP_CAN_service();
|
||||
if(CanGO)
|
||||
{
|
||||
CanGO=0;
|
||||
for(i=0;i<2;i++)
|
||||
if(Cancount[i])
|
||||
if(++cancount[i] >= Cancount[i])
|
||||
{
|
||||
cancount[i] = 0;
|
||||
while(1)
|
||||
{
|
||||
if(cancell[i] >= 0x80) cancell[i]=0;
|
||||
mask = Maska[i][cancell[i]/16] >> (cancell[i]%16);
|
||||
if(!mask) cancell[i] = (cancell[i] + 0x10) & 0xFFF0;
|
||||
else
|
||||
{
|
||||
while(!(mask & 1))
|
||||
{
|
||||
cancell[i]++; mask >>= 1;
|
||||
}
|
||||
break;
|
||||
} }
|
||||
CAN_send(0,modbus,cancell[i]);
|
||||
cancell[i]+=3;
|
||||
} }
|
||||
CAN_telemetry_service(); /* Обслуживает heartbeat 1 Гц, изменения масок данных и индивидуальные повторы 1,3 с без ожидания CAN ACK. */
|
||||
|
||||
if(cDefParam)
|
||||
{
|
||||
@@ -169,10 +145,10 @@ void main()
|
||||
presbyt = bTermoCal;
|
||||
}
|
||||
|
||||
/* START/STOP исполняются при приёме CAN/RS, снимаются таймером.
|
||||
* Здесь больше нет опроса уровня: main может ожидать EEPROM. */
|
||||
/* START/STOP исполняются при приёме CAN/RS, снимаются таймером.
|
||||
* Здесь больше нет опроса уровня: main может ожидать EEPROM. */
|
||||
|
||||
/* Снимок после обновления входов и формирования пусковых выходов. */
|
||||
/* Снимок после обновления входов и формирования пусковых выходов. */
|
||||
ump_log_io();
|
||||
|
||||
for(i=0;i<2;i++)
|
||||
@@ -188,16 +164,16 @@ void main()
|
||||
led2_toggle();
|
||||
switch(j)
|
||||
{
|
||||
case CMD_INIT: init(rs); break; // начальные установки
|
||||
case CMD_INITLOAD: initload(rs); break; // настройка загрузки
|
||||
case CMD_RUN: run(rs); break; // загрузить блок
|
||||
case CMD_LOAD: load(rs); break; // загрузить блок
|
||||
case CMD_PEEK: peek(rs); break; // прочитать ачейку памати
|
||||
case CMD_POKE: poke(rs); break; // записать в ачейку памати
|
||||
case CMD_UPLOAD: upload(rs); break; // передать блок памати
|
||||
case CMD_EXTEND: extendbios(rs); break; // расширенные команды дла биоса
|
||||
case CMD_INIT: init(rs); break; // начальные установки
|
||||
case CMD_INITLOAD: initload(rs); break; // настройка загрузки
|
||||
case CMD_RUN: run(rs); break; // загрузить блок
|
||||
case CMD_LOAD: load(rs); break; // загрузить блок
|
||||
case CMD_PEEK: peek(rs); break; // прочитать ачейку памати
|
||||
case CMD_POKE: poke(rs); break; // записать в ачейку памати
|
||||
case CMD_UPLOAD: upload(rs); break; // передать блок памати
|
||||
case CMD_EXTEND: extendbios(rs); break; // расширенные команды дла биоса
|
||||
|
||||
case CMD_TFLASH: tflash(rs); break; // прошить TMS
|
||||
case CMD_TFLASH: tflash(rs); break; // прошить TMS
|
||||
|
||||
case CMD_MODBUS_3: ReceiveCommandModbus3(rs); break;
|
||||
case CMD_MODBUS_6: ReceiveCommandModbus6(rs); break;
|
||||
|
||||
@@ -73,3 +73,25 @@ SetGUI проверено программно; обмен новой проши
|
||||
|
||||
Конфигурация **Release** перенесена из 167 без изменений и не проверялась;
|
||||
для сборки прошивки используйте **Debug**.
|
||||
|
||||
## Телеметрия CAN: heartbeat, изменения и повтор 1,3 с
|
||||
|
||||
Для всех Mode технологические пакеты выбранных масками регистров R0–R127
|
||||
передаются при первом снимке, изменении любого из трёх слов или через 1300 мс
|
||||
после последнего успешного CAN ACK этого пакета. Неудачная попытка не обновляет
|
||||
подтверждённый снимок. Конечные пакеты карты дополняются нулями за R127.
|
||||
Сканирование изменений идёт по Timer1 с шагом 20 мс; готовая очередь выдаётся
|
||||
по мере освобождения mailbox, без ожидания нового периода между её кадрами.
|
||||
Фактическую отправку могут задержать занятая шина, ошибки и работа main.
|
||||
|
||||
Heartbeat отправляется независимо раз в секунду: адрес `0x0100`, затем три
|
||||
16-битных слова — счётчик секунд, Mode, Desk. После задержки main отправляется
|
||||
последнее значение, без очереди пропущенных секундных кадров.
|
||||
|
||||
Ноль в `Cancount[0]` или `Cancount[1]` по-прежнему отключает данные соответствующей
|
||||
маски. Любое ненулевое значение включает новый режим изменения/1300 мс;
|
||||
прежний числовой делитель периода больше не используется. Heartbeat остаётся
|
||||
включённым при отключённых потоках данных. Параметры EEPROM не переносятся.
|
||||
После ошибки TX новая попытка разрешается через секунду без ожидания в цикле.
|
||||
|
||||
Проверка без CAN-адаптера: `python tests/can_telemetry/run_test.py`.
|
||||
|
||||
107
tests/can_telemetry/model.c
Normal file
107
tests/can_telemetry/model.c
Normal file
@@ -0,0 +1,107 @@
|
||||
#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-адаптера и не отправляет команды реальному оборудованию. */
|
||||
22
tests/can_telemetry/run_test.py
Normal file
22
tests/can_telemetry/run_test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path # Находим production-исходники относительно установленного теста либо явно выбранного проверяемого снимка.
|
||||
import os # Подключаем runtime GCC только дочернему процессу теста.
|
||||
import re # Извлекаем настоящий ISR с сохранением вложенных условий и без отдельной копии алгоритма ошибки.
|
||||
import subprocess # Компилируем и выполняем поведенческую модель передатчика.
|
||||
import sys # Разрешаем проверить изолированный снимок коммита вместо текущего рабочего дерева.
|
||||
import tempfile # Создаём исполняемый файл проверки только во временном каталоге теста.
|
||||
test = Path(__file__).resolve().parent # Производственная установка хранит этот файл в tests/can_telemetry.
|
||||
root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else test.parents[1] # При обычном запуске проверяет текущий проект прошивки.
|
||||
source = (root / 'Source/Internal/ecan.c').read_text(encoding='utf-8') # Изменённый production-файл и русские комментарии обязаны быть корректным UTF-8.
|
||||
clean = re.sub(r'/\*.*?\*/|//[^\n]*', '', source, flags=re.S) # Скобки комментариев не влияют на выделение ISR.
|
||||
match = re.search(r'interrupt\s+void\s+CANa_reset_err\(void\)\s*\{', clean) # Отличаем определение обработчика от прототипа и регистрации IRQ.
|
||||
assert match # Отсутствующий обработчик не считается успешной проверкой передачи.
|
||||
end, depth = match.end(), 1 # Начинаем поиск после внешней открывающей скобки функции.
|
||||
while depth: depth += (clean[end] == '{') - (clean[end] == '}'); end += 1 # Сохраняем все вложенные аппаратные проверки оригинала.
|
||||
model = (test / 'model.c').read_text(encoding='utf-8').replace('/* TIMEOUT_ISR */', clean[match.start():end]) # Сама реализация телеметрии подключается напрямую через production-header.
|
||||
compiler = os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe') # Поддерживаем установленный GCC и явный выбор другого совместимого компилятора.
|
||||
environment = dict(os.environ); environment['PATH'] = str(Path(compiler).parent) + os.pathsep + environment.get('PATH', '') # Не меняем системный PATH пользователя или настройки CCS.
|
||||
with tempfile.TemporaryDirectory(prefix='can-telemetry-166-') as directory: # Временные файлы не попадают в источник прошивки или Git-коммит.
|
||||
cfile, exe = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Отделяем исходник модели и её исполняемый результат.
|
||||
cfile.write_text(model, encoding='utf-8') # Тестовые комментарии сохраняются в той же кодировке UTF-8.
|
||||
subprocess.run([compiler, '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', '-I' + str(root / 'Source/Internal/Include'), str(cfile), '-o', str(exe)], env=environment, check=True) # Все предупреждения нового алгоритма считаются ошибками до создания коммита.
|
||||
subprocess.run([str(exe)], env=environment, check=True, timeout=10) # Зависание очереди или нарушенный тайминг завершают проверку ошибкой.
|
||||
Reference in New Issue
Block a user