Files
templates/c/set-protocol/ports/stm32-bxcan
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
..

ProtoCAN: порт STM32 bxCAN (STM32 HAL)

stm32-bxcan — порт прикладного ProtoCAN для STM32 HAL, перенесённый из SETCAN. Модуль принимает кадры, управляет HAL CAN/RTC/TIM и вызывает обработчики приложения. Формат 29-битного идентификатора и его переносимая реализация принадлежат общему ядру c/set-protocol в этом же репозитории templates.

Порт использует classic bxCAN (HAL_CAN_*), RTC и TIM. Для FDCAN на STM32G4/H7 требуется другой аппаратный порт. Здесь нет .ioc, startup-файлов, linker script или настроек тактирования: их предоставляет проект STM32CubeIDE.

Схема подключения: приложение → stm32-bxcan → STM32 HAL → bxCAN/RTC/TIM. Для упаковки и разбора CAN ID порт вызывает общее ядро pcan_id. Старый API PROTOCAN_* и имена protocan.c/.h сохранены для существующих прошивок. Отдельный репозиторий и вложенный сабмодуль SETCAN больше не нужны.

Исходная версия: SETCAN c8eec559785ab809469483dc58d3976ec9db340a. История SETCAN включена в историю templates отдельным родителем коммита переноса; прежние пути можно посмотреть через git show c8eec55:Inc/protocan.h и git log c8eec55.

Состав

c/set-protocol/
├── include/pcan_id.h
├── src/pcan_id.c
└── ports/stm32-bxcan/
    ├── protocan.h   # типы протокола, настройки устройства и публичный API
    ├── protocan.c   # приём, буферизация, разбор и отправка CAN-кадров
    └── README.md

Модуль зависит от файлов main.h и can.h, сгенерированных STM32CubeMX, а также от HAL-драйверов CAN, RTC и TIM. В сборку добавляются c/set-protocol/ports/stm32-bxcan/protocan.c, c/set-protocol/src/pcan_id.c и c/set-protocol/src/pcan_gas_slot.c. В include paths добавляются c/set-protocol/ports/stm32-bxcan, c/set-protocol/include и каталог CubeMX с main.h/can.h. Если прошивка уже собирает общее ядро SETProtocol, второй раз добавлять pcan_id.c не нужно. HAL-порт не входит в host-сборку CMake общего ядра.

Файл Зависимости
protocan.h main.h, can.h, общий pcan_id.h
protocan.c protocan.h, STM32 HAL CAN/RTC/TIM, CMSIS
../../src/pcan_id.c переносимое ядро C99, ../../include

Документация протокола

Каноническое описание теперь находится рядом с переносимой реализацией:

Файлы в doc/setcan/protocan сохранены как исторический снимок SETCAN и больше не являются источником истины.

Формат расширенного CAN ID

Протокол размещает служебные поля в 29-битном Extended ID:

Биты Поле Размер Назначение
28 Priority 1 0 — критический, 1 — стандартный приоритет
27 Route 1 0 — от управляющего модуля, 1 — от устройства
26..24 DeviceType 3 тип устройства, 0..7
23..20 DeviceID 4 номер устройства, 0..15
19..16 MsgType 4 тип сообщения
15..0 MsgBody 16 тип команды, адрес, ID датчика или код ошибки

Типы сообщений:

Значение Тип
0x0 широковещательное (BROADCAST)
0x1 дискретное (DISCRETE)
0x2 аналоговое (ANALOG)
0x3 общее адресное пространство
0x4 GAS SLOT: Word0-маска и три значения
0x5 Modbus Discrete
0x6 Modbus Holding
0x7 Modbus Input
0x8 ошибка
0x9 управление загрузчиком (BOOT_CONTROL, зарезервировано)
0xA данные прошивки, слот A (BOOT_DATA_A, зарезервировано)
0xB данные прошивки, слот B (BOOT_DATA_B, зарезервировано)
0xC состояние загрузчика (BOOT_STATUS, зарезервировано)
0xD обнаружение загрузчиков (BOOT_DISCOVERY, зарезервировано)
0xE настройка привязки датчика (SETTINGS)
0xF пульс присутствия устройства

