Сократить запись логгера УМП до 12 слов формата v4

This commit is contained in:
2026-10-16 11:26:14 +03:00
parent ba9a54959e
commit 1955f2c98f
6 changed files with 246 additions and 147 deletions

View File

@@ -2,7 +2,7 @@
Текущая логика дискретов описана в [SIFU_START_STOP.md](SIFU_START_STOP.md):
CAN-фронты обрабатываются при приёме, импульсы снимаются таймером, STOP
имеет приоритет. Формат записи v2 и интерфейс SetGUI сохранены.
имеет приоритет. Компактный формат записи v4 совпадает с исполнением 167.
## Назначение и работа оператора
@@ -13,8 +13,9 @@ CAN-фронты обрабатываются при приёме, импуль
Общий RS-адрес 16 здесь не рекомендуется: ответ содержит собственный адрес,
и SetGUI проверяет совпадение адресов. CAN-настройки: [PERIPH28335_CAN.md](PERIPH28335_CAN.md).
Перенесён протокол v2 из 167: 40 слов на запись, два банка по 800 записей.
Существующий SetGUI понимает эту прошивку без изменений программы.
Используется формат v4 из 167: 12 слов на запись вместо прежних 40;
ёмкость исполнения 166 сохранена — два банка по 800 записей.
Для чтения нужен SetGUI с поддержкой компактного формата v4, как для 167.
Кнопки записи управляют регистратором; команду запуска УМП подаёт контроллер.
1. **Начать запись** включает только регистратор, не пуск УМП.
@@ -45,7 +46,8 @@ EEPROM не используется: под отключением в этой
## Память и точность
Использована существующая секция `.logg` по адресу 0x200000: 64000
16-битных слов. Два банка по 800 записей × 40 слов занимают её целиком.
16-битных слов. Два банка по 800 записей × 12 слов используют 19200 слов;
размер выделенной секции сохранён. Одна запись занимает 24 байта вместо 80.
Текущий банк кольцевой. При триггере сохраняются последние до 399 записей,
добавляется событие отключения, оставшаяся половина банка отведена
постинтервалу. При 20 мс это до примерно 8 секунд до отключения и 1 секунды
@@ -61,7 +63,7 @@ EEPROM не используется: под отключением в этой
одинаковую отметку; порядок сохраняется порядком записей. На перезапуске
регистратора счётчик продолжает прежнюю шкалу, при перезагрузке платы — 0.
Онлайн-опрос возвращает три последних записи каждые 40 мс. Он не гарантирует
Онлайн-опрос возвращает до десяти последних записей каждые 40 мс. Он не гарантирует
передачу каждого события при высоком CAN-трафике, медленном UART или паузах
GUI. Номера записей позволяют убрать дубликаты и явно показать пропуски.
Полный сохранившийся фрагмент следует читать кнопкой архива. Частота 20 мс
@@ -73,7 +75,12 @@ GUI. Номера записей позволяют убрать дублика
EEPROM, выделения памяти или копирования целого банка. Реальную задержку
прерываний и длительность критических секций нужно измерить на F28335.
## Состав записи: 40 слов
## Состав записи v4: 12 слов
Состав и позиции полей совпадают с компактным логгером 167. Убраны сырые
данные АЦП и CAN, ошибки, счётчики, код и калибровки ЦАП, состояние разгона
и прочие дополнительные диагностические поля. Управление нагрузкой 166,
ёмкость банка и секундный постинтервал отключения сохранены.
| Слова | Данные | Единицы/смысл |
|---|---|---|
@@ -81,33 +88,21 @@ EEPROM, выделения памяти или копирования целог
| 2 | Событие | 1 период, 2 CAN RX, 3 изменение I/O, 4 RS-команда, 5 конец постинтервала |
| 2, бит 8 | Триггер отключения | флаг поверх причины события |
| 2, бит 9 | Усечён постинтервал | установлен в конечном снимке |
| 3 | Commands / modbus[0x7F] | все 16 бит, в том числе пуск, сброс, инициализация и калибровка ЦАП |
| 3 | Commands & 0xC0 | только пуск УМП (бит 7) и сброс УМП (бит 6) |
| 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 | Фиксированные границы разгона 166 | 6 и 16 мА; это не регистры настройки |
| 26–28 | DAC_min(), DAC_max(), DAC_cal() | коды калибровки и ручного задания ЦАП |
| 29–30 | DAC_04, DAC_20 | значения соответствующих регистров |
| 31–32 | count_load, time_dac | положение и длительность разгона в тиках |
| 33–34 | Эффективные границы разгона 166 | 60 и 160 в 0.1 мА; GUI показывает 6 и 16 мА |
| 35 | kod | целочисленное положение, используемое Anal_output |
| 36–39 | adc_table_lem[0..3] | ADCRESULT2..5 после сдвига на 4, диапазон 0..4095 |
| 6 | modbus[0x1C] | расчётное задание в 0.1 мА; GUI/CSV переводит в мА |
| 7–8 | Фиксированные начальная и конечная уставки 166 | 6 и 16 мА; это не регистры настройки |
| 9 | ЗПТ1: U1 + U2 | ПМ67, регистр 306 / нулевой CAN-индекс 305, В |
| 10 | ЗПТ2: U3 + U4 | ПМ67, регистр 9 / нулевой CAN-индекс 8, В |
| 11 | Резерв | всегда ноль; чётная длина нужна для парной передачи CAN |
**Отправленный код ЦАП не является измеренным током выхода.** В этом проекте
не установлено, какой вход аппаратно измеряет ток ЦАП. При включённом
**Расчётное задание тока не является измеренным током выхода.** При включённом
`cCalibrDac` отправляется `DAC_cal()`, а расчётное задание 0x1C остаётся
результатом алгоритма разгона. Для диагностики это различие сохранено.
В 166 каналы «АЦП 0..3» берутся из `adc_table_lem`, обновляемой в режиме
нагрузки. Таблица `adc_table_tpl` в этом режиме не обновляется и не используется.
результатом алгоритма разгона. Сырой код ЦАП и каналы АЦП в запись v4 не входят.
Напряжения обновляются по подтверждённым маской словам CAN даже при остановленной
записи: первый снимок содержит последние принятые значения. До первого приёма
они равны нулю; отдельного флага свежести в формате v4 нет.
Управляющая формула 166 сохранена: `sinus = 10/16 * count_load + 2/16 * time_dac`.
Регулируемый разгон и ограничители 167 не переносились. Поля границ показывают
@@ -128,9 +123,9 @@ FC03, 0x1000, 16 слов:
| Смещение | Значение |
|---|---|
| 0,1 | 0x554D, версия 2 |
| 0,1 | 0x554D, версия 4 |
| 2,3 | состояние и число записей выбранного источника |
| 4,5 | 40 слов в записи, 800 записей в банке |
| 4,5 | 12 слов в записи, 800 записей в банке |
| 6 | период периодических снимков, мс |
| 7,8 | счётчик времени, младшее/старшее слово |
| 9,10 | Desk (требуется 6 / dsk_LOAD), Mode |
@@ -148,15 +143,17 @@ FC06:
- 0x1002: хронологический индекс первой записи окна 0..799;
- 0x1003: источник окна, 0 — текущий остановленный банк, 1 — архив отключения.
FC03, 0x1010, до 120 слов: окно до трёх записей от установленного индекса.
FC03, 0x1010, до 120 слов: окно до десяти записей от установленного индекса.
Для CAN длина должна быть кратна 12 словам: 12, 24, ..., 120; прежние
блоки v2 по 40 или 80 слов отклоняются как обрывающие компактную запись.
Чтение текущего банка этим окном во время записи не выдаёт изменяющиеся
данные. GUI сначала останавливает его; архив можно читать при работающем
текущем банке. После всех блоков GUI повторно проверяет поколение архива;
при замене во время чтения результат не подменяет предыдущий график.
FC03, 0x1100, ровно 124 слова: онлайн-блок. Слова 0,1 — 32-битный номер
первой записи, 2 — число записей 0..3, 3 — поколение архива, далее до трёх
записей по 40 слов. Неиспользованные слова равны нулю. Окно онлайн не
первой записи, 2 — число записей 0..10, 3 — поколение архива, далее до десяти
записей по 12 слов. Неиспользованные слова равны нулю. Окно онлайн не
зависит от курсора/выбора архива. Разделение 0x1010 и 0x1100 предотвращает
пересечение двух окон.
@@ -167,7 +164,14 @@ CRC и разбор стандартного ответа GUI использую
## Проверка и запуск
Перенесены регистратор и RS/CAN-доступ из коммитов 167 `df71922` и `a3633d8`.
Для компактного формата v4 пройден host-тест настоящего `log_to_mem.c`
с MSVC 14.30.30705. Модули `log_to_mem.c`, `ecan.c` и `message.c`
скомпилированы TI C2000 22.6.0.LTS с COFF, FPU32 и O1 без ошибок.
Полная линковка и обновление образов BIN/HEX для этой правки не выполнялись.
Первоначально регистратор и RS/CAN-доступ перенесены из коммитов 167
`df71922` и `a3633d8`. Приведённые ниже результаты полной сборки относятся
к исходному формату v2 до сокращения полей.
Сборка всех 27 C/ASM-файлов и линковка TI C2000 22.6.1.LTS (Debug, COFF,
FPU32, O1) прошли. Остались шесть прежних предупреждений в measure.c/peripher.c.
В MAP: RAML0 занято 0x38D1 из 0x4000 слов, .ebss — 0xFA9 из 0x3000,
@@ -176,8 +180,10 @@ FPU32, O1) прошли. Остались шесть прежних предуп
Сборка проверки лежит вне репозитория, штатные файлы Bin не заменены.
Host-тест компилирует настоящий log_to_mem.c с подменой GPIO/IRQ.
Проверяет кольцо, архив, переполнение постинтервала, онлайн-окно, CAN-протокол,
готовность именно 166, фиксированные границы и четыре канала АЦП.
Проверяет кольцо, архив, переполнение постинтервала, онлайн-окно из десяти
12-словных записей, CAN-протокол v4, готовность именно 166, фиксированные
уставки 6/16 мА, маску команд и получение ЗПТ из трёх позиций CAN-кадра.
Сторожевые значения контролируют границу записи и разделение банков.
Запуск из корня 166: `py -3 tests/ump_logger/run_test.py` (Clang/lld или MSVC
в PATH); при необходимости передать `--compiler "полный путь к cl.exe"`.
Тест не проверяет физические GPIO, тайминги прерываний и 16-битную ABI TI.
@@ -186,7 +192,7 @@ Host-тест компилирует настоящий log_to_mem.c с подм
начать запись → подать CAN-пуск → подать отключение → через секунду прочитать
архив. Сверить START/STOP с физическими выходами и измерениями, повторить с
разрывом RS/CAN. Проверить период 1 мс и задержки IRQ под нагрузкой.
Тесты существующего SetGUI (`test_ump_logger.py`, `test_ump_logger_can.py`,
Для первоначального формата v2 тесты SetGUI (`test_ump_logger.py`, `test_ump_logger_can.py`,
`test_periph28335_tab.py`) в окружении Qt 5: **47 passed**. Проверены протокол,
онлайн/архив, CAN-фрагменты, графики, курсоры и CSV.
Проверка на устройстве не выполнялась.

View File

@@ -1,5 +1,5 @@
/* Контракт логгера с SetGUI: размер записи, версия и адреса.
* Чтение целого блока защищается от IRQ на стороне RS/CAN обработчика.
/* Контракт логгера с SetGUI: размер записи, версия и адреса.
* Чтение целого блока защищается от IRQ на стороне RS/CAN обработчика.
*/
#ifndef _LOG_TO_MEM
#define _LOG_TO_MEM
@@ -8,11 +8,11 @@
extern "C" {
#endif
/* Определениa длa работы логгера */
/* Определениa длa работы логгера */
#define LOG_PAGE_START 0x0200000
#define LOG_PAGE_LEN 0xFA00
extern int no_write, never_write; // Флаги, чтобы не писать (если что)
extern int no_write, never_write; // Флаги, чтобы не писать (если что)
typedef struct
{
@@ -25,22 +25,25 @@ typedef struct
extern LOG Log;
/* Запись словa в памать, где логи лежат */
/* Запись словa в памать, где логи лежат */
#define Log_to_mem(x) *(int *)(Log.Adres++) = x
/* Проверка границы памати дла логов */
/* Проверка границы памати дла логов */
#define Test_mem_limit(x) if(Log.Adres > (Log.Finis - (x))) Log.Adres = Log.Start
/* Очистка памати (обнуление) */
/* Очистка памати (обнуление) */
void clear_mem();
/* Логгер УМП: отдельное адресное пространство Modbus, без изменения
* 128 технологических регистров. Два банка по 800 записей:
* текущий кольцевой журнал и зафиксированный фрагмент отключения. */
/* Логгер УМП: отдельное адресное пространство Modbus, без изменения
* 128 технологических регистров. Два банка по 800 записей:
* текущий кольцевой журнал и зафиксированный фрагмент отключения. */
#define UMP_LOG_BASE 0x1000U
#define UMP_LOG_DATA 0x1010U
#define UMP_LOG_WORDS 40U
/* Формат v4 совпадает с 167: 12 слов по 16 бит содержат выбранные сигналы и нулевой резерв для парной передачи CAN. */
#define UMP_LOG_WORDS 12U
#define UMP_LOG_CAPACITY 800U
/* Онлайн-окно вмещает 120 слов полезных данных: при формате v4 это десять полных записей. */
#define UMP_LOG_LIVE_RECORDS (120U / UMP_LOG_WORDS)
#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);

View File

@@ -1,20 +1,20 @@
/* Служебный CAN-протокол логгера УМП. Не использует адреса 0..127
* технологического обмена. Формат полезных данных совместим с порядком
* слов старого CAN: MDL (слова 1,2), затем MDH (маркер, слово 0).
* Запрос: маркер 0x1F00, слово 0 = token:8 | function:8,
* слова 1,2 = адрес расширения и число слов/записываемое значение.
* Ответы: маркер 0x1F00 + номер фрагмента, слово 0 = token:8 | error:8,
* слова 1,2 = два слова единого зафиксированного снимка.
* Биты маски записи в заголовке запроса равны нулю: старая плата
* игнорирует запрос, не изменяя технологические регистры. */
/* Служебный CAN-протокол логгера УМП. Не использует адреса 0..127
* технологического обмена. Формат полезных данных совместим с порядком
* слов старого CAN: MDL (слова 1,2), затем MDH (маркер, слово 0).
* Запрос: маркер 0x1F00, слово 0 = token:8 | function:8,
* слова 1,2 = адрес расширения и число слов/записываемое значение.
* Ответы: маркер 0x1F00 + номер фрагмента, слово 0 = token:8 | error:8,
* слова 1,2 = два слова единого зафиксированного снимка.
* Биты маски записи в заголовке запроса равны нулю: старая плата
* игнорирует запрос, не изменяя технологические регистры. */
#ifndef UMP_CAN_PROTOCOL_H
#define UMP_CAN_PROTOCOL_H
#include "log_to_mem.h"
#define UMP_CAN_MARKER 0x1F00U
#define UMP_CAN_MAX_WORDS 124U
/* Вызывающий код защищает весь снимок от IRQ. Функция не ждёт CAN и
* не пишет за пределами reply[124]. Ошибки возвращаются отдельным кадром. */
/* Вызывающий код защищает весь снимок от IRQ. Функция не ждёт CAN и
* не пишет за пределами reply[124]. Ошибки возвращаются отдельным кадром. */
static unsigned int ump_can_prepare(unsigned int function, unsigned int address,
unsigned int value, unsigned int *reply, unsigned int *error)
{
@@ -22,7 +22,8 @@ static unsigned int ump_can_prepare(unsigned int function, unsigned int address,
*error = 0;
if (function == 3) {
if (!((address == UMP_LOG_BASE && value == 16) ||
(address == UMP_LOG_DATA && (value == 40 || value == 80 || value == 120)) ||
/* Разрешает от одной до десяти целых 12-словных записей v4 в пределах 120 слов полезного окна CAN. */
(address == UMP_LOG_DATA && value >= UMP_LOG_WORDS && value <= 120U && value % UMP_LOG_WORDS == 0U) ||
(address == UMP_LOG_LIVE && value == 124))) *error = 2;
else {
for (i = 0; i < value; i++) reply[i] = ump_log_read(address+i);
@@ -32,7 +33,7 @@ static unsigned int ump_can_prepare(unsigned int function, unsigned int address,
if (address < UMP_LOG_BASE || address > UMP_LOG_BASE+3) *error = 2;
else if (!ump_log_write(address, value)) *error = 3;
} else *error = 1;
/* Для записи возвращаем эхо адреса и значения, для ошибки — её контекст. */
/* Для записи возвращаем эхо адреса и значения, для ошибки — её контекст. */
reply[0] = address; reply[1] = value;
return 2;
}

View File

@@ -1,8 +1,8 @@
/* Регистратор УМП во внешней памяти .logg: два банка по 800 записей
* из 40 слов для SetGUI. Архив отключения не мешает дальнейшей записи.
* Писатели из main/CAN/таймера защищаются короткими секциями IRQ
* с восстановлением прежней маски. Архив не энергонезависимый.
* clear_mem выполняется при запуске до включения прерываний.
/* Регистратор УМП во внешней памяти .logg: два банка по 800 записей
* из 12 слов формата v4 для SetGUI. Архив отключения не мешает дальнейшей записи.
* Писатели из main/CAN/таймера защищаются короткими секциями IRQ
* с восстановлением прежней маски. Архив не энергонезависимый.
* clear_mem выполняется при запуске до включения прерываний.
*/
#include "DSP2833x_Device.h"
#include "filter_bat2.h"
@@ -16,7 +16,7 @@
#include "log_to_mem.h"
int no_write = 0,
never_write = 0; // Флаги, чтобы не писать (если что)
never_write = 0; // Изначально разрешает запись; установленный флаг прекращает сохранение снимков до следующего запуска регистратора.
#pragma DATA_SECTION(logs_block,".logg");
unsigned int logs_block[0xFA00];
@@ -24,7 +24,7 @@ unsigned int logs_block[0xFA00];
LOG Log;
unsigned int flog=0;
// Очищение памати, где логи лежат
// Очищение памати, где логи лежат
void clear_mem()
{
unsigned long i;
@@ -37,27 +37,27 @@ void clear_mem()
for (i=Log.Start; i<Log.Finis; i++)
*(volatile int *)i = 0;
}
/* Два банка используют уже выделенную внешнюю RAM .logg (64000 слов).
* Активный банк кольцевой. Через 1000 мс после команды отключения он
* становится архивом, а запись продолжается во втором банке. Переключение
* занимает постоянное время: копирования 30 тысяч слов под запретом IRQ нет.
* Архив переживает разрыв RS-связи, но НЕ исчезновение питания платы. */
/* Два банка используют уже выделенную внешнюю 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;
/* Хранит последние ЗПТ1/ЗПТ2 в вольтах из регистров ПМ67 306/9; явные нули попадают в инициализацию COFF до первого CAN-кадра. */
static unsigned int ump_pm67_voltage[2] = {0, 0};
static unsigned int ump_prev_commands, ump_prev_inputs, ump_prev_outputs;
extern int CanTimeOutErrorTR;
/* Диагностика внутренних переменных разгона. Обновляется самим Load_runner,
* не пересчитывает и не изменяет управляющий алгоритм. */
/* Диагностика внутренних переменных разгона. Обновляется самим Load_runner,
* не пересчитывает и не изменяет управляющий алгоритм. */
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
/* Логические уровни входов платы нагрузки: физический ноль означает 1.
* Чтение GPIO непосредственно здесь позволяет записать вход до следующего
* обновления регистра Inputs в основном цикле. */
/* Логические уровни входов платы нагрузки: физический ноль означает 1.
* Чтение GPIO непосредственно здесь позволяет записать вход до следующего
* обновления регистра Inputs в основном цикле. */
static unsigned int ump_inputs(void)
{
return (!GpioDataRegs.GPADAT.bit.GPIO6) |
@@ -65,7 +65,7 @@ static unsigned int ump_inputs(void)
((!GpioDataRegs.GPADAT.bit.GPIO11) << 2);
}
/* START/STOP активны единицей; RES_OUT_2 активен физическим нулём. */
/* START/STOP активны единицей; RES_OUT_2 активен физическим нулём. */
static unsigned int ump_outputs(void)
{
return GpioDataRegs.GPBDAT.bit.GPIO62 |
@@ -73,62 +73,62 @@ static unsigned int ump_outputs(void)
((!GpioDataRegs.GPADAT.bit.GPIO3) << 2);
}
/* Вызывается только внутри короткой критической секции. Все три источника
* записей (main, CAN ISR, timer ISR) используют одну блокировку IRQ с
* восстановлением прежнего состояния, включая вызовы из прерывания. */
/* Вызывается только внутри короткой критической секции. Все три источника
* записей (main, CAN ISR, timer ISR) используют одну блокировку IRQ с
* восстановлением прежнего состояния, включая вызовы из прерывания. */
static void ump_capture(unsigned int reason)
{
volatile unsigned int *p;
unsigned int i, commands;
/* Указывает на начало одной 12-словной записи во внешней RAM; volatile сохраняет фактические обращения к памяти. */
volatile unsigned int *record;
/* Сохраняет полное слово команд для прежней логики триггера; в запись попадут только биты пуска и сброса. */
unsigned int 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).
* Повторные команды не сдвигают конец уже начатого постинтервала. */
/* Отключение: фронт cUMPreset (бит 6) либо спад cUMPstart (бит 7).
* Повторные команды не сдвигают конец уже начатого постинтервала. */
if (!ump_post && (((commands & 0x40) && !(ump_prev_commands & 0x40)) ||
(!(commands & 0x80) && (ump_prev_commands & 0x80)))) {
/* Половину банка резервируем под постинтервал. Даже при периоде
* 1 мс или шквале CAN событие отключения не вытесняется кольцом. */
/* Половину банка резервируем под постинтервал. Даже при периоде
* 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 в конечной записи. */
/* Последнее место оставляем для снимка конца постинтервала. При
* переполнении честно отмечаем усечение битом 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] — расчётное задание, НЕ измерение тока. */
/* 166: fixed ramp endpoints; no configurable DAC_go/DAC_stop. */
p[24] = 6; p[25] = 16;
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_lem[i];
ump_prev_commands = commands; ump_prev_inputs = p[4]; ump_prev_outputs = p[5];
/* Выбирает слот банка по числу 16-битных слов записи; ёмкость 800 записей сохранена для исполнения 166. */
record = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS;
/* Слова 0/1 передают 32-битное время в миллисекундах младшей половиной вперёд, как в формате v4 исполнения 167. */
record[0] = (unsigned int)ump_time; record[1] = (unsigned int)(ump_time >> 16);
/* Слово 2 содержит событие, а маска 0xC0 оставляет в слове 3 только назначенные протоколом биты 7/6 пуска и сброса УМП. */
record[2] = reason; record[3] = commands & 0xC0U;
/* Слова 4/5 сохраняют фактические логические входы и выходы нагрузки для дискретных дорожек SetGUI. */
record[4] = ump_inputs(); record[5] = ump_outputs();
/* Слово 6 содержит расчётное задание тока регистра 0x1C в единицах 0,1 мА; физическое измерение тока здесь не выполняется. */
record[6] = modbus[0x1C];
/* Слова 7/8 содержат штатные фиксированные уставки разгона 166: начальные 6 мА и конечные 16 мА. */
record[7] = 6; record[8] = 16;
/* Слова 9/10 передают последние ЗПТ1/ЗПТ2 в вольтах из CAN-индексов 305/8, соответствующих регистрам ПМ67 306/9. */
record[9] = ump_pm67_voltage[0]; record[10] = ump_pm67_voltage[1];
/* Нулевое слово 11 дополняет запись до чётной длины: служебный CAN передаёт данные парами 16-битных слов. */
record[11] = 0;
/* Обновляет полные команды и фактические дискреты для обнаружения следующих событий, сохраняя алгоритм триггера 166. */
ump_prev_commands = commands; ump_prev_inputs = record[4]; ump_prev_outputs = record[5];
ump_next++;
if (ump_next == UMP_LOG_CAPACITY) ump_next = 0;
if (ump_count < UMP_LOG_CAPACITY) ump_count++;
ump_sequence++;
}
/* Завершённый банк больше не изменяется до следующего отключения.
* Номер поколения позволяет GUI обнаружить замену архива во время чтения. */
/* Завершённый банк больше не изменяется до следующего отключения.
* Номер поколения позволяет GUI обнаружить замену архива во время чтения. */
static void ump_freeze(void)
{
ump_archive_bank = ump_bank;
@@ -152,8 +152,8 @@ void ump_log_tick(void)
__restore_interrupts(saved);
}
/* Дополнительный снимок только при изменении команд, входов или выходов.
* Это не заменяет периодическую запись аналоговых величин в таймере. */
/* Дополнительный снимок только при изменении команд, входов или выходов.
* Это не заменяет периодическую запись аналоговых величин в таймере. */
void ump_log_io(void)
{
unsigned int saved;
@@ -164,17 +164,34 @@ void ump_log_io(void)
__restore_interrupts(saved);
}
/* Вызывается после применения слов принятого CAN-кадра: снимок содержит
* новые команды, а также исходный адрес/маску и все три слова кадра. */
/* Обновляет напряжения из принятых слов 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;
/* Хранит прежнюю маску IRQ, индекс слова, текущий CAN-адрес, маску разрешения и три принятых 16-битных значения. */
unsigned int saved, word_index, address, mask, can_words[3];
/* Напряжения и журнал принадлежат плате нагрузки; другие аппаратные режимы их не обновляют. */
if (Desk != dsk_LOAD) return;
/* Исключает смешивание обновления напряжений с периодическим снимком таймера и восстанавливает прежние IRQ в конце. */
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++;
/* Располагает три принятых слова в том же порядке, в котором заголовок задаёт их последовательные адреса. */
can_words[0] = a; can_words[1] = b; can_words[2] = c;
/* Младшие 13 бит заголовка задают нулевой индекс первого регистра CAN; верхние три бита являются масками слов. */
address = header & 0x1FFFU;
/* Старший бит 0x8000 разрешает первое слово, далее используются биты 0x4000 и 0x2000. */
mask = 0x8000U;
/* Продвигает адрес и маску для всех трёх позиций, включая пропущенные слова, чтобы не перепутать регистры ПМ67. */
for (word_index = 0; word_index < 3; word_index++, address++, mask >>= 1) {
/* Не использует содержимое позиции, если соответствующий бит заголовка не подтверждает её передачу. */
if (!(header & mask)) continue;
/* Индекс 305 соответствует регистру 306 ПМ67: суммарное напряжение ЗПТ1 передаётся в целых вольтах. */
if (address == 305U) ump_pm67_voltage[0] = can_words[word_index];
/* Индекс 8 соответствует регистру 9 ПМ67: суммарное напряжение ЗПТ2 передаётся в целых вольтах. */
if (address == 8U) ump_pm67_voltage[1] = can_words[word_index];
} /* Завершает разбор трёх слов CAN после обновления только подтверждённых напряжений. */
/* Сохраняет компактный снимок только при активной записи и готовности 166, которые проверяет ump_capture. */
ump_capture(2);
/* Возвращает исходную маску IRQ, в том числе при вызове из обработчика CAN. */
__restore_interrupts(saved);
}
@@ -185,8 +202,8 @@ void ump_log_rs(void)
__restore_interrupts(saved);
}
/* Управление журналом отделено от технологических команд: начало записи
* НЕ запускает УМП. Выбор архива не останавливает текущую запись. */
/* Управление журналом отделено от технологических команд: начало записи
* НЕ запускает УМП. Выбор архива не останавливает текущую запись. */
int ump_log_write(unsigned int address, unsigned int value)
{
unsigned int saved, valid = 1;
@@ -211,8 +228,8 @@ int ump_log_write(unsigned int address, unsigned int value)
return valid;
}
/* Преобразование хронологического индекса в физическую позицию кольца.
* Для архива сохранены собственные count/next, независимые от живого банка. */
/* Преобразование хронологического индекса в физическую позицию кольца.
* Для архива сохранены собственные 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;
@@ -225,16 +242,17 @@ static unsigned int ump_word(unsigned int archived, unsigned int record, unsigne
return logs_block[((unsigned long)bank * UMP_LOG_CAPACITY + slot) * UMP_LOG_WORDS + word];
}
/* Весь Modbus-блок читается вызывающим кодом под одной блокировкой IRQ.
* Поэтому timestamp/данные и заголовок онлайн-блока относятся к одному
* состоянию кольца, даже если в этот момент приходит новая CAN-команда. */
/* Весь 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;
/* Версия 4 сообщает SetGUI о 12-словной записи, исключая её ошибочный разбор как прежних 40 слов v2. */
case UMP_LOG_BASE+1: return 4;
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;
@@ -254,10 +272,11 @@ unsigned int ump_log_read(unsigned int address)
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;
/* Ограничивает число целых записей ёмкостью 120-словного окна: формат v4 позволяет передать до десяти снимков. */
count = ump_count < UMP_LOG_LIVE_RECORDS ? ump_count : UMP_LOG_LIVE_RECORDS;
first = ump_sequence - count;
offset = address - UMP_LOG_LIVE;
if (offset == 0) return (unsigned int)first;

View File

@@ -1,6 +1,4 @@
eclipse.preferences.version=1
# Показывает русские комментарии изменённого исходника в CCS в сохранённой кодировке UTF-8.
encoding//Source/Internal/peripher.c=UTF-8
encoding//Debug/Libraries/subdir_rules.mk=UTF-8
encoding//Debug/Libraries/subdir_vars.mk=UTF-8
encoding//Debug/Source/External/v120/DSP2833x_common/source/subdir_rules.mk=UTF-8
@@ -106,3 +104,8 @@ encoding//Debug/optimizerAssistant/opt_for_speed__5/subdir_vars.mk=UTF-8
encoding//Debug/sources.mk=UTF-8
encoding//Debug/subdir_rules.mk=UTF-8
encoding//Debug/subdir_vars.mk=UTF-8
encoding//Source/Internal/Include/log_to_mem.h=UTF-8
encoding//Source/Internal/Include/ump_can_protocol.h=UTF-8
encoding//Source/Internal/log_to_mem.c=UTF-8
encoding//Source/Internal/peripher.c=UTF-8
encoding/<project>=UTF-8

View File

@@ -13,37 +13,76 @@ unsigned int adc_table_lem[4];
__declspec(dllexport) int run_tests(void)
{
unsigned int i, generation, archived, reply[UMP_CAN_MAX_WORDS], error, length;
/* Индексы перебирают все поля десяти записей онлайн-окна, проверяя отсутствие сдвига на старые 40 слов. */
unsigned int record_index, word_index, block_words;
unsigned long sequence;
READY = 100;
CHECK(!ump_log_write(UMP_LOG_BASE, 1));
READY = 0;
/* Явная инициализация кэша гарантирует нулевые ЗПТ до первого принятого измерения. */
CHECK(ump_pm67_voltage[0] == 0 && ump_pm67_voltage[1] == 0);
/* Регистр 306 ПМ67 передаётся нулевым CAN-индексом 305 в первой позиции кадра. */
ump_log_can(0x8000U | 305U, 321, 0, 0);
/* Регистр 9 ПМ67 находится во второй позиции кадра с начальным индексом 7 и маской 0x4000. */
ump_log_can(0x4000U | 7U, 0, 456, 0);
/* Напряжения должны обновляться ещё до включения регистратора, чтобы первый снимок уже содержал измерения. */
CHECK(ump_pm67_voltage[0] == 321 && ump_pm67_voltage[1] == 456 && ump_count == 0);
/* Нулевая маска не разрешает первое слово и не должна подменять сохранённое напряжение. */
ump_log_can(305U, 999, 0, 0);
/* Индекс 306 не является индексом регистра 306: прошивка принимает именно нулевую нумерацию CAN. */
ump_log_can(0x8000U | 306U, 999, 0, 0);
/* Оба неверных заголовка обязаны оставить последнее корректное ЗПТ1 без изменения. */
CHECK(ump_pm67_voltage[0] == 321);
/* Третья позиция соответствует индексу 305 даже при пропущенных первых двух словах кадра. */
ump_log_can(0x2000U | 303U, 0, 0, 123);
/* Аналогично проверяет третью позицию для нулевого индекса 8 второго напряжения. */
ump_log_can(0x2000U | 6U, 0, 0, 789);
/* Последний принятый набор должен совпадать с ожидаемыми ЗПТ1/ЗПТ2 в вольтах. */
CHECK(ump_pm67_voltage[0] == 123 && ump_pm67_voltage[1] == 789);
adc_table_lem[0] = 2048; adc_table_lem[3] = 4095;
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;
/* Дополнительные биты полного слова команд не должны попадать в компактное поле пуска/сброса v4. */
modbus[127] = 0xAB80;
/* Unrelated registers must not determine the fixed 166 ramp. */
modbus[0x50] = 99; modbus[0x51] = 123;
modbus[0x64] = 88; modbus[0x65] = 111;
modbus[0x7B] = 1234; modbus[0x1C] = 160;
/* Сторожевое значение во втором слоте обнаружит случайную запись старых полей за 12-словную границу. */
logs_block[UMP_LOG_WORDS] = 0xA55A;
/* Второй банк не должен затрагиваться заполнением и оборотом кольца первого банка. */
logs_block[UMP_LOG_CAPACITY * UMP_LOG_WORDS] = 0x5AA5;
CHECK(ump_log_write(UMP_LOG_BASE, 1));
/* Число слов и чётная длина совпадают с компактным форматом исполнения 167. */
CHECK(UMP_LOG_WORDS == 12 && 2UL * UMP_LOG_CAPACITY * UMP_LOG_WORDS <= LOG_PAGE_LEN);
/* Первый снимок обязан содержать напряжения, принятые до запуска записи. */
CHECK(ump_word(0, 0, 9) == 123 && ump_word(0, 0, 10) == 789);
/* Нулевой резерв необходим для парной упаковки CAN и не должен содержать старый код ЦАП. */
CHECK(ump_word(0, 0, 11) == 0 && logs_block[UMP_LOG_WORDS] == 0xA55A);
/* В начале доступен один снимок, а оставшаяся часть 124-словного онлайн-окна заполнена нулями. */
CHECK(ump_log_read(UMP_LOG_LIVE+2) == 1 && ump_log_read(UMP_LOG_LIVE+4+UMP_LOG_WORDS) == 0);
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);
CHECK(ump_word(0, 0, 25) == 16);
CHECK(ump_word(0, 0, 36) == 2048);
CHECK(ump_word(0, 0, 39) == 4095);
/* После оборота кольца поле команд содержит только биты пуска и сброса, несмотря на посторонние биты 0xAB80. */
CHECK(ump_word(0, 0, 3) == 0x80);
/* Расчётное задание тока переместилось в слово 6 и сохраняет масштаб 0,1 мА. */
CHECK(ump_word(0, 0, 6) == 160);
/* Уставки исполнения 166 остаются фиксированными 6/16 мА независимо от технологических регистров 167. */
CHECK(ump_word(0, 0, 7) == 6 && ump_word(0, 0, 8) == 16);
/* При обороте кольца не теряются оба напряжения и не изменяется сторожевое значение следующего банка. */
CHECK(ump_word(0, 0, 9) == 123 && ump_word(0, 0, 10) == 789 && logs_block[UMP_LOG_CAPACITY * UMP_LOG_WORDS] == 0x5AA5);
/* Короткая 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);
/* Событие CAN хранит выбранные команды в слове 3 вместо удалённого сырого заголовка в прежнем слове 18. */
CHECK(ump_word(0, ump_count-1, 3) == 0x40);
for (i=0; i<999; i++) ump_log_tick();
CHECK(ump_generation == 0);
ump_log_tick();
@@ -59,8 +98,16 @@ __declspec(dllexport) int run_tests(void)
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_read(UMP_LOG_LIVE+2) == UMP_LOG_LIVE_RECORDS && UMP_LOG_LIVE_RECORDS == 10);
/* Первое онлайн-время относится к самому старому из десяти переданных снимков. */
CHECK(ump_log_read(UMP_LOG_LIVE+4) == ump_time-UMP_LOG_LIVE_RECORDS+1);
/* Сопоставляет каждую запись онлайн-окна с хронологическим чтением кольцевого банка. */
for (record_index = 0; record_index < UMP_LOG_LIVE_RECORDS; record_index++)
/* Проверяет все 12 слов, включая время, выбранные сигналы и нулевой резерв. */
for (word_index = 0; word_index < UMP_LOG_WORDS; word_index++)
/* Смещения должны заполнять ровно 120 полезных слов после четырёх служебных слов. */
CHECK(ump_log_read(UMP_LOG_LIVE+4+record_index*UMP_LOG_WORDS+word_index) == ump_word(0, ump_count-UMP_LOG_LIVE_RECORDS+record_index, word_index));
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);
@@ -81,7 +128,27 @@ __declspec(dllexport) int run_tests(void)
/* CAN читает те же регистры и формирует цельный снимок из 124 слов.
* Запись по старым технологическим адресам через сервис запрещена. */
length = ump_can_prepare(3, UMP_LOG_BASE, 16, reply, &error);
CHECK(!error && length == 16 && reply[0] == 0x554D && reply[1] == 2);
/* Служебный ответ должен объявлять версию v4, длину 12 слов и сохранённую ёмкость банка исполнения 166. */
CHECK(!error && length == 16 && reply[0] == 0x554D && reply[1] == 4 && reply[4] == 12 && reply[5] == UMP_LOG_CAPACITY);
/* Проверяет каждое разрешённое число целых компактных записей в архивном CAN-окне. */
for (block_words = UMP_LOG_WORDS; block_words <= 120; block_words += UMP_LOG_WORDS) {
/* Запрашивает от одной до десяти записей, не превышая полезную длину 120 слов. */
length = ump_can_prepare(3, UMP_LOG_DATA, block_words, reply, &error);
/* Все кратные длине записи запросы должны проходить без исключения Modbus. */
CHECK(!error && length == block_words);
} /* Завершает проверку допустимых размеров окна компактного архива. */
/* Прежний блок v2 из 40 слов теперь обрывает запись v4 и должен отклоняться CAN-протоколом. */
ump_can_prepare(3, UMP_LOG_DATA, 40, reply, &error);
/* Исключение 2 сообщает о недопустимом диапазоне регистров. */
CHECK(error == 2);
/* Отклоняет пустой архивный запрос вместо его трактовки как успешного чтения. */
ump_can_prepare(3, UMP_LOG_DATA, 0, reply, &error);
/* Нулевая длина не содержит ни одной целой записи v4. */
CHECK(error == 2);
/* Блок из 11 записей уже превышает разрешённые 120 слов полезного окна. */
ump_can_prepare(3, UMP_LOG_DATA, 132, reply, &error);
/* Ограничение защищает буфер ответа от записи за его границами. */
CHECK(error == 2);
length = ump_can_prepare(3, UMP_LOG_LIVE, 124, reply, &error);
CHECK(!error && length == 124);
ump_can_prepare(3, UMP_LOG_LIVE, 126, reply, &error);