diff --git a/Doc/UMP_LOGGER.md b/Doc/UMP_LOGGER.md new file mode 100644 index 0000000..dc5dcfa --- /dev/null +++ b/Doc/UMP_LOGGER.md @@ -0,0 +1,178 @@ +# Логгер запуска и отключения УМП: прошивка Бальзам 167 и SetGUI + +## Назначение и работа оператора + +В SetGUI открыть ПМ35 → Настройки, выбрать COM, скорость (обычно 115200) +и адрес контроллера (обычно 16). Затем открыть вкладку **Логгер УМП**. +Подключение используется существующее: прямой RS232/RS485 Modbus RTU. +CAN-команды от управляющего контроллера фиксируются самой платой нагрузки. + +1. **Начать запись** включает только регистратор, не пуск УМП. + Период снимков по умолчанию 20 мс, диапазон 1–1000 мс. +2. Флажки выбирают отображаемые сигналы. Аналоги рисуются линиями, + дискретные сигналы — отдельными дорожками. Выбор не меняет состав + записи на плате: скрытую кривую можно включить после захвата. +3. **Онлайн** подключает график к уже идущей записи, в том числе после + восстановления соединения. Тайм-аут не посылает плате команду останова. +4. Фронт `cUMPreset` (бит 6 команды 0x7F) или спад `cUMPstart` (бит 7) + запускает фиксацию отключения. Ещё через 1000 тиков таймера / 1 секунду + банк становится архивом. Повторные команды внутри этой секунды + не продлевают интервал. Другие изменения команд не служат триггером. +5. **Прочитать отключение** загружает архив, не останавливая запись на плате. + На время чтения и просмотра архива онлайн-график приостанавливается; + вернуться к нему можно кнопкой **Онлайн**. +6. **Остановить и прочитать** останавливает только регистратор и читает + текущий банк. При этом существующий архив отключения остаётся доступным. +7. **Сохранить CSV** сохраняет все поля загруженного/онлайн-буфера, + включая невидимые кривые, время и код события. История GUI ограничена + 800 последними точками; это не непрерывная запись на диск. + +При разрыве связи журнал на плате продолжает работать. Архив хранится в ОЗУ +до следующей фиксации отключения либо перезагрузки/пропадания питания. +EEPROM не используется: под отключением в этой задаче понимается команда +управляющего контроллера, а не потеря питания. + +## Память и точность + +Использована существующая секция `.logg` по адресу 0x200000: 64000 +16-битных слов. Два банка по 800 записей × 40 слов занимают её целиком. +Текущий банк кольцевой. При триггере сохраняются последние до 399 записей, +добавляется событие отключения, оставшаяся половина банка отведена +постинтервалу. При 20 мс это до примерно 8 секунд до отключения и 1 секунды +после. Дополнительные события CAN и дискретов сокращают временную глубину. + +При интенсивном потоке событий или периоде 1 мс постинтервал может заполнить +свои места раньше секунды. Последнее место зарезервировано под конечный +снимок; он помечается `event & 0x200`, и GUI сообщает об усечении. +Событие отключения и предыстория при этом не вытесняются. + +Время — 32-битный счётчик миллисекунд записи. Он идёт только при включённом +регистраторе и не является абсолютным временем. События в одном тике имеют +одинаковую отметку; порядок сохраняется порядком записей. На перезапуске +регистратора счётчик продолжает прежнюю шкалу, при перезагрузке платы — 0. + +Онлайн-опрос возвращает три последних записи каждые 40 мс. Он не гарантирует +передачу каждого события при высоком CAN-трафике, медленном UART или паузах +GUI. Номера записей позволяют убрать дубликаты и явно показать пропуски. +Полный сохранившийся фрагмент следует читать кнопкой архива. Частота 20 мс +выбрана с учётом размера кадра; на низких скоростях нужна большая пауза +снимков и подходящий тайм-аут RS. + +Все писатели и чтение одного Modbus-блока используют короткие критические +секции с восстановлением прежнего состояния IRQ. Внутри нет ожидания UART, +EEPROM, выделения памяти или копирования целого банка. Реальную задержку +прерываний и длительность критических секций нужно измерить на F28335. + +## Состав записи: 40 слов + +| Слова | Данные | Единицы/смысл | +|---|---|---| +| 0–1 | Время, младшее/старшее слово | мс записи | +| 2 | Событие | 1 период, 2 CAN RX, 3 изменение I/O, 4 RS-команда, 5 конец постинтервала | +| 2, бит 8 | Триггер отключения | флаг поверх причины события | +| 2, бит 9 | Усечён постинтервал | установлен в конечном снимке | +| 3 | Commands / modbus[0x7F] | все 16 бит, в том числе пуск, сброс, инициализация и калибровка ЦАП | +| 4 | Входы OMEGA, Start_1, Res_in | биты 0,1,2, активный уровень приведён к 1 | +| 5 | START_DPTB, STOP_DPTB, RES_OUT_2 | биты 0,1,2, активный уровень приведён к 1 | +| 6 | IMPowse | остаток импульса, мс | +| 7 | WAKEpowse | остаток инициализации, тики DAC_FREQ | +| 8 | modbus[0x7B] | 12-битный код, переданный функцией dSEND в ЦАП | +| 9 | modbus[0x1C] | расчётное задание в 0.1 мА; GUI/CSV переводит в мА | +| 10–13 | sens_data[0..3] | измеренные сигналы, знаковое 16-битное значение в исходном масштабе | +| 14–17 | modbus[0..3] | маски ошибок измерений | +| 18 | CAN заголовок | адрес регистра в младших 13 битах и маска трёх слов в старших | +| 19–21 | Три слова последнего принятого CAN-кадра | без преобразования | +| 22 | Счётчик записанных CAN RX | младшие 16 бит | +| 23 | CanTimeOutErrorTR | младшие 16 бит счётчика тайм-аутов CAN TX | +| 24–25 | DAC_go, DAC_stop | исходные начальная/конечная уставки, мА | +| 26–28 | DAC_min(), DAC_max(), DAC_cal() | коды калибровки и ручного задания ЦАП | +| 29–30 | DAC_04, DAC_20 | значения соответствующих регистров | +| 31–32 | count_load, time_dac | положение и длительность разгона в тиках | +| 33–34 | эффективные dac_go/dac_stop + 4 | 0.1 мА после ограничений алгоритма; GUI переводит в мА | +| 35 | kod | целочисленное положение, используемое Anal_output | +| 36–39 | ADC_table[0..3] | значения таблицы АЦП в исходном масштабе прошивки | + +**Отправленный код ЦАП не является измеренным током выхода.** В этом проекте +не установлено, какой вход аппаратно измеряет ток ЦАП. При включённом +`cCalibrDac` отправляется `DAC_cal()`, а расчётное задание 0x1C остаётся +результатом алгоритма разгона. Для диагностики это различие сохранено. +ADC_table также уже обрабатывается существующим измерительным кодом; +это не обязательно необработанный регистр ADCRESULT. + +Управляющая логика не исправлялась. Например, существующая строка +`if(dac_stop>16) dac_go=16;` в Load_runner сохранена; журнал показывает +фактически получившиеся эффективные уставки, чтобы выявить такое поведение. + +## Протокол расширения Modbus + +Технологические адреса 0..127 сохранены. Новая область использует полный +16-битный адрес; исходная реализация игнорировала старший байт. +Сначала GUI читает 16 слов 0x1000 и проверяет сигнатуру, версию, размер записи +и тип платы. До успешной проверки расширенные записи запрещены. +Обычный ответ расширения не содержит исторических хвостовых нулей. +Между ответом и следующим запросом GUI выдерживает паузу RTU. + +FC03, 0x1000, 16 слов: + +| Смещение | Значение | +|---|---| +| 0,1 | 0x554D, версия 2 | +| 2,3 | состояние и число записей выбранного источника | +| 4,5 | 40 слов в записи, 800 записей в банке | +| 6 | период периодических снимков, мс | +| 7,8 | счётчик времени, младшее/старшее слово | +| 9,10 | Desk (требуется 6 / dsk_LOAD), Mode | +| 11 | частота временной базы, 1000 Гц | +| 12 | поколение архива, mod 65536 | +| 13 | число записей архива | +| 14 | оставшиеся миллисекунды постинтервала | +| 15 | состояние текущего регистратора независимо от выбора источника | + +Состояния: 0 — ещё не запущен, 1 — запись, 3 — остановлен/архив. + +FC06: +- 0x1000: 1 — новый текущий журнал, 0 — остановить его; +- 0x1001: период 1..1000, менять только при остановленном журнале; +- 0x1002: хронологический индекс первой записи окна 0..799; +- 0x1003: источник окна, 0 — текущий остановленный банк, 1 — архив отключения. + +FC03, 0x1010, до 120 слов: окно до трёх записей от установленного индекса. +Чтение текущего банка этим окном во время записи не выдаёт изменяющиеся +данные. GUI сначала останавливает его; архив можно читать при работающем +текущем банке. После всех блоков GUI повторно проверяет поколение архива; +при замене во время чтения результат не подменяет предыдущий график. + +FC03, 0x1100, ровно 124 слова: онлайн-блок. Слова 0,1 — 32-битный номер +первой записи, 2 — число записей 0..3, 3 — поколение архива, далее до трёх +записей по 40 слов. Неиспользованные слова равны нулю. Окно онлайн не +зависит от курсора/выбора архива. Разделение 0x1010 и 0x1100 предотвращает +пересечение двух окон. + +Недопустимые диапазоны и значения возвращают исключения Modbus 2/3. +CRC и разбор стандартного ответа GUI используют существующее общее ядро. +Расширение описано отдельно в core/ump_logger.py, без изменения общей +библиотеки и без необходимости пересобирать её DLL. + +## Проверка и запуск + +Изменены исходники прошивки и SetGUI. Готовый образ в Bin не обновлялся. +Для платы необходима сборка штатным TI C2000/CCS, проверка размещения .text, +.stack, .ebss и .logg по MAP, прошивка и испытание на стенде. + +Автотест C в tests/ump_logger компилирует непосредственно log_to_mem.c +с подменой GPIO/IRQ. Проверяет кольцо, граничные адреса, обе команды +отключения, защиту триггера от переполнения, переключение банков, +сохранность архива во время дальнейшей записи и контрольные поля CAN/ЦАП. +Это проверка алгоритма на компьютере, не проверка таймингов и 16-битной ABI +TI. Отдельно выполнена синтаксическая проверка изменённых C-файлов Clang +с исходными заголовками и декларациями TI-интринсиков. + +Тесты SetGUI: tests/test_ump_logger.py и tests/test_periph28335_tab.py. +Они проверяют единицы мА, битовые сигналы, временные метки, несовместимую +прошивку, порядок команд, архив при активной записи, замену поколения, +дубликаты/пропуски онлайн, потерю связи и флажки графиков. + +Проверка на стенде: включить запись → подать CAN-пуск → изменить уставку → +подать откл. → через секунду прочитать архив → сверить код ЦАП, ток внешним +измерителем и физические входы/выходы; затем повторить с разрывом RS. +Период 1 мс проверить отдельно под максимальной CAN-нагрузкой. diff --git a/Source/Internal/DAC.c b/Source/Internal/DAC.c index 40faafd..ba02453 100644 --- a/Source/Internal/DAC.c +++ b/Source/Internal/DAC.c @@ -138,6 +138,10 @@ void Load_runner() if(dac_go>5) dac_go=5; if(dac_stop>16) dac_go=16; if(dac_stop<=dac_go) dac_stop=dac_go+1; + /* : + * dac_go/stop 4 . */ + ump_dac_go10 = (unsigned int)((dac_go + 4.0) * 10.0); + ump_dac_stop10 = (unsigned int)((dac_stop + 4.0) * 10.0); } if(count_load > (time_dac/2))count_load++; @@ -154,6 +158,11 @@ void Load_runner() x = (x+1)&0x3FF; } + /* CAN, + * , . */ + ump_dac_ramp = count_load; + ump_dac_duration = time_dac; + ump_dac_position = (unsigned int)sinus; preStart = cUMPstart; if(!x || cInitDac) diff --git a/Source/Internal/Include/DAC.h b/Source/Internal/Include/DAC.h index 31e0786..257b94f 100644 --- a/Source/Internal/Include/DAC.h +++ b/Source/Internal/Include/DAC.h @@ -3,5 +3,6 @@ void Anal_output(long vrot, long maxx); void Init_DAC(void); void Setup_DAC_time(void); -extern unsigned long IMPowse; +/* . */ +extern unsigned long IMPowse, WAKEpowse; extern unsigned int period_dac, time_dac; diff --git a/Source/Internal/Include/log_to_mem.h b/Source/Internal/Include/log_to_mem.h index f6a9e82..0af4048 100644 --- a/Source/Internal/Include/log_to_mem.h +++ b/Source/Internal/Include/log_to_mem.h @@ -42,6 +42,22 @@ extern LOG Log; /* () */ void clear_mem(); +/* : Modbus, + * 128 . 800 : + * . */ +#define UMP_LOG_BASE 0x1000U +#define UMP_LOG_DATA 0x1010U +#define UMP_LOG_WORDS 40U +#define UMP_LOG_CAPACITY 800U +#define UMP_LOG_LIVE 0x1100U +extern unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position; +void ump_log_tick(void); +void ump_log_io(void); +void ump_log_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c); +void ump_log_rs(void); +unsigned int ump_log_read(unsigned int address); +int ump_log_write(unsigned int address, unsigned int value); + #ifdef __cplusplus } #endif diff --git a/Source/Internal/ecan.c b/Source/Internal/ecan.c index 82b901b..db0dfeb 100644 --- a/Source/Internal/ecan.c +++ b/Source/Internal/ecan.c @@ -11,6 +11,7 @@ #include "RS485.h" #include "message.h" #include "peripher.h" +#include "log_to_mem.h" // Prototype statements for functions found within this file. @@ -212,6 +213,8 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes) if(bit[1]) if(adr < ANSWER_LEN) modbus[adr] = Data[1]; adr++; if(bit[2]) if(adr < ANSWER_LEN) modbus[adr] = Data[2]; + /* CAN. */ + ump_log_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]); led2_toggle(); } diff --git a/Source/Internal/log_to_mem.c b/Source/Internal/log_to_mem.c index 1b0a255..81813c1 100644 --- a/Source/Internal/log_to_mem.c +++ b/Source/Internal/log_to_mem.c @@ -8,6 +8,15 @@ * y * ****************************************************************/ +#include "DSP2833x_Device.h" +#include "filter_bat2.h" +#include "measure.h" +#include "package.h" +#include "RS485.h" +#include "message.h" +#include "peripher.h" +#include "DAC.h" +#include "ADC.h" #include "log_to_mem.h" int no_write = 0, @@ -32,3 +41,235 @@ void clear_mem() for (i=Log.Start; i= UMP_LOG_CAPACITY/2) ump_count = UMP_LOG_CAPACITY/2-1; + ump_post_dropped = 0; + ump_post = 1000; + reason |= 0x100; + } + /* . + * 0x200 . */ + if (ump_post && ump_count >= UMP_LOG_CAPACITY-1) { + ump_post_dropped = 1; + return; + } + if (reason == 5 && ump_post_dropped) reason |= 0x200; + p = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS; + p[0] = (unsigned int)ump_time; p[1] = (unsigned int)(ump_time >> 16); + p[2] = reason; p[3] = commands; + p[4] = ump_inputs(); p[5] = ump_outputs(); + p[6] = (unsigned int)IMPowse; p[7] = (unsigned int)WAKEpowse; + p[8] = modbus[0x7B]; p[9] = modbus[0x1C]; + for (i = 0; i < 4; i++) { p[10+i] = sens_data[i]; p[14+i] = modbus[i]; } + p[18] = ump_can_header; + p[19] = ump_can_data[0]; p[20] = ump_can_data[1]; p[21] = ump_can_data[2]; + p[22] = ump_can_count; p[23] = CanTimeOutErrorTR; + /* ; 0.1 . + * p[8] , p[9] , . */ + p[24] = DAC_go; p[25] = DAC_stop; + p[26] = DAC_min(); p[27] = DAC_max(); p[28] = DAC_cal(); + p[29] = DAC_04; p[30] = DAC_20; + p[31] = ump_dac_ramp; p[32] = ump_dac_duration; + p[33] = ump_dac_go10; p[34] = ump_dac_stop10; p[35] = ump_dac_position; + for (i = 0; i < 4; i++) p[36+i] = ADC_table[i]; + ump_prev_commands = commands; ump_prev_inputs = p[4]; ump_prev_outputs = p[5]; + ump_next++; + if (ump_next == UMP_LOG_CAPACITY) ump_next = 0; + if (ump_count < UMP_LOG_CAPACITY) ump_count++; + ump_sequence++; +} + +/* . + * GUI . */ +static void ump_freeze(void) +{ + ump_archive_bank = ump_bank; + ump_archive_count = ump_count; ump_archive_next = ump_next; + ump_generation++; + ump_bank ^= 1; + ump_count = 0; ump_next = 0; ump_post = 0; +} + +void ump_log_tick(void) +{ + unsigned int saved = __disable_interrupts(); + if (ump_state == 1) { + ump_time++; + if (++ump_divider >= ump_period) { ump_divider = 0; ump_capture(1); } + if (ump_post && --ump_post == 0) { + ump_capture(5); + ump_freeze(); + } + } + __restore_interrupts(saved); +} + +/* , . + * . */ +void ump_log_io(void) +{ + unsigned int saved; + if (ump_state != 1 || Desk != dsk_LOAD) return; + saved = __disable_interrupts(); + if (Commands != ump_prev_commands || ump_inputs() != ump_prev_inputs || + ump_outputs() != ump_prev_outputs) ump_capture(3); + __restore_interrupts(saved); +} + +/* CAN-: + * , / . */ +void ump_log_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) +{ + unsigned int saved; + if (ump_state != 1 || Desk != dsk_LOAD) return; + saved = __disable_interrupts(); + ump_can_header = header; + ump_can_data[0] = a; ump_can_data[1] = b; ump_can_data[2] = c; + ump_can_count++; + ump_capture(2); + __restore_interrupts(saved); +} + +void ump_log_rs(void) +{ + unsigned int saved = __disable_interrupts(); + ump_capture(4); + __restore_interrupts(saved); +} + +/* : + * . . */ +int ump_log_write(unsigned int address, unsigned int value) +{ + unsigned int saved, valid = 1; + saved = __disable_interrupts(); + if (address == UMP_LOG_BASE && value == 0) { + ump_state = 3; ump_post = 0; + } else if (address == UMP_LOG_BASE && value == 1 && Desk == dsk_LOAD && READY && ump_state != 1) { + ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0; + ump_source = 0; + ump_prev_commands = Commands; + no_write = 0; never_write = 0; + ump_state = 1; + ump_capture(1); + } + else if (address == UMP_LOG_BASE+1 && ump_state != 1 && value >= 1 && value <= 1000) + ump_period = value; + else if (address == UMP_LOG_BASE+2 && value < UMP_LOG_CAPACITY) + ump_cursor = value; + else if (address == UMP_LOG_BASE+3 && value <= 1) ump_source = value; + else valid = 0; + __restore_interrupts(saved); + return valid; +} + +/* . + * count/next, . */ +static unsigned int ump_word(unsigned int archived, unsigned int record, unsigned int word) +{ + unsigned int count = archived ? ump_archive_count : ump_count; + unsigned int next = archived ? ump_archive_next : ump_next; + unsigned int bank = archived ? ump_archive_bank : ump_bank; + unsigned int slot; + if (record >= count) return 0; + slot = next + UMP_LOG_CAPACITY - count + record; + while (slot >= UMP_LOG_CAPACITY) slot -= UMP_LOG_CAPACITY; + return logs_block[((unsigned long)bank * UMP_LOG_CAPACITY + slot) * UMP_LOG_WORDS + word]; +} + +/* Modbus- IRQ. + * timestamp/ - + * , CAN-. */ +unsigned int ump_log_read(unsigned int address) +{ + unsigned int offset, count; + unsigned long first; + switch (address) { + case UMP_LOG_BASE: return 0x554D; + case UMP_LOG_BASE+1: return 2; + case UMP_LOG_BASE+2: return ump_source ? 3 : ump_state; + case UMP_LOG_BASE+3: return ump_source ? ump_archive_count : ump_count; + case UMP_LOG_BASE+4: return UMP_LOG_WORDS; + case UMP_LOG_BASE+5: return UMP_LOG_CAPACITY; + case UMP_LOG_BASE+6: return ump_period; + case UMP_LOG_BASE+7: return (unsigned int)ump_time; + case UMP_LOG_BASE+8: return (unsigned int)(ump_time >> 16); + case UMP_LOG_BASE+9: return Desk; + case UMP_LOG_BASE+10: return Mode; + case UMP_LOG_BASE+11: return (unsigned int)READY_FREQ; + case UMP_LOG_BASE+12: return ump_generation; + case UMP_LOG_BASE+13: return ump_archive_count; + case UMP_LOG_BASE+14: return ump_post; + case UMP_LOG_BASE+15: return ump_state; + } + if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) { + offset = address - UMP_LOG_DATA; + return ump_word(ump_source, ump_cursor + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); + } + /* -, + * . . */ + if (address >= UMP_LOG_LIVE && address < UMP_LOG_LIVE+124) { + count = ump_count < 3 ? ump_count : 3; + first = ump_sequence - count; + offset = address - UMP_LOG_LIVE; + if (offset == 0) return (unsigned int)first; + if (offset == 1) return (unsigned int)(first >> 16); + if (offset == 2) return count; + if (offset == 3) return ump_generation; + offset -= 4; + if (offset / UMP_LOG_WORDS < count) + return ump_word(0, ump_count - count + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); + } + return 0; +} diff --git a/Source/Internal/main.c b/Source/Internal/main.c index 62c8722..d7827db 100644 --- a/Source/Internal/main.c +++ b/Source/Internal/main.c @@ -197,6 +197,9 @@ void main() if(!IMPowse) { clear_START_DPTB(); clear_STOP_DPTB(); } } + /* . */ + ump_log_io(); + for(i=0;i<2;i++) { if(i) rs = &rs_a; diff --git a/Source/Internal/measure.c b/Source/Internal/measure.c index 4510bb0..abd9ccf 100644 --- a/Source/Internal/measure.c +++ b/Source/Internal/measure.c @@ -151,6 +151,8 @@ interrupt void cpu_timer1_isr_SENS(void) if(Desk==dsk_LOAD) { Load_runner(); + /* 1 ; . */ + ump_log_tick(); return; } diff --git a/Source/Internal/message.c b/Source/Internal/message.c index a0166b2..ac71829 100644 --- a/Source/Internal/message.c +++ b/Source/Internal/message.c @@ -17,6 +17,7 @@ #include "spise2p.h" #include "caliber.h" +#include "log_to_mem.h" int modbus[ANSWER_LEN+1]; unsigned int param[ANSWER_LEN+1]; @@ -182,15 +183,36 @@ void Save_params() /***************************************************************/ /***************************************************************/ +static void modbus_exception(RS_DATA *rs_arr, unsigned int function, unsigned int error) +{ + unsigned int crc; + rs_arr->buffer[0] = CNTRL_ADDR; + rs_arr->buffer[1] = function | 0x80; + rs_arr->buffer[2] = error; + crc = get_crc_16(0xFFFF, rs_arr->buffer, 3); + rs_arr->buffer[3] = LOBYTE(crc); rs_arr->buffer[4] = HIBYTE(crc); + rs_arr->flag_TIMEOUT_to_Send = true; + RS_Send(rs_arr, rs_arr->buffer, 5); +} + void ReceiveCommandModbus3(RS_DATA *rs_arr) { - unsigned int crc, Address_MB, Length_MB, i; + unsigned int crc, Address_MB, Length_MB, i, value, saved = 0; // - Address_MB =/*(rs_arr->RS_Header[2] << 8) |*/ rs_arr->RS_Header[3]; + Address_MB = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3]; // Length_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5]; + if (!Length_MB || Length_MB > 125 || + !((Address_MB < ANSWER_LEN && Length_MB <= ANSWER_LEN-Address_MB) || + (Address_MB >= UMP_LOG_BASE && Address_MB <= UMP_LOG_BASE+15 && + Length_MB <= UMP_LOG_BASE+16-Address_MB) || + (Address_MB >= UMP_LOG_DATA && Address_MB < UMP_LOG_DATA+120 && + Length_MB <= UMP_LOG_DATA+120-Address_MB) || + (Address_MB == UMP_LOG_LIVE && Length_MB == 124))) { + modbus_exception(rs_arr, CMD_MODBUS_3, 2); return; + } ///////////////////////////////////////////////// // @@ -200,12 +222,17 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr) rs_arr->buffer[1] = CMD_MODBUS_3; rs_arr->buffer[2] = Length_MB*2; + /* /; UART + * /. */ + if (Address_MB >= UMP_LOG_BASE) saved = __disable_interrupts(); for (i=0;ibuffer[3+i*2 ]=(Modbus[Address_MB+i].byt.byte_hi); - rs_arr->buffer[3+i*2+1]=(Modbus[Address_MB+i].byt.byte_lo); + value = Address_MB >= UMP_LOG_BASE ? ump_log_read(Address_MB+i) : modbus[Address_MB+i]; + rs_arr->buffer[3+i*2] = (value >> 8) & 0xFF; + rs_arr->buffer[3+i*2+1] = value & 0xFF; } + if (Address_MB >= UMP_LOG_BASE) __restore_interrupts(saved); crc = 0xffff; crc = get_crc_16(crc, rs_arr->buffer, Length_MB*2+3); @@ -218,7 +245,10 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr) rs_arr->buffer[Length_MB*2+8] = 0; rs_arr->flag_TIMEOUT_to_Send=true; - RS_Send(rs_arr, rs_arr->buffer, Length_MB*2+8); + /* Modbus + * : GUI. + * . */ + RS_Send(rs_arr, rs_arr->buffer, Length_MB*2+(Address_MB >= UMP_LOG_BASE ? 5 : 8)); return; } @@ -236,15 +266,22 @@ void ReceiveCommandModbus6(RS_DATA *rs_arr) // - Address_MB = (/*(rs_arr->RS_Header[2] << 8) | */rs_arr->RS_Header[3]); + Address_MB = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3]; // Data_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5]; - Modbus[Address_MB].all = Data_MB; + if (Address_MB >= UMP_LOG_BASE) { + if (!ump_log_write(Address_MB, Data_MB)) { + modbus_exception(rs_arr, CMD_MODBUS_6, 3); return; + } + } else if (Address_MB < ANSWER_LEN) { + Modbus[Address_MB].all = Data_MB; + if (Address_MB == 127) ump_log_rs(); + } else { modbus_exception(rs_arr, CMD_MODBUS_6, 2); return; } rs_arr->flag_TIMEOUT_to_Send=true; - RS_Send(rs_arr, rs_arr->buffer, 10); + RS_Send(rs_arr, rs_arr->buffer, Address_MB >= UMP_LOG_BASE ? 8 : 10); } void SendCommandModbus4(RS_DATA *rs_arr) diff --git a/tests/ump_logger/recorder_test.c b/tests/ump_logger/recorder_test.c new file mode 100644 index 0000000..3dbcbd3 --- /dev/null +++ b/tests/ump_logger/recorder_test.c @@ -0,0 +1,71 @@ +#include "../../Source/Internal/log_to_mem.c" +struct mock_gpio GpioDataRegs; +int READY = 1, Desk = 6, Mode = 5; +int modbus[128]; +int CanTimeOutErrorTR; +unsigned long IMPowse, WAKEpowse; +unsigned int ADC_table[4]; +#define CHECK(x) if (!(x)) return __LINE__ + +/* Возвращаем номер строки при ошибке, ноль при успехе; CRT не требуется. */ +__declspec(dllexport) int run_tests(void) +{ + unsigned int i, generation, archived; + unsigned long sequence; + CHECK(!ump_log_write(UMP_LOG_BASE+1, 0)); + CHECK(!ump_log_write(UMP_LOG_BASE+1, 1001)); + CHECK(ump_log_write(UMP_LOG_BASE+1, 1)); + modbus[127] = 0x80; + modbus[0x50] = 6; modbus[0x51] = 16; + modbus[0x7B] = 1234; modbus[0x1C] = 160; + CHECK(ump_log_write(UMP_LOG_BASE, 1)); + CHECK(!ump_log_write(UMP_LOG_BASE, 1)); + CHECK(!ump_log_write(UMP_LOG_BASE+1, 20)); + for (i=0; i<900; i++) ump_log_tick(); + CHECK(ump_count == UMP_LOG_CAPACITY); + CHECK(ump_word(0, 0, 0) == 101); + CHECK(ump_word(0, UMP_LOG_CAPACITY-1, 0) == 900); + CHECK(ump_word(0, 0, 8) == 1234); + CHECK(ump_word(0, 0, 24) == 6); + /* Короткая CAN-команда должна остаться отдельным событием. */ + modbus[127] = 0x40; + ump_log_can(0x807F, 0x40, 0, 0); + CHECK(ump_post == 1000); + CHECK(ump_word(0, ump_count-1, 2) == 0x102); + CHECK(ump_word(0, ump_count-1, 18) == 0x807F); + for (i=0; i<999; i++) ump_log_tick(); + CHECK(ump_generation == 0); + ump_log_tick(); + CHECK(ump_generation == 1); + CHECK(ump_archive_count == UMP_LOG_CAPACITY); + CHECK(ump_word(1, UMP_LOG_CAPACITY/2-1, 2) == 0x102); + CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 2) == 0x205); + CHECK(ump_count == 0 && ump_state == 1); + generation = ump_generation; + archived = ump_word(1, UMP_LOG_CAPACITY-1, 0); + sequence = ump_sequence; + for (i=0; i<200; i++) ump_log_tick(); + CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 0) == archived); + CHECK(ump_generation == generation); + CHECK(ump_sequence == sequence + 200); + CHECK(ump_log_read(UMP_LOG_LIVE+2) == 3); + CHECK(ump_log_read(UMP_LOG_LIVE+4) == ump_time-2); + CHECK(ump_log_write(UMP_LOG_BASE+3, 1)); + CHECK(ump_log_write(UMP_LOG_BASE+2, UMP_LOG_CAPACITY-1)); + CHECK(ump_log_read(UMP_LOG_DATA) == archived); + CHECK(ump_log_read(UMP_LOG_DATA+UMP_LOG_WORDS) == 0); + CHECK(!ump_log_write(UMP_LOG_BASE+2, UMP_LOG_CAPACITY)); + CHECK(ump_log_write(UMP_LOG_BASE, 0)); + sequence = ump_sequence; + ump_log_tick(); ump_log_io(); ump_log_can(1,2,3,4); + CHECK(sequence == ump_sequence); + CHECK(ump_log_write(UMP_LOG_BASE, 1)); + CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 0) == archived); + /* Новый пуск и спад start без reset также фиксируют отключение. */ + modbus[127] = 0x80; ump_log_rs(); + modbus[127] = 0; ump_log_rs(); + CHECK(ump_post == 1000); + for (i=0; i<1000; i++) ump_log_tick(); + CHECK(ump_generation == generation+1); + return 0; +} diff --git a/tests/ump_logger/run_test.py b/tests/ump_logger/run_test.py new file mode 100644 index 0000000..fa34440 --- /dev/null +++ b/tests/ump_logger/run_test.py @@ -0,0 +1,27 @@ +"""Сборка и запуск исходного регистратора с подменой GPIO/IRQ на Windows. + +Нужны Clang и lld-link в PATH. Проверяется алгоритм, но не ABI и тайминги TI. +DLL создаётся во временной папке; исходные .out/.hex прошивки не затрагиваются. +""" +import ctypes +import os +from pathlib import Path +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[2] +with tempfile.TemporaryDirectory(prefix='ump-recorder-') as directory: + library = Path(directory) / 'recorder_test.dll' + subprocess.run([ + 'clang', '-shared', '-nostdlib', '-fuse-ld=lld', + '-Wl,/noentry,/nodefaultlib', '-Wno-unknown-pragmas', '-Wno-int-to-pointer-cast', + '-Itests/ump_logger/stubs', '-ISource/Internal/Include', + 'tests/ump_logger/recorder_test.c', '-o', str(library), + ], cwd=root, check=True) + # Отдельный процесс освобождает DLL до удаления временной папки Windows. + subprocess.run([ + os.sys.executable, '-c', + 'import ctypes,sys; r=ctypes.CDLL(sys.argv[1]).run_tests(); ' + 'print("PASS" if r==0 else f"FAIL: C line {r}"); sys.exit(bool(r))', + str(library), + ], check=True) diff --git a/tests/ump_logger/stubs/ADC.h b/tests/ump_logger/stubs/ADC.h new file mode 100644 index 0000000..ce6c720 --- /dev/null +++ b/tests/ump_logger/stubs/ADC.h @@ -0,0 +1 @@ +extern unsigned int ADC_table[4]; diff --git a/tests/ump_logger/stubs/DAC.h b/tests/ump_logger/stubs/DAC.h new file mode 100644 index 0000000..a7c38cf --- /dev/null +++ b/tests/ump_logger/stubs/DAC.h @@ -0,0 +1 @@ +extern unsigned long IMPowse, WAKEpowse; diff --git a/tests/ump_logger/stubs/DSP2833x_Device.h b/tests/ump_logger/stubs/DSP2833x_Device.h new file mode 100644 index 0000000..ee0095c --- /dev/null +++ b/tests/ump_logger/stubs/DSP2833x_Device.h @@ -0,0 +1,9 @@ +#ifndef UMP_TEST_DEVICE_H +#define UMP_TEST_DEVICE_H +struct mock_bits { unsigned int GPIO3, GPIO6, GPIO8, GPIO11, GPIO58, GPIO62; }; +struct mock_reg { struct mock_bits bit; }; +struct mock_gpio { struct mock_reg GPADAT, GPBDAT; }; +extern struct mock_gpio GpioDataRegs; +static unsigned int __disable_interrupts(void) { return 0; } +static void __restore_interrupts(unsigned int saved) { (void)saved; } +#endif diff --git a/tests/ump_logger/stubs/RS485.h b/tests/ump_logger/stubs/RS485.h new file mode 100644 index 0000000..63631bf --- /dev/null +++ b/tests/ump_logger/stubs/RS485.h @@ -0,0 +1 @@ +/* Подмена зависимостей для проверки регистратора без платы. */ diff --git a/tests/ump_logger/stubs/filter_bat2.h b/tests/ump_logger/stubs/filter_bat2.h new file mode 100644 index 0000000..63631bf --- /dev/null +++ b/tests/ump_logger/stubs/filter_bat2.h @@ -0,0 +1 @@ +/* Подмена зависимостей для проверки регистратора без платы. */ diff --git a/tests/ump_logger/stubs/measure.h b/tests/ump_logger/stubs/measure.h new file mode 100644 index 0000000..e9586f5 --- /dev/null +++ b/tests/ump_logger/stubs/measure.h @@ -0,0 +1,2 @@ +extern int READY, Desk, Mode; +#define READY_FREQ 1000 diff --git a/tests/ump_logger/stubs/message.h b/tests/ump_logger/stubs/message.h new file mode 100644 index 0000000..63631bf --- /dev/null +++ b/tests/ump_logger/stubs/message.h @@ -0,0 +1 @@ +/* Подмена зависимостей для проверки регистратора без платы. */ diff --git a/tests/ump_logger/stubs/package.h b/tests/ump_logger/stubs/package.h new file mode 100644 index 0000000..9e68013 --- /dev/null +++ b/tests/ump_logger/stubs/package.h @@ -0,0 +1,11 @@ +extern int modbus[128]; +#define dsk_LOAD 6 +#define Commands modbus[127] +#define sens_data (modbus+24) +#define DAC_go modbus[0x50] +#define DAC_stop modbus[0x51] +#define DAC_min() modbus[0x79] +#define DAC_max() modbus[0x78] +#define DAC_cal() modbus[0x7A] +#define DAC_04 modbus[0x6F] +#define DAC_20 modbus[0x6E] diff --git a/tests/ump_logger/stubs/peripher.h b/tests/ump_logger/stubs/peripher.h new file mode 100644 index 0000000..63631bf --- /dev/null +++ b/tests/ump_logger/stubs/peripher.h @@ -0,0 +1 @@ +/* Подмена зависимостей для проверки регистратора без платы. */ diff --git a/tests/ump_logger/ti_syntax.h b/tests/ump_logger/ti_syntax.h new file mode 100644 index 0000000..861a5f2 --- /dev/null +++ b/tests/ump_logger/ti_syntax.h @@ -0,0 +1,3 @@ +/* Декларации TI-интринсиков только для проверки синтаксиса на компьютере. */ +unsigned int __disable_interrupts(void); +void __restore_interrupts(unsigned int saved);