178 lines
13 KiB
Markdown
178 lines
13 KiB
Markdown
# 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 и
|
||
испытание на устройстве. Проверки на хосте не измеряют аппаратные тайминги.
|