docs: document CAN bootloader protocol
This commit is contained in:
198
README.md
198
README.md
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user