Разметка MsgBody зависит от типа сообщения:

  • broadcast: младшие 4 бита — дополнительное поле, старшие 12 бит — команда;
  • discrete: младшие 12 бит — данные/адрес, старшие 4 бита — подтип;
  • analog: младшие 12 бит — ID датчика, старшие 4 бита — тип величины;
  • Modbus: младшие 4 бита — количество регистров, старшие 12 бит — начальный адрес;
  • SETTINGS: старший байт — номер сборки Z, младший байт — позиция Y;
  • error: младший байт — код ошибки, старший байт — дополнительная информация.

Для регистров uint16_t в CAN payload используется порядок байтов little-endian: сначала младший байт, затем старший.

Пара DeviceType/DeviceID образует уникальный адрес прибора. Три бита DeviceType задают 8 типов, четыре бита DeviceID — 16 экземпляров каждого типа; всего на одной шине можно адресовать до 128 приборов. Загрузочные кадры так же адресуются этой парой и не требуют изменения 29-битного CAN ID.

Разметка ID реализована C-битовыми полями. Она соответствует используемому STM32 GCC ABI, но не является переносимой между произвольными компиляторами без проверки фактического расположения битов.

Подключение к STM32-проекту

1. Добавить файлы

Скопировать или подключить к сборке:

  • c/set-protocol/ports/stm32-bxcan/protocan.h;
  • c/set-protocol/ports/stm32-bxcan/protocan.c;
  • c/set-protocol/src/pcan_id.c (если ещё не собирается в составе ядра).

Каталоги c/set-protocol/ports/stm32-bxcan и c/set-protocol/include добавить в include paths компилятора.

2. Настроить устройство

В protocan.h для прибора привязки DS18B20 заданы выбранные значения:

#define CURRENT_TYPE_DEVICE 0b111  /* DeviceType = 0x7 */
#define CURRENT_ID_DEVICE   0b1111 /* DeviceID   = 0xF */

При необходимости изменить размер программного RX-буфера:

#define PROTOCAN_RX_BUFFER_SIZE 128

Полезная ёмкость кольцевого буфера на один элемент меньше заданного размера, то есть при значении 128 в нём помещается 127 ожидающих кадров.

3. Настроить CubeMX

В проекте должны быть инициализированы:

  • CAN с поддержкой Extended ID;
  • RTC;
  • базовый таймер, задающий период отправки пульса.

Модуль использует CAN FIFO0 и фильтры с номерами 0, 1, 2. Адресованный фильтр принимает Route=0 (запрос от ПМ) для текущих DeviceType/DeviceID; отдельные фильтры принимают broadcast и pulse. В PROTOCAN_CONFIG_FILTER() также жёстко задано SlaveStartFilterBank = 14. Если приложение уже использует фильтры, номера и границу банков нужно согласовать.

4. Инициализировать и запустить периферию

Пример для main.c:

#include "protocan.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_CAN_Init();
    MX_RTC_Init();
    MX_TIM2_Init();

    PROTOCAN_INIT_StatusTypeDef init_status =
        PROTOCAN_INIT(&hcan, &hrtc, &htim2);

    if (init_status != PROTOCAN_INIT_OK) {
        Error_Handler();
    }

    /* PROTOCAN_INIT() этого не делает. */
    if (HAL_CAN_Start(&hcan) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_CAN_ActivateNotification(
            &hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_TIM_Base_Start_IT(&htim2) != HAL_OK) {
        Error_Handler();
    }

    while (1) {
        (void)PROTOCAN_ProcessAllRxMsgs();
    }
}

PROTOCAN_INIT() требует ненулевые указатели на CAN, RTC и TIM, настраивает CAN-фильтры и включает отправку пульса. Функцию следует вызвать после MX_CAN_Init(), MX_RTC_Init() и MX_TIMx_Init(), но до запуска CAN.

5. Подключить callback’и HAL

Если в HAL включены USE_HAL_CAN_REGISTER_CALLBACKS == 1 и USE_HAL_TIM_REGISTER_CALLBACKS == 1, PROTOCAN_INIT() зарегистрирует callback’и автоматически.

Если регистрация callback’ов отключена, добавить перенаправление из стандартных HAL-callback’ов:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    if (hcan_ptr == &hcan) {
        ProtoCanRxFifo0MsgPendingCallback(hcan_ptr);
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim_ptr)
{
    if (htim_ptr == &htim2) {
        ProtoCanPulseCallback(htim_ptr);
    }
}

Не следует одновременно регистрировать callback через HAL и вручную вызывать его из стандартного callback — иначе один кадр или событие таймера может быть обработано дважды.

