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:
129
c/protocan-transport/include/pcan_gas.h
Normal file
129
c/protocan-transport/include/pcan_gas.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @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 */
|
||||
Reference in New Issue
Block a user