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,195 @@
#include "gui_catalog.h"
static size_t name_len(const char *name)
{
size_t n = 0U;
if (name == NULL) {
return 0U;
}
while ((name[n] != '\0') && (n <= GUI_NAME_SIZE)) {
n++;
}
return n;
}
static uint8_t object_registers(uint8_t type)
{
return ((type == GUI_OBJ_U32) || (type == GUI_OBJ_I32)) ? 2U : 1U;
}
bool gui_catalog_validate(const gui_catalog_t *catalog, const pcan_gas_map_t *map)
{
if ((catalog == NULL) || ((catalog->count != 0U) && (catalog->items == NULL))) {
return false;
}
for (uint16_t i = 0U; i < catalog->count; i++) {
const gui_object_t *item = &catalog->items[i];
if (name_len(item->name) > GUI_NAME_SIZE) {
return false;
}
if (item->type > GUI_OBJ_BITS) {
return false;
}
if (map != NULL) {
uint8_t words = object_registers(item->type);
for (uint8_t w = 0U; w < words; w++) {
uint32_t addr = (uint32_t)item->address + w;
if (addr > 0xFFFFUL) {
return false;
}
if (pcan_gas_find(map, (uint16_t)addr) == NULL) {
return false;
}
}
}
}
return true;
}
size_t gui_catalog_encode(const gui_catalog_t *catalog, uint16_t start_index,
uint16_t max_count, uint8_t *out, size_t out_size)
{
if ((catalog == NULL) || (out == NULL) || (start_index >= catalog->count)) {
return 0U;
}
uint16_t available = (uint16_t)(catalog->count - start_index);
uint16_t count = ((max_count == 0U) || (max_count > available)) ? available : max_count;
if (count > GUI_ENTRIES_PER_FRAME) {
count = (uint16_t)GUI_ENTRIES_PER_FRAME;
}
size_t total = (size_t)GUI_CATALOG_HEADER + ((size_t)count * GUI_ENTRY_SIZE);
if (out_size < total) {
return 0U;
}
gui_put_u16(&out[0], catalog->count);
gui_put_u16(&out[2], start_index);
gui_put_u16(&out[4], count);
for (uint16_t i = 0U; i < count; i++) {
const gui_object_t *item = &catalog->items[start_index + i];
uint8_t *dst = &out[GUI_CATALOG_HEADER + ((size_t)i * GUI_ENTRY_SIZE)];
gui_put_u16(&dst[0], item->address);
dst[2] = item->type;
dst[3] = item->flags;
dst[4] = (uint8_t)item->scale_pow10;
dst[5] = item->unit;
dst[6] = 0U;
dst[7] = 0U;
size_t n = name_len(item->name);
if (n > GUI_NAME_SIZE) {
n = GUI_NAME_SIZE;
}
for (size_t c = 0U; c < n; c++) {
dst[8U + c] = (uint8_t)item->name[c];
}
for (size_t c = n; c < GUI_NAME_SIZE; c++) {
dst[8U + c] = 0U;
}
}
return total;
}
void gui_watch_init(gui_watch_t *watch)
{
watch->period_ms = 0U;
watch->count = 0U;
watch->next_ms = 0U;
watch->sent = 0U;
watch->skipped = 0U;
}
uint16_t gui_watch_apply(gui_watch_t *watch, const pcan_gas_map_t *map,
const uint8_t *payload, uint16_t size)
{
if (size < 4U) {
return 0xFFFFU;
}
uint16_t period = gui_get_u16(&payload[0]);
uint16_t requested = gui_get_u16(&payload[2]);
if (size != (uint16_t)(4U + (requested * 2U))) {
return 0xFFFFU;
}
if (requested > GUI_WATCH_MAX) {
return 0xFFFFU;
}
uint16_t accepted = 0U;
for (uint16_t i = 0U; i < requested; i++) {
uint16_t address = gui_get_u16(&payload[4U + (i * 2U)]);
/* Адрес вне карты молча пропускаем: иначе одна опечатка в GUI
отменяла бы всю подписку. */
if ((map != NULL) && (pcan_gas_find(map, address) == NULL)) {
continue;
}
watch->address[accepted++] = address;
}
watch->count = accepted;
watch->period_ms = (accepted == 0U) ? 0U : period;
watch->next_ms = 0U;
return accepted;
}
size_t gui_watch_encode_ack(const gui_watch_t *watch, uint8_t *out, size_t out_size)
{
if (out_size < 4U) {
return 0U;
}
gui_put_u16(&out[0], watch->period_ms);
gui_put_u16(&out[2], watch->count);
return 4U;
}
bool gui_watch_due(const gui_watch_t *watch, uint32_t now_ms)
{
if ((watch->period_ms == 0U) || (watch->count == 0U)) {
return false;
}
/* Разность беззнаковая: переполнение счётчика миллисекунд обрабатывается
само собой, сравнение с абсолютным временем - нет. */
return (uint32_t)(now_ms - watch->next_ms) < 0x80000000UL;
}
size_t gui_watch_encode_data(const gui_watch_t *watch, const pcan_gas_map_t *map,
uint32_t timestamp_ms, uint8_t *out, size_t out_size)
{
if ((watch->count == 0U) || (out == NULL)) {
return 0U;
}
size_t total = 6U + ((size_t)watch->count * 2U);
if (out_size < total) {
return 0U;
}
gui_put_u32(&out[0], timestamp_ms);
gui_put_u16(&out[4], watch->count);
for (uint16_t i = 0U; i < watch->count; i++) {
uint16_t value = 0U;
/* Регистр мог исчезнуть между подпиской и опросом - тогда 0,
а не мусор из стека. */
(void)pcan_gas_read(map, watch->address[i], &value);
gui_put_u16(&out[6U + ((size_t)i * 2U)], value);
}
return total;
}
void gui_watch_advance(gui_watch_t *watch, uint32_t now_ms)
{
if (watch->period_ms == 0U) {
return;
}
uint32_t next = watch->next_ms + watch->period_ms;
/* Если отстали больше чем на такт (линия была занята), не пытаемся
нагнать пачкой пакетов - идём от текущего момента. */
if ((uint32_t)(now_ms - next) >= 0x80000000UL) {
watch->next_ms = next;
} else {
watch->next_ms = now_ms + watch->period_ms;
}
watch->sent++;
}

