Добавлен регистратор запуска и отключения УМП для стендовой проверки

Реализованы кольцевая запись CAN-команд, дискретных сигналов, уставок и состояния ЦАП, а также отдельный архив отключения с секундой постинтервала.
Добавлено расширение Modbus для онлайн-графиков и чтения архива через SetGUI. Сохранены управляющая логика и существующая область технологических регистров.

Добавлены русские комментарии, описание протокола и тест регистратора. Проверка алгоритма и синтаксиса C пройдена; сборка TI/CCS и испытания на плате ещё требуются.
This commit is contained in:
2026-09-21 00:15:27 +03:00
parent 799e6ba46d
commit df719229b7
21 changed files with 628 additions and 9 deletions

178
Doc/UMP_LOGGER.md Normal file
View File

@@ -0,0 +1,178 @@
# Логгер запуска и отключения УМП: прошивка Бальзам 167 и SetGUI
## Назначение и работа оператора
В SetGUI открыть ПМ35 → Настройки, выбрать COM, скорость (обычно 115200)
и адрес контроллера (обычно 16). Затем открыть вкладку **Логгер УМП**.
Подключение используется существующее: прямой RS232/RS485 Modbus RTU.
CAN-команды от управляющего контроллера фиксируются самой платой нагрузки.
1. **Начать запись** включает только регистратор, не пуск УМП.
Период снимков по умолчанию 20 мс, диапазон 11000 мс.
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 слов
| Слова | Данные | Единицы/смысл |
|---|---|---|
| 01 | Время, младшее/старшее слово | мс записи |
| 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 переводит в мА |
| 1013 | sens_data[0..3] | измеренные сигналы, знаковое 16-битное значение в исходном масштабе |
| 1417 | modbus[0..3] | маски ошибок измерений |
| 18 | CAN заголовок | адрес регистра в младших 13 битах и маска трёх слов в старших |
| 1921 | Три слова последнего принятого CAN-кадра | без преобразования |
| 22 | Счётчик записанных CAN RX | младшие 16 бит |
| 23 | CanTimeOutErrorTR | младшие 16 бит счётчика тайм-аутов CAN TX |
| 2425 | DAC_go, DAC_stop | исходные начальная/конечная уставки, мА |
| 2628 | DAC_min(), DAC_max(), DAC_cal() | коды калибровки и ручного задания ЦАП |
| 2930 | DAC_04, DAC_20 | значения соответствующих регистров |
| 3132 | count_load, time_dac | положение и длительность разгона в тиках |
| 3334 | эффективные dac_go/dac_stop + 4 | 0.1 мА после ограничений алгоритма; GUI переводит в мА |
| 35 | kod | целочисленное положение, используемое Anal_output |
| 3639 | 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-нагрузкой.

View File

@@ -138,6 +138,10 @@ void Load_runner()
if(dac_go>5) dac_go=5; if(dac_go>5) dac_go=5;
if(dac_stop>16) dac_go=16; if(dac_stop>16) dac_go=16;
if(dac_stop<=dac_go) dac_stop=dac_go+1; 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++; if(count_load > (time_dac/2))count_load++;
@@ -154,6 +158,11 @@ void Load_runner()
x = (x+1)&0x3FF; x = (x+1)&0x3FF;
} }
/* Снимок стадии разгона для сопоставления команды CAN,
* ожидаемого тока и кода, действительно отправленного ЦАП. */
ump_dac_ramp = count_load;
ump_dac_duration = time_dac;
ump_dac_position = (unsigned int)sinus;
preStart = cUMPstart; preStart = cUMPstart;
if(!x || cInitDac) if(!x || cInitDac)

View File

@@ -3,5 +3,6 @@ void Anal_output(long vrot, long maxx);
void Init_DAC(void); void Init_DAC(void);
void Setup_DAC_time(void); void Setup_DAC_time(void);
extern unsigned long IMPowse; /* Счётчик пускового импульса и выдержки инициализации доступны логгеру. */
extern unsigned long IMPowse, WAKEpowse;
extern unsigned int period_dac, time_dac; extern unsigned int period_dac, time_dac;

View File

@@ -42,6 +42,22 @@ extern LOG Log;
/* Очистка памати (обнуление) */ /* Очистка памати (обнуление) */
void clear_mem(); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -11,6 +11,7 @@
#include "RS485.h" #include "RS485.h"
#include "message.h" #include "message.h"
#include "peripher.h" #include "peripher.h"
#include "log_to_mem.h"
// Prototype statements for functions found within this file. // 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[1]) if(adr < ANSWER_LEN) modbus[adr] = Data[1]; adr++;
if(bit[2]) if(adr < ANSWER_LEN) modbus[adr] = Data[2]; 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(); led2_toggle();
} }

