Перенесён из репозитория protocan-transport, который подключался сабмодулем в CAN_to_RS485. Кадрирование AA 55 с CRC16 поверх любого байтового потока (RS485, RS232, USB CDC), разбор 29-битного идентификатора, общее адресное пространство регистров и каталог с подпиской на поток значений для SETGUI. Состояние живёт в структурах вызывающего, поэтому в одной прошивке поднимается сколько угодно независимых каналов. Порт STM32F4 (USART + DMA) в комплекте. Хостовые тесты test_transport и test_gui проходят.
88 lines
4.8 KiB
Markdown
88 lines
4.8 KiB
Markdown
# Общее адресное пространство (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()`
|
||
возвращает число фактически записанных регистров.
|
||
- Атомарности между регистрами нет. Если два регистра обязаны меняться
|
||
вместе, заведите колбэк, который применяет их по записи второго.
|