feat(protocan-transport): транспортный уровень ProtoCAN и каталог GUI
Перенесён из репозитория protocan-transport, который подключался сабмодулем в CAN_to_RS485. Кадрирование AA 55 с CRC16 поверх любого байтового потока (RS485, RS232, USB CDC), разбор 29-битного идентификатора, общее адресное пространство регистров и каталог с подпиской на поток значений для SETGUI. Состояние живёт в структурах вызывающего, поэтому в одной прошивке поднимается сколько угодно независимых каналов. Порт STM32F4 (USART + DMA) в комплекте. Хостовые тесты test_transport и test_gui проходят.
This commit is contained in:
87
c/protocan-transport/docs/GAS.md
Normal file
87
c/protocan-transport/docs/GAS.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Общее адресное пространство (GAS)
|
||||
|
||||
Плоское пространство 16-битных регистров с адресом `0x0000..0xFFFF`.
|
||||
Пространство собирается из **регионов**; регионы не перекрываются и
|
||||
хранятся отсортированными по адресу, поиск — двоичный.
|
||||
|
||||
## Регион
|
||||
|
||||
```c
|
||||
typedef struct pcan_gas_region {
|
||||
uint16_t base; /* адрес первого регистра */
|
||||
uint16_t count; /* число регистров */
|
||||
uint16_t *storage; /* массив либо NULL */
|
||||
pcan_gas_read_fn read;
|
||||
pcan_gas_write_fn write;
|
||||
uint8_t flags; /* PCAN_GAS_RDONLY / PCAN_GAS_WRONLY */
|
||||
void *user;
|
||||
const char *name;
|
||||
} pcan_gas_region_t;
|
||||
```
|
||||
|
||||
Если `storage != NULL`, чтение и запись идут прямо в массив — это самый
|
||||
дешёвый вариант для обычных уставок. Если нужен вычисляемый регистр
|
||||
(счётчик, состояние периферии, время работы), задайте `read`/`write`:
|
||||
колбэк получает смещение внутри региона и указатель `user`.
|
||||
|
||||
`pcan_gas_map_validate()` проверяет карту на этапе старта: нулевые регионы,
|
||||
выход за `0xFFFF`, перекрытие, нарушение порядка и регион без источника
|
||||
данных. Вызывайте её один раз при инициализации — ошибка в таблице ловится
|
||||
сразу, а не через месяц в поле.
|
||||
|
||||
## Доступ
|
||||
|
||||
```c
|
||||
uint16_t v;
|
||||
pcan_gas_read(&map, 0x0002, &v);
|
||||
pcan_gas_write(&map, 0x0002, 0x1234);
|
||||
|
||||
uint16_t block[4];
|
||||
uint16_t n = pcan_gas_read_block(&map, 0x0000, block, 4);
|
||||
```
|
||||
|
||||
Блочное чтение обрывается на первом адресе, которого нет в карте, поэтому
|
||||
вызывающий всегда получает непрерывный кусок и знает его длину.
|
||||
|
||||
## Отображение на кадры ProtoCAN
|
||||
|
||||
Тип сообщения `PCAN_MSG_GAS` (`0b0011`). Адрес первого регистра лежит
|
||||
в `MsgBody`, данные — до 4 регистров подряд, младшим байтом вперёд.
|
||||
Это ровно то, что делает `PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()`
|
||||
в `SETCAN/Src/protocan.c`, поэтому обмен совместим с существующими
|
||||
устройствами.
|
||||
|
||||
| Кадр | DLC | Смысл |
|
||||
|---|---:|---|
|
||||
| GAS, `MsgBody = addr` | 0 | **запрос на чтение** |
|
||||
| GAS, `MsgBody = addr` | 2..8 | значения регистров начиная с `addr` |
|
||||
|
||||
Запрос на чтение с `DLC = 0` — **расширение**: в исходном коде SETCAN
|
||||
такой кодировки нет, там ответы на GAS формировала заглушка `ProtoCanMsgToGeneralAddressSpace()`,
|
||||
возвращавшая строку `GAS-XXXX`. Кодировка выбрана так, чтобы не занимать
|
||||
новых типов сообщений и не конфликтовать с существующим форматом ответа.
|
||||
|
||||
```c
|
||||
pcan_frame_t rsp;
|
||||
if (pcan_gas_handle(&map, &incoming, &rsp)) {
|
||||
pcan_link_send(&link, &rsp); /* был запрос на чтение */
|
||||
}
|
||||
```
|
||||
|
||||
`pcan_gas_handle()`:
|
||||
|
||||
- на запрос чтения кладёт в `rsp` до 4 регистров и переключает `Route`
|
||||
на `FROM_DEVICE`, возвращает `true`;
|
||||
- на запись пишет регистры в карту и возвращает `false` — ответа нет;
|
||||
- если адреса нет в карте, возвращает `false`: отвечать нечем, а молчание
|
||||
честнее, чем ответ с нулями.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- В один кадр помещается не больше 4 регистров (`PCAN_GAS_REGS_PER_FRAME`).
|
||||
Длинные блоки разбивайте на несколько кадров.
|
||||
- Частичная запись: если в середине блока попался адрес вне карты или
|
||||
регион только для чтения, запись обрывается на нём. `pcan_gas_write_block()`
|
||||
возвращает число фактически записанных регистров.
|
||||
- Атомарности между регистрами нет. Если два регистра обязаны меняться
|
||||
вместе, заведите колбэк, который применяет их по записи второго.
|
||||
Reference in New Issue
Block a user