diff --git a/Source/Internal/measure.c b/Source/Internal/measure.c index 4909173..187a5f3 100644 --- a/Source/Internal/measure.c +++ b/Source/Internal/measure.c @@ -1,8 +1,8 @@ -/* Èçìåðåíèÿ, äèàãíîñòèêà è ïåðèîäè÷åñêîå îáñëóæèâàíèå. ADC ISR - * ïîñòàâëÿåò âûáîðêè; òàéìåð 1 îáñëóæèâàåò èíäèêàöèþ è òåëåìåòðèþ. - * Äëÿ dsk_LOAD îí ñíà÷àëà çàâåðøàåò èìïóëüñ ÑÈÔÓ, çàòåì âûçûâàåò - * ðàñ÷¸ò ÖÀÏ è ñíèìîê ëîããåðà. ×àñòîòû íàõîäÿòñÿ â measure.h; - * ïðè èõ èçìåíåíèè òðåáóåòñÿ ïåðåñ÷èòàòü çàâèñèìûå èíòåðâàëû. +/* Измерения, диагностика и периодическое обслуживание. ADC ISR + * поставляет выборки; таймер 1 обслуживает индикацию и телеметрию. + * Для dsk_LOAD он сначала завершает импульс СИФУ, затем вызывает + * расчёт ЦАП и снимок логгера. Частоты находятся в measure.h; + * при их изменении требуется пересчитать зависимые интервалы. */ #include "DSP2833x_Device.h" // DSP2833x Headerfile Include File #include "DSP2833x_SWPrioritizedIsrLevels.h" @@ -20,13 +20,13 @@ #include "message.h" #include "log_to_mem.h" -#include // Ýòî ÷òîáû ìåðèòü àìïëèòóäó! sqrt áåç ýòîãî áóäåò êðèâ!!! +#include // Это чтобы мерить амплитуду! sqrt без этого будет крив!!! unsigned int CanPowse=CANPOWSE,CanGO=0; unsigned int Maska[2][8]; -int TPL_CANS=0; // Êîëè÷åñòâî òåìïåðàòóðíûõ êàíàëîâ +int TPL_CANS=0; // Количество температурных каналов int tpl_cans=0; int cal_addr=0; @@ -53,23 +53,23 @@ int Curr_Edge; float powK[] = { - 0.127, // ïèòàíèå 380 - 0.127, // ïèòàíèå 380 + 0.127, // питание 380В + 0.127, // питание 380В - 1.000, // òîê - 1.000, // íàïðàæåíèå + 1.000, // ток + 1.000, // напражение - 0.0076, // ïèòàíèå 31 - 0.0076, // ïèòàíèå 24 - 0.0076, // ïèòàíèå 24 - 0.0076, // ïèòàíèå 15 - 1.0000, // òåðìîäàò÷èê ìåëêîñõåìà + 0.0076, // питание 31В + 0.0076, // питание 24В + 0.0076, // питание 24В + 0.0076, // питание 15В + 1.0000, // термодатчик мелкосхема }; FILTERBAT def_FILTERBAT = DEF_FILTERBAT; -FILTERBAT adc_filter[20+4]; // ìàêñ.TPL*2 + 4 êàëèáð -FILTERBAT out_filter[20+4]; // ìàêñ.TPL*2 + 4 íàïðàæ -FILTERBAT zer_filter[4]; // 4 íàïðàæ +FILTERBAT adc_filter[20+4]; // макс.TPL*2 + 4 калибр +FILTERBAT out_filter[20+4]; // макс.TPL*2 + 4 напраж +FILTERBAT zer_filter[4]; // 4 напраж long sens_count[24+4]; @@ -79,7 +79,7 @@ float K_T1,K_T2; interrupt void cpu_timer1_isr_SENS(void); /********************************************************************/ -/* Ðàñ÷åò ìîäóëà òîêà èç ïîêàçàíèé äâóõ ôàç */ +/* Расчет модула тока из показаний двух фаз */ /********************************************************************/ float im_calc(float ia,float ib) { @@ -115,8 +115,8 @@ interrupt void cpu_timer1_isr_SENS(void) int TST; -#define err_lamp work_lamp // ýòî äëà øêàïà, -#define alm_lamp heat_lamp // ÷òîá áûëî ïîíàòíåå +#define err_lamp work_lamp // это дла шкапа, +#define alm_lamp heat_lamp // чтоб было понатнее EALLOW; CpuTimer1.InterruptCount++; @@ -140,10 +140,10 @@ interrupt void cpu_timer1_isr_SENS(void) if(Desk==dsk_LOAD) { - /* Ñíÿòü èñò¸êøèé èìïóëüñ äî ðàñ÷¸òà ÖÀÏ, íåçàâèñèìî îò main. */ + /* Снять истёкший импульс до расчёта ЦАП, независимо от main. */ UMP_pulse_tick(); Load_runner(); - /* Áàçà âðåìåíè 1 ìñ; ñíèìîê ñîäåðæèò ðåçóëüòàò òåêóùåãî øàãà ÖÀÏ. */ + /* База времени 1 мс; снимок содержит результат текущего шага ЦАП. */ ump_log_tick(); return; } @@ -156,9 +156,9 @@ interrupt void cpu_timer1_isr_SENS(void) ((Desk==dsk_COMM) && heat_lamp) ) set_RES_OUT_1(); else clear_RES_OUT_1(); - if(work_lamp) set_LED_OUT_1(); // Îíà æå err_lamp äëà øêàïà + if(work_lamp) set_LED_OUT_1(); // Она же err_lamp дла шкапа else clear_LED_OUT_1(); - if(heat_lamp) set_LED_OUT_2(); // Îíà æå alm_lamp äëà øêàïà + if(heat_lamp) set_LED_OUT_2(); // Она же alm_lamp дла шкапа else clear_LED_OUT_2(); } @@ -360,37 +360,37 @@ void Init_packMask() switch(Desk) { case dsk_EPLT: - Maska[m_SLOW][0]|= 0x000F; // Ïîëó÷åííîå - Maska[m_SLOW][6]|= 0x01FC; // ßðêîñòü ëàìï è îäèíî÷åñòâî + Maska[m_FAST][0]|= 0x000F; // Возвращает R0–R3 задатчика в быстром потоке: маска 0x000F выбирает четыре младших регистра, чтобы подтверждение R3 для ПМ67 не ожидало медленного обхода параметров. + Maska[m_SLOW][6]|= 0x01FC; // Яркость ламп и одиночество break; default: for(i=0;itpl_cans) return; -// Kun èìåííî ÷òîá ñêîìïåíñèðîâàòü íå÷åòíîñòü +// Kun именно чтоб скомпенсировать нечетность if(kun&1) Numb = (Numb-K300_2)*K_T2+ZERO; else Numb = (Numb-K300_1)*K_T1+ZERO; } @@ -706,7 +706,7 @@ sens_data[chan+8] = adc_table_tpl[chan]; error.all = 0; no_ignor = !sens_error[chan].bit.Ignor; -// Îáðûâ èëè íåèñïðàâíîñòü +// Обрыв или неисправность if(Temper==-20 || Temper==200) { error.bit.Tear = 1; @@ -714,7 +714,7 @@ sens_data[chan+8] = adc_table_tpl[chan]; } else -// Ïåðåãðåâ +// Перегрев if(((Temper>sens_hi_edge[chan]-Cooling) && (sens_error[chan].bit.Hyper)) || (Temper>sens_hi_edge[chan]) ) { @@ -724,7 +724,7 @@ sens_data[chan+8] = adc_table_tpl[chan]; } else -// Ïðåäóïðåæäåíèå ïî òåìïåðàòóðå +// Предупреждение по температуре if(Temper>sens_lo_edge[chan]) { error.bit.Over = 1; @@ -796,7 +796,7 @@ void Power_count(int chan) error.bit.Stop = 1; } } -/* Ïîâûøåííîå íàïðàæåíèå +/* Повышенное напражение if(Power > sens_hi_edge[chan]) { error.bit.Hyper = 1; diff --git a/tests/can_vpu_rx/run_test.py b/tests/can_vpu_rx/run_test.py new file mode 100644 index 0000000..dd69ac5 --- /dev/null +++ b/tests/can_vpu_rx/run_test.py @@ -0,0 +1,83 @@ +"""Проверяет частоту подтверждения R3 на настоящих масках и планировщике 166.""" # Проверка выполняется на ПК без отправки команд плате. +from pathlib import Path # Находит исходники проекта и отдельно подготовленный вариант маски. +import os # Передаёт библиотеки GCC только дочерним процессам проверки. +import re # Извлекает определения и блоки C без комментариев. +import subprocess # Компилирует и исполняет реальные алгоритмы прошивки в модели времени. +import sys # Позволяет проверить старый и исправленный файлы одинаковым сценарием. +import tempfile # Ограничивает результаты компиляции временным каталогом. + +root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[2] # Установленная проверка по умолчанию использует свой проект прошивки. +measure = Path(sys.argv[2]) if len(sys.argv) > 2 else root / 'Source/Internal/measure.c' # Подмена только этого файла позволяет воспроизвести ошибку до применения исправления. +def clean(path): # Удаляет комментарии перед поиском парных скобок C. + raw = path.read_bytes() # Сохраняет возможность чтения старых исходников Windows-1251. + source = raw.decode('utf-8') if _utf8(raw) else raw.decode('cp1251') # Не теряет русские комментарии исходника при выборе кодировки. + return re.sub(r'/\*.*?\*/|//[^\n]*', '', source, flags=re.S) # Скобки в пояснениях не меняют границы блока алгоритма. +def _utf8(raw): # Отличает старую Windows-кодировку от требуемого UTF-8 без замены символов. + try: # Корректный UTF-8 имеет приоритет перед совместимой однобайтовой кодировкой. + raw.decode('utf-8'); return True # Успешное декодирование разрешает чтение современного исходника. + except UnicodeDecodeError: # Старый файл будет прочитан в его исходной кодировке. + return False # Не выдаёт повреждённые комментарии за исходный текст. +def block(source, start): # Выделяет настоящий блок с вложенными условиями. + cursor, depth = source.index('{', start) + 1, 1 # Начинает после открывающей скобки внешнего блока. + while depth: # Ищет соответствующую закрывающую скобку. + depth += (source[cursor] == '{') - (source[cursor] == '}') # Учитывает только скобки очищенного C-кода. + cursor += 1 # Включает завершающую скобку в извлечённый текст. + return source[start:cursor] # Проверка не поддерживает отдельную копию алгоритма прошивки. +package = clean(root / 'Source/Internal/Include/package.h') # Значения режимов и индексов потоков берутся из проекта 166. +names = ('dsk_COMM', 'dsk_BKSD', 'dsk_BKST', 'dsk_EPLT', 'dsk_SHKF', 'dsk_LOAD', 'm_FAST', 'm_SLOW') # Модели нужны только константы, используемые масками. +defines = '\n'.join(re.search(r'^#define\s+' + name + r'\s+\d+', package, re.M).group() for name in names) # Не дублирует номера плат в тестовой карте. +measure_source = clean(measure) # Выбирает проверяемую версию Init_packMask. +mask_function = block(measure_source, measure_source.index('void Init_packMask(')) # Состав быстрой и медленной рассылки полностью берётся из прошивки. +main = clean(root / 'Source/Internal/main.c') # Последовательность передачи берётся из текущего рабочего main.c. +scheduler = block(main, main.index('if(CanGO)')) # Проверяет настоящий учёт занятого mailbox и продвижение адресов. +base_tick = int(re.search(r'^#define\s+CANPOWSE\s+(\d+)', clean(root / 'Source/Internal/Include/measure.h'), re.M).group(1)) # При READY_FREQ=1000 Гц делитель CANPOWSE равен числу миллисекунд базового шага. +model = r''' +#include /* Выводит измеренные моделью интервалы подтверждения. */ +#include /* Сбрасывает состояние перед каждым сочетанием периодов EEPROM. */ +/* DEFINES */ +static int Desk, Kurrent, tpl_cans, CanGO, modbus[131], busy; /* Запас слов повторяет трёхсловное чтение у границы R127. */ +static unsigned int Maska[2][8], cancount[2], cancell[2], now, received_at, max_gap, received_count; /* Каждое состояние соответствует маскам, очередям и статистике RX. */ +#define Cancount (modbus+0x60) /* R96/R97 задают периоды fast/slow в базовых интервалах CANPOWSE. */ +#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Ошибка остаётся видимой и при включённой оптимизации. */ +static int CAN_send(int port, int data[], int address) { /* Моделирует только принятие кадра свободным mailbox, не аппаратный ACK. */ + (void)port; /* Номер порта не влияет на порядок технологических кадров. */ + if(busy) return 0; /* Второй поток в том же тике должен повторить попытку позднее. */ + busy=1; /* Один передатчик одновременно принимает только один кадр. */ + if(address<=3 && address+2>=3) { /* R3 передаётся и отдельным кадром, и внутри трёхсловного фрагмента. */ + if(data[3] != (int)now) return 0; /* Проверяет передачу свежего слова, изменённого перед текущим шагом. */ + if(now-received_at>max_gap) max_gap=now-received_at; /* Наблюдает худшее ожидание следующего реального кадра с R3. */ + received_at=now; received_count++; /* Фиксирует только принятый передатчиком регистр. */ + } /* Завершает наблюдение очередного фрагмента. */ + return 1; /* Успешная постановка позволяет настоящему main продвинуть адрес. */ +} /* Модель намеренно не заменяет проверку физического CAN на плате. */ +/* MASK */ +static void step(void) { int i, mask; /* Локальные переменные повторяют контекст фонового цикла main.c. */ +/* SCHEDULER */ +} /* Выполняет один базовый интервал настоящего планировщика. */ +int main(void) { /* Покрывает штатные периоды 166 и варианты из EEPROM, включая отключённый медленный поток. */ + const unsigned periods[][2]={{2,101},{10,101},{12,50},{2,0}}; /* Периоды заданы в тиках CANPOWSE: 2 — штатный быстрый делитель VPU-166, 101 — медленный. */ + unsigned scenario, tick; /* Сценарии независимы, каждый охватывает сто секунд модельного времени. */ + Desk=dsk_EPLT; Init_packMask(); /* Строит реальную карту задатчика выбранной версии прошивки. */ + CHECK((Maska[m_FAST][0]&15)==15 && !(Maska[m_SLOW][0]&15)); /* R0–R3 должны подтверждаться быстрым потоком независимо от периода параметров. */ + CHECK(Maska[m_FAST][1]==1 && Maska[m_SLOW][6]==0x1FF && Maska[m_SLOW][7]==0xE000); /* Кнопки R16 и медленные параметры R96–R104/R125–R127 сохраняют состав. */ + for(scenario=0;scenario0 && max_gap<=3*(periods[scenario][0]+1)); /* Три быстрых кадра R0–R2/R3–R5/R16–R18 ограничивают ожидание независимо от slow. */ + printf("PASS fast=%u slow=%u: R3 max=%u ms, updates=%u\n",periods[scenario][0],periods[scenario][1],max_gap*BASE_TICK,received_count); /* Переводит тики в миллисекунды базового таймера платы. */ + } /* Все сочетания периодов обязаны давать частое подтверждение R3. */ + return 0; /* Успех означает прохождение модели, а не испытание физического устройства. */ +} /* Завершает регрессию задержки обратной связи VPU. */ +''' # Вставки ниже исполняют текущий код прошивки, а не его переписанную модель. +model = model.replace('/* DEFINES */', defines + f'\n#define BASE_TICK {base_tick} /* Длительность базового шага в миллисекундах при таймере 1000 Гц. */').replace('/* MASK */', mask_function).replace('/* SCHEDULER */', scheduler) # Связывает реальные маски и планировщик с моделью одного занятого передатчика. +compiler = Path(os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe')) # Использует установленный GCC без загрузки инструментов. +environment = dict(os.environ, PATH=str(compiler.parent) + os.pathsep + os.environ.get('PATH', '')) # Делает библиотеки компилятора доступными только дочерним программам. +with tempfile.TemporaryDirectory(prefix='vpu-r3-rx-') as directory: # Удаляет только собственные временные результаты после проверки. + source, executable = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Разделяет C-код проверки и исполняемую программу. + source.write_text(model, encoding='utf-8') # Все создаваемые исходники и русские комментарии сохраняются в UTF-8. + subprocess.run([str(compiler), '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', str(source), '-o', str(executable)], env=environment, check=True) # Требует чистой компиляции настоящих алгоритмов при оптимизации. + subprocess.run([str(executable)], env=environment, check=True, timeout=10) # Зависание либо нарушение ограничения задержки проваливает регрессию.