feat(gas-logger): добавь непрерывный логгер и JSON-карту
This commit is contained in:
177
c/gas-logger/README.md
Normal file
177
c/gas-logger/README.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# GAS Logger — экспериментальная версия 1
|
||||
|
||||
Непрерывный регистратор с двумя кольцевыми банками, неизменяемым снимком и
|
||||
общей картой GAS / ProtoCAN GAS / Modbus FC03/FC06. Весь код и проектные/MCU
|
||||
адаптеры находятся в `templates`; приложения подключают ревизию сабмодулем.
|
||||
Ядро C99 не содержит HAL, malloc, глобальных переменных, драйвера связи или
|
||||
прерываний. Хранение — RAM, после отключения питания данные исчезают.
|
||||
|
||||
Приложение → проектный источник сигналов → `gas_logger` → MCU-порт блокировки.
|
||||
GAS/Modbus/CAN диспетчеры → тот же экземпляр `gas_logger`.
|
||||
|
||||
## Состав
|
||||
|
||||
| Файл | Назначение / зависимости |
|
||||
|---|---|
|
||||
| `include/gas_logger.h`, `src/gas_logger.c` | переносимое ядро, только стандартный C99 |
|
||||
| `ports/pm35/` | источники ПМ35 и кэш напряжений ПМ67, сгенерированная карта |
|
||||
| `ports/c28x/` | сохранение/восстановление IRQ для TI F2812/F28335 |
|
||||
| `ports/stm32/` | PRIMASK для STM32 F1/F4/G4, CMSIS проекта |
|
||||
| `src/gas_logger_pcan.c` | регион сервиса `pcan_gas`, только MCU с поддержкой `uint8_t` |
|
||||
| `../../python/set_devices/gas_logger.py` | валидация карты, генератор C, клиент снимков; stdlib |
|
||||
| `../../python/set_devices/gas_logger_maps/pm35.json` | имена, адреса GAS/CAN/Modbus, источники и регистры сервиса |
|
||||
| `tests/` | ядро и сквозной тест клиента с настоящим C-регистратором |
|
||||
|
||||
## JSON и адреса
|
||||
|
||||
JSON — исходное описание. `ports/pm35/pm35_map.h` — автоматически созданная
|
||||
копия для прошивки. `tools/generate_map.py --check` проверяет их соответствие.
|
||||
Изменения выполняются в JSON, затем запускается генератор. CRC32 канонического
|
||||
JSON публикуется платой: клиент откажется читать/записывать при несовпадении
|
||||
карты. Это идентификатор совместимости, не механизм аутентификации.
|
||||
|
||||
Все адреса — **нулевые индексы 16-битных регистров**, без префикса 4xxxx.
|
||||
В поле `can.address` указан MsgBody для **ProtoCAN GAS**, а не полный CAN ID.
|
||||
DeviceType/DeviceID/Route выбирает транспорт проекта. Идентификаторы legacy
|
||||
CAN ПМ35 и ПМ67 не переименовываются в GAS: проект сначала обновляет их кэш,
|
||||
после чего регистратор читает его через `source`.
|
||||
|
||||
Экспериментальный профиль ПМ35:
|
||||
|
||||
| GAS | Modbus FC03 | ProtoCAN GAS | Имя | Источник |
|
||||
|---|---|---|---|---|
|
||||
| 0x5000 | 0x5000 | 0x5000 | Команды УМП | modbus[127] |
|
||||
| 0x5001 | 0x5001 | 0x5001 | Ток, 0.1 мА | modbus[28] |
|
||||
| 0x5002 | 0x5002 | 0x5002 | Код ЦАП | modbus[123] |
|
||||
| 0x5003 | 0x5003 | 0x5003 | Начальная уставка | modbus[80] |
|
||||
| 0x5004 | 0x5004 | 0x5004 | Конечная уставка | modbus[81] |
|
||||
| 0x5005 | 0x5005 | 0x5005 | ЗПТ1 | pm67_voltage[0], ранее принятый CAN адрес 305 |
|
||||
| 0x5006 | 0x5006 | 0x5006 | ЗПТ2 | pm67_voltage[1], ранее принятый CAN адрес 8 |
|
||||
|
||||
Адреса 0x5000/0x6000 — выделение **для тестового профиля**, не утверждение о
|
||||
поддержке этих окон существующей прошивкой. Перед включением в другую карту
|
||||
проект обязан проверить отсутствие пересечений со своими регионами.
|
||||
|
||||
## Карта сервиса
|
||||
|
||||
База одинакова для всех трёх транспортов: `0x6000`; имена и смещения также
|
||||
перечислены в JSON. Многословные числа: сначала младшее 16-битное слово.
|
||||
Endian байтов задаёт транспорт: Modbus BE, ProtoCAN GAS LE.
|
||||
|
||||
| Смещение | Доступ | Значение |
|
||||
|---|---|---|
|
||||
| 0, 1 | R | сигнатура 0x474C, версия 1 |
|
||||
| 2 | R | бит 0 запись, бит 1 снимок закреплён |
|
||||
| 3..5 | R | каналов, слов в записи, ёмкость банка |
|
||||
| 6, 7 | R | записей в текущем банке / снимке |
|
||||
| 8..9 | R | поколение снимка u32 |
|
||||
| 10..11 | R | CRC32 JSON-карты |
|
||||
| 12..13 | R | следующий номер записи u32 |
|
||||
| 14..15 | R | пропущенные снимки: запрос во время закреплённого снимка |
|
||||
| 16..17 | R | ошибки источников при записи |
|
||||
| 18 | W | 1 START, 2 SNAPSHOT, 3 RELEASE |
|
||||
| 19..20 | W | поколение, разрешённое к освобождению |
|
||||
| 0x100.. | R | записи снимка по прямому адресу, без команд курсора |
|
||||
|
||||
Запись: `time_ms:u32, sequence:u32, event:u16, channels[N]:u16`.
|
||||
В профиле ПМ35 — 12 слов, 760 записей на банк, всего 18240 слов RAM для двух
|
||||
банков. Блочное чтение разрешено по любой границе, максимум 125 слов.
|
||||
Клиент собирает записи из блоков, в том числе неполных по размеру записи.
|
||||
Классический CAN использует до 4 слов в кадре. Для UART подходит 120/125 слов.
|
||||
Для 760 записей этого профиля требуется 76 чтений по 120 слов без записи
|
||||
курсора; состав полей отличается от старого 42-словного УМП, поэтому это не
|
||||
прямое измерение ускорения прежнего лога.
|
||||
|
||||
## Непрерывная запись и снимки
|
||||
|
||||
`START` идемпотентен: включает запись, не очищает историю. STOP-команды нет.
|
||||
`SNAPSHOT` кратко блокирует IRQ, переключает активный банк и закрепляет старый.
|
||||
Во время загрузки новые записи продолжают поступать в другой кольцевой банк.
|
||||
Снимок не перезаписывается, пока клиент не передаст совпадающее поколение и
|
||||
`RELEASE`. Одновременные управляющие клиенты не поддерживаются.
|
||||
|
||||
Повторный SNAPSHOT при занятом снимке возвращает BUSY и увеличивает счётчик.
|
||||
Пустой банк возвращает EMPTY. Долгий обрыв связи не останавливает запись, но
|
||||
закреплённый снимок остаётся занят: нужно явно продолжить чтение (`resume=True`)
|
||||
либо освободить его. Автоматического таймаута освобождения нет.
|
||||
После переключения новый банк начинает историю с нуля; непрерывность означает
|
||||
продолжение сбора, а не дублирование предыстории между снимками.
|
||||
|
||||
Триггер/постинтервал определяет проект. Например, ПМ35 вызывает `gl_snapshot()`
|
||||
по завершении своей секунды после отключения, а не при начале события.
|
||||
Этот модуль не заменяет автоматически старую логику УМП.
|
||||
|
||||
## Контракт порта
|
||||
|
||||
```c
|
||||
uint32_t enter(void *user); /* сохранить и запретить IRQ */
|
||||
void leave(void *user, uint32_t previous); /* восстановить ровно previous */
|
||||
int source(void *user, uint16_t gas, uint16_t *value);
|
||||
```
|
||||
|
||||
`source` выполняется под блокировкой: только быстрые чтения RAM. Ни запросов
|
||||
CAN/Modbus, ни ожиданий периферии. Ошибка любого канала отбрасывает всю запись.
|
||||
Кэш CAN обновляется под той же блокировкой. Для RTOS/многоядерных платформ
|
||||
нужен порт с подходящей межпоточной синхронизацией; PRIMASK — для одного ядра.
|
||||
На стеке блочного чтения до 125 слов; при захвате до 64 слов каналов.
|
||||
Массивы банков не должны перекрываться друг с другом и контекстом.
|
||||
|
||||
## Подключение ПМ35 / F28335
|
||||
|
||||
```c
|
||||
#include "gas_logger_pm35.h"
|
||||
#include "gas_logger_c28x.h"
|
||||
static gas_logger logger;
|
||||
static uint16_t bank0[9120], bank1[9120]; /* разместить в RAM по linker map */
|
||||
static gl_pm35_context source;
|
||||
void logger_init(volatile uint16_t *modbus, volatile uint16_t *pm67) {
|
||||
source.modbus = modbus; source.pm67_voltage = pm67;
|
||||
source.enter = gl_c28x_enter; source.leave = gl_c28x_leave;
|
||||
source.irq_user = 0;
|
||||
if (gl_pm35_init(&logger, &source, bank0, bank1, 9120) == GL_OK)
|
||||
gl_write(&logger, 0x6012, GL_START);
|
||||
}
|
||||
/* Из существующего таймера: gl_capture(&logger, time_ms, event); */
|
||||
```
|
||||
|
||||
Включить `src/gas_logger.c`, `ports/pm35/gas_logger_pm35.c` и
|
||||
`ports/c28x/gas_logger_c28x.c`. Include-пути — `include`, `ports/pm35`,
|
||||
`ports/c28x`. F2812/F28335 используют одни intrinsics TI.
|
||||
Для STM32 подключить `ports/stm32/gas_logger_stm32.c`, выбрать
|
||||
`GL_STM32_DEVICE_HEADER` (`stm32f1xx.h`/`stm32f4xx.h`/`stm32g4xx.h`) и передать
|
||||
`gl_stm32_enter/leave`. Проектный PM35-адаптер применим к STM-эмулятору только
|
||||
если его массивы соответствуют указанному контракту.
|
||||
|
||||
FC03 диспетчер вызывает `gl_modbus_read()`, FC06 — `gl_modbus_write()`.
|
||||
GL_ADDRESS → exception 2, GL_ARGUMENT → 3, GL_BUSY → 6, остальные ошибки → 4.
|
||||
Ответ на запись отправляется лишь после GL_OK. CRC/кадры остаются в транспорте.
|
||||
Для `pcan_gas` добавляется регион `gl_pcan_region_init()`; сигнальные адреса
|
||||
маршрутизируются отдельно в `gl_can_read()` (или уже существующие GAS-регионы).
|
||||
Стандартные GAS-записи CAN не имеют ACK: CAN-адаптер клиента должен подтвердить
|
||||
результат чтением статуса; просто отправить CAN-кадр недостаточно.
|
||||
Передача UART/CAN выполняется **после** снятия IRQ-блокировки.
|
||||
|
||||
## Клиент и проверка
|
||||
|
||||
```python
|
||||
from set_devices.gas_logger import GasLoggerClient
|
||||
client = GasLoggerClient(read_registers, write_register, block_words=120)
|
||||
client.start() # после проверки сигнатуры и JSON CRC
|
||||
records = client.download() # фиксирует, читает, проверяет, освобождает
|
||||
```
|
||||
|
||||
Вызовы транспорта синхронные, подтверждённые; для GUI запускать в его рабочем
|
||||
потоке. Исключение/отмена оставляет снимок закреплённым для явного продолжения.
|
||||
Записи с таймаутом автоматически не повторяются.
|
||||
|
||||
```powershell
|
||||
python c/gas-logger/tools/generate_map.py --check
|
||||
python c/gas-logger/tests/run_tests.py --cc C:/msys64/mingw64/bin/gcc.exe
|
||||
```
|
||||
|
||||
Либо `cmake -S c/gas-logger -B <build>`, сборка и `ctest --test-dir <build>`.
|
||||
Ветка `codex/test-gas-logger`: потребитель SETGUI импортирует модуль из
|
||||
`third_party/templates` и включает JSON в пакет. Производственные прошивки
|
||||
ПМ35/166 пока не переключены; адреса не включены в их текущие диспетчеры.
|
||||
Нужны подключение к таймеру/карте конкретной платы, проверка linker RAM и
|
||||
испытание на устройстве. Проверки на хосте не измеряют аппаратные тайминги.
|
||||
Reference in New Issue
Block a user