Files
SETCAN/docs/protocan/protocol.html

834 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ProtoCAN — спецификация</title>
<style>body { max-width: 1180px; margin: 32px auto; padding: 0 24px;
color: #111827; background: #fff; font: 15px/1.55 Arial, sans-serif; }
h1, h2, h3 { line-height: 1.25; }
h1 { margin-top: 48px; border-bottom: 2px solid #334155; padding-bottom: 8px; }
h2 { margin-top: 32px; }
a { color: #1155cc; }
code { font-family: Consolas, "Courier New", monospace; }
pre { overflow: auto; padding: 12px; border: 1px solid #cbd5e1; background: #f8fafc; }
table { width: 100%; border-collapse: collapse; margin: 12px 0 24px; }
th, td { border: 1px solid #94a3b8; padding: 6px 9px; vertical-align: top; }
th { background: #e2e8f0; }
tr:nth-child(even) td { background: #f8fafc; }</style>
</head>
<body>
<h1 id="protocan">Документация ProtoCAN</h1>
<p>Статус комплекта: <strong>Draft</strong><br />
Версия комплекта: <strong>1.0</strong><br />
Дата редакции: <strong>2026-08-29</strong><br />
Транспорт: <strong>Classic CAN 2.0B, Extended ID, DLC 0…8</strong></p>
<p>Этот каталог разделяет нормативное описание протокола, загрузчик и реестр
общего адресного пространства. Большой исходный документ
<a href="../../%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20CAN%20%D0%B8%20%D0%9E%D0%90%D0%9F.md"><code>Протокол CAN и ОАП.md</code></a> сохранён как
совместимое представление таблиц из Excel.</p>
<h2 id="section">Документы</h2>
<table>
<thead>
<tr>
<th>Документ</th>
<th>Назначение</th>
<th>Статус источника</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="PROTOCOL.md">PROTOCOL.md</a></td>
<td>29-битный CAN ID, адресация, реестр <code>MsgType</code>, порядок байтов</td>
<td>нормативный</td>
</tr>
<tr>
<td><a href="BOOTLOADER.md">BOOTLOADER.md</a></td>
<td>обновление прошивки, кадры, состояния, ошибки и A/B-слоты</td>
<td>нормативный draft</td>
</tr>
<tr>
<td><a href="OAP.md">OAP.md</a></td>
<td>правила ведения общего адресного пространства</td>
<td>нормативный индекс</td>
</tr>
<tr>
<td><a href="../../%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20CAN%20%D0%B8%20%D0%9E%D0%90%D0%9F.xlsx">../../Протокол CAN и ОАП.xlsx</a></td>
<td>редактируемый реестр ОАП</td>
<td>источник таблиц</td>
</tr>
<tr>
<td><a href="examples/test-vectors.json">examples/test-vectors.json</a></td>
<td>машинные эталоны CAN ID и payload</td>
<td>нормативные примеры</td>
</tr>
<tr>
<td><a href="CHANGELOG.md">CHANGELOG.md</a></td>
<td>история версий документа</td>
<td>нормативный</td>
</tr>
</tbody>
</table>
<h2 id="section-1">Приоритет источников</h2>
<p>При расхождении данных действует следующий порядок:</p>
<ol>
<li><code>PROTOCOL.md</code> — структура ProtoCAN и реестр типов сообщений.</li>
<li><code>BOOTLOADER.md</code> — загрузочный сервис <code>0x9…0xD</code>.</li>
<li>XLSX — адреса и свойства регистров ОАП.</li>
<li>Сгенерированный HTML — только представление, не самостоятельный источник.</li>
</ol>
<h2 id="html">Сборка HTML</h2>
<p>В PowerShell 7:</p>
<pre><code class="language-powershell">./build-html.ps1
</code></pre>
<p>Результат создаётся в <code>build/protocol.html</code>. Скрипт не изменяет исходные
Markdown/XLSX и пригоден для запуска в CI.</p>
<hr>
<h1 id="protocan">ProtoCAN — базовый протокол</h1>
<p>Статус: <strong>Stable с зарезервированным загрузочным расширением</strong><br />
Версия: <strong>1.0</strong><br />
Порядок байтов payload: <strong>little-endian</strong>, если явно не указано иное</p>
<h2 id="section">Назначение</h2>
<p>ProtoCAN — прикладной протокол поверх classic CAN 2.0B. Используются только
расширенные 29-битные идентификаторы (<code>IDE=1</code>) и payload длиной 0…8 байт.</p>
<h2 id="section-1">Термины</h2>
<table>
<thead>
<tr>
<th>Термин</th>
<th>Значение</th>
</tr>
</thead>
<tbody>
<tr>
<td>ПМ</td>
<td>управляющий модуль</td>
</tr>
<tr>
<td>прибор</td>
<td>адресуемый узел на шине</td>
</tr>
<tr>
<td><code>DeviceType</code></td>
<td>тип прибора, 0…7</td>
</tr>
<tr>
<td><code>DeviceID</code></td>
<td>экземпляр прибора данного типа, 0…15</td>
</tr>
<tr>
<td><code>MsgType</code></td>
<td>класс сообщения или сервис</td>
</tr>
<tr>
<td><code>MsgBody</code></td>
<td>16-битное поле, формат которого зависит от <code>MsgType</code></td>
</tr>
</tbody>
</table>
<p>Пара <code>DeviceType/DeviceID</code> задаёт до <code>8 × 16 = 128</code> уникальных адресов.</p>
<h2 id="can-id">Расширенный CAN ID</h2>
<pre><code class="language-text">28 27 26...24 23...20 19...16 15........0
Priority Route DeviceType DeviceID MsgType MsgBody
1 бит 1 бит 3 бита 4 бита 4 бита 16 бит
</code></pre>
<pre><code class="language-c">can_id =
((uint32_t)priority &lt;&lt; 28) |
((uint32_t)route &lt;&lt; 27) |
((uint32_t)device_type &lt;&lt; 24) |
((uint32_t)device_id &lt;&lt; 20) |
((uint32_t)msg_type &lt;&lt; 16) |
msg_body;
</code></pre>
<table>
<thead>
<tr>
<th>Поле</th>
<th>Значения</th>
<th>Назначение</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Priority</code></td>
<td><code>0</code> critical, <code>1</code> standard</td>
<td>CAN-арбитраж</td>
</tr>
<tr>
<td><code>Route</code></td>
<td><code>0</code> от ПМ, <code>1</code> от прибора</td>
<td>логическое направление</td>
</tr>
<tr>
<td><code>DeviceType</code></td>
<td><code>0…7</code></td>
<td>тип прибора</td>
</tr>
<tr>
<td><code>DeviceID</code></td>
<td><code>0…15</code></td>
<td>номер экземпляра</td>
</tr>
<tr>
<td><code>MsgType</code></td>
<td><code>0…15</code></td>
<td>тип сообщения</td>
</tr>
<tr>
<td><code>MsgBody</code></td>
<td><code>0…65535</code></td>
<td>команда, адрес или номер блока</td>
</tr>
</tbody>
</table>
<p><code>Route</code> не является направлением физического трансивера. Ответ прибора
сохраняет адрес <code>DeviceType/DeviceID</code> и устанавливает <code>Route=1</code>.</p>
<h2 id="msgtype">Реестр <code>MsgType</code></h2>
<table>
<thead>
<tr>
<th style="text-align: right;">Код</th>
<th>Имя</th>
<th>Основное направление</th>
<th style="text-align: right;">DLC</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;"><code>0x0</code></td>
<td><code>BROADCAST</code></td>
<td>ПМ → все</td>
<td style="text-align: right;">зависит от команды</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x1</code></td>
<td><code>DISCRETE</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x2</code></td>
<td><code>ANALOG</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x3</code></td>
<td><code>GAS</code></td>
<td>оба</td>
<td style="text-align: right;">0/2/4/6/8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x4</code></td>
<td><code>MODBUS_COIL</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x5</code></td>
<td><code>MODBUS_DISCRETE</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x6</code></td>
<td><code>MODBUS_HOLDING</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x7</code></td>
<td><code>MODBUS_INPUT</code></td>
<td>оба</td>
<td style="text-align: right;">0…8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x8</code></td>
<td><code>ERROR</code></td>
<td>прибор → ПМ</td>
<td style="text-align: right;">0</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x9</code></td>
<td><code>BOOT_CONTROL</code></td>
<td>ПМ → прибор</td>
<td style="text-align: right;">0/8</td>
<td>draft</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xA</code></td>
<td><code>BOOT_DATA_A</code></td>
<td>ПМ → прибор</td>
<td style="text-align: right;">8</td>
<td>draft</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xB</code></td>
<td><code>BOOT_DATA_B</code></td>
<td>ПМ → прибор</td>
<td style="text-align: right;">8</td>
<td>draft</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xC</code></td>
<td><code>BOOT_STATUS</code></td>
<td>прибор → ПМ</td>
<td style="text-align: right;">8</td>
<td>draft</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xD</code></td>
<td><code>BOOT_DISCOVERY</code></td>
<td>прибор → ПМ</td>
<td style="text-align: right;">8</td>
<td>draft</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xE</code></td>
<td><code>SETTINGS</code></td>
<td>оба</td>
<td style="text-align: right;">0/1/8</td>
<td>stable</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xF</code></td>
<td><code>PULSE</code></td>
<td>прибор → сеть</td>
<td style="text-align: right;">1</td>
<td>stable</td>
</tr>
</tbody>
</table>
<p>Подробный формат <code>0x9…0xD</code> находится в <a href="BOOTLOADER.md">BOOTLOADER.md</a>.</p>
<h2 id="msgbody">Разметки <code>MsgBody</code></h2>
<table>
<thead>
<tr>
<th><code>MsgType</code></th>
<th>Биты <code>MsgBody</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>broadcast</td>
<td>команда <code>[15:4]</code>, параметр <code>[3:0]</code></td>
</tr>
<tr>
<td>discrete/analog</td>
<td>подтип <code>[15:12]</code>, значение/адрес <code>[11:0]</code></td>
</tr>
<tr>
<td>Modbus</td>
<td>начальный адрес <code>[15:4]</code>, количество <code>[3:0]</code></td>
</tr>
<tr>
<td>GAS</td>
<td>адрес первого 16-битного регистра <code>[15:0]</code></td>
</tr>
<tr>
<td>error</td>
<td>дополнительная информация <code>[15:8]</code>, код <code>[7:0]</code></td>
</tr>
<tr>
<td>settings</td>
<td>номер сборки <code>[15:8]</code>, позиция <code>[7:0]</code></td>
</tr>
<tr>
<td>boot control/status</td>
<td><code>SessionID[15:8]</code>, команда <code>[7:0]</code></td>
</tr>
<tr>
<td>boot data</td>
<td><code>BlockIndex[15:0]</code></td>
</tr>
</tbody>
</table>
<h2 id="section-2">Общие правила обмена</h2>
<ul>
<li>Многобайтовые значения в <code>DATA</code> передаются little-endian.</li>
<li>Узел игнорирует адресованные кадры с чужим <code>DeviceType/DeviceID</code>.</li>
<li>Прибор принимает команды ПМ с <code>Route=0</code>; ПМ принимает ответы с <code>Route=1</code>.</li>
<li>Стандартные 11-битные CAN ID не являются кадрами ProtoCAN.</li>
<li>RTR для загрузочного сервиса запрещён.</li>
<li>Неописанные комбинации <code>MsgType/MsgBody/DLC</code> должны отвергаться.</li>
</ul>
<h2 id="id">Эталон упаковки ID</h2>
<pre><code class="language-text">Priority = 1
Route = 0
DeviceType = 3
DeviceID = 5
MsgType = 0x9
MsgBody = 0x0702
CAN ID = 0x13590702
</code></pre>
<p>Этот пример соответствует <code>ENTER_BOOT</code>, <code>SessionID=7</code>. Машинные варианты
находятся в <a href="examples/test-vectors.json">examples/test-vectors.json</a>.</p>
<hr>
<h1 id="protocan-boot-protocol">ProtoCAN Boot Protocol</h1>
<p>Статус: <strong>Draft</strong><br />
Версия протокола: <strong>1.0</strong><br />
Совместимость: <strong>classic CAN 2.0B, Extended ID, DLC 0…8</strong><br />
Реализация: <a href="../../../templates/c/protocan-boot"><code>templates/c/protocan-boot</code></a></p>
<h2 id="section">Назначение</h2>
<p>Сервис обновляет адресованный прибор по CAN и поддерживает два логических
слота A/B. Активный слот не стирается: новый образ записывается в неактивный,
проверяется и атомарно назначается кандидатом на запуск.</p>
<h2 id="section-1">Карта сообщений</h2>
<table>
<thead>
<tr>
<th style="text-align: right;"><code>MsgType</code></th>
<th>Имя</th>
<th><code>MsgBody</code></th>
<th>Payload</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;"><code>0x9</code></td>
<td><code>BOOT_CONTROL</code></td>
<td><code>SessionID[15:8] \| Command[7:0]</code></td>
<td>параметры команды</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xA</code></td>
<td><code>BOOT_DATA_A</code></td>
<td><code>BlockIndex[15:0]</code></td>
<td>8 байт слота A</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xB</code></td>
<td><code>BOOT_DATA_B</code></td>
<td><code>BlockIndex[15:0]</code></td>
<td>8 байт слота B</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xC</code></td>
<td><code>BOOT_STATUS</code></td>
<td><code>SessionID[15:8] \| Command[7:0]</code></td>
<td>статус и прогресс</td>
</tr>
<tr>
<td style="text-align: right;"><code>0xD</code></td>
<td><code>BOOT_DISCOVERY</code></td>
<td>подтип ответа</td>
<td>идентификация</td>
</tr>
</tbody>
</table>
<p>Все команды записи адресуются конкретному <code>DeviceType/DeviceID</code> и имеют
<code>Route=0</code>. Ответы сохраняют адрес прибора и имеют <code>Route=1</code>.</p>
<h2 id="section-2">Адресация образа</h2>
<p><code>MsgBody</code> кадра данных — номер 8-байтового блока:</p>
<pre><code class="language-c">offset = (uint32_t)BlockIndex * 8U;
address = SLOT_X_BASE + offset;
</code></pre>
<pre><code class="language-text">512 КиБ = 524 288 байт
524 288 / 8 = 65 536 блоков
BlockIndex = 0x0000…0xFFFF
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: right;"><code>BlockIndex</code></th>
<th style="text-align: right;">Смещение</th>
<th style="text-align: right;">Диапазон байтов</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;"><code>0x0000</code></td>
<td style="text-align: right;"><code>0x00000</code></td>
<td style="text-align: right;"><code>0x00000…0x00007</code></td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0001</code></td>
<td style="text-align: right;"><code>0x00008</code></td>
<td style="text-align: right;"><code>0x00008…0x0000F</code></td>
</tr>
<tr>
<td style="text-align: right;"><code>0xFFFF</code></td>
<td style="text-align: right;"><code>0x7FFF8</code></td>
<td style="text-align: right;"><code>0x7FFF8…0x7FFFF</code></td>
</tr>
</tbody>
</table>
<p><code>0x80000</code> является первой позицией за границей слота. Последний кадр
дополняется <code>0xFF</code>, но CRC32 вычисляется только по <code>ImageSize</code> байтам.</p>
<h2 id="boot_control">Команды <code>BOOT_CONTROL</code></h2>
<table>
<thead>
<tr>
<th style="text-align: right;">Код</th>
<th>Команда</th>
<th style="text-align: right;">DLC</th>
<th>Payload</th>
<th>Допустимое состояние</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;"><code>0x01</code></td>
<td><code>IDENTIFY</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>любое</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x02</code></td>
<td><code>ENTER_BOOT</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>любое; <code>SessionID != 0</code></td>
</tr>
<tr>
<td style="text-align: right;"><code>0x03</code></td>
<td><code>BEGIN_IMAGE</code></td>
<td style="text-align: right;">8</td>
<td>размер и CRC32</td>
<td>metadata</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x04</code></td>
<td><code>BEGIN_COMPAT</code></td>
<td style="text-align: right;">8</td>
<td>совместимость и версия</td>
<td>metadata</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x05</code></td>
<td><code>ERASE</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>ready-to-erase</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x06</code></td>
<td><code>VERIFY</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>образ получен</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x07</code></td>
<td><code>COMMIT</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>verified</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x08</code></td>
<td><code>CONFIRM</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>запущенное приложение</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x09</code></td>
<td><code>REBOOT</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>активная сессия</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0A</code></td>
<td><code>ABORT</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>активная сессия</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0B</code></td>
<td><code>QUERY_PROGRESS</code></td>
<td style="text-align: right;">0</td>
<td>отсутствует</td>
<td>активная сессия</td>
</tr>
</tbody>
</table>
<h3 id="begin_image"><code>BEGIN_IMAGE</code></h3>
<pre><code class="language-text">DATA[0..3] ImageSize, uint32 little-endian
DATA[4..7] ImageCRC32, uint32 little-endian
</code></pre>
<h3 id="begin_compat"><code>BEGIN_COMPAT</code></h3>
<pre><code class="language-text">DATA[0..1] ProductType, uint16 little-endian
DATA[2] HardwareRevisionMin
DATA[3] HardwareRevisionMax
DATA[4..7] FirmwareVersion, uint32 little-endian
</code></pre>
<p>До <code>ERASE</code> прибор обязан получить обе части метаданных и проверить размер,
тип изделия, аппаратную ревизию, версию и политику anti-rollback.</p>
<h2 id="boot_status"><code>BOOT_STATUS</code></h2>
<pre><code class="language-text">MsgBody[15..8] SessionID
MsgBody[7..0] команда, на которую дан ответ
DATA[0] Status
DATA[1] TargetSlot: 0=A, 1=B, 0xFF=не выбран
DATA[2..3] NextBlock, uint16 little-endian
DATA[4..7] RunningCRC32, uint32 little-endian
</code></pre>
<table>
<thead>
<tr>
<th style="text-align: right;">Код</th>
<th>Статус</th>
<th>Повтор допустим</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;"><code>0x00</code></td>
<td><code>OK</code></td>
<td></td>
</tr>
<tr>
<td style="text-align: right;"><code>0x01</code></td>
<td><code>BUSY</code></td>
<td>да, после задержки</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x02</code></td>
<td><code>INVALID_COMMAND</code></td>
<td>после исправления</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x03</code></td>
<td><code>WRONG_DEVICE</code></td>
<td>нет для этого образа</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x04</code></td>
<td><code>WRONG_HARDWARE</code></td>
<td>нет для этого образа</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x05</code></td>
<td><code>INVALID_SIZE</code></td>
<td>нет для этого образа</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x06</code></td>
<td><code>CRC_ERROR</code></td>
<td>новая передача</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x07</code></td>
<td><code>FLASH_ERROR</code></td>
<td>зависит от платформы</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x08</code></td>
<td><code>SEQUENCE_ERROR</code></td>
<td>да, с <code>NextBlock</code></td>
</tr>
<tr>
<td style="text-align: right;"><code>0x09</code></td>
<td><code>SIGNATURE_ERROR</code></td>
<td>нет</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0A</code></td>
<td><code>SESSION_ERROR</code></td>
<td>открыть новую сессию</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0B</code></td>
<td><code>VOLTAGE_ERROR</code></td>
<td>да после нормализации питания</td>
</tr>
<tr>
<td style="text-align: right;"><code>0x0C</code></td>
<td><code>INVALID_STATE</code></td>
<td>выполнить правильный переход</td>
</tr>
</tbody>
</table>
<h2 id="state-machine">State machine</h2>
<pre><code class="language-text">IDLE
└─ ENTER_BOOT ─&gt; METADATA
├─ BEGIN_IMAGE
└─ BEGIN_COMPAT
v
READY_TO_ERASE
│ ERASE
v
RECEIVING
│ VERIFY
v
VERIFIED
│ COMMIT
v
PENDING + REBOOT
│ CONFIRM
v
CONFIRMED
</code></pre>
<p>Ошибка Flash, CRC, совместимости или подписи переводит сессию в <code>FAILED</code>.
Новая <code>ENTER_BOOT</code> создаёт чистую сессию. <code>ABORT</code> прекращает текущую передачу,
не активируя частично записанный слот.</p>
<h2 id="section-3">Надёжность и повторы</h2>
<ul>
<li>Блоки передаются строго по возрастанию <code>BlockIndex</code>.</li>
<li>Дубликат или пропуск возвращает <code>SEQUENCE_ERROR</code> и ожидаемый <code>NextBlock</code>.</li>
<li>Базовый режим подтверждает каждый блок.</li>
<li>Рабочий режим может подтверждать окно из 16 блоков.</li>
<li>После потери связи <code>QUERY_PROGRESS</code> возвращает следующий ожидаемый блок,
пока состояние загрузчика сохранено.</li>
<li>Для продолжения после перезагрузки порт должен сохранять session metadata
и восстановить её при инициализации; ядро версии 1.0 само это не делает.</li>
</ul>
<h2 id="ab">Безопасность и A/B-обновление</h2>
<pre><code class="language-text">active=A -&gt; target=B -&gt; verify -&gt; pending=B
active=B -&gt; target=A -&gt; verify -&gt; pending=A
</code></pre>
<p>CRC32 защищает только от случайного повреждения. Серийный загрузчик должен
дополнительно проверить подпись контейнера, границы вектора, совместимость и
anti-rollback. Bootloader не обновляется командами <code>BOOT_DATA_A/B</code>.</p>
<p>Boot metadata должна атомарно хранить:</p>
<ul>
<li>активный слот;</li>
<li>pending-слот;</li>
<li>подтверждение запуска;</li>
<li>число неудачных попыток;</li>
<li>версию и CRC32 образа.</li>
</ul>
<p>Если приложение не выполняет <code>CONFIRM</code> за установленное число запусков,
загрузчик возвращается к предыдущему подтверждённому слоту.</p>
<h2 id="section-4">Эталонный сценарий</h2>
<ol>
<li>ПМ адресно отправляет <code>IDENTIFY</code>.</li>
<li>ПМ открывает ненулевой <code>SessionID</code> командой <code>ENTER_BOOT</code>.</li>
<li>ПМ отправляет <code>BEGIN_IMAGE</code> и <code>BEGIN_COMPAT</code>.</li>
<li>Прибор сообщает выбранный неактивный слот.</li>
<li>ПМ выполняет <code>ERASE</code> и передаёт <code>BOOT_DATA_A</code> либо <code>BOOT_DATA_B</code>.</li>
<li>ПМ выполняет <code>VERIFY</code>, затем <code>COMMIT</code> и <code>REBOOT</code>.</li>
<li>Новое приложение после самопроверки выполняет <code>CONFIRM</code>.</li>
</ol>
<hr>
<h1 id="section">Общее адресное пространство</h1>
<p>Статус: <strong>Stable, данные ведутся в XLSX</strong><br />
Порядок значений: <strong>16-битные регистры, little-endian в CAN payload</strong></p>
<p>Редактируемый источник реестра:
<a href="../../%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20CAN%20%D0%B8%20%D0%9E%D0%90%D0%9F.xlsx"><code>Протокол CAN и ОАП.xlsx</code></a>.</p>
<p>Просматриваемая большая таблица находится в
<a href="../../%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20CAN%20%D0%B8%20%D0%9E%D0%90%D0%9F.html"><code>Протокол CAN и ОАП.html</code></a> и
<a href="../../%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20CAN%20%D0%B8%20%D0%9E%D0%90%D0%9F.md"><code>Протокол CAN и ОАП.md</code></a>.</p>
<h2 id="section-1">Назначение</h2>
<p>ОАП связывает 16-битный адрес регистра с его типом, назначением, доступом и
масштабом. В ProtoCAN используется <code>MsgType=0x3</code>, а <code>MsgBody</code> содержит адрес
первого регистра.</p>
<h2 id="section-2">Обязательные поля реестра</h2>
<table>
<thead>
<tr>
<th>Поле</th>
<th>Требование</th>
</tr>
</thead>
<tbody>
<tr>
<td>AddressHex</td>
<td><code>0x0000…0xFFFF</code>, уникальное значение</td>
</tr>
<tr>
<td>AddressDec</td>
<td>десятичный эквивалент AddressHex</td>
</tr>
<tr>
<td>Group</td>
<td>функциональная группа</td>
</tr>
<tr>
<td>Name</td>
<td>однозначное имя параметра</td>
</tr>
<tr>
<td>Type</td>
<td><code>u16</code>, <code>i16</code>, <code>u32</code>, <code>i32</code>, <code>float32</code>, bitmap или массив</td>
</tr>
<tr>
<td>Registers</td>
<td>число занятых 16-битных регистров</td>
</tr>
<tr>
<td>Access</td>
<td><code>R</code>, <code>W</code> или <code>RW</code></td>
</tr>
<tr>
<td>Unit</td>
<td>физическая единица либо <code></code></td>
</tr>
<tr>
<td>Scale</td>
<td>множитель/делитель представления</td>
</tr>
<tr>
<td>Default</td>
<td>значение после сброса, если применимо</td>
</tr>
<tr>
<td>Description</td>
<td>семантика, диапазон и особые значения</td>
</tr>
</tbody>
</table>
<h2 id="section-3">Правила ведения</h2>
<ul>
<li>Адрес не переиспользуется с другим смыслом после выпуска стабильной версии.</li>
<li>Многорегистровое значение занимает непрерывный диапазон.</li>
<li>Порядок 16-битных слов для 32-битного значения фиксируется в строке типа.</li>
<li>Резервные диапазоны явно отмечаются и не используются без изменения версии.</li>
<li>Удалённый параметр помечается deprecated, а не исчезает молча.</li>
<li>Изменение адреса, типа или масштаба отражается в <code>CHANGELOG.md</code>.</li>
</ul>
<h2 id="section-4">Экспорт</h2>
<p>Для программной генерации каталог следует экспортировать из XLSX в CSV с
UTF-8 и фиксированными английскими именами колонок. CSV должен проверяться на:</p>
<ul>
<li>уникальность адресов;</li>
<li>пересечение многорегистровых значений;</li>
<li>допустимые типы и права доступа;</li>
<li>равенство шестнадцатеричного и десятичного адреса;</li>
<li>попадание адреса в диапазон <code>0x0000…0xFFFF</code>.</li>
</ul>
<p>До появления автоматического экспортёра нормативным источником адресов
остаётся XLSX, а HTML/Markdown считаются представлением.</p>
<hr>
<h1 id="protocan">История изменений ProtoCAN</h1>
<p>Формат основан на Keep a Changelog. Версия относится к спецификации, а не к
версии прошивки отдельного прибора.</p>
<h2 id="unreleased">[Unreleased]</h2>
<h3 id="added">Added</h3>
<ul>
<li>Структурированный комплект документации <code>docs/protocan</code>.</li>
<li>Загрузочный сервис <code>MsgType=0x9…0xD</code>.</li>
<li>Два логических слота по 512 КиБ, 65 536 блоков по 8 байт каждый.</li>
<li>Машинные эталоны CAN ID в <code>examples/test-vectors.json</code>.</li>
</ul>
<h2 id="section">[1.0] — 2026-08-29</h2>
<h3 id="added-1">Added</h3>
<ul>
<li>Зафиксирована 29-битная структура ProtoCAN ID.</li>
<li>Зафиксирована адресация 8 типов по 16 экземпляров.</li>
<li>Существующие сообщения <code>0x0…0x8</code>, <code>0xE</code>, <code>0xF</code> сохранены.</li>
</ul>
</body>
</html>