150 lines
7.6 KiB
Markdown
150 lines
7.6 KiB
Markdown
# 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` | общее адресное пространство: карта регионов, чтение/запись, мост к кадрам |
|
||
| `pcan_abi` | стабильный C ABI для Python, JNI, Swift и других FFI |
|
||
| `ports/stm32f4` | готовый порт USART + DMA (пакетная передача, кольцевой приём) |
|
||
| `ports/android` | JNI и Kotlin-фасад; собирает `libsetcore.so` из этого же C99-кода |
|
||
|
||
## Кадр
|
||
|
||
```
|
||
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).
|
||
|
||
Полное описание прикладного ProtoCAN и общего адресного пространства теперь
|
||
также хранится здесь: [docs/PROTOCOL.md](docs/PROTOCOL.md) и
|
||
[docs/OAP.md](docs/OAP.md). Эталонные данные находятся в
|
||
`tests/vectors/test-vectors.json`. Это канонические документы; копии в SETCAN
|
||
считаются историческим снимком legacy HAL-адаптера.
|
||
|
||
## Общее адресное пространство
|
||
|
||
Плоское пространство 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
|
||
```
|
||
|
||
Для desktop CMake дополнительно собирает shared library `setcore`
|
||
(`setcore.dll`, `libsetcore.so` или `libsetcore.dylib`). Публичная граница
|
||
для приложений описана в `include/pcan_abi.h`; внутренние структуры ядра через
|
||
FFI не экспортируются. Python-обёртка находится в `python/protocan/native.py`.
|
||
|
||
Android-проект подключает `ports/android/Android.mk` и добавляет
|
||
`ports/android/kotlin` в `sourceSets`. JNI-код занимается только преобразованием
|
||
типов и временем жизни parser context, а правила кадра и CAN ID остаются в C.
|
||
|
||
Если на Windows нет CMake, host-библиотеку тем же MSVC можно собрать так:
|
||
|
||
```powershell
|
||
python tools/build_host.py --output native/setcore.dll
|
||
```
|
||
|
||
Либо напрямую:
|
||
|
||
```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) и не зависит от порядка
|
||
байт хоста.
|