View File

@@ -0,0 +1,230 @@
#include "gui_frame.h"
uint32_t gui_crc32(const uint8_t *data, size_t len)
{
uint32_t crc = 0xFFFFFFFFUL;
for (size_t i = 0U; i < len; i++) {
crc ^= data[i];
for (uint8_t bit = 0U; bit < 8U; bit++) {
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
}
}
return crc ^ 0xFFFFFFFFUL;
}
size_t gui_frame_encode(uint8_t type, uint16_t sequence,
const uint8_t *payload, uint16_t size,
uint8_t *out, size_t out_size)
{
if ((out == NULL) || (size > GUI_MAX_PAYLOAD) ||
((size > 0U) && (payload == NULL))) {
return 0U;
}
size_t total = (size_t)GUI_HEADER_SIZE + size + GUI_CRC_SIZE;
if (out_size < total) {
return 0U;
}
out[0] = GUI_SOF0;
out[1] = GUI_SOF1;
/* CRC считается от version до конца payload; SOF в него не входит. */
out[2] = GUI_PROTOCOL_VERSION;
out[3] = type;
out[4] = (uint8_t)(sequence >> 8); /* заголовок big-endian */
out[5] = (uint8_t)(sequence & 0xFFU);
out[6] = (uint8_t)(size >> 8);
out[7] = (uint8_t)(size & 0xFFU);
for (uint16_t i = 0U; i < size; i++) {
out[GUI_HEADER_SIZE + i] = payload[i];
}
uint32_t crc = gui_crc32(&out[2], (size_t)6U + size);
gui_put_u32(&out[GUI_HEADER_SIZE + size], crc); /* CRC little-endian */
return total;
}
void gui_parser_init(gui_parser_t *p)
{
p->state = GUI_ST_SOF0;
p->idx = 0U;
p->size = 0U;
p->stats.frames = 0U;
p->stats.crc_errors = 0U;
p->stats.version_errors = 0U;
p->stats.length_errors = 0U;
p->stats.stray_bytes = 0U;
}
bool gui_parser_push(gui_parser_t *p, uint8_t byte, gui_frame_t *out)
{
switch (p->state) {
case GUI_ST_SOF0:
if (byte == GUI_SOF0) {
p->state = GUI_ST_SOF1;
} else {
p->stats.stray_bytes++;
}
break;
case GUI_ST_SOF1:
if (byte == GUI_SOF1) {
p->idx = 0U;
p->state = GUI_ST_HEADER;
} else if (byte == GUI_SOF0) {
/* A5 A5 5A - тоже валидное начало, остаёмся здесь. */
p->stats.stray_bytes++;
} else {
p->stats.stray_bytes += 2U;
p->state = GUI_ST_SOF0;
}
break;
case GUI_ST_HEADER:
/* Версию проверяем сразу на первом байте заголовка. Если ждать все
шесть, ложная сигнатура успеет проглотить начало настоящего кадра
и он будет потерян целиком. */
if ((p->idx == 0U) && (byte != GUI_PROTOCOL_VERSION)) {
p->stats.version_errors++;
p->state = (byte == GUI_SOF0) ? GUI_ST_SOF1 : GUI_ST_SOF0;
break;
}
p->header[p->idx++] = byte;
if (p->idx < 6U) {
break;
}
p->size = (uint16_t)(((uint16_t)p->header[4] << 8) | p->header[5]);
if (p->size > GUI_RX_PAYLOAD_MAX) {
/* Либо мусор, либо кадр, который прибор обслужить не может.
И то и другое - повод вернуться к поиску сигнатуры. */
p->stats.length_errors++;
p->state = GUI_ST_SOF0;
break;
}
p->idx = 0U;
p->state = (p->size > 0U) ? GUI_ST_PAYLOAD : GUI_ST_CRC;
break;
case GUI_ST_PAYLOAD:
p->payload[p->idx++] = byte;
if (p->idx >= p->size) {
p->idx = 0U;
p->state = GUI_ST_CRC;
}
break;
case GUI_ST_CRC: {
p->crc_bytes[p->idx++] = byte;
if (p->idx < GUI_CRC_SIZE) {
break;
}
p->state = GUI_ST_SOF0;
uint32_t received = ((uint32_t)p->crc_bytes[0])
| ((uint32_t)p->crc_bytes[1] << 8)
| ((uint32_t)p->crc_bytes[2] << 16)
| ((uint32_t)p->crc_bytes[3] << 24);
/* CRC защищает header[0..5] и payload; считаем по частям,
чтобы не собирать кадр целиком во втором буфере. */
uint32_t crc = 0xFFFFFFFFUL;
for (uint8_t i = 0U; i < 6U; i++) {
crc ^= p->header[i];
for (uint8_t bit = 0U; bit < 8U; bit++) {
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
}
}
for (uint16_t i = 0U; i < p->size; i++) {
crc ^= p->payload[i];
for (uint8_t bit = 0U; bit < 8U; bit++) {
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
}
}
crc ^= 0xFFFFFFFFUL;
if (crc != received) {
p->stats.crc_errors++;
return false;
}
out->type = p->header[1];
out->sequence = (uint16_t)(((uint16_t)p->header[2] << 8) | p->header[3]);
out->size = p->size;
out->payload = p->payload;
p->stats.frames++;
return true;
}
default:
p->state = GUI_ST_SOF0;
break;
}
return false;
}
size_t gui_parser_feed(gui_parser_t *p, const uint8_t *data, size_t len,
gui_frame_cb_t cb, void *user)
{
gui_frame_t frame;
size_t count = 0U;
for (size_t i = 0U; i < len; i++) {
if (gui_parser_push(p, data[i], &frame)) {
count++;
if (cb != NULL) {
cb(&frame, user);
}
}
}
return count;
}
bool gui_link_init(gui_link_t *link, const pcan_io_t *io,
gui_frame_cb_t on_frame, void *user)
{
if ((link == NULL) || (io == NULL) || (io->write == NULL)) {
return false;
}
link->io = *io;
link->on_frame = on_frame;
link->user = user;
link->tx_frames = 0U;
link->tx_dropped = 0U;
gui_parser_init(&link->parser);
return true;
}
bool gui_link_can_send(const gui_link_t *link, uint16_t size)
{
if (link->io.tx_space == NULL) {
return true;
}
return link->io.tx_space(link->io.ctx) >= ((size_t)GUI_HEADER_SIZE + size + GUI_CRC_SIZE);
}
bool gui_link_send(gui_link_t *link, uint8_t type, uint16_t sequence,
const uint8_t *payload, uint16_t size)
{
size_t n = gui_frame_encode(type, sequence, payload, size,
link->tx_buf, sizeof link->tx_buf);
if (n == 0U) {
link->tx_dropped++;
return false;
}
if (link->io.write(link->io.ctx, link->tx_buf, n) != n) {
link->tx_dropped++;
return false;
}
link->tx_frames++;
return true;
}
bool gui_link_nack(gui_link_t *link, uint16_t sequence, uint16_t result)
{
uint8_t payload[2];
gui_put_u16(payload, result);
return gui_link_send(link, GUI_MSG_NACK, sequence, payload, sizeof payload);
}
size_t gui_link_feed(gui_link_t *link, const uint8_t *data, size_t len)
{
return gui_parser_feed(&link->parser, data, len, link->on_frame, link->user);
}