Приём сообщений

В прерывании ProtoCanRxFifo0MsgPendingCallback() модуль вычитывает все кадры из FIFO0:

  • стандартные CAN ID игнорируются;
  • PULSE не занимает место в RX-буфере, а сразу обновляет внутреннюю таблицу присутствующих устройств;
  • остальные Extended-кадры помещаются в кольцевой буфер;
  • при переполнении новые кадры молча отбрасываются.

Обработку прикладной логики следует выполнять вне прерывания одним из трёх способов:

PROTOCAN_ProcessSingleRxMsg(); /* обработать не более одного кадра */
PROTOCAN_ProcessAllRxMsgs();   /* обработать все накопленные кадры */
PROTOCAN_LoopProcessRxMsgs();  /* бесконечный блокирующий цикл */

Для обычного while (1) удобнее PROTOCAN_ProcessAllRxMsgs(). Она возвращает PROTOCAN_TIMEOUT, если очередь была пуста, PROTOCAN_OK после успешной обработки или код первой ошибки.

Свои обработчики входящих команд

Большинство обработчиков объявлены как __weak. Приложение может определить функцию с тем же именем и заменить демонстрационную реализацию своей логикой.

Пример обработки запроса температуры:

PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogTSens(struct RXMsg msg)
{
    msgBodyAnalogType body = {0};
    body.Body = msg.eID.Fields.MsgBody;

    uint16_t sensor_id = body.Fields.SensorID;
    /* Прочитать датчик sensor_id и сформировать нужный ответ. */

    return PROTOCAN_OK;
}

Доступные точки переопределения находятся в protocan.h:

  • ProtoCanMsgToBroadcast...() — статус, включение/выключение, restart, RTC;
  • ProtoCanMsgToDiscrete...() — аварии, предупреждения, флаги и команды;
  • ProtoCanMsgToAnalog...() — универсальные данные, настройки, U/I/T;
  • ProtoCanMsgToSettings() — GET/WRITE/CLEAR привязки ROM к локации;
  • ProtoCanMsgToGeneralAddressSpace();
  • ProtoCanMsgToModbus...();
  • PROTOCAN_RequestError().

Стандартные слабые обработчики в основном являются демонстрационными: часть возвращает PROTOCAN_OK без действий, часть отправляет текстовые ответы вроде TS0001 или GAS-0001. Для рабочего изделия их обычно нужно переопределить.

SETTINGS: привязка DS18B20 к локации

Для прибора DeviceType=0x7, DeviceID=0xF используется MsgType=0xE:

Body = (Z << 8) | Y

запрос от ПМ = 0x17FEZZYY
ответ прибора = 0x1FFEZZYY
Запрос Операция
DLC=0 GET: прочитать ROM локации
DLC=8, ROM не нулевой WRITE/REPLACE: записать или заменить датчик
DLC=8, восемь нулей CLEAR: очистить локацию
другой DLC или RTR автоматический ответ INVALID_DLC

Успешный ответ имеет DLC=8 и содержит текущее состояние локации: ROM после GET/WRITE либо восемь нулей после CLEAR. Ошибка сохраняет тот же Body, имеет DLC=1 и передаёт ProtoCanSettingsResultType в Data[0].

PROTOCAN_SettingsProcessing() выполняет транспортный разбор и вызывает слабую функцию ProtoCanMsgToSettings(). Приложение переопределяет её и выполняет проверку family code/CRC8, поиск ROM на 1-Wire и запись EEPROM:

