Files
templates/c/protocan-transport/include/pcan_gas.h
Andrey Kruchinkin 3dc636e012 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 проходят.
2026-08-23 01:15:35 +03:00

130 lines
5.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file pcan_gas.h
* @brief Общее адресное пространство (General Address Space).
*
* Плоское пространство 16-битных регистров с адресом 0x0000..0xFFFF,
* собранное из регионов. Регион либо ссылается на массив в памяти,
* либо обслуживается колбэками - так в карту попадают и обычные
* переменные, и вычисляемые значения, и регистры периферии.
*
* Транспорт ProtoCAN (MsgType = 0b0011) кладёт в MsgBody адрес первого
* регистра, а в данные - до 4 регистров подряд, младшим байтом вперёд.
* Это ровно то, что делает PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()
* в SETCAN/Src/protocan.c, поэтому обмен совместим с существующими
* устройствами.
*
* Запрос на чтение кодируется кадром GAS с DLC = 0: в исходном коде
* SETCAN такой кодировки нет, это расширение - см. docs/GAS.md.
*/
#ifndef PCAN_GAS_H
#define PCAN_GAS_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "pcan_config.h"
#include "pcan_frame.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Сколько регистров помещается в один кадр (8 байт / 2). */
#define PCAN_GAS_REGS_PER_FRAME 4U
typedef enum {
PCAN_GAS_OK = 0,
PCAN_GAS_NO_REG, /**< адрес не покрыт ни одним регионом */
PCAN_GAS_READ_ONLY, /**< запись в регион только для чтения */
PCAN_GAS_WRITE_ONLY,
PCAN_GAS_REJECTED /**< колбэк отверг значение */
} pcan_gas_status_t;
/** Регион только для чтения. */
#define PCAN_GAS_RDONLY 0x01U
/** Регион только для записи. */
#define PCAN_GAS_WRONLY 0x02U
struct pcan_gas_region;
typedef pcan_gas_status_t (*pcan_gas_read_fn)(const struct pcan_gas_region *region,
uint16_t offset, uint16_t *value);
typedef pcan_gas_status_t (*pcan_gas_write_fn)(const struct pcan_gas_region *region,
uint16_t offset, uint16_t value);
/**
* @brief Непрерывный участок адресного пространства.
*
* Если storage != NULL, чтение и запись идут прямо в массив.
* Иначе вызываются read/write.
*/
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;
void *user;
const char *name; /**< для отладки, может быть NULL */
} pcan_gas_region_t;
/** Карта: набор регионов. Регионы не должны перекрываться. */
typedef struct {
const pcan_gas_region_t *regions;
uint16_t count;
} pcan_gas_map_t;
/** @return true, если регионы отсортированы и не перекрываются. */
bool pcan_gas_map_validate(const pcan_gas_map_t *map);
const pcan_gas_region_t *pcan_gas_find(const pcan_gas_map_t *map, uint16_t addr);
pcan_gas_status_t pcan_gas_read(const pcan_gas_map_t *map, uint16_t addr,
uint16_t *value);
pcan_gas_status_t pcan_gas_write(const pcan_gas_map_t *map, uint16_t addr,
uint16_t value);
/**
* @brief Читает подряд идущие регистры.
* @return сколько регистров удалось прочитать, начиная с addr.
*
* Чтение прекращается на первом адресе, которого нет в карте,
* поэтому вызывающий всегда получает непрерывный блок.
*/
uint16_t pcan_gas_read_block(const pcan_gas_map_t *map, uint16_t addr,
uint16_t *out, uint16_t max);
uint16_t pcan_gas_write_block(const pcan_gas_map_t *map, uint16_t addr,
const uint16_t *in, uint16_t count);
/* --- Мост между картой и кадрами ProtoCAN ---------------------------------- */
/** Извлекает регистры из данных кадра GAS (младший байт первым). */
uint16_t pcan_gas_frame_to_regs(const pcan_frame_t *frame, uint16_t *out,
uint16_t max);
/** Заполняет кадр GAS: адрес в MsgBody, до 4 регистров в данных. */
void pcan_gas_regs_to_frame(pcan_frame_t *frame, uint32_t base_id,
uint16_t addr, const uint16_t *regs, uint16_t count);
/**
* @brief Обрабатывает входящий кадр GAS.
*
* DLC = 0 - запрос на чтение: в rsp кладётся до 4 регистров с адреса
* MsgBody, Route переключается на FROM_DEVICE.
* DLC > 0 - запись: регистры пишутся в карту, ответ не формируется.
*
* @param[out] rsp кадр ответа; заполняется, только если функция вернула true.
* @return true, если ответ нужно передать.
*/
bool pcan_gas_handle(const pcan_gas_map_t *map, const pcan_frame_t *req,
pcan_frame_t *rsp);
#ifdef __cplusplus
}
#endif
#endif /* PCAN_GAS_H */