View File

@@ -0,0 +1,66 @@
#include "pcan_crc.h"
#if PCAN_CRC_TABLE
static const uint16_t s_table[256] = {
0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U,
0x8108U, 0x9129U, 0xA14AU, 0xB16BU, 0xC18CU, 0xD1ADU, 0xE1CEU, 0xF1EFU,
0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U,
0x9339U, 0x8318U, 0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU,
0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64E6U, 0x74C7U, 0x44A4U, 0x5485U,
0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU,
0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76D7U, 0x66F6U, 0x5695U, 0x46B4U,
0xB75BU, 0xA77AU, 0x9719U, 0x8738U, 0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU,
0x48C4U, 0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U,
0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U, 0xA90AU, 0xB92BU,
0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U, 0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U,
0xDBFDU, 0xCBDCU, 0xFBBFU, 0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU,
0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U,
0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU, 0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U,
0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U, 0x2E32U, 0x1E51U, 0x0E70U,
0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U,
0x9188U, 0x81A9U, 0xB1CAU, 0xA1EBU, 0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU,
0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U,
0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU,
0x02B1U, 0x1290U, 0x22F3U, 0x32D2U, 0x4235U, 0x5214U, 0x6277U, 0x7256U,
0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU,
0x34E2U, 0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U,
0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U, 0xE75FU, 0xF77EU, 0xC71DU, 0xD73CU,
0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U,
0xD94CU, 0xC96DU, 0xF90EU, 0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU,
0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18C0U, 0x08E1U, 0x3882U, 0x28A3U,
0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU,
0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U, 0x1AD0U, 0x2AB3U, 0x3A92U,
0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU, 0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U,
0x7C26U, 0x6C07U, 0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U,
0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U, 0x9FF8U,
0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U, 0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U
};
uint16_t pcan_crc16_update(uint16_t crc, uint8_t byte)
{
return (uint16_t)((crc << 8) ^ s_table[(uint8_t)((crc >> 8) ^ byte)]);
}
#else /* побитовый расчёт: 0 байт таблиц, ~8 итераций на байт */
uint16_t pcan_crc16_update(uint16_t crc, uint8_t byte)
{
crc ^= (uint16_t)((uint16_t)byte << 8);
for (uint8_t bit = 0U; bit < 8U; bit++) {
crc = (crc & 0x8000U) ? (uint16_t)((crc << 1) ^ 0x1021U)
: (uint16_t)(crc << 1);
}
return crc;
}
#endif
uint16_t pcan_crc16(const uint8_t *data, size_t len)
{
uint16_t crc = PCAN_CRC_INIT;
for (size_t i = 0U; i < len; i++) {
crc = pcan_crc16_update(crc, data[i]);
}
return crc;
}

