/* Дополнительная карта GAS 167/v1 только для новой EEPROM M95M02. Русские пояснения сохранены в UTF-8. */ /* Подключает границы GAS и общий выбор EEPROM; при профиле 0 интерфейс GAS исключён. */ #include "gas167.h" /* Весь дополнительный код включается только для физически установленной M95M02; вариант AT25256B не растёт. */ #if EEPROM_M95M02 /* Подключает тип платы dsk_LOAD и псевдонимы регистров, чтобы сохранять правила обычного Modbus. */ #include "package.h" /* Использует существующие технологические регистры без копии массива и без добавления RAM. */ extern int modbus[]; /* Тип платы нужен для ограничений записи статусов и тайм-аута нагрузки. */ extern int Desk; /* Вызывает штатную регистрацию изменения команды; нового регистратора GAS не создаёт. */ extern void ump_log_rs(void); /* Постоянный дескриптор из восьми слов описывает карту 167/v1; область 0x5F00..0x5F07 доступна только для чтения. */ static const uint16_t gas167_info[GAS167_INFO_WORDS] = { /* Слова 0..3: сигнатура GA/S1, версия профиля и номер проекта 167. */ 0x4741U, 0x5331U, GAS167_PROFILE_VERSION, 167U, /* Слова 4..7: база технологических регистров, число слов, маска RTU/CAN/FC06 и признак EEPROM M95M02 = 1; последний элемент описывает аппаратный профиль. */ GAS167_REG_BASE, GAS167_REG_COUNT, GAS167_CAPABILITIES, 1U /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ }; /* Проверяет одиночный адрес в двух непересекающихся диапазонах; весь блок дополнительно проверяет gas167_prepare. */ int gas167_contains(uint16_t address) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Беззнаковое 16-битное смещение проверяет 0x5000..0x507F: адрес ниже начала даёт большое смещение. */ return (uint16_t)(address - GAS167_REG_BASE) < GAS167_REG_COUNT || /* Аналогично проверяет 0x5F00..0x5F07; промежуток между диапазонами остаётся недоступным. */ (uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Записывает технологическое слово с теми же ограничениями, что и штатная функция Modbus FC06. */ static unsigned gas167_write(uint16_t offset, uint16_t value) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Правила записи совпадают со штатным Modbus: статусы защиты нельзя * перезаписать, а недопустимый тайм-аут не позволяет обойти защиту. */ /* Состояние защиты 0x7C остаётся только для чтения; GAS-псевдоним лимита 0x507D допускает запись. */ if (Desk == dsk_LOAD && offset == 0x7CU) return 2; /* Отклоняет нулевой лимит кодом Modbus 3 одинаково для RTU и служебного CAN; uint16_t ограничивает верхнюю границу 65535. */ if (Desk == dsk_LOAD && offset == 0x7DU && value == 0U) return 3; /* Для сохраняемого тайм-аута CAN нагрузки применяется проверка диапазона независимо от транспорта. */ if (Desk == dsk_LOAD && offset == 0x52U && /* Значение 0 или больше 60000 мс недопустимо и возвращает код 3; защиту нельзя отключить такой записью. */ (value == 0U || value > 60000U)) return 3; /* int C28x занимает одно слово регистра; чтение возвращает исходный uint16_t. */ /* Сохраняет 16-битный рисунок регистра: знаковый int C28x занимает ровно одно слово, чтение вернёт uint16_t. */ modbus[offset] = (int)value; /* Запись слова команд 127 вызывает существующий хук регистратора, как обычный Modbus. */ if (offset == 127U) ump_log_rs(); /* Возвращает успех записи; исходное правило ошибок Modbus сохраняется. */ return 0; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Готовит FC03/FC06: value — число слов чтения или значение записи, address — глобальный словный адрес. */ unsigned gas167_prepare(unsigned function, uint16_t address, uint16_t value, /* can включает парное чтение CAN; reply вмещает 124 слова, error получает код Modbus; вызывающий код удерживает краткую блокировку IRQ. */ int can, uint16_t *reply, unsigned *error) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Смещение адреса внутри выбранной области; uint16_t задаёт правила вычисления на границах 0xFFFF. */ uint16_t offset; /* Индекс копирования и число доступных слов в выбранной области — 128 или 8. */ unsigned i, available; /* Признак обращения к постоянному дескриптору вместо технологических регистров. */ int info; /* Не разыменовывает отсутствующие выходные буферы; ответ сформировать нельзя. */ if (!reply || !error) return 0; /* Сначала считает запрос успешным; последующие проверки задают конкретный код ошибки. */ *error = 0; /* Выбирает дескриптор только для его восьми допустимых стартовых адресов. */ info = (uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS; /* Переводит глобальный адрес в индекс выбранной области без изменения штатных адресов Modbus. */ offset = (uint16_t)(address - (info ? GAS167_INFO_BASE : GAS167_REG_BASE)); /* Определяет границу выбранной области, чтобы следующий запрос не пересёк её конец. */ available = info ? GAS167_INFO_WORDS : GAS167_REG_COUNT; /* GAS поддерживает только чтение FC03 и одиночную запись FC06; остальные функции возвращают исключение 1. */ if (function != 3U && function != 6U) *error = 1; /* Отклоняет адрес вне выбранной области до любого чтения массива; исключение 2. */ else if (offset >= available) *error = 2; /* Для FC06 длина не проверяется: value содержит записываемое слово. */ else if (function == 6U) { /* Дескриптор запрещён для записи; технологическая область проходит проверку прав и тайм-аута. */ *error = info ? 2U : gas167_write(offset, value); /* Чтение RTU допускает 1..124 слова; служебный CAN передаёт пары и поэтому требует чётную ненулевую длину. */ } else if (!value || value > GAS167_MAX_WORDS || (can && (value & 1U))) { /* Неверная длина чтения получает исключение Modbus 3 без частичного ответа. */ *error = 3; /* Проверяет остаток области через вычитание: запрос не выходит за границу, не оборачивается и не читает скрытый modbus[128]. */ } else if (value > available - offset) { /* Пересечение конца области получает исключение Modbus 2; данные ещё не копировались. */ *error = 2; /* Только полностью проверенный запрос попадает в ветвь копирования снимка. */ } else { /* Проверяется весь блок до чтения: нельзя пересечь конец области, * перейти через 0xFFFF или получить скрытое слово параметров. */ /* Копирует точно запрошенное число слов, не выполняя EEPROM I/O и не выделяя память. */ for (i = 0; i < value; ++i) /* Берёт постоянное слово дескриптора либо сырой 16-битный регистр; согласованность обеспечена блокировкой вызывающего транспорта. */ reply[i] = info ? gas167_info[offset+i] : (uint16_t)modbus[offset+i]; /* Для успешного чтения возвращает фактическое число слов; транспорт сам строит RTU или CAN-кадры. */ return value; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Для записи либо ошибки сохраняет исходный адрес, используемый транспортом при формировании ответа. */ reply[0] = address; /* Сохраняет исходное значение или длину рядом с адресом; массив ответа содержит два слова. */ reply[1] = value; /* Возвращает двухсловный результат записи/ошибки; конкретный код ошибки остаётся в error. */ return 2; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Завершает код GAS, доступный только при EEPROM_M95M02=1. */ #endif