docs: document CAN bootloader protocol
This commit is contained in:
@@ -4,6 +4,200 @@
|
||||
|
||||
- [Интерфейс протокола](#sheet-protocol)
|
||||
- [Общее адресное пространство](#sheet-oap)
|
||||
- [Прошивка приборов по CAN](#can-firmware)
|
||||
|
||||
<a id="can-firmware"></a>
|
||||
## Прошивка приборов по 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` адресует ровно 65 536 блоков:
|
||||
|
||||
```text
|
||||
512 КиБ = 512 * 1024 = 524 288 байт
|
||||
524 288 / 8 = 65 536 блоков
|
||||
```
|
||||
|
||||
| `MsgBody` | Номер блока | Смещение от начала слота | Диапазон байтов |
|
||||
|---:|---:|---:|---:|
|
||||
| `0x0000` | 0 | `0x00000` | `0x00000..0x00007` |
|
||||
| `0x0001` | 1 | `0x00008` | `0x00008..0x0000F` |
|
||||
| `0x0002` | 2 | `0x00010` | `0x00010..0x00017` |
|
||||
| `0xFFFF` | 65 535 | `0x7FFF8` | `0x7FFF8..0x7FFFF` |
|
||||
|
||||
Формула физического адреса блока:
|
||||
|
||||
```c
|
||||
address = SLOT_X_BASE + ((uint32_t)MsgBody * 8U);
|
||||
```
|
||||
|
||||
Последний допустимый блок начинается со смещения `0x7FFF8` и заканчивается
|
||||
на `0x7FFFF` включительно. Следующее смещение `0x80000` уже находится за
|
||||
границей 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`.
|
||||
|
||||
|
||||
<style>
|
||||
@@ -724,12 +918,68 @@
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td colspan="4" class="x156"> </td>
|
||||
<td colspan="4" class="x149">0x9 – 0xE. Резерв</td>
|
||||
<td colspan="16" class="x162">0x0 – 0xFFFF. Резерв</td>
|
||||
<td colspan="4" class="x149">0x9. BOOT_CONTROL</td>
|
||||
<td colspan="16" class="x162">SessionID[15:8] | Command[7:0]. Управление загрузчиком</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="4" class="x101">NULL</td>
|
||||
<td colspan="4" class="x101">0/8</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="8" class="x101">NULL</td>
|
||||
<td colspan="8" class="x101">Параметры команды</td>
|
||||
</tr>
|
||||
<tr style="height:17px">
|
||||
<td class="x26"> </td>
|
||||
<td class="x27"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td colspan="4" class="x156"> </td>
|
||||
<td colspan="4" class="x149">0xA. BOOT_DATA_A</td>
|
||||
<td colspan="16" class="x162">MsgBody = BlockIndex 0x0000–0xFFFF: 65 536 блоков по 8 байт, слот A 512 КиБ</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="4" class="x101">8</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="8" class="x101">8 байт прошивки слота A</td>
|
||||
</tr>
|
||||
<tr style="height:17px">
|
||||
<td class="x26"> </td>
|
||||
<td class="x27"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td colspan="4" class="x156"> </td>
|
||||
<td colspan="4" class="x149">0xB. BOOT_DATA_B</td>
|
||||
<td colspan="16" class="x162">MsgBody = BlockIndex 0x0000–0xFFFF: 65 536 блоков по 8 байт, слот B 512 КиБ</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="4" class="x101">8</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="8" class="x101">8 байт прошивки слота B</td>
|
||||
</tr>
|
||||
<tr style="height:17px">
|
||||
<td class="x26"> </td>
|
||||
<td class="x27"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td colspan="4" class="x156"> </td>
|
||||
<td colspan="4" class="x149">0xC. BOOT_STATUS</td>
|
||||
<td colspan="16" class="x162">SessionID[15:8] | Command[7:0]. Ответ и точка продолжения</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="4" class="x101">8</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="8" class="x101">Status, Slot, NextBlock, RunningCRC32</td>
|
||||
</tr>
|
||||
<tr style="height:17px">
|
||||
<td class="x26"> </td>
|
||||
<td class="x27"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td class="x50"> </td>
|
||||
<td colspan="4" class="x156"> </td>
|
||||
<td colspan="4" class="x149">0xD. BOOT_DISCOVERY</td>
|
||||
<td colspan="16" class="x162">Подтип запроса/ответа идентификации загрузчика</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="4" class="x101">0/8</td>
|
||||
<td class="x14"> </td>
|
||||
<td colspan="8" class="x101">ProductType, HardwareRevision, BootVersion, FirmwareVersion</td>
|
||||
</tr>
|
||||
<tr style="height:17px">
|
||||
<td class="x26"> </td>
|
||||
|
||||
Reference in New Issue
Block a user