View File

@@ -0,0 +1,141 @@
#include "pcan_frame.h"
#include "pcan_crc.h"
size_t pcan_frame_encode(const pcan_frame_t *frame, uint8_t *out, size_t out_size)
{
if ((frame == NULL) || (out == NULL) || (out_size < PCAN_FRAME_MAX)) {
return 0U;
}
uint8_t dlc = (frame->dlc > PCAN_DATA_MAX) ? (uint8_t)PCAN_DATA_MAX : frame->dlc;
size_t n = 0U;
out[n++] = PCAN_SOF0;
out[n++] = PCAN_SOF1;
size_t body = n;
out[n++] = (uint8_t)(PCAN_LEN_MIN + dlc);
out[n++] = frame->seq;
out[n++] = frame->flags;
out[n++] = (uint8_t)(frame->id & 0xFFU);
out[n++] = (uint8_t)((frame->id >> 8) & 0xFFU);
out[n++] = (uint8_t)((frame->id >> 16) & 0xFFU);
out[n++] = (uint8_t)((frame->id >> 24) & 0x1FU);
for (uint8_t i = 0U; i < dlc; i++) {
out[n++] = frame->data[i];
}
uint16_t crc = pcan_crc16(&out[body], n - body);
out[n++] = (uint8_t)(crc & 0xFFU);
out[n++] = (uint8_t)(crc >> 8);
return n;
}
void pcan_parser_init(pcan_parser_t *p)
{
p->state = PCAN_ST_SOF0;
p->len = 0U;
p->idx = 0U;
p->crc_rx = 0U;
p->stats.frames = 0U;
p->stats.crc_errors = 0U;
p->stats.bad_len = 0U;
p->stats.stray_bytes = 0U;
}
bool pcan_parser_push(pcan_parser_t *p, uint8_t byte, pcan_frame_t *out)
{
switch (p->state) {
case PCAN_ST_SOF0:
if (byte == PCAN_SOF0) {
p->state = PCAN_ST_SOF1;
} else {
p->stats.stray_bytes++;
}
break;
case PCAN_ST_SOF1:
if (byte == PCAN_SOF1) {
p->state = PCAN_ST_LEN;
} else if (byte == PCAN_SOF0) {
/* 0xAA 0xAA 0x55 - тоже валидное начало, остаёмся здесь. */
p->stats.stray_bytes++;
} else {
p->stats.stray_bytes += 2U;
p->state = PCAN_ST_SOF0;
}
break;
case PCAN_ST_LEN:
if ((byte < PCAN_LEN_MIN) || (byte > PCAN_LEN_MAX)) {
p->stats.bad_len++;
/* Байт мог оказаться началом настоящего кадра. */
p->state = (byte == PCAN_SOF0) ? PCAN_ST_SOF1 : PCAN_ST_SOF0;
break;
}
p->len = byte;
p->body[0] = byte;
p->idx = 1U;
p->state = PCAN_ST_BODY;
break;
case PCAN_ST_BODY:
p->body[p->idx++] = byte;
if (p->idx >= (uint8_t)(p->len + 1U)) {
p->state = PCAN_ST_CRC_L;
}
break;
case PCAN_ST_CRC_L:
p->crc_rx = byte;
p->state = PCAN_ST_CRC_H;
break;
case PCAN_ST_CRC_H: {
p->crc_rx |= (uint16_t)((uint16_t)byte << 8);
p->state = PCAN_ST_SOF0;
if (p->crc_rx != pcan_crc16(p->body, (size_t)p->len + 1U)) {
p->stats.crc_errors++;
return false;
}
out->seq = p->body[1];
out->flags = p->body[2];
out->id = ((uint32_t)p->body[3])
| ((uint32_t)p->body[4] << 8)
| ((uint32_t)p->body[5] << 16)
| ((uint32_t)p->body[6] << 24);
out->id &= 0x1FFFFFFFUL;
out->dlc = (uint8_t)(p->len - PCAN_LEN_MIN);
for (uint8_t i = 0U; i < out->dlc; i++) {
out->data[i] = p->body[7U + i];
}
for (uint8_t i = out->dlc; i < PCAN_DATA_MAX; i++) {
out->data[i] = 0U;
}
p->stats.frames++;
return true;
}
default:
p->state = PCAN_ST_SOF0;
break;
}
return false;
}
size_t pcan_parser_feed(pcan_parser_t *p, const uint8_t *data, size_t len,
pcan_frame_cb_t cb, void *user)
{
pcan_frame_t frame;
size_t count = 0U;
for (size_t i = 0U; i < len; i++) {
if (pcan_parser_push(p, data[i], &frame)) {
count++;
if (cb != NULL) {
cb(&frame, user);
}
}
}
return count;
}