PROTOCAN_StatusTypeDef ProtoCanMsgToSettings(
    const ProtoCanSettingsMsg_t *message)
{
    uint8_t current_rom[PROTOCAN_SETTINGS_ROM_SIZE] = {0};

    switch (message->Operation) {
    case PROTOCAN_SETTINGS_GET:
        /* Загрузить ROM локации в current_rom; свободная локация = нули. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, current_rom);

    case PROTOCAN_SETTINGS_WRITE:
        /* Проверить ROM и атомарно записать его в каталог/EEPROM. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, message->Rom);

    case PROTOCAN_SETTINGS_CLEAR:
        /* Удалить локацию из каталога/EEPROM. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, current_rom);

    default:
        return PROTOCAN_SEND_SETTINGS_ERROR(
            message->AssemblySerial, message->Position,
            PROTOCAN_SETTINGS_RESULT_INVALID_DLC);
    }
}

Слабая реализация специально не подтверждает операцию: без прикладного каталога библиотека не должна сообщать, что ROM был сохранён.

Отправка

Публичная точка отправки — PROTOCAN_SEND(id, data). Сначала заполняется общий Extended ID, затем соответствующая часть ProtoCanData_t.

Пример отправки диапазона 16-битных регистров общего адресного пространства:

uint16_t registers[] = { 0x1234, 0x5678, 0x9ABC };

ProtoCanId_t id = {0};
id.Fields.Priority   = PROTOCAN_PRIORITY_STANDARD;
id.Fields.Route      = PROTOCAN_ROUTE_FROM_DEVICE;
id.Fields.DeviceType = CURRENT_TYPE_DEVICE;
id.Fields.DeviceID   = CURRENT_ID_DEVICE;
id.Fields.MsgType    = PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE;

ProtoCanData_t tx = {0};
tx.GeneralAddressSpaceData.RegStartAdr = 100;
tx.GeneralAddressSpaceData.Data        = registers;
tx.GeneralAddressSpaceData.RegCount    = 3;

if (PROTOCAN_SEND(id, tx) != PROTOCAN_OK) {
    /* Очередь CAN занята или произошла ошибка HAL. */
}

Массив автоматически разбивается на кадры максимум по четыре регистра (8 байт) с увеличением адреса в MsgBody.

Пример сообщения об ошибке без payload:

ProtoCanId_t id = {0};
id.Fields.Priority   = PROTOCAN_PRIORITY_CRITICAL;
id.Fields.Route      = PROTOCAN_ROUTE_FROM_DEVICE;
id.Fields.DeviceType = CURRENT_TYPE_DEVICE;
id.Fields.DeviceID   = CURRENT_ID_DEVICE;
id.Fields.MsgType    = PROTOCAN_MSGTYPE_ERROR;

ProtoCanData_t tx = {0};
tx.ErrorData.Code = 0x12;
tx.ErrorData.Info = 0x34;

(void)PROTOCAN_SEND(id, tx);

Для BROADCAST, DISCRETE и ANALOG используется tx.CoreData; для Modbus-типов — tx.ModbusData, для SETTINGS — tx.SettingsData. Для успешных ответов и ошибок SETTINGS удобнее использовать готовые функции PROTOCAN_SEND_SETTINGS_RESPONSE() и PROTOCAN_SEND_SETTINGS_ERROR(). Если количество передаваемых элементов больше нуля, соответствующий указатель Data должен быть валиден до завершения вызова.

Пульс устройства

Каждое прерывание переданного в PROTOCAN_INIT() таймера вызывает ProtoCanPulseCallback() и отправляет кадр:

  • MsgType = PROTOCAN_MSGTYPE_PULSE;
  • DLC = 1;
  • Data[0] — циклический счётчик 0..255.

Период пульса полностью определяется настройками таймера. Broadcast-команда PROTOCAN_BROADCAST_ONOFF в стандартной реализации переключает его отправку.

Полученный пульс помечает удалённое устройство как активное и обнуляет TimeFromLastPulse. Увеличение этого времени и перевод устройства в offline в текущем модуле не реализованы — если это требуется, контроль таймаута нужно добавить в приложение.

Синхронизация RTC

Broadcast-команда PROTOCAN_BROADCAST_RTCSETUP ожидает ровно 7 байт:

Индекс Значение
0 часы, 0..23
1 минуты, 0..59
2 секунды, 0..59
3 год как смещение от 2000, 0..99
4 месяц, 1..12
5 число месяца
6 день недели в формате, ожидаемом данной прошивкой

Перед записью проверяются диапазоны и количество дней с учётом високосного года.

Прошивка приборов по CAN

Ниже зафиксирован формат планируемого загрузочного сервиса. Значения MsgType=0x9..0xD зарезервированы в протоколе, но обработчики загрузчика в текущих protocan.c/.h ещё не реализованы.

Загрузчик работает поверх classic CAN 2.0B с Extended ID. Управляющий модуль использует Route=0, прибор отвечает с Route=1. Команды стирания и записи всегда должны быть адресованы конкретной паре DeviceType/DeviceID; широковещательный режим допустим только для обнаружения.

Карта загрузочных сообщений

MsgType Имя Назначение MsgBody CAN payload
0x9 BOOT_CONTROL SessionID:8 | Command:8 параметры команды
0xA BOOT_DATA_A номер 8-байтового блока слота A 8 байт образа
0xB BOOT_DATA_B номер 8-байтового блока слота B 8 байт образа
0xC BOOT_STATUS SessionID:8 | Command:8 статус и прогресс
0xD BOOT_DISCOVERY подтип запроса/ответа идентификация прибора

MsgBody в кадрах данных является не байтовым адресом, а номером блока:

block_offset = (uint32_t)MsgBody * 8U;

if (MsgType == PROTOCAN_MSGTYPE_BOOT_DATA_A) {
    address = SLOT_A_BASE + block_offset;
} else if (MsgType == PROTOCAN_MSGTYPE_BOOT_DATA_B) {
    address = SLOT_B_BASE + block_offset;
}

Диапазон MsgBody=0x0000..0xFFFF адресует 65536 блоков:

65536 блоков * 8 байт = 524288 байт = 512 КиБ на слот

Таким образом, BOOT_DATA_A и BOOT_DATA_B адресуют два логических слота по 512 КиБ, всего 1 МиБ пространства образов. Физические SLOT_A_BASE и SLOT_B_BASE задаёт конкретный загрузчик. Если внутренняя Flash имеет ровно 1 МиБ, два полных слота в ней не поместятся вместе с загрузчиком и метаданными: нужно уменьшить слоты, выбрать MCU с большей Flash или хранить staging-образ во внешней памяти.

Кадр данных

Extended CAN ID
  Priority   = STANDARD
  Route      = FROM_PM
  DeviceType = тип целевого прибора
  DeviceID   = экземпляр целевого прибора
  MsgType    = 0xA (слот A) или 0xB (слот B)
  MsgBody    = BlockIndex, 0x0000..0xFFFF

DATA[0..7]   = очередные 8 байт образа

Например, MsgBody=0x0123 задаёт смещение 0x0123 * 8 = 0x0918 от начала выбранного слота. Последний неполный блок дополняется значениями 0xFF; фактический размер передаётся командой BEGIN_UPDATE, поэтому CRC32 считается только по байтам образа.

Управляющие команды

В BOOT_CONTROL поле MsgBody имеет формат:

15........8 7.........0
 SessionID     Command

Рекомендуемые команды:

Код Команда Назначение
0x01 IDENTIFY прочитать тип, аппаратную и программную версии
0x02 ENTER_BOOT перейти из приложения в загрузчик
0x03 BEGIN_IMAGE передать размер и CRC32 образа
0x04 BEGIN_COMPAT передать тип, аппаратную и программную версии
0x05 ERASE подготовить неактивный слот
0x06 VERIFY проверить размер, CRC32 и подпись
0x07 COMMIT назначить проверенный слот кандидатом на запуск
0x08 CONFIRM подтвердить успешный запуск новой программы
0x09 REBOOT перезагрузить прибор
0x0A ABORT отменить текущую сессию
0x0B QUERY_PROGRESS запросить слот и следующий ожидаемый блок

BEGIN_IMAGE содержит размер и CRC образа:

DATA[0..3] ImageSize, uint32 little-endian
DATA[4..7] ImageCRC32, uint32 little-endian

BEGIN_COMPAT содержит совместимость и версию:

DATA[0..1] ProductType, uint16 little-endian
DATA[2]    минимальная HardwareRevision
DATA[3]    максимальная HardwareRevision
DATA[4..7] FirmwareVersion, uint32 little-endian

Обе команды передаются с одним SessionID. До стирания Flash загрузчик обязан получить обе части метаданных и проверить ProductType, аппаратную ревизию, размер образа, границы выбранного слота и допустимость версии.

Ответ состояния

BOOT_STATUS возвращает результат команды и точку продолжения:

MsgBody[15..8] = SessionID
MsgBody[7..0]  = команда, на которую дан ответ

DATA[0]        Status
DATA[1]        TargetSlot: 0 = A, 1 = B
DATA[2..3]     NextBlock, uint16 little-endian
DATA[4..7]     RunningCRC32, uint32 little-endian

Минимальный набор статусов:

Код Статус
0x00 OK
0x01 BUSY
0x02 INVALID_COMMAND
0x03 WRONG_DEVICE
0x04 WRONG_HARDWARE
0x05 INVALID_SIZE
0x06 CRC_ERROR
0x07 FLASH_ERROR
0x08 SEQUENCE_ERROR
0x09 SIGNATURE_ERROR
0x0A SESSION_ERROR
0x0B VOLTAGE_ERROR

NextBlock позволяет возобновить загрузку после разрыва связи. Для первой реализации допустимо подтверждать каждый блок. Для рабочей скорости лучше передавать окна по 16 кадров и подтверждать окно одним BOOT_STATUS; при необходимости протокол статуса можно расширить битовой картой потерянных блоков.

Выбор слота и безопасное обновление

GUI не должен самостоятельно перезаписывать активный слот. После получения BEGIN_IMAGE и BEGIN_COMPAT загрузчик выбирает неактивный слот и сообщает его в BOOT_STATUS:

активен A -> принимать BOOT_DATA_B
активен B -> принимать BOOT_DATA_A

Рекомендуемый цикл обновления:

  1. Обнаружить прибор и сверить DeviceType/DeviceID, ProductType, UID и версии.
  2. Выполнить адресную команду ENTER_BOOT и получить новый SessionID.
  3. Передать BEGIN_IMAGE и BEGIN_COMPAT; загрузчик выберет неактивный слот.
  4. Стереть выбранный слот и передать блоки BOOT_DATA_A или BOOT_DATA_B.
  5. Выполнить VERIFY: проверить размер, CRC32 и цифровую подпись образа.
  6. Выполнить COMMIT и перезагрузить устройство.
  7. Новое приложение вызывает CONFIRM после успешной самопроверки.
  8. При отсутствии подтверждения загрузчик возвращается к предыдущему слоту.

CRC32 обнаруживает случайное повреждение, но не защищает от подмены. Для серийных изделий образ следует подписывать, а открытый ключ проверки хранить в неизменяемой части загрузчика. Сам загрузчик не должен обновляться обычными командами BOOT_DATA_A/B.

Важные ограничения текущей реализации

  • Модуль рассчитан на classic CAN с payload до 8 байт, не на CAN FD.
  • PROTOCAN_INIT() не запускает CAN/таймер и не включает CAN notification.
  • Все три аппаратных фильтра и номера банков заданы внутри библиотеки.
  • Фильтр адресованных сообщений рассчитан на устройство: он принимает Route=0 от ПМ. Для использования библиотеки на стороне ПМ потребуется отдельная конфигурация фильтров Route=1.
  • Проверку ROM, физического датчика и EEPROM выполняет приложение в переопределённом ProtoCanMsgToSettings().
  • Таблица присутствующих устройств обновляется при пульсе, но автоматического offline-таймаута нет.
  • Буфер переполнения не ведёт счётчик потерь и не сообщает ошибку приложению.
  • У порта нет полного примера прошивки STM32; его сборку и проверку на плате выполняет проект устройства. Host-тесты общего ядра находятся в ../../tests.
  • Несколько внутренних функций отправки реализованы только в protocan.c и не входят в публичный заголовок; для прикладного кода следует использовать PROTOCAN_SEND().

Краткий порядок запуска

  1. Подключить templates к проекту устройства.
  2. Добавить protocan.c/.h и общий pcan_id.c в STM32-проект.
  3. Задать CURRENT_TYPE_DEVICE и CURRENT_ID_DEVICE.
  4. Настроить CAN, RTC и периодический TIM в CubeMX.
  5. Вызвать PROTOCAN_INIT() после MX_..._Init().
  6. Запустить CAN, активировать CAN_IT_RX_FIFO0_MSG_PENDING и запустить таймер с прерыванием.
  7. При необходимости подключить HAL-callback’и вручную.
  8. Переопределить нужные __weak-обработчики.
  9. Вызывать PROTOCAN_ProcessAllRxMsgs() в основном цикле.

GAS: регистры 0x3 и слоты 0x4

Адресация GAS: 0x3 — отдельные 16-битные регистры 0…65535, запись 1…4 слов. 0x4 — слот с Word0-маской и тремя значениями; PERIPH сохраняет 200 слов в 67 слотах 0xMM00…0xMM42. Формат определяется MsgType, без настройки профиля.

PROTOCAN_GAS_SLOT_BIND(&port) подключает provider типа 0x4 до обработки очереди. ProtoCanMsgToGasSlot() вызывает общий адресный обработчик, PROTOCAN_SEND_GAS_SLOT() кодирует Word0-маску и три значения. Неподключённый provider возвращает ошибку. Типы Modbus 0x5…0x7 сохраняют прежние номера.