/** * @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 #include #include #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 */