Files
templates/c/set-protocol/src/pcan_gas.c
T
Andrey 876b58517c feat(set-protocol): Раздели GAS и добавь пакетный сбор регистров
Сохрани адресацию отдельных регистров GAS 0x3 и выдели слотам тип 0x4 с маской Word0. Добавь единое C-ядро GAS_WATCH2, тонкие Python/JNI-порты и проверки общих эталонных байтов.

Совместимость: тип 0x4 больше не обслуживает Modbus Coil. Потребителям PCAN_MSG_MODBUS_COIL и ProtoCanMsgToModbusCoil нужно перейти на GAS_SLOT и отдельный API слотов; регистровый GAS остаётся на 0x3.
2026-10-23 14:14:48 +03:00

209 lines
7.6 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.
/*
* Общее адресное пространство ProtoCAN, разбитое на регионы с обработчиками чтения и записи.
* Поиск региона и проверка доступа предшествуют обращению к приложению; номер регистра не
* является адресом памяти процессора.
*/
#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; /* Счётчик увеличивается только после проверки длины и наличия обоих октетов слова. */
if (frame == NULL || out == NULL || frame->dlc > PCAN_DATA_MAX || (frame->dlc & 1U) != 0U) return 0U; /* Отвергаем нечётные и превышающие classic CAN длины до доступа к данным. */
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; /* Адрес регистра и направление проверяются до вызова provider, чтобы ответ не стал записью. */
if (map == NULL || req == NULL || (req->id & ~PCAN_ID_MASK) != 0U || (req->flags & PCAN_FLAG_IDE) == 0U || (req->flags & (PCAN_FLAG_RTR | PCAN_FLAG_ERR)) != 0U || req->dlc > PCAN_DATA_MAX || (req->dlc & 1U) != 0U) return false; /* Принимаем только extended data frame с целыми 16-битными регистрами. */
pcan_id_unpack(req->id, &id); /* Разбираем проверенный 29-битный идентификатор единственным общим кодеком. */
if (id.msg_type != (uint8_t)PCAN_MSG_GAS || id.route != PCAN_ROUTE_FROM_PM || (uint32_t)id.msg_body + req->dlc / 2U > 0x10000UL || (req->dlc == 0U && rsp == NULL)) { /* Тип 0x4, ответы прибора и блоки за адресом 65535 не изменяют карту; чтению нужен выходной кадр. */
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;
}