Files
templates/c/protocan-boot

ProtoCAN Boot

protocan-boot — переносимое C99-ядро адресной прошивки приборов по classic CAN 2.0B и 29-битному ProtoCAN ID. Оно реализует сессию обновления, два логических слота по 512 КиБ, последовательную запись 8-байтовых блоков, CRC32, проверку совместимости, продолжение по номеру следующего блока и безопасный выбор неактивного слота.

Это не готовый загрузчик конкретного STM32: внутри нет HAL, регистров Flash, linker script, обработчиков прерываний, криптографии и перехода в приложение. Эти операции предоставляет проект через таблицу callbacks.

Слои

приложение / CAN RX loop
        |
        v
pcan_boot_process()       — протокол и state machine
        |
        v
pcan_boot_port_t          — Flash, политика подписи, commit, reboot
        |
        v
STM32 HAL / другой MCU / host-тест

Файлы

Файл Назначение Зависимости
include/pcan_boot.h публичный API, команды и структуры C99 stdint/stdbool
src/pcan_boot.c ProtoCAN ID, state machine и CRC32 только публичный заголовок
tests/test_pcan_boot.c хостовые тесты с RAM вместо Flash стандартная библиотека C
CMakeLists.txt сборка библиотеки и теста CMake 3.13+

Модуль не зависит от protocan-transport: разбор пяти полей 29-битного ID сделан переносимыми сдвигами, без непереносимых C-битовых полей.

Карта MsgType

Значение Имя Назначение
0x9 BOOT_CONTROL команды и метаданные
0xA BOOT_DATA_A блоки слота A
0xB BOOT_DATA_B блоки слота B
0xC BOOT_STATUS ответы и прогресс
0xD BOOT_DISCOVERY идентификация

Для BOOT_DATA_A/B поле MsgBody — номер блока 0..65535, а payload — 8 байт образа:

offset = MsgBody * 8
65536 * 8 = 512 КиБ на слот

Последний кадр дополняется 0xFF; ядро передаёт во Flash только оставшиеся байты фактического образа и включает в CRC только их.

Контракт порта

typedef struct {
    bool (*send)(void *, uint32_t can_id, const uint8_t *, uint8_t dlc);
    bool (*erase_slot)(void *, uint8_t slot);
    bool (*write_slot)(void *, uint8_t slot, uint32_t offset,
                       const uint8_t *, uint8_t length);
    bool (*authorize)(void *, const pcan_boot_manifest_t *);
    bool (*verify_image)(void *, uint8_t slot,
                         const pcan_boot_manifest_t *);
    bool (*set_pending_slot)(void *, uint8_t slot,
                             const pcan_boot_manifest_t *);
    bool (*confirm_running_slot)(void *);
    void (*reboot)(void *);
} pcan_boot_port_t;

Обязательны send, erase_slot, write_slot и set_pending_slot. authorize проверяет политику до стирания (тип, версия, anti-rollback). verify_image выполняет платформенную проверку подписанного контейнера после CRC32. Если callbacks отсутствуют, соответствующие дополнительные проверки пропускаются; для серийного изделия их следует реализовать.

set_pending_slot должен атомарно сохранить boot metadata. До подтверждения нового приложения загрузчик сохраняет возможность отката. confirm_running_slot снимает pending-флаг после самопроверки приложения.

Быстрый старт

pcan_boot_t boot;
pcan_boot_config_t cfg = {
    .device_type = 3, .device_id = 5, .product_type = 0x1234,
    .hardware_revision = 2, .firmware_version = 0x01020000,
    .active_slot = 0, .ack_window = 16
};
pcan_boot_port_t port = {
    .send = can_send, .erase_slot = flash_erase_slot,
    .write_slot = flash_write_slot, .authorize = image_authorize,
    .verify_image = image_verify_signature,
    .set_pending_slot = boot_set_pending, .reboot = system_reboot
};

pcan_boot_init(&boot, &cfg, &port, &board);
/* Из CAN RX-задачи, не из длительного Flash-прерывания: */
pcan_boot_process(&boot, rx.ExtId, rx.Data, rx.DLC);

Последовательность обновления

  1. IDENTIFY или discovery получает тип и версии прибора.
  2. ENTER_BOOT открывает ненулевой SessionID.
  3. BEGIN_IMAGE передаёт размер и CRC32.
  4. BEGIN_COMPAT передаёт тип изделия, диапазон HW и версию FW.
  5. Ядро выбирает неактивный слот и отвечает его номером.
  6. ERASE вызывает порт стирания.
  7. BOOT_DATA_A либо BOOT_DATA_B передаются строго по порядку.
  8. VERIFY сравнивает CRC32 и вызывает проверку образа/подписи порта.
  9. COMMIT атомарно помечает слот как pending.
  10. После reboot приложение подтверждает запуск через CONFIRM.

Все изменяющие Flash команды адресуются конкретному DeviceType/DeviceID. Ядро не принимает broadcast-запись и игнорирует кадры с чужим адресом или Route=FROM_DEVICE.

State machine

IDLE -> ENTER_BOOT -> METADATA -> BEGIN_IMAGE + BEGIN_COMPAT
     -> READY_TO_ERASE -> ERASE -> RECEIVING -> VERIFY -> VERIFIED
     -> COMMIT -> REBOOT -> CONFIRM

При ошибке Flash, CRC, совместимости или подписи состояние переходит в FAILED. Новая сессия начинается командой ENTER_BOOT; ABORT очищает текущую сессию без изменения Flash.

Сборка тестов

cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure

Что остаётся проекту устройства

  • зарезервировать bootloader, slot A/B и metadata в linker script;
  • реализовать выравнивание и размеры страниц Flash;
  • не выполнять длительное стирание непосредственно в CAN IRQ;
  • атомарно хранить active/pending/confirmed и счётчик попыток запуска;
  • проверить вектор, границы и подпись образа;
  • включить watchdog и rollback при неподтверждённом запуске;
  • согласовать фильтры CAN для MsgType=0x9..0xD.

Проект пока не подключён ни к одной конкретной плате; это самостоятельный шаблон для интеграции в будущие загрузчики приборов SET.