View File

@@ -0,0 +1,200 @@
#include "pcan_gas.h"
#include "pcan_id.h"
bool pcan_gas_map_validate(const pcan_gas_map_t *map)
{
if ((map == NULL) || ((map->count != 0U) && (map->regions == NULL))) {
return false;
}
if (map->count > PCAN_GAS_MAX_REGIONS) {
return false;
}
uint32_t prev_end = 0U;
for (uint16_t i = 0U; i < map->count; i++) {
const pcan_gas_region_t *r = &map->regions[i];
if (r->count == 0U) {
return false;
}
/* Регион не должен уходить за 0xFFFF. */
if (((uint32_t)r->base + r->count) > 0x10000UL) {
return false;
}
if ((r->storage == NULL) && (r->read == NULL) && (r->write == NULL)) {
return false;
}
if ((i != 0U) && ((uint32_t)r->base < prev_end)) {
return false; /* не отсортировано либо перекрытие */
}
prev_end = (uint32_t)r->base + r->count;
}
return true;
}
const pcan_gas_region_t *pcan_gas_find(const pcan_gas_map_t *map, uint16_t addr)
{
if ((map == NULL) || (map->regions == NULL)) {
return NULL;
}
/* Регионы отсортированы, поэтому двоичный поиск. */
uint16_t lo = 0U;
uint16_t hi = map->count;
while (lo < hi) {
uint16_t mid = (uint16_t)(lo + ((hi - lo) / 2U));
const pcan_gas_region_t *r = &map->regions[mid];
if (addr < r->base) {
hi = mid;
} else if ((uint32_t)addr >= ((uint32_t)r->base + r->count)) {
lo = (uint16_t)(mid + 1U);
} else {
return r;
}
}
return NULL;
}
pcan_gas_status_t pcan_gas_read(const pcan_gas_map_t *map, uint16_t addr,
uint16_t *value)
{
const pcan_gas_region_t *r = pcan_gas_find(map, addr);
if (r == NULL) {
return PCAN_GAS_NO_REG;
}
if ((r->flags & PCAN_GAS_WRONLY) != 0U) {
return PCAN_GAS_WRITE_ONLY;
}
uint16_t offset = (uint16_t)(addr - r->base);
if (r->read != NULL) {
return r->read(r, offset, value);
}
if (r->storage != NULL) {
*value = r->storage[offset];
return PCAN_GAS_OK;
}
return PCAN_GAS_WRITE_ONLY;
}
pcan_gas_status_t pcan_gas_write(const pcan_gas_map_t *map, uint16_t addr,
uint16_t value)
{
const pcan_gas_region_t *r = pcan_gas_find(map, addr);
if (r == NULL) {
return PCAN_GAS_NO_REG;
}
if ((r->flags & PCAN_GAS_RDONLY) != 0U) {
return PCAN_GAS_READ_ONLY;
}
uint16_t offset = (uint16_t)(addr - r->base);
if (r->write != NULL) {
return r->write(r, offset, value);
}
if (r->storage != NULL) {
r->storage[offset] = value;
return PCAN_GAS_OK;
}
return PCAN_GAS_READ_ONLY;
}
uint16_t pcan_gas_read_block(const pcan_gas_map_t *map, uint16_t addr,
uint16_t *out, uint16_t max)
{
uint16_t n = 0U;
while (n < max) {
/* Останавливаемся на 0xFFFF, чтобы адрес не завернулся в 0. */
if (pcan_gas_read(map, (uint16_t)(addr + n), &out[n]) != PCAN_GAS_OK) {
break;
}
n++;
if (((uint32_t)addr + n) > 0xFFFFUL) {
break;
}
}
return n;
}
uint16_t pcan_gas_write_block(const pcan_gas_map_t *map, uint16_t addr,
const uint16_t *in, uint16_t count)
{
uint16_t n = 0U;
while (n < count) {
if (pcan_gas_write(map, (uint16_t)(addr + n), in[n]) != PCAN_GAS_OK) {
break;
}
n++;
if (((uint32_t)addr + n) > 0xFFFFUL) {
break;
}
}
return n;
}
uint16_t pcan_gas_frame_to_regs(const pcan_frame_t *frame, uint16_t *out,
uint16_t max)
{
uint16_t n = 0U;
for (uint8_t i = 0U; (uint8_t)(i + 1U) < frame->dlc; i = (uint8_t)(i + 2U)) {
if (n >= max) {
break;
}
out[n++] = (uint16_t)((uint16_t)frame->data[i] |
((uint16_t)frame->data[i + 1U] << 8));
}
return n;
}
void pcan_gas_regs_to_frame(pcan_frame_t *frame, uint32_t base_id,
uint16_t addr, const uint16_t *regs, uint16_t count)
{
if (count > PCAN_GAS_REGS_PER_FRAME) {
count = PCAN_GAS_REGS_PER_FRAME;
}
pcan_id_t id;
pcan_id_unpack(base_id, &id);
id.msg_type = PCAN_MSG_GAS;
id.msg_body = addr;
frame->id = pcan_id_pack(&id);
frame->flags = PCAN_FLAG_IDE;
frame->seq = 0U;
frame->dlc = (uint8_t)(count * 2U);
for (uint16_t i = 0U; i < count; i++) {
frame->data[i * 2U] = (uint8_t)(regs[i] & 0xFFU);
frame->data[(i * 2U) + 1U] = (uint8_t)(regs[i] >> 8);
}
for (uint8_t i = frame->dlc; i < PCAN_DATA_MAX; i++) {
frame->data[i] = 0U;
}
}
bool pcan_gas_handle(const pcan_gas_map_t *map, const pcan_frame_t *req,
pcan_frame_t *rsp)
{
pcan_id_t id;
pcan_id_unpack(req->id, &id);
if (id.msg_type != (uint8_t)PCAN_MSG_GAS) {
return false;
}
if (req->dlc == 0U) {
uint16_t regs[PCAN_GAS_REGS_PER_FRAME];
uint16_t n = pcan_gas_read_block(map, id.msg_body, regs,
PCAN_GAS_REGS_PER_FRAME);
if (n == 0U) {
return false; /* адреса нет в карте - отвечать нечем */
}
pcan_gas_regs_to_frame(rsp, req->id, id.msg_body, regs, n);
pcan_id_unpack(rsp->id, &id);
id.route = PCAN_ROUTE_FROM_DEVICE;
rsp->id = pcan_id_pack(&id);
rsp->flags = (uint8_t)(req->flags & PCAN_FLAG_IDE);
return true;
}
uint16_t regs[PCAN_GAS_REGS_PER_FRAME];
uint16_t n = pcan_gas_frame_to_regs(req, regs, PCAN_GAS_REGS_PER_FRAME);
(void)pcan_gas_write_block(map, id.msg_body, regs, n);
return false;
}