View File

@@ -8,6 +8,15 @@
* Çàïèñü ëîãîâ â ïàìyòü * * Çàïèñü ëîãîâ â ïàì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" #include "log_to_mem.h"
int no_write = 0, int no_write = 0,
@@ -32,3 +41,235 @@ void clear_mem()
for (i=Log.Start; i<Log.Finis; i++) for (i=Log.Start; i<Log.Finis; i++)
*(volatile int *)i = 0; *(volatile int *)i = 0;
} }
/* Äâà áàíêà èñïîëüçóþò óæå âûäåëåííóþ âíåøíþþ RAM .logg (64000 ñëîâ).
* Àêòèâíûé áàíê êîëüöåâîé. ×åðåç 1000 ìñ ïîñëå êîìàíäû îòêëþ÷åíèÿ îí
* ñòàíîâèòñÿ àðõèâîì, à çàïèñü ïðîäîëæàåòñÿ âî âòîðîì áàíêå. Ïåðåêëþ÷åíèå
* çàíèìàåò ïîñòîÿííîå âðåìÿ: êîïèðîâàíèÿ 30 òûñÿ÷ ñëîâ ïîä çàïðåòîì IRQ íåò.
* Àðõèâ ïåðåæèâàåò ðàçðûâ RS-ñâÿçè, íî ÍÅ èñ÷åçíîâåíèå ïèòàíèÿ ïëàòû. */
static volatile unsigned int ump_state, ump_count, ump_cursor, ump_next;
static volatile unsigned int ump_period = 20, ump_divider;
static volatile unsigned long ump_time, ump_sequence;
static unsigned int ump_bank, ump_archive_bank = 1;
static unsigned int ump_archive_count, ump_archive_next, ump_generation;
static unsigned int ump_source, ump_post, ump_post_dropped;
static unsigned int ump_can_header, ump_can_data[3], ump_can_count;
static unsigned int ump_prev_commands, ump_prev_inputs, ump_prev_outputs;
extern int CanTimeOutErrorTR;
/* Äèàãíîñòèêà âíóòðåííèõ ïåðåìåííûõ ðàçãîíà. Îáíîâëÿåòñÿ ñàìèì Load_runner,
* íå ïåðåñ÷èòûâàåò è íå èçìåíÿåò óïðàâëÿþùèé àëãîðèòì. */
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
/* Ëîãè÷åñêèå óðîâíè âõîäîâ ïëàòû íàãðóçêè: ôèçè÷åñêèé íîëü îçíà÷àåò 1.
* ×òåíèå GPIO íåïîñðåäñòâåííî çäåñü ïîçâîëÿåò çàïèñàòü âõîä äî ñëåäóþùåãî
* îáíîâëåíèÿ ðåãèñòðà Inputs â îñíîâíîì öèêëå. */
static unsigned int ump_inputs(void)
{
return (!GpioDataRegs.GPADAT.bit.GPIO6) |
((!GpioDataRegs.GPADAT.bit.GPIO8) << 1) |
((!GpioDataRegs.GPADAT.bit.GPIO11) << 2);
}
/* START/STOP àêòèâíû åäèíèöåé; RES_OUT_2 àêòèâåí ôèçè÷åñêèì íóë¸ì. */
static unsigned int ump_outputs(void)
{
return GpioDataRegs.GPBDAT.bit.GPIO62 |
(GpioDataRegs.GPBDAT.bit.GPIO58 << 1) |
((!GpioDataRegs.GPADAT.bit.GPIO3) << 2);
}
/* Âûçûâàåòñÿ òîëüêî âíóòðè êîðîòêîé êðèòè÷åñêîé ñåêöèè. Âñå òðè èñòî÷íèêà
* çàïèñåé (main, CAN ISR, timer ISR) èñïîëüçóþò îäíó áëîêèðîâêó IRQ ñ
* âîññòàíîâëåíèåì ïðåæíåãî ñîñòîÿíèÿ, âêëþ÷àÿ âûçîâû èç ïðåðûâàíèÿ. */
static void ump_capture(unsigned int reason)
{
volatile unsigned int *p;
unsigned int i, commands;
if (ump_state != 1 || Desk != dsk_LOAD || !READY) return;
if (no_write || never_write) { ump_state = 3; return; }
commands = Commands;
/* Îòêëþ÷åíèå: ôðîíò cUMPreset (áèò 6) ëèáî ñïàä cUMPstart (áèò 7).
* Ïîâòîðíûå êîìàíäû íå ñäâèãàþò êîíåö óæå íà÷àòîãî ïîñòèíòåðâàëà. */
if (!ump_post && (((commands & 0x40) && !(ump_prev_commands & 0x40)) ||
(!(commands & 0x80) && (ump_prev_commands & 0x80)))) {
/* Ïîëîâèíó áàíêà ðåçåðâèðóåì ïîä ïîñòèíòåðâàë. Äàæå ïðè ïåðèîäå
* 1 ìñ èëè øêâàëå CAN ñîáûòèå îòêëþ÷åíèÿ íå âûòåñíÿåòñÿ êîëüöîì. */
if (ump_count >= 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;
}

View File

@@ -197,6 +197,9 @@ void main()
if(!IMPowse) { clear_START_DPTB(); clear_STOP_DPTB(); } if(!IMPowse) { clear_START_DPTB(); clear_STOP_DPTB(); }
} }
/* Снимок после обновления входов и формирования пусковых выходов. */
ump_log_io();
for(i=0;i<2;i++) for(i=0;i<2;i++)
{ {
if(i) rs = &rs_a; if(i) rs = &rs_a;

View File

@@ -151,6 +151,8 @@ interrupt void cpu_timer1_isr_SENS(void)
if(Desk==dsk_LOAD) if(Desk==dsk_LOAD)
{ {
Load_runner(); Load_runner();
/* База времени 1 мс; снимок содержит результат текущего шага ЦАП. */
ump_log_tick();
return; return;
} }

View File

@@ -17,6 +17,7 @@
#include "spise2p.h" #include "spise2p.h"
#include "caliber.h" #include "caliber.h"
#include "log_to_mem.h"
int modbus[ANSWER_LEN+1]; int modbus[ANSWER_LEN+1];
unsigned int param[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) 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]; 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[1] = CMD_MODBUS_3;
rs_arr->buffer[2] = Length_MB*2; rs_arr->buffer[2] = Length_MB*2;
/* Çàùèùàåì öåëîñòíîñòü îäíîãî ñíèìêà/áëîêà; ïåðåäà÷à UART èä¸ò óæå
* ñ ðàçðåø¸ííûìè ïðåðûâàíèÿìè è íå çàäåðæèâàåò èçìåðåíèå/óïðàâëåíèå. */
if (Address_MB >= UMP_LOG_BASE) saved = __disable_interrupts();
for (i=0;i<Length_MB;i++) for (i=0;i<Length_MB;i++)
{ {
rs_arr->buffer[3+i*2 ]=(Modbus[Address_MB+i].byt.byte_hi); value = Address_MB >= UMP_LOG_BASE ? ump_log_read(Address_MB+i) : modbus[Address_MB+i];
rs_arr->buffer[3+i*2+1]=(Modbus[Address_MB+i].byt.byte_lo); 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 = 0xffff;
crc = get_crc_16(crc, rs_arr->buffer, Length_MB*2+3); 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->buffer[Length_MB*2+8] = 0;
rs_arr->flag_TIMEOUT_to_Send=true; 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; 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]; 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_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) void SendCommandModbus4(RS_DATA *rs_arr)

View File

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

View File

@@ -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)

View File

@@ -0,0 +1 @@
extern unsigned int ADC_table[4];

View File

@@ -0,0 +1 @@
extern unsigned long IMPowse, WAKEpowse;

View File

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

View File

@@ -0,0 +1 @@
/* Подмена зависимостей для проверки регистратора без платы. */

View File

@@ -0,0 +1 @@
/* Подмена зависимостей для проверки регистратора без платы. */

View File

@@ -0,0 +1,2 @@
extern int READY, Desk, Mode;
#define READY_FREQ 1000

View File

@@ -0,0 +1 @@
/* Подмена зависимостей для проверки регистратора без платы. */

View File

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

View File

@@ -0,0 +1 @@
/* Подмена зависимостей для проверки регистратора без платы. */

View File

@@ -0,0 +1,3 @@
/* Декларации TI-интринсиков только для проверки синтаксиса на компьютере. */
unsigned int __disable_interrupts(void);
void __restore_interrupts(unsigned int saved);