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:
2026-08-23 01:15:35 +03:00
parent 873ac438f3
commit 3dc636e012
27 changed files with 3646 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
# protocan-transport
Переносимая библиотека транспортного уровня для протокола **ProtoCAN**:
кадрирование поверх любого байтового потока (RS485, RS232, USB CDC),
разбор идентификатора и общее адресное пространство регистров.
Написана на C99, без динамической памяти, без ОС, без зависимостей от HAL
и от конкретного микроконтроллера. Состояние живёт в структурах вызывающего,
поэтому в одной прошивке поднимается сколько угодно независимых каналов.
```
ваш код protocan-transport платформа
┌────────┐ ┌────────────────────┐ ┌──────────────┐
│ кадры │─────►│ pcan_link_send() │─────►│ io.write() │──► UART/DMA
│ │◄─────│ on_frame() │◄─────│ pcan_link_feed()
└────────┘ └────────────────────┘ └──────────────┘
pcan_gas_* pcan_id_*
```
## Состав
| Модуль | Назначение |
|---|---|
| `pcan_frame` | кадр `AA 55 … CRC16` и потоковый разборщик с ресинхронизацией |
| `pcan_crc` | CRC-16/CCITT-FALSE, побитовый или табличный |
| `pcan_link` | экземпляр канала: приём, передача, SEQ, счётчики |
| `pcan_ring` | кольцевой буфер, отдаёт непрерывный участок для DMA |
| `pcan_id` | упаковка и разбор 29-битного идентификатора ProtoCAN |
| `pcan_gas` | общее адресное пространство: карта регионов, чтение/запись, мост к кадрам |
| `ports/stm32f4` | готовый порт USART + DMA (пакетная передача, кольцевой приём) |
## Кадр
```
AA 55 | LEN | SEQ | FLAGS | ID0 ID1 ID2 ID3 | DATA[0..8] | CRC_L CRC_H
```
`LEN = 6 + DLC` (6..14), CRC-16/CCITT-FALSE по байтам `LEN..DATA`,
little-endian. Подробности — [docs/FRAME.md](docs/FRAME.md).
## Общее адресное пространство
Плоское пространство 16-битных регистров `0x0000..0xFFFF`, собранное из
регионов. Регион ссылается либо на массив в памяти, либо на пару колбэков —
так в карту попадают и переменные, и вычисляемые значения, и регистры
периферии. Подробности — [docs/GAS.md](docs/GAS.md).
```c
static uint16_t holding[8];
static const pcan_gas_region_t regions[] = {
{ 0x0000, 8, holding, NULL, NULL, 0, NULL, "holding" },
{ 0xFF00, 4, NULL, diag_read, NULL, PCAN_GAS_RDONLY, NULL, "diag" },
};
static const pcan_gas_map_t map = { regions, 2 };
```
## Использование
```c
#include "protocan_transport.h"
static void on_frame(const pcan_frame_t *f, void *user)
{
/* ... */
}
pcan_io_t io;
pcan_uart_io(&uart, &io); /* или свой io */
pcan_link_t link;
pcan_link_init(&link, &io, on_frame, NULL);
pcan_frame_t f = { .flags = PCAN_FLAG_IDE, .id = 0x1234567, .dlc = 2 };
f.data[0] = 0xAA; f.data[1] = 0xBB;
pcan_link_send(&link, &f); /* SEQ проставится сам */
```
Для своей платформы достаточно реализовать две функции:
```c
size_t my_write(void *ctx, const uint8_t *data, size_t len); /* всё-или-ничего */
size_t my_space(void *ctx);
```
## Сборка
Библиотека — шесть файлов в `src/` и заголовки в `include/`. Добавьте их
в проект и укажите `include/` в путях поиска. CMake для тестов и хостовых
сборок:
```bash
cmake -B build && cmake --build build && ctest --test-dir build
```
Либо напрямую:
```bash
clang -std=c99 -Wall -Wextra -Iinclude tests/test_transport.c src/pcan_*.c -o test && ./test
```
Порт `ports/stm32f4` в тесты не входит: ему нужен CMSIS-заголовок
`stm32f4xx.h`, подключайте его в проект прошивки отдельно.
## Настройки
Переопределяются через `-D` либо через свой `pcan_config_user.h`
(с `-DPCAN_USE_USER_CONFIG`):
| Макрос | По умолчанию | Смысл |
|---|---|---|
| `PCAN_DATA_MAX` | 8 | длина поля данных CAN |
| `PCAN_CRC_TABLE` | 0 | 1 — таблица на 512 байт вместо побитового расчёта |
| `PCAN_GAS_MAX_REGIONS` | 16 | предел числа регионов в карте |
| `PCAN_BARRIER()` | барьер компилятора | для очередей, разделяемых с прерыванием |
## Ограничения
- Очереди рассчитаны на схему «один писатель + один читатель». Если
писателей несколько, оборачивайте вызовы своей блокировкой.
- Размер кольцевого буфера обязан быть степенью двойки; `pcan_ring_init()`
вернёт `false`, а не станет молча портить индексы.
- На STM32F407 DMA не видит CCM RAM (`0x10000000`) — буферы держите
в основном SRAM.
- Порядок байт на линии фиксирован (little-endian) и не зависит от порядка
байт хоста.