View File

@@ -0,0 +1,22 @@
#include "pcan_id.h"
uint32_t pcan_id_pack(const pcan_id_t *id)
{
return ((uint32_t)(id->priority & 0x1U) << 28)
| ((uint32_t)(id->route & 0x1U) << 27)
| ((uint32_t)(id->device_type & 0x7U) << 24)
| ((uint32_t)(id->device_id & 0xFU) << 20)
| ((uint32_t)(id->msg_type & 0xFU) << 16)
| ((uint32_t)id->msg_body);
}
void pcan_id_unpack(uint32_t raw, pcan_id_t *id)
{
raw &= PCAN_ID_MASK;
id->msg_body = (uint16_t)(raw & 0xFFFFU);
id->msg_type = (uint8_t)((raw >> 16) & 0xFU);
id->device_id = (uint8_t)((raw >> 20) & 0xFU);
id->device_type = (uint8_t)((raw >> 24) & 0x7U);
id->route = (uint8_t)((raw >> 27) & 0x1U);
id->priority = (uint8_t)((raw >> 28) & 0x1U);
}

View File

@@ -0,0 +1,59 @@
#include "pcan_link.h"
bool pcan_link_init(pcan_link_t *link, const pcan_io_t *io,
pcan_frame_cb_t on_frame, void *user)
{
if ((link == NULL) || (io == NULL) || (io->write == NULL)) {
return false;
}
link->io = *io;
link->on_frame = on_frame;
link->user = user;
link->tx_seq = 0U;
link->tx.tx_frames = 0U;
link->tx.tx_dropped = 0U;
pcan_parser_init(&link->parser);
return true;
}
bool pcan_link_can_send(const pcan_link_t *link)
{
if (link->io.tx_space == NULL) {
return true;
}
return link->io.tx_space(link->io.ctx) >= PCAN_FRAME_MAX;
}
bool pcan_link_send_raw(pcan_link_t *link, const pcan_frame_t *frame)
{
uint8_t raw[PCAN_FRAME_MAX];
size_t n = pcan_frame_encode(frame, raw, sizeof raw);
if (n == 0U) {
link->tx.tx_dropped++;
return false;
}
if (link->io.write(link->io.ctx, raw, n) != n) {
link->tx.tx_dropped++;
return false;
}
link->tx.tx_frames++;
return true;
}
bool pcan_link_send(pcan_link_t *link, pcan_frame_t *frame)
{
frame->seq = link->tx_seq;
if (!pcan_link_send_raw(link, frame)) {
return false;
}
/* SEQ двигаем только на успешно отданном кадре: иначе приёмник
засчитает потерю там, где кадра просто не было. */
link->tx_seq++;
return true;
}
size_t pcan_link_feed(pcan_link_t *link, const uint8_t *data, size_t len)
{
return pcan_parser_feed(&link->parser, data, len, link->on_frame, link->user);
}

