docs: move SETCAN documentation into templates
This commit is contained in:
@@ -18,6 +18,9 @@ templates/
|
|||||||
Пошаговая раскладка нового проекта и выбор портов для STM32F103, STM32G431
|
Пошаговая раскладка нового проекта и выбор портов для STM32F103, STM32G431
|
||||||
и STM32G474 описаны в [`NEW_PROJECT.md`](NEW_PROJECT.md).
|
и STM32G474 описаны в [`NEW_PROJECT.md`](NEW_PROJECT.md).
|
||||||
|
|
||||||
|
Нормативная документация SETCAN/ProtoCAN, реестр общего адресного пространства
|
||||||
|
и исходный Excel собраны в [`doc/setcan`](doc/setcan/README.md).
|
||||||
|
|
||||||
## Что лежит
|
## Что лежит
|
||||||
|
|
||||||
### C
|
### C
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
30
doc/setcan/README.md
Normal file
30
doc/setcan/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Документация SETCAN / ProtoCAN
|
||||||
|
|
||||||
|
Комплект перенесён из отдельного репозитория `SETCAN` (commit `ab60e58`) в
|
||||||
|
единый репозиторий `templates`. Здесь сохранены нормативные документы,
|
||||||
|
тестовые векторы, исходный реестр ОАП в Excel и автономная HTML-версия.
|
||||||
|
|
||||||
|
## Актуальное расположение кода
|
||||||
|
|
||||||
|
| Область | Модуль templates |
|
||||||
|
|---|---|
|
||||||
|
| Однокадровые SETTINGS и bxCAN STM32F1 | [`c/can-sensor`](../../c/can-sensor) |
|
||||||
|
| Единый SET protocol v2 | [`c/set-protocol`](../../c/set-protocol) |
|
||||||
|
| Транспорт ProtoCAN | [`c/protocan-transport`](../../c/protocan-transport) |
|
||||||
|
| CAN-загрузчик | [`c/protocan-boot`](../../c/protocan-boot) |
|
||||||
|
|
||||||
|
Файлы `Inc/protocan.h` и `Src/protocan.c`, упомянутые в историческом
|
||||||
|
[руководстве](index.html), больше не являются подключаемым исходным кодом.
|
||||||
|
В новых проектах используются нормализованные модули из таблицы выше.
|
||||||
|
|
||||||
|
## Документы
|
||||||
|
|
||||||
|
- [Интерактивное руководство](index.html)
|
||||||
|
- [Базовый протокол](protocan/PROTOCOL.md)
|
||||||
|
- [Загрузчик](protocan/BOOTLOADER.md)
|
||||||
|
- [Общее адресное пространство](protocan/OAP.md)
|
||||||
|
- [Редактируемый реестр ОАП](Протокол%20CAN%20и%20ОАП.xlsx)
|
||||||
|
- [Тестовые векторы](protocan/examples/test-vectors.json)
|
||||||
|
|
||||||
|
Для пересборки HTML запустите `doc/setcan/build-html.bat` из корня
|
||||||
|
репозитория `templates`.
|
||||||
13
doc/setcan/build-html.bat
Normal file
13
doc/setcan/build-html.bat
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
|
||||||
|
where pwsh.exe >nul 2>nul
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
pwsh.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build-html.ps1"
|
||||||
|
) else (
|
||||||
|
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build-html.ps1"
|
||||||
|
)
|
||||||
|
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
87
doc/setcan/build-html.ps1
Normal file
87
doc/setcan/build-html.ps1
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$outputPath = Join-Path $PSScriptRoot 'index.html'
|
||||||
|
$sourceDirectory = Join-Path $PSScriptRoot 'protocan'
|
||||||
|
$documents = @(
|
||||||
|
'README.md',
|
||||||
|
'PROTOCOL.md',
|
||||||
|
'BOOTLOADER.md',
|
||||||
|
'OAP.md',
|
||||||
|
'CHANGELOG.md'
|
||||||
|
)
|
||||||
|
|
||||||
|
function Convert-LocalLinks {
|
||||||
|
param(
|
||||||
|
[string]$Html,
|
||||||
|
[string]$SourcePath
|
||||||
|
)
|
||||||
|
|
||||||
|
$sourceParent = Split-Path -Parent $SourcePath
|
||||||
|
$outputParent = Split-Path -Parent $outputPath
|
||||||
|
$pattern = '(?<attribute>href|src)="(?<target>(?![a-z]+:|/|#)[^"]+)"'
|
||||||
|
|
||||||
|
return [regex]::Replace($Html, $pattern, {
|
||||||
|
param($match)
|
||||||
|
|
||||||
|
$target = $match.Groups['target'].Value
|
||||||
|
$parts = $target -split '#', 2
|
||||||
|
$targetPath = [Uri]::UnescapeDataString($parts[0])
|
||||||
|
$absoluteTarget = [System.IO.Path]::GetFullPath((Join-Path $sourceParent $targetPath))
|
||||||
|
$relativeTarget = [System.IO.Path]::GetRelativePath($outputParent, $absoluteTarget).Replace('\', '/')
|
||||||
|
if ($parts.Count -eq 2) {
|
||||||
|
$relativeTarget += '#' + $parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return $match.Groups['attribute'].Value + '="' + $relativeTarget + '"'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$sections = foreach ($document in $documents) {
|
||||||
|
$path = Join-Path $sourceDirectory $document
|
||||||
|
$markdown = Get-Content -Raw -LiteralPath $path -Encoding UTF8
|
||||||
|
$html = (ConvertFrom-Markdown -InputObject $markdown).Html
|
||||||
|
$html = Convert-LocalLinks -Html $html -SourcePath $path
|
||||||
|
"<article class=`"card protocan-document`" data-source=`"$document`">$html</article>"
|
||||||
|
}
|
||||||
|
|
||||||
|
$vectorsPath = Join-Path $sourceDirectory 'examples\test-vectors.json'
|
||||||
|
$vectors = [System.Net.WebUtility]::HtmlEncode(
|
||||||
|
(Get-Content -Raw -LiteralPath $vectorsPath -Encoding UTF8)
|
||||||
|
)
|
||||||
|
$sections += @"
|
||||||
|
<article class="card protocan-document" data-source="examples/test-vectors.json">
|
||||||
|
<h1>Тестовые векторы</h1>
|
||||||
|
<p>Машинные эталоны из <code>examples/test-vectors.json</code>.</p>
|
||||||
|
<pre><code>$vectors</code></pre>
|
||||||
|
</article>
|
||||||
|
"@
|
||||||
|
|
||||||
|
$generatedBlock = @"
|
||||||
|
<!-- PROTOCAN:START -->
|
||||||
|
<section id="protocan-full" class="section">
|
||||||
|
<h2>Полная документация ProtoCAN</h2>
|
||||||
|
<p class="lead">Нормативные документы и тестовые векторы собраны в эту страницу из исходников <code>doc/setcan/protocan</code>.</p>
|
||||||
|
<div class="grid protocan-grid">
|
||||||
|
$($sections -join "`n")
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- PROTOCAN:END -->
|
||||||
|
"@
|
||||||
|
|
||||||
|
$page = Get-Content -Raw -LiteralPath $outputPath -Encoding UTF8
|
||||||
|
$pattern = '(?s)<!-- PROTOCAN:START -->.*?<!-- PROTOCAN:END -->'
|
||||||
|
if ($page -notmatch $pattern) {
|
||||||
|
throw 'Не найдены маркеры PROTOCAN:START/END в doc/setcan/index.html.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = [regex]::Replace($page, $pattern, [System.Text.RegularExpressions.MatchEvaluator]{
|
||||||
|
param($match)
|
||||||
|
$generatedBlock
|
||||||
|
}, 1)
|
||||||
|
|
||||||
|
$page = $page.TrimEnd("`r", "`n") + [Environment]::NewLine
|
||||||
|
$utf8WithoutBom = [System.Text.UTF8Encoding]::new($false)
|
||||||
|
[System.IO.File]::WriteAllText($outputPath, $page, $utf8WithoutBom)
|
||||||
|
Write-Host "[DONE] Создан единый HTML: $outputPath"
|
||||||
1203
doc/setcan/index.html
Normal file
1203
doc/setcan/index.html
Normal file
File diff suppressed because one or more lines are too long
183
doc/setcan/protocan/BOOTLOADER.md
Normal file
183
doc/setcan/protocan/BOOTLOADER.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# ProtoCAN Boot Protocol
|
||||||
|
|
||||||
|
Статус: **Draft**<br>
|
||||||
|
Версия протокола: **1.0**<br>
|
||||||
|
Совместимость: **classic CAN 2.0B, Extended ID, DLC 0…8**<br>
|
||||||
|
Реализация: [`templates/c/protocan-boot`](../../../c/protocan-boot)
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Сервис обновляет адресованный прибор по CAN и поддерживает два логических
|
||||||
|
слота A/B. Активный слот не стирается: новый образ записывается в неактивный,
|
||||||
|
проверяется и атомарно назначается кандидатом на запуск.
|
||||||
|
|
||||||
|
## Карта сообщений
|
||||||
|
|
||||||
|
| `MsgType` | Имя | `MsgBody` | Payload |
|
||||||
|
|---:|---|---|---|
|
||||||
|
| `0x9` | `BOOT_CONTROL` | `SessionID[15:8] \| Command[7:0]` | параметры команды |
|
||||||
|
| `0xA` | `BOOT_DATA_A` | `BlockIndex[15:0]` | 8 байт слота A |
|
||||||
|
| `0xB` | `BOOT_DATA_B` | `BlockIndex[15:0]` | 8 байт слота B |
|
||||||
|
| `0xC` | `BOOT_STATUS` | `SessionID[15:8] \| Command[7:0]` | статус и прогресс |
|
||||||
|
| `0xD` | `BOOT_DISCOVERY` | подтип ответа | идентификация |
|
||||||
|
|
||||||
|
Все команды записи адресуются конкретному `DeviceType/DeviceID` и имеют
|
||||||
|
`Route=0`. Ответы сохраняют адрес прибора и имеют `Route=1`.
|
||||||
|
|
||||||
|
## Адресация образа
|
||||||
|
|
||||||
|
`MsgBody` кадра данных — номер 8-байтового блока:
|
||||||
|
|
||||||
|
```c
|
||||||
|
offset = (uint32_t)BlockIndex * 8U;
|
||||||
|
address = SLOT_X_BASE + offset;
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
512 КиБ = 524 288 байт
|
||||||
|
524 288 / 8 = 65 536 блоков
|
||||||
|
BlockIndex = 0x0000…0xFFFF
|
||||||
|
```
|
||||||
|
|
||||||
|
| `BlockIndex` | Смещение | Диапазон байтов |
|
||||||
|
|---:|---:|---:|
|
||||||
|
| `0x0000` | `0x00000` | `0x00000…0x00007` |
|
||||||
|
| `0x0001` | `0x00008` | `0x00008…0x0000F` |
|
||||||
|
| `0xFFFF` | `0x7FFF8` | `0x7FFF8…0x7FFFF` |
|
||||||
|
|
||||||
|
`0x80000` является первой позицией за границей слота. Последний кадр
|
||||||
|
дополняется `0xFF`, но CRC32 вычисляется только по `ImageSize` байтам.
|
||||||
|
|
||||||
|
## Команды `BOOT_CONTROL`
|
||||||
|
|
||||||
|
| Код | Команда | DLC | Payload | Допустимое состояние |
|
||||||
|
|---:|---|---:|---|---|
|
||||||
|
| `0x01` | `IDENTIFY` | 0 | отсутствует | любое |
|
||||||
|
| `0x02` | `ENTER_BOOT` | 0 | отсутствует | любое; `SessionID != 0` |
|
||||||
|
| `0x03` | `BEGIN_IMAGE` | 8 | размер и CRC32 | metadata |
|
||||||
|
| `0x04` | `BEGIN_COMPAT` | 8 | совместимость и версия | metadata |
|
||||||
|
| `0x05` | `ERASE` | 0 | отсутствует | ready-to-erase |
|
||||||
|
| `0x06` | `VERIFY` | 0 | отсутствует | образ получен |
|
||||||
|
| `0x07` | `COMMIT` | 0 | отсутствует | verified |
|
||||||
|
| `0x08` | `CONFIRM` | 0 | отсутствует | запущенное приложение |
|
||||||
|
| `0x09` | `REBOOT` | 0 | отсутствует | активная сессия |
|
||||||
|
| `0x0A` | `ABORT` | 0 | отсутствует | активная сессия |
|
||||||
|
| `0x0B` | `QUERY_PROGRESS` | 0 | отсутствует | активная сессия |
|
||||||
|
|
||||||
|
### `BEGIN_IMAGE`
|
||||||
|
|
||||||
|
```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] HardwareRevisionMin
|
||||||
|
DATA[3] HardwareRevisionMax
|
||||||
|
DATA[4..7] FirmwareVersion, uint32 little-endian
|
||||||
|
```
|
||||||
|
|
||||||
|
До `ERASE` прибор обязан получить обе части метаданных и проверить размер,
|
||||||
|
тип изделия, аппаратную ревизию, версию и политику anti-rollback.
|
||||||
|
|
||||||
|
## `BOOT_STATUS`
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
| Код | Статус | Повтор допустим |
|
||||||
|
|---:|---|---|
|
||||||
|
| `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` | да, с `NextBlock` |
|
||||||
|
| `0x09` | `SIGNATURE_ERROR` | нет |
|
||||||
|
| `0x0A` | `SESSION_ERROR` | открыть новую сессию |
|
||||||
|
| `0x0B` | `VOLTAGE_ERROR` | да после нормализации питания |
|
||||||
|
| `0x0C` | `INVALID_STATE` | выполнить правильный переход |
|
||||||
|
|
||||||
|
## State machine
|
||||||
|
|
||||||
|
```text
|
||||||
|
IDLE
|
||||||
|
└─ ENTER_BOOT ─> METADATA
|
||||||
|
├─ BEGIN_IMAGE
|
||||||
|
└─ BEGIN_COMPAT
|
||||||
|
│
|
||||||
|
v
|
||||||
|
READY_TO_ERASE
|
||||||
|
│ ERASE
|
||||||
|
v
|
||||||
|
RECEIVING
|
||||||
|
│ VERIFY
|
||||||
|
v
|
||||||
|
VERIFIED
|
||||||
|
│ COMMIT
|
||||||
|
v
|
||||||
|
PENDING + REBOOT
|
||||||
|
│ CONFIRM
|
||||||
|
v
|
||||||
|
CONFIRMED
|
||||||
|
```
|
||||||
|
|
||||||
|
Ошибка Flash, CRC, совместимости или подписи переводит сессию в `FAILED`.
|
||||||
|
Новая `ENTER_BOOT` создаёт чистую сессию. `ABORT` прекращает текущую передачу,
|
||||||
|
не активируя частично записанный слот.
|
||||||
|
|
||||||
|
## Надёжность и повторы
|
||||||
|
|
||||||
|
- Блоки передаются строго по возрастанию `BlockIndex`.
|
||||||
|
- Дубликат или пропуск возвращает `SEQUENCE_ERROR` и ожидаемый `NextBlock`.
|
||||||
|
- Базовый режим подтверждает каждый блок.
|
||||||
|
- Рабочий режим может подтверждать окно из 16 блоков.
|
||||||
|
- После потери связи `QUERY_PROGRESS` возвращает следующий ожидаемый блок,
|
||||||
|
пока состояние загрузчика сохранено.
|
||||||
|
- Для продолжения после перезагрузки порт должен сохранять session metadata
|
||||||
|
и восстановить её при инициализации; ядро версии 1.0 само это не делает.
|
||||||
|
|
||||||
|
## Безопасность и A/B-обновление
|
||||||
|
|
||||||
|
```text
|
||||||
|
active=A -> target=B -> verify -> pending=B
|
||||||
|
active=B -> target=A -> verify -> pending=A
|
||||||
|
```
|
||||||
|
|
||||||
|
CRC32 защищает только от случайного повреждения. Серийный загрузчик должен
|
||||||
|
дополнительно проверить подпись контейнера, границы вектора, совместимость и
|
||||||
|
anti-rollback. Bootloader не обновляется командами `BOOT_DATA_A/B`.
|
||||||
|
|
||||||
|
Boot metadata должна атомарно хранить:
|
||||||
|
|
||||||
|
- активный слот;
|
||||||
|
- pending-слот;
|
||||||
|
- подтверждение запуска;
|
||||||
|
- число неудачных попыток;
|
||||||
|
- версию и CRC32 образа.
|
||||||
|
|
||||||
|
Если приложение не выполняет `CONFIRM` за установленное число запусков,
|
||||||
|
загрузчик возвращается к предыдущему подтверждённому слоту.
|
||||||
|
|
||||||
|
## Эталонный сценарий
|
||||||
|
|
||||||
|
1. ПМ адресно отправляет `IDENTIFY`.
|
||||||
|
2. ПМ открывает ненулевой `SessionID` командой `ENTER_BOOT`.
|
||||||
|
3. ПМ отправляет `BEGIN_IMAGE` и `BEGIN_COMPAT`.
|
||||||
|
4. Прибор сообщает выбранный неактивный слот.
|
||||||
|
5. ПМ выполняет `ERASE` и передаёт `BOOT_DATA_A` либо `BOOT_DATA_B`.
|
||||||
|
6. ПМ выполняет `VERIFY`, затем `COMMIT` и `REBOOT`.
|
||||||
|
7. Новое приложение после самопроверки выполняет `CONFIRM`.
|
||||||
21
doc/setcan/protocan/CHANGELOG.md
Normal file
21
doc/setcan/protocan/CHANGELOG.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# История изменений ProtoCAN
|
||||||
|
|
||||||
|
Формат основан на Keep a Changelog. Версия относится к спецификации, а не к
|
||||||
|
версии прошивки отдельного прибора.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Структурированный комплект документации `doc/setcan/protocan`.
|
||||||
|
- Загрузочный сервис `MsgType=0x9…0xD`.
|
||||||
|
- Два логических слота по 512 КиБ, 65 536 блоков по 8 байт каждый.
|
||||||
|
- Машинные эталоны CAN ID в `examples/test-vectors.json`.
|
||||||
|
|
||||||
|
## [1.0] — 2026-08-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Зафиксирована 29-битная структура ProtoCAN ID.
|
||||||
|
- Зафиксирована адресация 8 типов по 16 экземпляров.
|
||||||
|
- Существующие сообщения `0x0…0x8`, `0xE`, `0xF` сохранены.
|
||||||
56
doc/setcan/protocan/OAP.md
Normal file
56
doc/setcan/protocan/OAP.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Общее адресное пространство
|
||||||
|
|
||||||
|
Статус: **Stable, данные ведутся в XLSX**<br>
|
||||||
|
Порядок значений: **16-битные регистры, little-endian в CAN payload**
|
||||||
|
|
||||||
|
Редактируемый источник реестра:
|
||||||
|
[`Протокол CAN и ОАП.xlsx`](../Протокол%20CAN%20и%20ОАП.xlsx).
|
||||||
|
|
||||||
|
Просматриваемая большая таблица находится в
|
||||||
|
[`Протокол CAN и ОАП.html`](../Протокол%20CAN%20и%20ОАП.html) и
|
||||||
|
[`Протокол CAN и ОАП.md`](../Протокол%20CAN%20и%20ОАП.md).
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
ОАП связывает 16-битный адрес регистра с его типом, назначением, доступом и
|
||||||
|
масштабом. В ProtoCAN используется `MsgType=0x3`, а `MsgBody` содержит адрес
|
||||||
|
первого регистра.
|
||||||
|
|
||||||
|
## Обязательные поля реестра
|
||||||
|
|
||||||
|
| Поле | Требование |
|
||||||
|
|---|---|
|
||||||
|
| AddressHex | `0x0000…0xFFFF`, уникальное значение |
|
||||||
|
| AddressDec | десятичный эквивалент AddressHex |
|
||||||
|
| Group | функциональная группа |
|
||||||
|
| Name | однозначное имя параметра |
|
||||||
|
| Type | `u16`, `i16`, `u32`, `i32`, `float32`, bitmap или массив |
|
||||||
|
| Registers | число занятых 16-битных регистров |
|
||||||
|
| Access | `R`, `W` или `RW` |
|
||||||
|
| Unit | физическая единица либо `—` |
|
||||||
|
| Scale | множитель/делитель представления |
|
||||||
|
| Default | значение после сброса, если применимо |
|
||||||
|
| Description | семантика, диапазон и особые значения |
|
||||||
|
|
||||||
|
## Правила ведения
|
||||||
|
|
||||||
|
- Адрес не переиспользуется с другим смыслом после выпуска стабильной версии.
|
||||||
|
- Многорегистровое значение занимает непрерывный диапазон.
|
||||||
|
- Порядок 16-битных слов для 32-битного значения фиксируется в строке типа.
|
||||||
|
- Резервные диапазоны явно отмечаются и не используются без изменения версии.
|
||||||
|
- Удалённый параметр помечается deprecated, а не исчезает молча.
|
||||||
|
- Изменение адреса, типа или масштаба отражается в `CHANGELOG.md`.
|
||||||
|
|
||||||
|
## Экспорт
|
||||||
|
|
||||||
|
Для программной генерации каталог следует экспортировать из XLSX в CSV с
|
||||||
|
UTF-8 и фиксированными английскими именами колонок. CSV должен проверяться на:
|
||||||
|
|
||||||
|
- уникальность адресов;
|
||||||
|
- пересечение многорегистровых значений;
|
||||||
|
- допустимые типы и права доступа;
|
||||||
|
- равенство шестнадцатеричного и десятичного адреса;
|
||||||
|
- попадание адреса в диапазон `0x0000…0xFFFF`.
|
||||||
|
|
||||||
|
До появления автоматического экспортёра нормативным источником адресов
|
||||||
|
остаётся XLSX, а HTML/Markdown считаются представлением.
|
||||||
114
doc/setcan/protocan/PROTOCOL.md
Normal file
114
doc/setcan/protocan/PROTOCOL.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# ProtoCAN — базовый протокол
|
||||||
|
|
||||||
|
Статус: **Stable с зарезервированным загрузочным расширением**<br>
|
||||||
|
Версия: **1.0**<br>
|
||||||
|
Порядок байтов payload: **little-endian**, если явно не указано иное
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
ProtoCAN — прикладной протокол поверх classic CAN 2.0B. Используются только
|
||||||
|
расширенные 29-битные идентификаторы (`IDE=1`) и payload длиной 0…8 байт.
|
||||||
|
|
||||||
|
## Термины
|
||||||
|
|
||||||
|
| Термин | Значение |
|
||||||
|
|---|---|
|
||||||
|
| ПМ | управляющий модуль |
|
||||||
|
| прибор | адресуемый узел на шине |
|
||||||
|
| `DeviceType` | тип прибора, 0…7 |
|
||||||
|
| `DeviceID` | экземпляр прибора данного типа, 0…15 |
|
||||||
|
| `MsgType` | класс сообщения или сервис |
|
||||||
|
| `MsgBody` | 16-битное поле, формат которого зависит от `MsgType` |
|
||||||
|
|
||||||
|
Пара `DeviceType/DeviceID` задаёт до `8 × 16 = 128` уникальных адресов.
|
||||||
|
|
||||||
|
## Расширенный CAN ID
|
||||||
|
|
||||||
|
```text
|
||||||
|
28 27 26...24 23...20 19...16 15........0
|
||||||
|
Priority Route DeviceType DeviceID MsgType MsgBody
|
||||||
|
1 бит 1 бит 3 бита 4 бита 4 бита 16 бит
|
||||||
|
```
|
||||||
|
|
||||||
|
```c
|
||||||
|
can_id =
|
||||||
|
((uint32_t)priority << 28) |
|
||||||
|
((uint32_t)route << 27) |
|
||||||
|
((uint32_t)device_type << 24) |
|
||||||
|
((uint32_t)device_id << 20) |
|
||||||
|
((uint32_t)msg_type << 16) |
|
||||||
|
msg_body;
|
||||||
|
```
|
||||||
|
|
||||||
|
| Поле | Значения | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `Priority` | `0` critical, `1` standard | CAN-арбитраж |
|
||||||
|
| `Route` | `0` от ПМ, `1` от прибора | логическое направление |
|
||||||
|
| `DeviceType` | `0…7` | тип прибора |
|
||||||
|
| `DeviceID` | `0…15` | номер экземпляра |
|
||||||
|
| `MsgType` | `0…15` | тип сообщения |
|
||||||
|
| `MsgBody` | `0…65535` | команда, адрес или номер блока |
|
||||||
|
|
||||||
|
`Route` не является направлением физического трансивера. Ответ прибора
|
||||||
|
сохраняет адрес `DeviceType/DeviceID` и устанавливает `Route=1`.
|
||||||
|
|
||||||
|
## Реестр `MsgType`
|
||||||
|
|
||||||
|
| Код | Имя | Основное направление | DLC | Статус |
|
||||||
|
|---:|---|---|---:|---|
|
||||||
|
| `0x0` | `BROADCAST` | ПМ → все | зависит от команды | stable |
|
||||||
|
| `0x1` | `DISCRETE` | оба | 0…8 | stable |
|
||||||
|
| `0x2` | `ANALOG` | оба | 0…8 | stable |
|
||||||
|
| `0x3` | `GAS` | оба | 0/2/4/6/8 | stable |
|
||||||
|
| `0x4` | `MODBUS_COIL` | оба | 0…8 | stable |
|
||||||
|
| `0x5` | `MODBUS_DISCRETE` | оба | 0…8 | stable |
|
||||||
|
| `0x6` | `MODBUS_HOLDING` | оба | 0…8 | stable |
|
||||||
|
| `0x7` | `MODBUS_INPUT` | оба | 0…8 | stable |
|
||||||
|
| `0x8` | `ERROR` | прибор → ПМ | 0 | stable |
|
||||||
|
| `0x9` | `BOOT_CONTROL` | ПМ → прибор | 0/8 | draft |
|
||||||
|
| `0xA` | `BOOT_DATA_A` | ПМ → прибор | 8 | draft |
|
||||||
|
| `0xB` | `BOOT_DATA_B` | ПМ → прибор | 8 | draft |
|
||||||
|
| `0xC` | `BOOT_STATUS` | прибор → ПМ | 8 | draft |
|
||||||
|
| `0xD` | `BOOT_DISCOVERY` | прибор → ПМ | 8 | draft |
|
||||||
|
| `0xE` | `SETTINGS` | оба | 0/1/8 | stable |
|
||||||
|
| `0xF` | `PULSE` | прибор → сеть | 1 | stable |
|
||||||
|
|
||||||
|
Подробный формат `0x9…0xD` находится в [BOOTLOADER.md](BOOTLOADER.md).
|
||||||
|
|
||||||
|
## Разметки `MsgBody`
|
||||||
|
|
||||||
|
| `MsgType` | Биты `MsgBody` |
|
||||||
|
|---|---|
|
||||||
|
| broadcast | команда `[15:4]`, параметр `[3:0]` |
|
||||||
|
| discrete/analog | подтип `[15:12]`, значение/адрес `[11:0]` |
|
||||||
|
| Modbus | начальный адрес `[15:4]`, количество `[3:0]` |
|
||||||
|
| GAS | адрес первого 16-битного регистра `[15:0]` |
|
||||||
|
| error | дополнительная информация `[15:8]`, код `[7:0]` |
|
||||||
|
| settings | номер сборки `[15:8]`, позиция `[7:0]` |
|
||||||
|
| boot control/status | `SessionID[15:8]`, команда `[7:0]` |
|
||||||
|
| boot data | `BlockIndex[15:0]` |
|
||||||
|
|
||||||
|
## Общие правила обмена
|
||||||
|
|
||||||
|
- Многобайтовые значения в `DATA` передаются little-endian.
|
||||||
|
- Узел игнорирует адресованные кадры с чужим `DeviceType/DeviceID`.
|
||||||
|
- Прибор принимает команды ПМ с `Route=0`; ПМ принимает ответы с `Route=1`.
|
||||||
|
- Стандартные 11-битные CAN ID не являются кадрами ProtoCAN.
|
||||||
|
- RTR для загрузочного сервиса запрещён.
|
||||||
|
- Неописанные комбинации `MsgType/MsgBody/DLC` должны отвергаться.
|
||||||
|
|
||||||
|
## Эталон упаковки ID
|
||||||
|
|
||||||
|
```text
|
||||||
|
Priority = 1
|
||||||
|
Route = 0
|
||||||
|
DeviceType = 3
|
||||||
|
DeviceID = 5
|
||||||
|
MsgType = 0x9
|
||||||
|
MsgBody = 0x0702
|
||||||
|
|
||||||
|
CAN ID = 0x13590702
|
||||||
|
```
|
||||||
|
|
||||||
|
Этот пример соответствует `ENTER_BOOT`, `SessionID=7`. Машинные варианты
|
||||||
|
находятся в [examples/test-vectors.json](examples/test-vectors.json).
|
||||||
43
doc/setcan/protocan/README.md
Normal file
43
doc/setcan/protocan/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Документация ProtoCAN
|
||||||
|
|
||||||
|
Статус комплекта: **Draft**<br>
|
||||||
|
Версия комплекта: **1.0**<br>
|
||||||
|
Дата редакции: **2026-08-29**<br>
|
||||||
|
Транспорт: **Classic CAN 2.0B, Extended ID, DLC 0…8**
|
||||||
|
|
||||||
|
Этот каталог разделяет нормативное описание протокола, загрузчик и реестр
|
||||||
|
общего адресного пространства. Большой исходный документ
|
||||||
|
[`Протокол CAN и ОАП.md`](../Протокол%20CAN%20и%20ОАП.md) сохранён как
|
||||||
|
совместимое представление таблиц из Excel.
|
||||||
|
|
||||||
|
## Документы
|
||||||
|
|
||||||
|
| Документ | Назначение | Статус источника |
|
||||||
|
|---|---|---|
|
||||||
|
| [PROTOCOL.md](PROTOCOL.md) | 29-битный CAN ID, адресация, реестр `MsgType`, порядок байтов | нормативный |
|
||||||
|
| [BOOTLOADER.md](BOOTLOADER.md) | обновление прошивки, кадры, состояния, ошибки и A/B-слоты | нормативный draft |
|
||||||
|
| [OAP.md](OAP.md) | правила ведения общего адресного пространства | нормативный индекс |
|
||||||
|
| [../Протокол CAN и ОАП.xlsx](../Протокол%20CAN%20и%20ОАП.xlsx) | редактируемый реестр ОАП | источник таблиц |
|
||||||
|
| [examples/test-vectors.json](examples/test-vectors.json) | машинные эталоны CAN ID и payload | нормативные примеры |
|
||||||
|
| [CHANGELOG.md](CHANGELOG.md) | история версий документа | нормативный |
|
||||||
|
|
||||||
|
## Приоритет источников
|
||||||
|
|
||||||
|
При расхождении данных действует следующий порядок:
|
||||||
|
|
||||||
|
1. `PROTOCOL.md` — структура ProtoCAN и реестр типов сообщений.
|
||||||
|
2. `BOOTLOADER.md` — загрузочный сервис `0x9…0xD`.
|
||||||
|
3. XLSX — адреса и свойства регистров ОАП.
|
||||||
|
4. Сгенерированный [`../index.html`](../index.html) — только представление, не самостоятельный источник.
|
||||||
|
|
||||||
|
## Сборка HTML
|
||||||
|
|
||||||
|
Из корня проекта:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
./doc/setcan/build-html.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
Все документы и тестовые векторы включаются в единый файл
|
||||||
|
`doc/setcan/index.html`.
|
||||||
|
Скрипт не изменяет исходные Markdown/XLSX и пригоден для запуска в CI.
|
||||||
45
doc/setcan/protocan/examples/test-vectors.json
Normal file
45
doc/setcan/protocan/examples/test-vectors.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"byte_order": "little-endian",
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"name": "enter_boot_session_7",
|
||||||
|
"direction": "pm_to_device",
|
||||||
|
"priority": 1,
|
||||||
|
"route": 0,
|
||||||
|
"device_type": 3,
|
||||||
|
"device_id": 5,
|
||||||
|
"msg_type": 9,
|
||||||
|
"msg_body": 1794,
|
||||||
|
"can_id_hex": "0x13590702",
|
||||||
|
"dlc": 0,
|
||||||
|
"data_hex": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slot_b_block_1",
|
||||||
|
"direction": "pm_to_device",
|
||||||
|
"priority": 1,
|
||||||
|
"route": 0,
|
||||||
|
"device_type": 3,
|
||||||
|
"device_id": 5,
|
||||||
|
"msg_type": 11,
|
||||||
|
"msg_body": 1,
|
||||||
|
"can_id_hex": "0x135B0001",
|
||||||
|
"dlc": 8,
|
||||||
|
"data_hex": "1011121314151617"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enter_boot_ok",
|
||||||
|
"direction": "device_to_pm",
|
||||||
|
"priority": 1,
|
||||||
|
"route": 1,
|
||||||
|
"device_type": 3,
|
||||||
|
"device_id": 5,
|
||||||
|
"msg_type": 12,
|
||||||
|
"msg_body": 1794,
|
||||||
|
"can_id_hex": "0x1B5C0702",
|
||||||
|
"dlc": 8,
|
||||||
|
"data_hex": "00FF000000000000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13404
doc/setcan/Протокол CAN и ОАП.html
Normal file
13404
doc/setcan/Протокол CAN и ОАП.html
Normal file
File diff suppressed because it is too large
Load Diff
13276
doc/setcan/Протокол CAN и ОАП.md
Normal file
13276
doc/setcan/Протокол CAN и ОАП.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
doc/setcan/Протокол CAN и ОАП.xlsx
Normal file
BIN
doc/setcan/Протокол CAN и ОАП.xlsx
Normal file
Binary file not shown.
1
doc/setcan/ссылки на док.md
Normal file
1
doc/setcan/ссылки на док.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://disk.yandex.ru/i/TMyHlpYxIP75YQ
|
||||||
Reference in New Issue
Block a user