docs: document CAN bootloader protocol

This commit is contained in:
2026-08-29 21:45:30 +03:00
parent 01de7651e8
commit bd8d22312e
13 changed files with 16060 additions and 4 deletions

198
README.md
View File

@@ -16,6 +16,20 @@ SETCAN/
Модуль зависит от файлов `main.h` и `can.h`, сгенерированных STM32CubeMX, а также от HAL-драйверов CAN, RTC и TIM.
## Документация протокола
Нормативное описание разделено по назначению:
- [`docs/protocan/PROTOCOL.md`](docs/protocan/PROTOCOL.md) — структура ProtoCAN;
- [`docs/protocan/BOOTLOADER.md`](docs/protocan/BOOTLOADER.md) — прошивка по CAN;
- [`docs/protocan/OAP.md`](docs/protocan/OAP.md) — правила ведения ОАП;
- [`docs/protocan/examples/test-vectors.json`](docs/protocan/examples/test-vectors.json) — машинные эталоны;
- [`docs/protocan/build/protocol.html`](docs/protocan/build/protocol.html) — собранная HTML-версия.
Большой файл `Протокол CAN и ОАП.md` и исходный XLSX сохранены как табличное
представление реестра. Правила приоритета источников описаны в
[`docs/protocan/README.md`](docs/protocan/README.md).
## Формат расширенного CAN ID
Протокол размещает служебные поля в 29-битном Extended ID:
@@ -42,6 +56,11 @@ SETCAN/
| `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` | пульс присутствия устройства |
@@ -56,6 +75,11 @@ SETCAN/
Для регистров `uint16_t` в CAN payload используется порядок байтов little-endian: сначала младший байт, затем старший.
Пара `DeviceType/DeviceID` образует уникальный адрес прибора. Три бита
`DeviceType` задают 8 типов, четыре бита `DeviceID` — 16 экземпляров каждого
типа; всего на одной шине можно адресовать до 128 приборов. Загрузочные кадры
так же адресуются этой парой и не требуют изменения 29-битного CAN ID.
> Разметка ID реализована C-битовыми полями. Она соответствует используемому STM32 GCC ABI, но не является переносимой между произвольными компиляторами без проверки фактического расположения битов.
## Подключение к STM32-проекту
@@ -348,6 +372,180 @@ Broadcast-команда `PROTOCAN_BROADCAST_RTCSETUP` ожидает ровно
Перед записью проверяются диапазоны и количество дней с учётом високосного года.
## Прошивка приборов по 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` в кадрах данных является не байтовым адресом, а номером блока:
```c
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 блоков:
```text
65536 блоков * 8 байт = 524288 байт = 512 КиБ на слот
```
Таким образом, `BOOT_DATA_A` и `BOOT_DATA_B` адресуют два логических слота
по 512 КиБ, всего 1 МиБ пространства образов. Физические `SLOT_A_BASE` и
`SLOT_B_BASE` задаёт конкретный загрузчик. Если внутренняя Flash имеет ровно
1 МиБ, два полных слота в ней не поместятся вместе с загрузчиком и метаданными:
нужно уменьшить слоты, выбрать MCU с большей Flash или хранить staging-образ
во внешней памяти.
### Кадр данных
```text
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` имеет формат:
```text
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 образа:
```text
DATA[0..3] ImageSize, uint32 little-endian
DATA[4..7] ImageCRC32, uint32 little-endian
```
`BEGIN_COMPAT` содержит совместимость и версию:
```text
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` возвращает результат команды и точку продолжения:
```text
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`:
```text
активен 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.