View File

@@ -0,0 +1,88 @@
#include "pcan_ring.h"
bool pcan_ring_init(pcan_ring_t *rb, uint8_t *storage, uint16_t size)
{
if ((rb == NULL) || (storage == NULL) || (size < 2U) ||
((size & (uint16_t)(size - 1U)) != 0U)) {
return false;
}
rb->buf = storage;
rb->size = size;
rb->head = 0U;
rb->tail = 0U;
rb->dropped = 0U;
return true;
}
uint16_t pcan_ring_count(const pcan_ring_t *rb)
{
return (uint16_t)((rb->head - rb->tail) & (uint16_t)(rb->size - 1U));
}
uint16_t pcan_ring_free(const pcan_ring_t *rb)
{
return (uint16_t)(rb->size - 1U - pcan_ring_count(rb));
}
bool pcan_ring_put(pcan_ring_t *rb, uint8_t byte)
{
uint16_t next = (uint16_t)((rb->head + 1U) & (uint16_t)(rb->size - 1U));
if (next == rb->tail) {
rb->dropped++;
return false;
}
rb->buf[rb->head] = byte;
PCAN_BARRIER();
rb->head = next;
return true;
}
bool pcan_ring_get(pcan_ring_t *rb, uint8_t *byte)
{
if (rb->head == rb->tail) {
return false;
}
*byte = rb->buf[rb->tail];
PCAN_BARRIER();
rb->tail = (uint16_t)((rb->tail + 1U) & (uint16_t)(rb->size - 1U));
return true;
}
bool pcan_ring_write(pcan_ring_t *rb, const uint8_t *data, uint16_t len)
{
if (pcan_ring_free(rb) < len) {
rb->dropped += len;
return false;
}
uint16_t mask = (uint16_t)(rb->size - 1U);
uint16_t head = rb->head;
for (uint16_t i = 0U; i < len; i++) {
rb->buf[head] = data[i];
head = (uint16_t)((head + 1U) & mask);
}
/* Публикуем весь блок одной записью: читатель никогда не увидит
кадр наполовину. */
PCAN_BARRIER();
rb->head = head;
return true;
}
uint16_t pcan_ring_linear(const pcan_ring_t *rb, const uint8_t **ptr)
{
uint16_t head = rb->head;
uint16_t tail = rb->tail;
if (head == tail) {
*ptr = NULL;
return 0U;
}
*ptr = &rb->buf[tail];
return (head > tail) ? (uint16_t)(head - tail)
: (uint16_t)(rb->size - tail);
}
void pcan_ring_consume(pcan_ring_t *rb, uint16_t len)
{
rb->tail = (uint16_t)((rb->tail + len) & (uint16_t)(rb->size - 1U));
}