feat(protocan-boot): Добавь порты MCU и сервисы загрузки
Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент. Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Результаты отдельных конфигураций CCS воспроизводятся установленным TI compiler.
|
||||
/projects/ccs/*/Debug/
|
||||
/projects/ccs/*/Release/
|
||||
# Служебные локальные настройки IDE и launch не входят в переносимый проект.
|
||||
/projects/ccs/*/.settings/
|
||||
/projects/ccs/*/.launches/
|
||||
# Python cache не является исходником общего модуля или проекта.
|
||||
**/__pycache__/
|
||||
@@ -1,6 +1,6 @@
|
||||
# Сборка библиотеки protocan_boot. Состав исходников и публичные include-пути задают
|
||||
# подключение к проекту потребителя. Файл также собирает и регистрирует хостовые проверки;
|
||||
# запускать их следует через CTest из каталога сборки.
|
||||
# Общая C99-библиотека ProtoCAN Boot и аппаратно независимые host-тесты.
|
||||
# Каждый контроллер сохраняет собственное состояние и отдельный аппаратный порт.
|
||||
# Дополнительные проекты CCS находятся в projects/ccs и не требуют CMake для TI-сборки.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(protocan_boot C)
|
||||
@@ -17,10 +17,60 @@ else()
|
||||
target_compile_options(protocan_boot PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
option(PCAN_BOOT_BUILD_TESTS "Собирать тесты ProtoCAN boot" ON)
|
||||
option(PCAN_BOOT_BUILD_TESTS "Собирать тесты ProtoCAN Boot" ON) # Не включает host-модели в код MCU.
|
||||
if(PCAN_BOOT_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(test_pcan_boot tests/test_pcan_boot.c)
|
||||
target_link_libraries(test_pcan_boot PRIVATE protocan_boot)
|
||||
add_test(NAME pcan_boot COMMAND test_pcan_boot)
|
||||
endif()
|
||||
|
||||
if(PCAN_BOOT_BUILD_TESTS)
|
||||
add_executable(test_f28335_can_boot tests/test_f28335_can_boot.c src/pcan_boot.c ports/tms320f28335/f28335_boot_store.c)
|
||||
target_compile_definitions(test_f28335_can_boot PRIVATE PCAN_BOOT_WIDE_BYTES)
|
||||
target_include_directories(test_f28335_can_boot PRIVATE tests/f28335_stubs)
|
||||
add_test(NAME f28335_can_boot COMMAND test_f28335_can_boot)
|
||||
endif()
|
||||
|
||||
if(PCAN_BOOT_BUILD_TESTS)
|
||||
add_executable(test_f28335_application tests/test_f28335_can_boot.c src/pcan_boot.c ports/tms320f28335/f28335_boot_store.c)
|
||||
target_compile_definitions(test_f28335_application PRIVATE PCAN_BOOT_WIDE_BYTES F28335_BOOT_LOADER=0)
|
||||
target_include_directories(test_f28335_application PRIVATE tests/f28335_stubs)
|
||||
add_test(NAME f28335_application COMMAND test_f28335_application)
|
||||
foreach(width native wide)
|
||||
add_executable(test_f28335_store_${width} tests/test_f28335_boot_store.c ports/tms320f28335/f28335_boot_store.c)
|
||||
if(width STREQUAL "wide")
|
||||
target_compile_definitions(test_f28335_store_${width} PRIVATE PCAN_BOOT_WIDE_BYTES)
|
||||
endif()
|
||||
add_test(NAME f28335_store_${width} COMMAND test_f28335_store_${width})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(PCAN_BOOT_BUILD_TESTS) # Подключает настоящий аппаратно независимый драйвер NOR F2812 и регрессию длительного BUSY.
|
||||
add_executable(test_f2812_nor tests/test_f2812_nor.c src/pcan_boot.c ports/tms320f2812/nor_flash.c ports/tms320f2812/boot_store.c) # Компилирует production-код вместе с x16 моделью чипа.
|
||||
target_compile_definitions(test_f2812_nor PRIVATE PCAN_BOOT_WIDE_BYTES BOOT2812_HOST) # Октеты C28x широкие; подменяется только физическая шина памяти.
|
||||
add_test(NAME f2812_nor COMMAND test_f2812_nor) # Регистрирует проверки CRC, SCI8, границ, manifest и отказов NOR.
|
||||
add_executable(test_pcan_busy tests/test_pcan_busy.c src/pcan_boot.c) # Использует тот же codec, который обслуживает длинные аппаратные операции.
|
||||
target_compile_definitions(test_pcan_busy PRIVATE PCAN_BOOT_WIDE_BYTES) # Проверяет wire-статус при 16-битном представлении октета C28x.
|
||||
add_test(NAME pcan_busy COMMAND test_pcan_busy) # Регистрирует проверку продления session при живом прогрессе erase.
|
||||
endif() # Включение библиотек без тестов не добавляет host-модель в прошивку.
|
||||
|
||||
if(PCAN_BOOT_BUILD_TESTS) # STM32-порты проверяются с моделью HAL и приватным отображением Flash, без доступа к плате.
|
||||
foreach(family f103 f407 g474) # Каждый процесс компилирует настоящую реализацию bxCAN/FDCAN одного семейства.
|
||||
add_executable(test_stm32${family} tests/test_stm32_ports.c ports/stm32${family}/pcan_boot_stm32${family}.c) # Подключает общий сценарий и семейный production-порт.
|
||||
target_link_libraries(test_stm32${family} PRIVATE protocan_boot) # Использует тот же автомат и CRC, что библиотека устройства.
|
||||
target_include_directories(test_stm32${family} PRIVATE tests/stm32_stubs) # Только host-цели видят подменённые семейные заголовки HAL.
|
||||
if(family STREQUAL "f103") # Один сценарий выбирает адреса и аппаратную единицу записи F103.
|
||||
target_compile_definitions(test_stm32${family} PRIVATE TEST_STM32F103) # F407 использует вторую ветку того же сценария.
|
||||
endif() # Завершает выбор семейного профиля теста.
|
||||
if(family STREQUAL "g474") # G474 использует FDCAN, doubleword и обе физические геометрии DBANK.
|
||||
target_compile_definitions(test_stm32${family} PRIVATE TEST_STM32G474) # Включает G474-модель и проверку старого shifted-DLC HAL.
|
||||
endif() # Завершает выбор аппаратного профиля G474.
|
||||
if(MSVC) # Assertions должны исполняться и в Release-конфигурации host-тестов.
|
||||
target_compile_options(test_stm32${family} PRIVATE /UNDEBUG) # Отменяет NDEBUG, чтобы аппаратные проверки и вызовы не исчезли.
|
||||
else() # GCC и Clang используют стандартную форму отмены определения макроса.
|
||||
target_compile_options(test_stm32${family} PRIVATE -UNDEBUG) # Сохраняет проверки границ памяти при любой конфигурации сборки.
|
||||
endif() # Завершает настройку обязательных assertions.
|
||||
add_test(NAME stm32${family} COMMAND test_stm32${family}) # Регистрирует регрессию полного обновления и аппаратных отказов.
|
||||
endforeach() # Завершает регистрацию трёх независимых STM32-портов.
|
||||
endif() # Без host-тестов CMake по-прежнему собирает только переносимое ядро.
|
||||
|
||||
@@ -10,12 +10,13 @@ CAN 2.0B и 29-битному ProtoCAN ID. Оно реализует сесси
|
||||
CRC32, проверку совместимости, продолжение по номеру следующего блока и
|
||||
безопасный выбор неактивного слота.
|
||||
|
||||
Это не готовый загрузчик конкретного STM32: внутри нет HAL, регистров Flash,
|
||||
linker script, обработчиков прерываний, криптографии и перехода в приложение.
|
||||
Эти операции предоставляет проект через таблицу callbacks.
|
||||
Ядро не содержит HAL или регистров Flash. Готовые аппаратные callbacks для
|
||||
STM32F103C8, STM32F407VE и STM32G474VE находятся в `ports`; подключение описано ниже.
|
||||
Linker script, startup, тактирование, GPIO и переход в приложение предоставляет
|
||||
проект устройства. Криптография в этих single-slot портах не реализована.
|
||||
|
||||
Для STM32G474VET6 из `candleLight_fw` задаются `slot_count = 1` и
|
||||
`max_image_size = 480 * 1024`. В этом режиме запись идёт в единственную
|
||||
Для [STM32G474VET6 из CANdle_stm32](ports/stm32g474/README.md) задаются
|
||||
`slot_count = 1` и `max_image_size = 480 * 1024`. В этом режиме запись идёт в единственную
|
||||
область приложения, callback `set_pending_slot` не требуется, а аппаратный
|
||||
rollback недоступен. Значение `slot_count = 0` сохраняет прежний A/B-режим.
|
||||
|
||||
@@ -97,7 +98,8 @@ typedef struct {
|
||||
} pcan_boot_port_t;
|
||||
```
|
||||
|
||||
Обязательны `send`, `erase_slot`, `write_slot` и `set_pending_slot`.
|
||||
Обязательны `send`, `erase_slot`, `write_slot`; в A/B также `set_pending_slot`.
|
||||
В single-slot этот callback необязателен и может завершать активацию образа.
|
||||
`authorize` проверяет политику до стирания (тип, версия, anti-rollback).
|
||||
`verify_image` выполняет платформенную проверку подписанного контейнера после
|
||||
CRC32. Если callbacks отсутствуют, соответствующие дополнительные проверки
|
||||
@@ -175,5 +177,41 @@ ctest --test-dir build --output-on-failure
|
||||
- включить watchdog и rollback при неподтверждённом запуске;
|
||||
- согласовать фильтры CAN для `MsgType=0x9..0xD`.
|
||||
|
||||
Проект пока не подключён ни к одной конкретной плате; это самостоятельный
|
||||
шаблон для интеграции в будущие загрузчики приборов SET.
|
||||
Ядро поддерживает платформенные порты; особенности интеграции перечислены ниже.
|
||||
|
||||
## F28335 / большая EEPROM
|
||||
|
||||
[Порт M95M02](ports/tms320f28335/README.md) подключён в `28335_periph`, ветка 167.
|
||||
Использует отдельный Flash-загрузчик и два слота EEPROM по 130560 байт.
|
||||
Реализованы readback/CRC, проверка SCI8, журнал метаданных, пробный запуск,
|
||||
CONFIRM/откат, PING/discovery и сервисный паспорт. Первичная установка Flash-
|
||||
загрузчика и выбор Flash boot описаны в README порта.
|
||||
|
||||
## STM32 / прошивка по ProtoCAN
|
||||
|
||||
| Порт | Источник профиля | Область приложения | Ёмкость |
|
||||
|---|---|---|---:|
|
||||
| [STM32F103C8](ports/stm32f103/README.md) | `KONOR_ds18b20/src/bootloader.c` | `0x08003000..0x08010000` | 52 КиБ |
|
||||
| [STM32F407VE](ports/stm32f407/README.md) | `climate_control_f407vet6_f4/Bootloader/bootloader_f407.c` | `0x08008000..0x08040000` | 224 КиБ |
|
||||
| [STM32G474VE](ports/stm32g474/README.md) | `CANdle_stm32/src/boot/g474_can_bootloader.c` | `0x08008000..0x08080000` | 480 КиБ |
|
||||
|
||||
Верхние границы исключены. F103/F407 предоставляют готовые CMSIS `boot_main.c`
|
||||
для исходных проектов и отдельные reusable HAL-порты. G474 предоставляет
|
||||
reusable HAL FDCAN/Flash-порт, CAN/UART boot main, startup и вход из приложения.
|
||||
Общие компоненты и правила подключения описаны в
|
||||
[`ports/stm32-common`](ports/stm32-common/README.md). Это ProtoCAN Boot v1 по
|
||||
CAN, с отдельной картой памяти относительно USB SET v2-загрузчиков из `set-protocol`.
|
||||
Конфигурация задаётся по именам полей и принудительно использует `slot_count=1`.
|
||||
Образы пишутся в логический слот A; A/B-rollback и постоянные commit-метаданные
|
||||
отсутствуют. Ограничения запуска после потери питания описаны в общей инструкции.
|
||||
|
||||
Проекты KONOR, climate и CANdle G474 подключены к этим исходникам через свои
|
||||
submodule `templates`; в старых путях оставлены тонкие include-обёртки.
|
||||
Состав сборок, разметка SRAM-запросов и результаты проверки перечислены в
|
||||
[`projects/STM32.md`](projects/STM32.md).
|
||||
|
||||
## Самостоятельные проекты CCS
|
||||
|
||||
Готовые проекты **F28335_CAN_AB** (M95M02) и **F2812_CAN_NOR** (AM29LV800BT),
|
||||
включая необходимые файлы поддержки TI, находятся в `projects/ccs`.
|
||||
[Импорт, сборка, карты памяти и установка](projects/README.md).
|
||||
|
||||
@@ -63,6 +63,10 @@ BlockIndex = 0x0000…0xFFFF
|
||||
| `0x09` | `REBOOT` | 0 | отсутствует | активная сессия |
|
||||
| `0x0A` | `ABORT` | 0 | отсутствует | активная сессия |
|
||||
| `0x0B` | `QUERY_PROGRESS` | 0 | отсутствует | активная сессия |
|
||||
| `0x0C` | `PING` | 0 | отсутствует | любое |
|
||||
| `0x0D` | `CAPABILITIES` | 0 | отсутствует | любое, при поддержке порта |
|
||||
| `0x0E` | `DEVICE_NAME` | 1 | номер страницы имени | любое, при поддержке порта |
|
||||
| `0x0F` | `MEMORY_INFO` | 1 | номер страницы памяти | любое, при поддержке порта |
|
||||
|
||||
### `BEGIN_IMAGE`
|
||||
|
||||
@@ -81,7 +85,8 @@ DATA[4..7] FirmwareVersion, uint32 little-endian
|
||||
```
|
||||
|
||||
До `ERASE` прибор обязан получить обе части метаданных и проверить размер,
|
||||
тип изделия, аппаратную ревизию, версию и политику anti-rollback.
|
||||
тип изделия и аппаратную ревизию. Проверка версии/anti-rollback выполняется
|
||||
только при наличии соответствующей политики платформы (callback authorize).
|
||||
|
||||
## `BOOT_STATUS`
|
||||
|
||||
@@ -181,3 +186,16 @@ Boot metadata должна атомарно хранить:
|
||||
5. ПМ выполняет `ERASE` и передаёт `BOOT_DATA_A` либо `BOOT_DATA_B`.
|
||||
6. ПМ выполняет `VERIFY`, затем `COMMIT` и `REBOOT`.
|
||||
7. Новое приложение после самопроверки выполняет `CONFIRM`.
|
||||
|
||||
## F28335 M95M02
|
||||
|
||||
Реализованы отдельный Flash-загрузчик, A/B по 130560 байт, журнал, пробный запуск
|
||||
и откат. Подробная раскладка памяти, сервисных ответов и первичная установка:
|
||||
[README порта](../ports/tms320f28335/README.md).
|
||||
|
||||
Общий discovery: Route=0, DeviceType=7, DeviceID=15, MsgType=D, Body=0, DLC=0.
|
||||
Он только запрашивает IDENTIFY; операции изменения всегда адресные.
|
||||
ENTER_BOOT может отложить ACK до готовности отдельного загрузчика после reset.
|
||||
CONFIRM с ненулевой session может обрабатываться приложением без ENTER_BOOT.
|
||||
`pcan_boot_poll` обслуживает задержку discovery и тайм-аут сессии, если он задан
|
||||
портом. Старым портам с timeout=0 периодический вызов нужен только для discovery.
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* C28x has 16-bit addressable chars; wire octets use the low eight bits. */
|
||||
#if defined(__TMS320C28XX__) || defined(PCAN_BOOT_WIDE_BYTES)
|
||||
typedef unsigned int pcan_boot_byte_t;
|
||||
#else
|
||||
typedef uint8_t pcan_boot_byte_t;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -41,7 +48,11 @@ typedef enum {
|
||||
PCAN_BOOT_CMD_CONFIRM = 0x08U,
|
||||
PCAN_BOOT_CMD_REBOOT = 0x09U,
|
||||
PCAN_BOOT_CMD_ABORT = 0x0AU,
|
||||
PCAN_BOOT_CMD_QUERY_PROGRESS = 0x0BU
|
||||
PCAN_BOOT_CMD_QUERY_PROGRESS = 0x0BU,
|
||||
PCAN_BOOT_CMD_PING = 0x0CU,
|
||||
PCAN_BOOT_CMD_CAPABILITIES = 0x0DU,
|
||||
PCAN_BOOT_CMD_DEVICE_NAME = 0x0EU,
|
||||
PCAN_BOOT_CMD_MEMORY_INFO = 0x0FU
|
||||
} pcan_boot_command_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -66,7 +77,8 @@ typedef enum {
|
||||
PCAN_BOOT_STATE_READY_TO_ERASE,
|
||||
PCAN_BOOT_STATE_RECEIVING,
|
||||
PCAN_BOOT_STATE_VERIFIED,
|
||||
PCAN_BOOT_STATE_FAILED
|
||||
PCAN_BOOT_STATE_FAILED,
|
||||
PCAN_BOOT_STATE_COMMITTED
|
||||
} pcan_boot_state_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -74,36 +86,47 @@ typedef struct {
|
||||
uint32_t image_crc32;
|
||||
uint32_t firmware_version;
|
||||
uint16_t product_type;
|
||||
uint8_t hardware_revision_min;
|
||||
uint8_t hardware_revision_max;
|
||||
pcan_boot_byte_t hardware_revision_min;
|
||||
pcan_boot_byte_t hardware_revision_max;
|
||||
} pcan_boot_manifest_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t device_type;
|
||||
uint8_t device_id;
|
||||
pcan_boot_byte_t device_type;
|
||||
pcan_boot_byte_t device_id;
|
||||
uint16_t product_type;
|
||||
uint8_t hardware_revision;
|
||||
pcan_boot_byte_t hardware_revision;
|
||||
uint32_t firmware_version;
|
||||
uint8_t active_slot;
|
||||
pcan_boot_byte_t active_slot;
|
||||
/** 1 for an in-place/single-slot target, 2 for A/B. Zero keeps legacy A/B. */
|
||||
uint8_t slot_count;
|
||||
pcan_boot_byte_t slot_count;
|
||||
/** Writable image capacity. Zero uses PCAN_BOOT_SLOT_SIZE. */
|
||||
uint32_t max_image_size;
|
||||
uint8_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
|
||||
pcan_boot_byte_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
|
||||
uint32_t session_timeout_ms; /**< 0 disables timeout for legacy ports. */
|
||||
} pcan_boot_config_t;
|
||||
|
||||
typedef struct {
|
||||
bool (*send)(void *user, uint32_t can_id, const uint8_t *data, uint8_t dlc);
|
||||
bool (*erase_slot)(void *user, uint8_t slot);
|
||||
bool (*write_slot)(void *user, uint8_t slot, uint32_t offset,
|
||||
const uint8_t *data, uint8_t length);
|
||||
bool (*send)(void *user, uint32_t can_id, const pcan_boot_byte_t *data, pcan_boot_byte_t dlc);
|
||||
bool (*erase_slot)(void *user, pcan_boot_byte_t slot);
|
||||
bool (*write_slot)(void *user, pcan_boot_byte_t slot, uint32_t offset,
|
||||
const pcan_boot_byte_t *data, pcan_boot_byte_t length);
|
||||
bool (*authorize)(void *user, const pcan_boot_manifest_t *manifest);
|
||||
bool (*verify_image)(void *user, uint8_t slot,
|
||||
bool (*verify_image)(void *user, pcan_boot_byte_t slot,
|
||||
const pcan_boot_manifest_t *manifest);
|
||||
bool (*set_pending_slot)(void *user, uint8_t slot,
|
||||
/** A/B metadata, or optional single-slot activation after verification. */
|
||||
bool (*set_pending_slot)(void *user, pcan_boot_byte_t slot,
|
||||
const pcan_boot_manifest_t *manifest);
|
||||
bool (*confirm_running_slot)(void *user);
|
||||
void (*reboot)(void *user);
|
||||
/** ENTER: 0 ready, 1 deferred ACK after reset, 2 denied. NULL = legacy ready. */
|
||||
unsigned int (*enter_boot)(void *user, pcan_boot_byte_t session);
|
||||
bool (*can_reboot)(void *user);
|
||||
/** Eight-octet service response, addressed by command + page. */
|
||||
bool (*service_info)(void *user, pcan_boot_byte_t command,
|
||||
pcan_boot_byte_t page, pcan_boot_byte_t *out);
|
||||
/** Optional precise result, replacing legacy boolean verify_image. */
|
||||
pcan_boot_status_t (*verify_status)(void *user, pcan_boot_byte_t slot,
|
||||
const pcan_boot_manifest_t *manifest);
|
||||
} pcan_boot_port_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -115,11 +138,13 @@ typedef struct {
|
||||
uint32_t running_crc32;
|
||||
uint32_t bytes_received;
|
||||
uint16_t next_block;
|
||||
uint8_t session_id;
|
||||
uint8_t target_slot;
|
||||
uint8_t last_status;
|
||||
pcan_boot_byte_t session_id;
|
||||
pcan_boot_byte_t target_slot;
|
||||
pcan_boot_byte_t last_status;
|
||||
bool have_image;
|
||||
bool have_compat;
|
||||
uint32_t now_ms, last_activity_ms, discovery_at;
|
||||
bool discovery_pending;
|
||||
} pcan_boot_t;
|
||||
|
||||
/** Инициализирует экземпляр. Все обязательные callbacks должны быть заданы. */
|
||||
@@ -134,16 +159,22 @@ void pcan_boot_abort(pcan_boot_t *boot);
|
||||
* Возвращает true, если кадр принадлежал загрузочному сервису этого прибора.
|
||||
*/
|
||||
bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc);
|
||||
|
||||
/** Call regularly with a wrapping monotonic millisecond clock. */
|
||||
void pcan_boot_poll(pcan_boot_t *boot, uint32_t now_ms);
|
||||
|
||||
uint32_t pcan_boot_crc32_update(uint32_t crc, const pcan_boot_byte_t *data, size_t length); /* Общий потоковый CRC32 для readback аппаратных хранилищ; начальное состояние равно нулю. */
|
||||
bool pcan_boot_report_busy(pcan_boot_t *boot, pcan_boot_byte_t command, uint32_t now_ms); /* Долгие аппаратные операции сообщают BUSY тем же codec и той же session, что обычный ответ. */
|
||||
|
||||
/** Переносимые операции с 29-битным ProtoCAN ID. */
|
||||
uint32_t pcan_boot_make_id(uint8_t priority, uint8_t route,
|
||||
uint8_t device_type, uint8_t device_id,
|
||||
uint8_t msg_type, uint16_t msg_body);
|
||||
uint8_t pcan_boot_id_route(uint32_t can_id);
|
||||
uint8_t pcan_boot_id_device_type(uint32_t can_id);
|
||||
uint8_t pcan_boot_id_device_id(uint32_t can_id);
|
||||
uint8_t pcan_boot_id_msg_type(uint32_t can_id);
|
||||
uint32_t pcan_boot_make_id(pcan_boot_byte_t priority, pcan_boot_byte_t route,
|
||||
pcan_boot_byte_t device_type, pcan_boot_byte_t device_id,
|
||||
pcan_boot_byte_t msg_type, uint16_t msg_body);
|
||||
pcan_boot_byte_t pcan_boot_id_route(uint32_t can_id);
|
||||
pcan_boot_byte_t pcan_boot_id_device_type(uint32_t can_id);
|
||||
pcan_boot_byte_t pcan_boot_id_device_id(uint32_t can_id);
|
||||
pcan_boot_byte_t pcan_boot_id_msg_type(uint32_t can_id);
|
||||
uint16_t pcan_boot_id_msg_body(uint32_t can_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# STM32: общий слой ProtoCAN Boot v1
|
||||
|
||||
Порты [F103C8](../stm32f103/README.md), [F407VE](../stm32f407/README.md) и
|
||||
[G474VE](../stm32g474/README.md) подключают bxCAN/FDCAN и Flash к текущему `pcan_boot`.
|
||||
F103/F407 предоставляют как лёгкий CMSIS boot main для существующих проектов,
|
||||
так и независимый HAL-порт. G474 использует HAL FDCAN/Flash.
|
||||
`pcan_boot_stm32.inc` включается семейным `.c`; отдельно его компилировать не надо.
|
||||
Сохраните рядом каталоги `stm32-common` и нужного семейства.
|
||||
|
||||
Reusable `pcan_boot_stm32*.c` не содержит `main` или startup. Готовые `boot_main.c`
|
||||
и вход из приложения находятся в семейных каталогах. Для F103/F407 общая
|
||||
CMSIS-реализация транспорта, callbacks и безопасного handoff — `boot_native.inc`;
|
||||
G474 также содержит `boot_startup.c` для исходных Keil/GCC-сборок.
|
||||
Тактирование платы, linker и привязка USB/CAN-приложения остаются в проектах.
|
||||
|
||||
KONOR, climate и CANdle G474 уже подключены через свои submodule: локальные
|
||||
boot main/control заменены include-обёртками, состав Keil/CMake обновлён.
|
||||
UART использует общий `rs485-boot` с сохранением SETGUI v1 wire-протокола.
|
||||
[Схема подключённых проектов и проверки сборок](../../projects/STM32.md).
|
||||
|
||||
## Подключение reusable HAL-портов F103/F407
|
||||
|
||||
1. Добавьте в сборку `src/pcan_boot.c` и семейный `pcan_boot_stm32f103.c`
|
||||
либо `pcan_boot_stm32f407.c`; в include paths — `include`, каталог порта,
|
||||
CMSIS, HAL и каталог вашего `stm32f1xx_hal_conf.h` / `stm32f4xx_hal_conf.h`.
|
||||
2. В HAL включите CAN, FLASH, RCC, GPIO и CORTEX. Выполните `HAL_Init`, настройте
|
||||
тактирование, SysTick и GPIO, вызовите `HAL_CAN_Init` для CAN1. Настройка скорости
|
||||
шины принадлежит проекту; исходные устройства используют 500 кбит/с.
|
||||
3. Выделите CAN1, FIFO0 и банки фильтров 0/1 загрузчику. Перед вызовом init CAN
|
||||
должен находиться в состоянии READY, без чужих активных фильтров. Init настраивает
|
||||
адресный фильтр и broadcast discovery, затем вызывает `HAL_CAN_Start`.
|
||||
Оба приоритета ProtoCAN разрешены, standard и remote кадры отвергаются.
|
||||
4. Не включайте CAN TX/RX IRQ для этого handle и не отправляйте через него кадры
|
||||
из других контекстов: порт использует опрос и сам читает TXOK до reset.
|
||||
5. Вызывайте семейный `poll` из основного цикла только после успешного init.
|
||||
Стирание Flash выполняется синхронно; вызов из IRQ не допускается.
|
||||
6. Linker приложения, VTOR и переход после reset должны соответствовать карте
|
||||
выбранного профиля. Изменение адресов требует отдельного согласования
|
||||
геометрии стирания, linker и host-клиента.
|
||||
|
||||
Пример для F103; `hcan` уже инициализирован проектом:
|
||||
|
||||
```c
|
||||
#include "pcan_boot_stm32f103.h" /* Подключает HAL-порт и текущий API общего автомата. */
|
||||
static pcan_boot_stm32_t updater; /* Сохраняет состояние между итерациями главного цикла. */
|
||||
static bool start_updater(CAN_HandleTypeDef *hcan) /* Вызывается после настройки CAN1, clock tree, GPIO и SysTick. */
|
||||
{ /* Настройка идентичности прибора не зависит от аппаратной геометрии Flash. */
|
||||
pcan_boot_config_t config = {0}; /* Не зависит от порядка полей расширяемого API. */
|
||||
config.device_type = 7U; /* Пример: тип 7 должен совпадать с выбранным адресом в клиенте прошивки. */
|
||||
config.device_id = 13U; /* Пример: номер 13 повторяет исходный адрес загрузчика KONOR. */
|
||||
config.ack_window = 16U; /* Подтверждает каждые 16 CAN-блоков по восемь байт. */
|
||||
config.session_timeout_ms = 30000U; /* Пример: 30 секунд без активности прерывают RAM-сессию. */
|
||||
return pcan_boot_stm32f103_init(&updater, hcan, &config); /* Настраивает single-slot геометрию, фильтры 0/1 и запускает CAN. */
|
||||
} /* После успеха основной цикл регулярно вызывает pcan_boot_stm32f103_poll(&updater). */
|
||||
```
|
||||
|
||||
Product/HW/FW в примере равны нулю; для изделия задайте их явно и согласуйте
|
||||
с manifest клиента. Порт сохраняет `ack_window` и `session_timeout_ms`, но сам
|
||||
задаёт `active_slot=0`, `slot_count=1`, `max_image_size` по физическому профилю.
|
||||
Конфигурацию `config` можно создавать на стеке; состояние `updater` и `hcan`
|
||||
должно существовать всё время работы загрузчика.
|
||||
|
||||
## Flash, подтверждения и reset
|
||||
|
||||
- Запись последовательная, блоки по восемь байт; неполная аппаратная единица
|
||||
допустима только в конце образа и дополняется `FF`. CRC учитывает полезную длину.
|
||||
- HAL-ошибки и readback каждого слова проверяются; после erase/program Flash
|
||||
снова блокируется, включая обработанные отказы. На F407 перед readback
|
||||
вызывается `FLASH_FlushCaches`, чтобы соседние записанные слова не читались
|
||||
из устаревшей строки Flash-кэша; состояние включения кэшей сохраняется HAL.
|
||||
- VERIFY перечитывает Flash, сравнивает её CRC и проверяет MSP/Thumb reset vector.
|
||||
Точка входа должна принадлежать фактическому образу, MSP — основной SRAM,
|
||||
с выравниванием восемь байт. CCM F407 в данном профиле для стека не используется.
|
||||
- COMMIT повторно проверяет образ и устанавливает признак в RAM.
|
||||
REBOOT разрешён только в состоянии COMMITTED. Ответ перед reset должен
|
||||
завершиться с TXOK; отсутствие mailbox, ошибка шины или тайм-аут 100 мс
|
||||
не вызывают reset. Для этого HAL tick должен работать при ожидании TX.
|
||||
- BUSY перед стиранием, между секторами F407 и после erase сохраняет активность
|
||||
сессии. На однобанковой Flash код не исполняется во время аппаратного стирания;
|
||||
непрерывные ответы в этот интервал не гарантируются. Настройте тайм-аут клиента
|
||||
больше максимального времени стирания одной аппаратной операции.
|
||||
|
||||
## Запуск приложения и потеря питания
|
||||
|
||||
Порт не меняет Option Bytes, защиту секторов и VDD; F407 требует 2,7..3,6 В
|
||||
для программирования x32. Переход из работающего приложения в загрузчик,
|
||||
backup/SRAM-флаг запроса, отключение периферии и безопасная замена MSP
|
||||
остаются в `main` и startup устройства.
|
||||
|
||||
`*_vectors_valid()` проверяет только векторы и диапазоны. После внешнего reset
|
||||
этого недостаточно для доказательства целостности образа: RAM-признак COMMIT
|
||||
потерян, а частично записанное приложение уже может иметь корректные векторы.
|
||||
Эти профили сохраняют исходную single-slot модель без постоянных метаданных,
|
||||
rollback, resume после питания и аутентификации. Для защищённого запуска
|
||||
проекту нужен отдельный постоянный manifest с полной проверкой образа.
|
||||
|
||||
## Проверка
|
||||
|
||||
`python tests/run_tests.py` из корня `protocan-boot` запускает три production-порта
|
||||
с моделью HAL и Flash, наряду с существующими проверками ядра и TI-портов.
|
||||
Можно также использовать обычные CMake/CTest: цели `test_stm32f103`,
|
||||
`test_stm32f407`, `test_stm32g474` работают на Windows и POSIX.
|
||||
|
||||
Проверяются все хвосты CAN-блока, полный ERASE/DATA/VERIFY/COMMIT/REBOOT,
|
||||
повреждение readback, отказы HAL/TX, фильтрация RX и сохранность памяти
|
||||
перед приложением и за верхней границей; для G474 проверяются оба DBANK-режима,
|
||||
переход между банками, запрет swap, ёмкость Flash и старое shifted-DLC HAL.
|
||||
Эти тесты не заменяют проверку
|
||||
на реальной плате с её linker, питанием, трансивером и SysTick.
|
||||
@@ -0,0 +1,127 @@
|
||||
static __attribute__((naked, noreturn)) void native_enter(uint32_t sp __attribute__((unused)), uint32_t pc __attribute__((unused))) /* Меняет стек без последующего обращения C-кода к старому кадру функции. */
|
||||
{ /* AAPCS передаёт новый MSP в r0, Thumb entry в r1; последовательность совместима с GCC и Arm Compiler 6. */
|
||||
__asm volatile("msr msp, r0\n" "movs r2, #0\n" "msr control, r2\n" "dsb\n" "isb\n" "cpsie i\n" "bx r1\n"); /* Возвращает привилегированный режим MSP, синхронизирует конвейер и передаёт управление Thumb-вектору. */
|
||||
} /* Завершает переход без эпилога C после замены MSP. */
|
||||
void SysTick_Handler(void) { ++ticks_ms; } /* Сохраняет миллисекундные часы только в самостоятельной сборке загрузчика. */
|
||||
static bool native_vectors_valid(uint32_t size) /* Проверяет начальный стек и reset handler в пределах фактического образа. */
|
||||
{ /* До чтения Flash отвергает некорректный размер. */
|
||||
const uint32_t *vectors = (const uint32_t *)(uintptr_t)NATIVE_APP_BASE; /* Таблица векторов лежит в начале зарезервированной области приложения. */
|
||||
uint32_t sp, pc; /* Хранит начальные значения Cortex-M без невыровненного доступа. */
|
||||
if (size < 8U || size > NATIVE_APP_SIZE) return false; /* Для двух векторов нужно минимум восемь байт; верхняя граница профиля исключена. */
|
||||
sp = vectors[0]; pc = vectors[1]; /* Читает MSP и Thumb-точку входа только после проверки геометрии. */
|
||||
return sp > 0x20000000UL && sp <= NATIVE_RAM_END && (sp & 7U) == 0U && (pc & 1U) != 0U && (pc & ~1UL) >= NATIVE_APP_BASE && (pc & ~1UL) < NATIVE_APP_BASE + size; /* Требует восемь байт выравнивания стека и точку входа внутри выбранной длины приложения. */
|
||||
} /* Структурная проверка не заменяет постоянный manifest после отключения питания. */
|
||||
static void native_jump(uint32_t address) /* Отключает загрузочную периферию перед запуском приложения либо ROM. */
|
||||
{ /* Вызывающий код предварительно проверяет векторы либо использует фиксированный заводской ROM. */
|
||||
uint32_t index; /* Перебирает все банки NVIC Cortex-M3/M4 с запасом до восьми банков. */
|
||||
const uint32_t *vectors = (const uint32_t *)(uintptr_t)address; /* Сохраняет адрес новой таблицы до отключения прерываний. */
|
||||
uint32_t sp = vectors[0], pc = vectors[1]; /* Считывает аргументы перехода, пока C-стек ещё принадлежит загрузчику. */
|
||||
__disable_irq(); SysTick->CTRL = 0U; /* Не позволяет SysTick загрузчика вызвать обработчик нового приложения в промежуточном состоянии. */
|
||||
native_peripheral_stop(); /* Семейный порт отключает CAN и используемый UART, не трогая резервную память. */
|
||||
for (index = 0U; index < 8U; ++index) { NVIC->ICER[index] = 0xFFFFFFFFUL; NVIC->ICPR[index] = 0xFFFFFFFFUL; } /* Сбрасывает enable/pending всех внешних IRQ записью W1C по CMSIS. */
|
||||
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk; /* Убирает отложенные SysTick и PendSV до смены VTOR. */
|
||||
SCB->VTOR = address; /* Согласует векторизацию с linker нового образа. */
|
||||
native_enter(sp, pc); /* Передаёт управление без возврата и без обращения к старому C-стеку. */
|
||||
} /* Завершает общую процедуру handoff. */
|
||||
static bool native_image_valid(uint32_t size, uint32_t crc) /* Проверяет реальную Flash независимо от CRC принятого CAN/UART-потока. */
|
||||
{ /* Одно правило проверки используется обоими транспортами KONOR. */
|
||||
return native_vectors_valid(size) && pcan_boot_crc32_update(0U, (const uint8_t *)(uintptr_t)NATIVE_APP_BASE, size) == crc; /* Проверяет точную полезную длину, исключая заполнение FF последней аппаратной единицы. */
|
||||
} /* Завершает проверку векторов и readback CRC. */
|
||||
static bool native_write_bytes(uint32_t image_size, uint32_t offset, const uint8_t *data, uint16_t length) /* Записывает CAN-блок или UART-часть с одной проверкой физической геометрии. */
|
||||
{ /* Неполная аппаратная единица разрешена только в самом конце образа. */
|
||||
uint32_t done = 0U; bool ok = true; /* Считает полезные байты и сохраняет результат до восстановления блокировки Flash. */
|
||||
if (image_size < 8U || image_size > NATIVE_APP_SIZE || data == NULL || length == 0U || offset > image_size || length > image_size - offset || offset % NATIVE_PROGRAM_UNIT != 0U) return false; /* Проверяет диапазоны вычитанием, исключая переполнение и неправильное выравнивание. */
|
||||
if (length % NATIVE_PROGRAM_UNIT != 0U && offset + length != image_size) return false; /* Не допускает повторного программирования уже дополненного слова в середине потока. */
|
||||
if (!native_unlock()) return false; /* Не изменяет заблокированную или занятую Flash. */
|
||||
while (done < length) { /* Делит полезные октеты на аппаратные полуслова F1 или слова F4. */
|
||||
uint32_t word = 0xFFFFFFFFUL, count = length - done; /* По умолчанию хвост аппаратной записи остаётся стёртым FF. */
|
||||
if (count > NATIVE_PROGRAM_UNIT) count = NATIVE_PROGRAM_UNIT; /* Не копирует больше одной аппаратной единицы за итерацию. */
|
||||
(void)memcpy(&word, data + done, count); /* Не читает за фактическим хвостом последнего блока. */
|
||||
if (!native_program(NATIVE_APP_BASE + offset + done, word)) { ok = false; break; } /* Проверяет аппаратные флаги и фактический readback каждого записанного слова. */
|
||||
done += count; /* Продвигается только на число полезных байтов без дополнения. */
|
||||
} /* Завершает программирование текущей части образа. */
|
||||
native_lock(); /* Восстанавливает защиту Flash как после успеха, так и после обработанной ошибки. */
|
||||
return ok; /* Передаёт отказ общему CAN либо UART-автомату. */
|
||||
} /* Завершает общую безопасную запись Flash. */
|
||||
static bool native_send(void *user, uint32_t id, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Передаёт служебный CAN-ответ с ожиданием реального TXOK до reset. */
|
||||
{ /* Использует только mailbox 0 выделенного загрузочного CAN1. */
|
||||
uint32_t start = ticks_ms, low = 0U, high = 0U; uint8_t index; bool sent; /* Сохраняет начало 100-мс тайм-аута и регистровые слова payload. */
|
||||
(void)user; /* Самостоятельный загрузчик содержит один статический экземпляр CAN. */
|
||||
if (length > 8U || id > 0x1FFFFFFFUL || (length != 0U && data == NULL)) return false; /* Не допускает чтение за DLC или передачу идентификатора шире 29 бит. */
|
||||
while ((CAN1->TSR & CAN_TSR_TME0) == 0U) if ((uint32_t)(ticks_ms - start) >= 100U) return false; /* Ограничивает ожидание свободного mailbox 100 миллисекундами с учётом переполнения часов. */
|
||||
for (index = 0U; index < length; ++index) { if (index < 4U) low |= (uint32_t)data[index] << (index * 8U); else high |= (uint32_t)data[index] << ((index - 4U) * 8U); } /* Читает только фактические октеты DLC, оставляя остальные позиции нулевыми. */
|
||||
CAN1->TSR = CAN_TSR_RQCP0; /* Сбрасывает прежние TXOK/ошибки mailbox 0 записью W1C. */
|
||||
CAN1->sTxMailBox[0].TDTR = length; CAN1->sTxMailBox[0].TDLR = low; CAN1->sTxMailBox[0].TDHR = high; /* Загружает длину и два безопасно сформированных слова данных. */
|
||||
CAN1->sTxMailBox[0].TIR = (id << 3U) | CAN_TI0R_IDE | CAN_TI0R_TXRQ; /* Запускает расширенный data-кадр ProtoCAN без remote-флага. */
|
||||
while ((CAN1->TSR & CAN_TSR_RQCP0) == 0U) if ((uint32_t)(ticks_ms - start) >= 100U) { CAN1->TSR = CAN_TSR_ABRQ0; return false; } /* Прерывает неподтверждённую передачу и запрещает reset после тайм-аута. */
|
||||
sent = (CAN1->TSR & CAN_TSR_TXOK0) != 0U; CAN1->TSR = CAN_TSR_RQCP0; /* Проверяет ACK шины до очистки аппаратного результата. */
|
||||
return sent; /* Ядро перезапустит MCU только после успешно отправленного ответа. */
|
||||
} /* Завершает синхронную передачу ответа CAN. */
|
||||
static bool native_erase_slot(void *user, pcan_boot_byte_t slot) /* Подключает проверенное стирание профиля к текущему ProtoCAN API. */
|
||||
{ /* Аппаратная реализация семейства никогда не затрагивает загрузчик или EEPROM-кольцо. */
|
||||
bool ok; (void)user; /* Хранит результат до финального обновления времени сессии. */
|
||||
if (slot != 0U || boot.manifest.image_size < 8U || boot.manifest.image_size > NATIVE_APP_SIZE) return false; /* Разрешает только логический слот A и полный набор векторов. */
|
||||
(void)pcan_boot_report_busy(&boot, PCAN_BOOT_CMD_ERASE, ticks_ms); /* Предупреждает клиента перед синхронным Flash stall. */
|
||||
ok = native_erase(boot.manifest.image_size); /* Стирает только реально необходимые страницы или секторы. */
|
||||
if (ok) (void)pcan_boot_report_busy(&boot, PCAN_BOOT_CMD_ERASE, ticks_ms); /* Продлевает сессию после завершения длительной аппаратной операции. */
|
||||
return ok; /* Сообщает результат стирания общему автомату. */
|
||||
} /* Завершает callback стирания CAN. */
|
||||
static bool native_write_slot(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Подключает единый Flash writer к последовательным блокам CAN. */
|
||||
{ /* Последовательность номеров блоков и максимум DLC уже проверены общим автоматом. */
|
||||
(void)user; /* Единственный образ управляется статическим экземпляром загрузчика. */
|
||||
return slot == 0U && offset == boot.bytes_received && native_write_bytes(boot.manifest.image_size, offset, data, length); /* Не допускает произвольное смещение даже при прямом вызове callback. */
|
||||
} /* Завершает callback записи CAN. */
|
||||
static pcan_boot_status_t native_verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) /* Различает повреждение реальной Flash и неверную таблицу векторов. */
|
||||
{ /* Операция не доверяет одному CRC входящего потока. */
|
||||
(void)user; /* Использует фиксированную область выбранного семейного профиля. */
|
||||
if (slot != 0U || manifest == NULL || !native_vectors_valid(manifest->image_size)) return PCAN_BOOT_STATUS_INVALID_SIZE; /* Отвергает неверный слот, геометрию и точку входа до чтения всего образа. */
|
||||
return native_image_valid(manifest->image_size, manifest->image_crc32) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_CRC_ERROR; /* Перечитывает Flash общим CRC32 ProtoCAN. */
|
||||
} /* Завершает независимую проверку принятого образа. */
|
||||
static bool native_commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) { return native_verify(user, slot, manifest) == PCAN_BOOT_STATUS_OK; } /* COMMIT повторно проверяет single-slot образ без имитации постоянных метаданных. */
|
||||
static bool native_can_reboot(void *user) { (void)user; return boot.state == PCAN_BOOT_STATE_COMMITTED; } /* Протокольный reset разрешён только после VERIFY и COMMIT. */
|
||||
static void native_reboot(void *user) { (void)user; NVIC_SystemReset(); } /* Вызывается ядром только после успешного TXOK ответа REBOOT. */
|
||||
static bool native_init(void) /* Инициализирует текущий API ядра без позиционных структур старой версии. */
|
||||
{ /* Идентичность устройства задаёт профиль либо конфигурация проекта. */
|
||||
pcan_boot_config_t config = {0}; pcan_boot_port_t callbacks = {0}; /* Обнуляет необязательные поля и callbacks расширяемого интерфейса. */
|
||||
config.device_type = PCAN_BOOT_DEVICE_TYPE; config.device_id = PCAN_BOOT_DEVICE_ID; config.product_type = PCAN_BOOT_PRODUCT_TYPE; /* Сохраняет адрес и Product исходного проекта. */
|
||||
config.hardware_revision = PCAN_BOOT_HW_REVISION; config.firmware_version = PCAN_BOOT_FW_VERSION; /* Сохраняет совместимость manifest по HW/FW. */
|
||||
config.active_slot = 0U; config.slot_count = 1U; config.max_image_size = NATIVE_APP_SIZE; /* Явно использует single-slot A вместо прежней фиктивной A/B-конфигурации. */
|
||||
config.ack_window = PCAN_BOOT_ACK_WINDOW; config.session_timeout_ms = 30000UL; /* Сохраняет исходное окно ACK и прерывает неактивную RAM-сессию через 30 секунд. */
|
||||
callbacks.send = native_send; callbacks.erase_slot = native_erase_slot; callbacks.write_slot = native_write_slot; /* Подключает аппаратный транспорт и защищённую область Flash. */
|
||||
callbacks.verify_status = native_verify; callbacks.set_pending_slot = native_commit; callbacks.can_reboot = native_can_reboot; callbacks.reboot = native_reboot; /* Подключает readback, COMMIT и согласованную политику reset. */
|
||||
return pcan_boot_init(&boot, &config, &callbacks, NULL); /* Проверяет конфигурацию и создаёт единственный экземпляр общего автомата. */
|
||||
} /* Завершает настройку ProtoCAN независимо от включения аппаратной шины. */
|
||||
static bool native_can_init(void) /* Настраивает выделенный CAN1 и два фильтра для адресных запросов и discovery. */
|
||||
{ /* Тактирование GPIO и CAN включает семейная функция; UART во время Flash/CAN init не изменяется. */
|
||||
static const uint8_t shifts[8] = {0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U}; /* RCC кодирует APB1: значения 0..3 без деления, 4..7 дают делители 2,4,8,16. */
|
||||
uint32_t clock = SystemCoreClock >> shifts[(RCC->CFGR >> NATIVE_PPRE1_SHIFT) & 7U]; /* Получает действительную частоту APB1 в Гц, без удвоения timer clocks. */
|
||||
uint32_t tq, btr = 0U, started, index; bool found = false; /* Сохраняет bit timing, тайм-аут init и индекс фильтра. */
|
||||
if (clock % PCAN_BOOT_CAN_BITRATE != 0U) return false; /* Не округляет скорость шины при неподходящем clock tree. */
|
||||
for (tq = 18U; tq >= 8U; --tq) { /* Сохраняет диапазон 8..18 time quanta из исходного KONOR. */
|
||||
uint32_t divisor = clock / PCAN_BOOT_CAN_BITRATE, prescaler = divisor / tq, ts2 = tq / 5U; /* Выбирает sample point около 80% при допустимом целочисленном делителе. */
|
||||
if (divisor % tq == 0U && prescaler >= 1U && prescaler <= 1024U) { btr = ((ts2 - 1U) << 20U) | ((tq - 2U - ts2) << 16U) | (prescaler - 1U); found = true; break; } /* Кодирует BRP/TS1/TS2 с аппаратным смещением минус один и SJW=1 tq. */
|
||||
} /* Завершает поиск точной скорости для штатного тактирования F103/F407. */
|
||||
if (!found) return false; /* Не включает шину с неизвестными битовыми интервалами. */
|
||||
native_gpio_init(); CAN1->MCR = CAN_MCR_INRQ | CAN_MCR_ABOM; started = ticks_ms; /* Включает семейные GPIO и запрашивает init с автоматическим восстановлением bus-off. */
|
||||
while ((CAN1->MSR & CAN_MSR_INAK) == 0U) if ((uint32_t)(ticks_ms - started) >= 100U) return false; /* Ограничивает вход в CAN init ста миллисекундами. */
|
||||
CAN1->BTR = btr; CAN1->FMR |= CAN_FMR_FINIT; CAN1->FA1R = 0U; CAN1->FS1R |= 3U; CAN1->FM1R &= ~3UL; CAN1->FFA1R &= ~3UL; /* Настраивает только два 32-битных mask-фильтра в FIFO0 и отключает чужие банки загрузчика. */
|
||||
for (index = 0U; index < 2U; ++index) { /* Банк 0 принимает собственный адрес, банк 1 — только broadcast discovery. */
|
||||
uint32_t id = index == 0U ? pcan_boot_make_id(0U, 0U, PCAN_BOOT_DEVICE_TYPE, PCAN_BOOT_DEVICE_ID, 0U, 0U) : pcan_boot_make_id(0U, 0U, 7U, 15U, PCAN_BOOT_MSG_DISCOVERY, 0U); /* Не маскирует приоритет, сохраняя оба штатных уровня ProtoCAN. */
|
||||
uint32_t mask = index == 0U ? 0x0FF00000UL : 0x0FFFFFFFUL; /* Адресный фильтр оставляет MsgType/Body открытыми; discovery сравнивает их точно. */
|
||||
CAN1->sFilterRegister[index].FR1 = (id << 3U) | CAN_RI0R_IDE; CAN1->sFilterRegister[index].FR2 = (mask << 3U) | CAN_RI0R_IDE | CAN_RI0R_RTR; /* Исключает standard и remote кадры на аппаратном уровне. */
|
||||
} /* Завершает настройку обоих фильтров. */
|
||||
CAN1->FA1R |= 3U; CAN1->FMR &= ~CAN_FMR_FINIT; CAN1->MCR &= ~CAN_MCR_INRQ; started = ticks_ms; /* Активирует фильтры и переводит CAN в рабочий режим. */
|
||||
while ((CAN1->MSR & CAN_MSR_INAK) != 0U) if ((uint32_t)(ticks_ms - started) >= 100U) return false; /* Отсутствие доступной шины не должно навсегда блокировать обычный запуск приложения. */
|
||||
return true; /* Рабочий CAN готов принимать адресные команды и discovery. */
|
||||
} /* Завершает общую аппаратную настройку bxCAN. */
|
||||
static void native_can_poll(void) /* Обрабатывает не больше трёх кадров — аппаратной глубины FIFO bxCAN. */
|
||||
{ /* Длительные операции Flash выполняются только из основного цикла. */
|
||||
uint32_t count; pcan_boot_poll(&boot, ticks_ms); /* Обновляет тайм-аут сессии и отложенное discovery независимо от входящих команд. */
|
||||
for (count = 0U; count < 3U && (CAN1->RF0R & CAN_RF0R_FMP0) != 0U; ++count) { /* Ограничивает время одного вызова на нагруженной шине. */
|
||||
uint32_t rir = CAN1->sFIFOMailBox[0].RIR, low = CAN1->sFIFOMailBox[0].RDLR, high = CAN1->sFIFOMailBox[0].RDHR; /* Сохраняет поля до освобождения аппаратного FIFO. */
|
||||
uint8_t dlc = (uint8_t)(CAN1->sFIFOMailBox[0].RDTR & 15U), data[8], index; /* Читает входящий DLC отдельно от фиксированного буфера classic CAN. */
|
||||
for (index = 0U; index < 8U; ++index) data[index] = (uint8_t)(index < 4U ? low >> (index * 8U) : high >> ((index - 4U) * 8U)); /* Извлекает октеты без невыровненного чтения аппаратных регистров. */
|
||||
CAN1->RF0R |= CAN_RF0R_RFOM0; /* Освобождает обработанный элемент FIFO0 до потенциально долгого erase. */
|
||||
if ((rir & CAN_RI0R_IDE) != 0U && (rir & CAN_RI0R_RTR) == 0U && dlc <= 8U) { pcan_boot_poll(&boot, ticks_ms); (void)pcan_boot_process(&boot, (rir >> 3U) & 0x1FFFFFFFUL, data, dlc); } /* Повторно проверяет формат внешнего кадра и вызывает общий автомат. */
|
||||
} /* Завершает ограниченный пакет входящих кадров. */
|
||||
} /* Завершает обслуживание транспорта и часов ядра. */
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef PCAN_BOOT_STM32_H /* Защищает общие объявления двух STM32-портов от повторного включения. */
|
||||
#define PCAN_BOOT_STM32_H /* Фиксирует включение интерфейса single-slot загрузчика. */
|
||||
#include "../../include/pcan_boot.h" /* Использует актуальные именованные поля конфигурации и callbacks ядра. */
|
||||
typedef struct { /* Состояние принадлежит одному загрузчику и одному выделенному bxCAN. */
|
||||
pcan_boot_t boot; /* Хранит сессию, manifest, CRC потока и состояние общего автомата. */
|
||||
void *can; /* Ссылается на CAN_HandleTypeDef bxCAN либо FDCAN_HandleTypeDef G474; время жизни задаёт проект. */
|
||||
uint32_t written; /* Следующее допустимое смещение записи, в байтах от начала приложения. */
|
||||
bool ready; /* Разрешает reset только после проверки Flash и успешного COMMIT текущего образа. */
|
||||
} pcan_boot_stm32_t; /* Не содержит startup, main или обработчиков прерываний проекта. */
|
||||
#endif /* Завершает общий интерфейс STM32F103/F407. */
|
||||
@@ -0,0 +1,167 @@
|
||||
static bool send_frame(void *user, uint32_t id, const pcan_boot_byte_t *data, pcan_boot_byte_t dlc) /* Передаёт только объявленные октеты и дожидается физического завершения ACK. */
|
||||
{ /* Порт владеет CAN1 без TX IRQ, поэтому флаги завершения не очищаются чужим обработчиком. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает состояние выделенного аппаратного порта. */
|
||||
CAN_HandleTypeDef *can = port->can; /* Восстанавливает тип handle семейства HAL текущей единицы трансляции. */
|
||||
CAN_TxHeaderTypeDef header = {0}; /* Не оставляет мусора в стандартном ID и timestamp-флагах. */
|
||||
uint8_t bytes[8] = {0}; /* CAN 2.0 передаёт не больше восьми октетов; короткий payload дополняется безопасными нулями. */
|
||||
uint32_t mailbox, mask, started = HAL_GetTick(); /* Запоминает mailbox, его TXOK-флаг и начало тайм-аута в мс. */
|
||||
if (dlc > 8U || id > 0x1FFFFFFFUL || (dlc != 0U && data == NULL)) return false; /* Запрещает чтение за payload и идентификатор шире 29 бит. */
|
||||
if (dlc != 0U) (void)memcpy(bytes, data, dlc); /* Не читает отсутствующие байты коротких служебных сообщений. */
|
||||
while (HAL_CAN_GetTxMailboxesFreeLevel(can) == 0U) { /* Ожидает освобождения аппаратного передатчика. */
|
||||
if ((uint32_t)(HAL_GetTick() - started) >= 100U) return false; /* Ограничивает ожидание 100 мс, включая переполнение счётчика времени. */
|
||||
} /* Завершает ожидание свободного mailbox. */
|
||||
header.ExtId = id; /* Передаёт адресный ProtoCAN ID без преобразования полей. */
|
||||
header.IDE = CAN_ID_EXT; /* ProtoCAN использует только расширенные 29-битные идентификаторы. */
|
||||
header.RTR = CAN_RTR_DATA; /* Все ответы загрузчика содержат обычные данные, а не remote-запрос. */
|
||||
header.DLC = dlc; /* Фиксирует фактическое число объявленных октетов кадра. */
|
||||
can->Instance->TSR = CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | CAN_TSR_RQCP2; /* Очищает прежние TXOK/ошибки записью W1C перед новой отправкой; CAN принадлежит только этому порту. */
|
||||
if (HAL_CAN_AddTxMessage(can, &header, bytes, &mailbox) != HAL_OK) return false; /* Не считает не поставленный в очередь ответ отправленным. */
|
||||
while (HAL_CAN_IsTxMessagePending(can, mailbox) != 0U) { /* Сохраняет CAN до завершения ответа, включая команду REBOOT. */
|
||||
if ((uint32_t)(HAL_GetTick() - started) >= 100U) { /* Не зависает навсегда при отсутствии ACK от шины. */
|
||||
(void)HAL_CAN_AbortTxRequest(can, mailbox); /* Снимает просроченный запрос передачи перед возвратом ошибки. */
|
||||
return false; /* Ядро не выполнит reset после неподтверждённого ответа. */
|
||||
} /* Завершает обработку тайм-аута TX. */
|
||||
} /* Аппаратная передача завершилась либо была отвергнута CAN. */
|
||||
mask = mailbox == CAN_TX_MAILBOX0 ? CAN_TSR_TXOK0 : mailbox == CAN_TX_MAILBOX1 ? CAN_TSR_TXOK1 : CAN_TSR_TXOK2; /* Сопоставляет bitmask mailbox HAL с флагом успешной передачи bxCAN. */
|
||||
return (can->Instance->TSR & mask) != 0U; /* Отличает подтверждённый ACK от снятия запроса вследствие ошибки. */
|
||||
} /* Завершает синхронный транспорт служебных ответов. */
|
||||
bool STM32_BOOT_VECTORS(void) /* Проверяет только структурную корректность таблицы векторов установленного приложения. */
|
||||
{ /* Проверка CRC образа выполняется отдельно при VERIFY. */
|
||||
const uint32_t *vectors = (const uint32_t *)(uintptr_t)STM32_BOOT_BASE; /* Читает первые два слова из фиксированной области приложения. */
|
||||
uint32_t sp = vectors[0], pc = vectors[1]; /* Извлекает начальный стек и Thumb-адрес reset handler. */
|
||||
return sp > 0x20000000UL && sp <= STM32_BOOT_RAM_END && (sp & 7U) == 0U && (pc & 1U) != 0U && (pc & ~1UL) >= STM32_BOOT_BASE && (pc & ~1UL) < STM32_BOOT_END; /* Требует доступный стек с выравниванием 8 байт и точку входа внутри приложения. */
|
||||
} /* Завершает проверку векторов без изменения MCU. */
|
||||
static bool erase_slot(void *user, pcan_boot_byte_t slot) /* Стирает единственную область после проверки manifest общим ядром. */
|
||||
{ /* Сохранение старого приложения и rollback в этом профиле отсутствуют. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает manifest и состояние активации текущего загрузчика. */
|
||||
uint32_t size = port->boot.manifest.image_size; /* Ограничивает стирание фактической длиной образа в байтах. */
|
||||
bool ok, locked; /* Сохраняет результаты операции и последующего восстановления защиты Flash. */
|
||||
if (slot != 0U || size < 8U || size > STM32_BOOT_END - STM32_BOOT_BASE) return false; /* Не допускает чужой слот, неполные векторы и выход за область приложения. */
|
||||
port->ready = false; /* Инвалидирует разрешение reset до первого изменения Flash. */
|
||||
port->written = 0U; /* Начинает последовательную запись заново после стирания. */
|
||||
(void)pcan_boot_report_busy(&port->boot, PCAN_BOOT_CMD_ERASE, HAL_GetTick()); /* Предупреждает клиента перед синхронным блокированием Flash. */
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) return false; /* Не продолжает работу при отказе разблокировки Flash. */
|
||||
ok = erase_region(port, size); /* Выполняет семейную операцию по страницам F1 либо секторам F4. */
|
||||
locked = HAL_FLASH_Lock() == HAL_OK; /* Восстанавливает защиту Flash и при успехе, и при отказе стирания. */
|
||||
if (ok && locked) (void)pcan_boot_report_busy(&port->boot, PCAN_BOOT_CMD_ERASE, HAL_GetTick()); /* Обновляет время после блокировки Flash, чтобы длительное стирание не погасило сессию. */
|
||||
return ok && locked; /* Не подтверждает ERASE при любом аппаратном отказе. */
|
||||
} /* Завершает callback стирания. */
|
||||
static bool write_slot(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Программирует последовательные блоки без пересечения защищённых областей. */
|
||||
{ /* Крайний неполный блок дополняется единицами до аппаратной единицы программирования. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает длину manifest и ожидаемое следующее смещение. */
|
||||
uint32_t done = 0U, size = port->boot.manifest.image_size; /* Считает только полезные байты без дополнения FF. */
|
||||
bool ok = true, locked; /* Позволяет гарантированно закрыть Flash после любой ошибки. */
|
||||
if (slot != 0U || data == NULL || length == 0U || length > 8U || size < 8U || size > STM32_BOOT_END - STM32_BOOT_BASE || offset != port->written || offset > size || length > size - offset || (offset % STM32_BOOT_UNIT) != 0U) return false; /* Проверяет адреса до сложения, выравнивание и принадлежность текущему образу. */
|
||||
if ((length % STM32_BOOT_UNIT) != 0U && offset + length != size) return false; /* Дополнение FF допустимо только у последнего блока; повторная запись слова запрещена. */
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) return false; /* Не выполняет запись при заблокированной Flash. */
|
||||
while (done < length) { /* Разбивает CAN-блок на аппаратные полуслова F1 либо слова F4. */
|
||||
uint32_t value = 0xFFFFFFFFUL, portion = length - done; /* Стертое значение FF заполняет только хвост последней аппаратной записи. */
|
||||
uint32_t address = STM32_BOOT_BASE + offset + done; /* Адрес остаётся в заранее проверенной области приложения. */
|
||||
HAL_StatusTypeDef status; /* Отделяет аппаратное завершение записи от последующей синхронизации Flash-кэша. */
|
||||
if (portion > STM32_BOOT_UNIT) portion = STM32_BOOT_UNIT; /* Ограничивает копирование размером аппаратной записи. */
|
||||
(void)memcpy(&value, data + done, portion); /* Не читает за фактическим payload последнего блока. */
|
||||
status = HAL_FLASH_Program(STM32_BOOT_PROGRAM, address, value); /* Дожидается завершения программирования выбранной аппаратной единицы. */
|
||||
STM32_BOOT_SYNC(); /* На F407 сбрасывает строки Flash-кэша после записи, чтобы readback соседнего слова не получил старое FF. */
|
||||
if (status != HAL_OK || memcmp((const void *)(uintptr_t)address, &value, STM32_BOOT_UNIT) != 0) { /* Проверяет результат HAL и чтение записанного значения, включая хвост FF. */
|
||||
ok = false; /* Делает частично записанный образ непригодным для VERIFY/COMMIT. */
|
||||
break; /* Не продолжает программирование после первого отказа. */
|
||||
} /* Завершает проверку результата программирования. */
|
||||
done += portion; /* Продвигает полезное смещение без включения дополнения FF в CRC. */
|
||||
} /* Завершает аппаратные записи текущего CAN-блока. */
|
||||
locked = HAL_FLASH_Lock() == HAL_OK; /* Обязательно восстанавливает аппаратную блокировку Flash. */
|
||||
if (ok && locked) port->written += length; /* Подтверждает смещение только после полного успеха и блокировки. */
|
||||
return ok && locked; /* Ошибка передаётся общему автомату как FLASH_ERROR. */
|
||||
} /* Завершает callback последовательной записи. */
|
||||
static pcan_boot_status_t verify_image(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) /* Перечитывает реальную Flash вместо доверия одному CRC входящего потока. */
|
||||
{ /* Проверяет геометрию до доступа к памяти и точный размер переданных данных. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает фактический счётчик успешно записанных байтов. */
|
||||
const uint8_t *image = (const uint8_t *)(uintptr_t)STM32_BOOT_BASE; /* Отображает фиксированную область приложения в адресное пространство MCU. */
|
||||
uint32_t offset, crc = 0U; /* Начинает общий потоковый CRC32 с нуля. */
|
||||
if (slot != 0U || manifest == NULL || manifest->image_size < 8U || manifest->image_size > STM32_BOOT_END - STM32_BOOT_BASE || port->written != manifest->image_size) return PCAN_BOOT_STATUS_INVALID_SIZE; /* Не читает произвольную память по недостоверному manifest. */
|
||||
for (offset = 0U; offset < manifest->image_size;) { /* Проверяет весь полезный образ ограниченными порциями. */
|
||||
uint32_t count = manifest->image_size - offset; /* Вычисляет остаток в байтах без выхода за границу образа. */
|
||||
if (count > 1024U) count = 1024U; /* Порция 1 КиБ ограничивает объём одного вызова потокового CRC. */
|
||||
crc = pcan_boot_crc32_update(crc, image + offset, count); /* Использует общий полином ProtoCAN и не включает дополнение FF. */
|
||||
offset += count; /* Продвигает позицию чтения проверенного образа. */
|
||||
} /* Завершает чтение Flash для независимой проверки CRC. */
|
||||
if (crc != manifest->image_crc32) return PCAN_BOOT_STATUS_CRC_ERROR; /* Обнаруживает повреждение памяти после успешного приёма CAN. */
|
||||
if (!STM32_BOOT_VECTORS() || ((*(const uint32_t *)(const void *)(image + 4U)) & ~1UL) >= STM32_BOOT_BASE + manifest->image_size) return PCAN_BOOT_STATUS_INVALID_SIZE; /* Не принимает reset handler вне фактически загруженного образа. */
|
||||
return PCAN_BOOT_STATUS_OK; /* Реальная Flash и векторы соответствуют полученному manifest. */
|
||||
} /* Завершает проверку записанного образа. */
|
||||
static bool commit_image(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) /* Активирует только повторно проверенный single-slot образ. */
|
||||
{ /* Не имитирует постоянные A/B-метаданные или защиту от потери питания. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает RAM-признак успешного COMMIT текущей сессии. */
|
||||
port->ready = verify_image(user, slot, manifest) == PCAN_BOOT_STATUS_OK; /* Перечитывает Flash перед разрешением reset. */
|
||||
return port->ready; /* Ядро перейдёт в COMMITTED только при успешной проверке. */
|
||||
} /* Завершает активацию без записи служебных страниц. */
|
||||
static bool can_reboot(void *user) /* Запрещает программный запуск недописанного приложения по команде протокола. */
|
||||
{ /* Политика запуска после внешнего reset остаётся обязанностью проекта. */
|
||||
pcan_boot_stm32_t *port = user; /* Получает состояние текущей сессии и подтверждение COMMIT. */
|
||||
return port->ready && port->boot.state == PCAN_BOOT_STATE_COMMITTED; /* ABORT, новая сессия и ошибки сами по себе не дают права reset. */
|
||||
} /* Завершает проверку допустимости протокольного reset. */
|
||||
static void reboot(void *user) /* Выполняет reset только после синхронно отправленного ответа ядра. */
|
||||
{ /* Повторно проверяет RAM-разрешение непосредственно перед сбросом. */
|
||||
if (can_reboot(user)) NVIC_SystemReset(); /* Использует штатный CMSIS reset, оставляя startup и выбор приложения проекту. */
|
||||
} /* Завершает callback перезапуска. */
|
||||
bool STM32_BOOT_INIT(pcan_boot_stm32_t *port, CAN_HandleTypeDef *can, const pcan_boot_config_t *config) /* Подключает текущий API ядра к выделенным CAN1 и single-slot Flash. */
|
||||
{ /* Тактирование, GPIO, bitrate, HAL_Init и SysTick должны быть настроены проектом заранее. */
|
||||
pcan_boot_port_t callbacks = {0}; /* Новые необязательные callbacks ядра всегда остаются явно нулевыми. */
|
||||
pcan_boot_config_t single; /* Копирует идентичность прибора, переопределяя геометрию профиля. */
|
||||
CAN_FilterTypeDef filter = {0}; /* Подготавливает адресный фильтр 0 и discovery-фильтр 1 в FIFO0. */
|
||||
uint32_t id, mask; /* Содержит идентификатор и аппаратную маску IDE/RTR сдвига bxCAN. */
|
||||
if (port == NULL || can == NULL || config == NULL || can->Instance != CAN1) return false; /* Отклоняет неверные указатели и неподдерживаемый CAN2. */
|
||||
single = *config; /* Сохраняет DeviceType/ID, Product, HW, FW, ack_window и тайм-аут сессии проекта. */
|
||||
single.active_slot = 0U; /* Single-slot обновление всегда адресует логический слот A. */
|
||||
single.slot_count = 1U; /* Исключает старую имитацию single-slot через фиктивный active_slot=1. */
|
||||
single.max_image_size = STM32_BOOT_END - STM32_BOOT_BASE; /* Ядро отвергает превышение физической области ещё до erase. */
|
||||
callbacks.send = send_frame; /* Подключает синхронную отправку подтверждённых CAN-ответов. */
|
||||
callbacks.erase_slot = erase_slot; /* Подключает стирание только области приложения. */
|
||||
callbacks.write_slot = write_slot; /* Подключает последовательное программирование с readback каждого слова. */
|
||||
callbacks.verify_status = verify_image; /* Возвращает отдельные статусы CRC_ERROR и INVALID_SIZE для Flash/vectors. */
|
||||
callbacks.set_pending_slot = commit_image; /* Согласует COMMIT с проверкой реального образа и RAM-признаком активации. */
|
||||
callbacks.can_reboot = can_reboot; /* Не позволяет REBOOT обойти проверку завершённого образа. */
|
||||
callbacks.reboot = reboot; /* Выполняет CMSIS reset после подтверждения ответа на шине. */
|
||||
(void)memset(port, 0, sizeof(*port)); /* Очищает старые счётчики и разрешение активации до настройки сессии. */
|
||||
port->can = can; /* Сохраняет заранее инициализированный handle HAL. */
|
||||
if (!pcan_boot_init(&port->boot, &single, &callbacks, port)) { port->can = NULL; return false; } /* При ошибке адреса оставляет последующий poll безопасным. */
|
||||
id = pcan_boot_make_id(0U, PCAN_BOOT_ROUTE_FROM_PM, single.device_type, single.device_id, 0U, 0U) << 3U; /* Переводит адрес прибора в регистровый формат bxCAN. */
|
||||
mask = (0x0FF00000UL << 3U) | 6U; /* Проверяет Route/Type/ID, IDE=1 и RTR=0; приоритет, MsgType и Body остаются открытыми. */
|
||||
id |= 4U; /* Устанавливает IDE в бите 2 аппаратного фильтра bxCAN. */
|
||||
filter.FilterBank = 0U; /* Выделенный загрузчик использует первый банк фильтров. */
|
||||
filter.FilterMode = CAN_FILTERMODE_IDMASK; /* Сравнивает адрес по маске вместо списка отдельных команд. */
|
||||
filter.FilterScale = CAN_FILTERSCALE_32BIT; /* Сохраняет все 29 бит расширенного идентификатора. */
|
||||
filter.FilterIdHigh = id >> 16U; /* Передаёт HAL старшую половину регистрового идентификатора. */
|
||||
filter.FilterIdLow = id & 0xFFFFU; /* Передаёт HAL младшую половину, включая IDE/RTR. */
|
||||
filter.FilterMaskIdHigh = mask >> 16U; /* Маскирует адрес прибора и направление обмена. */
|
||||
filter.FilterMaskIdLow = mask & 0xFFFFU; /* Отвергает стандартные и remote-кадры до программного разбора. */
|
||||
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; /* Приём выполняется опросом FIFO0 без IRQ. */
|
||||
filter.FilterActivation = ENABLE; /* Включает настроенный адресный фильтр. */
|
||||
filter.SlaveStartFilterBank = 14U; /* Стандартная граница банков CAN1/CAN2 в HAL; порт не использует CAN2. */
|
||||
if (HAL_CAN_ConfigFilter(can, &filter) != HAL_OK) { port->can = NULL; return false; } /* Не запускает загрузчик с неподтверждённой настройкой адресного фильтра. */
|
||||
id = (pcan_boot_make_id(0U, PCAN_BOOT_ROUTE_FROM_PM, 7U, 15U, PCAN_BOOT_MSG_DISCOVERY, 0U) << 3U) | 4U; /* Отдельно разрешает штатный broadcast discovery, сохраняя запрет broadcast-записи. */
|
||||
mask = (0x0FFFFFFFUL << 3U) | 6U; /* Проверяет всё кроме приоритета, включая MsgType=DISCOVERY и пустой Body. */
|
||||
filter.FilterBank = 1U; /* Второй выделенный банк обслуживает только обнаружение устройств. */
|
||||
filter.FilterIdHigh = id >> 16U; /* Записывает старшую половину broadcast discovery ID. */
|
||||
filter.FilterIdLow = id & 0xFFFFU; /* Записывает младшую половину с IDE=1 и RTR=0. */
|
||||
filter.FilterMaskIdHigh = mask >> 16U; /* Ограничивает тип и адрес broadcast запроса. */
|
||||
filter.FilterMaskIdLow = mask & 0xFFFFU; /* Ограничивает Body и формат кадра discovery. */
|
||||
if (HAL_CAN_ConfigFilter(can, &filter) != HAL_OK || HAL_CAN_Start(can) != HAL_OK) { port->can = NULL; return false; } /* Обезвреживает poll при отказе второго фильтра либо запуска CAN. */
|
||||
return true; /* Оба фильтра настроены, CAN успешно вышел из режима init. */
|
||||
} /* Завершает настройку одного аппаратного порта. */
|
||||
void STM32_BOOT_POLL(pcan_boot_stm32_t *port) /* Обслуживает загрузчик только из основного цикла, где допустимо синхронное стирание. */
|
||||
{ /* Вызов из CAN IRQ не допускается из-за длительных операций Flash. */
|
||||
CAN_HandleTypeDef *can; /* Получает аппаратный handle после проверки состояния порта. */
|
||||
uint32_t count; /* Ограничивает обработку глубиной аппаратного FIFO bxCAN. */
|
||||
if (port == NULL || port->can == NULL) return; /* Позволяет безопасно пропустить ещё не настроенный порт. */
|
||||
can = port->can; /* Восстанавливает семейный HAL-тип handle. */
|
||||
pcan_boot_poll(&port->boot, HAL_GetTick()); /* Обновляет монотонное время и тайм-аут сессии до приёма команд. */
|
||||
for (count = 0U; count < 3U && HAL_CAN_GetRxFifoFillLevel(can, CAN_RX_FIFO0) != 0U; ++count) { /* Не позволяет непрерывной нагрузке CAN захватить весь главный цикл. */
|
||||
CAN_RxHeaderTypeDef header = {0}; /* Принимает IDE/RTR/DLC независимо от аппаратной настройки фильтра. */
|
||||
uint8_t data[8]; /* Выделяет максимальный payload одного classic CAN-кадра. */
|
||||
if (HAL_CAN_GetRxMessage(can, CAN_RX_FIFO0, &header, data) != HAL_OK) break; /* Не разбирает неполученный кадр при аппаратной ошибке. */
|
||||
if (header.IDE == CAN_ID_EXT && header.RTR == CAN_RTR_DATA && header.DLC <= 8U && header.ExtId <= 0x1FFFFFFFUL) { /* Повторно проверяет внешний ввод до вызова общего decoder. */
|
||||
pcan_boot_poll(&port->boot, HAL_GetTick()); /* Учитывает время предыдущего синхронного erase перед следующей командой. */
|
||||
(void)pcan_boot_process(&port->boot, header.ExtId, data, (uint8_t)header.DLC); /* Передаёт адресные команды и восьмибайтовые блоки общему автомату. */
|
||||
} /* Завершает обработку допустимого формата кадра. */
|
||||
} /* Возвращает управление приложению после ограниченного пакета входящих кадров. */
|
||||
} /* Завершает обслуживание транспорта и таймеров. */
|
||||
@@ -0,0 +1,50 @@
|
||||
# STM32F103C8: ProtoCAN Boot v1
|
||||
|
||||
## Подключённый KONOR без HAL
|
||||
|
||||
`boot_main.c` содержит извлечённый CMSIS-загрузчик CAN/UART, `boot_request.c` —
|
||||
вход из приложения через BKP DR1, `boot_profile.h` — общие адреса и magic.
|
||||
`KONOR_ds18b20/src/bootloader.c`, `src/boot_control.c`, `inc/boot_control.h`
|
||||
теперь включают эти файлы из `lib/c/protocan-boot/ports/stm32f103`.
|
||||
Keil BOOT компилирует общие `pcan_boot.c` и `rs485_boot.c`; локальный `proto.c`
|
||||
исключён только из загрузчика, приложение продолжает использовать свой parser.
|
||||
UART BEGIN/DATA/END/STATUS сохраняют SETGUI v1, 512000 бод и PA9/PA10.
|
||||
Номера блоков, CRC и сессии CAN обслуживает только общий ProtoCAN-автомат.
|
||||
Оба транспорта используют один Flash writer; одновременная запись блокируется.
|
||||
|
||||
Native вариант не нужно собирать вместе с reusable HAL `.c` ниже.
|
||||
|
||||
## Reusable HAL-порт
|
||||
|
||||
Аппаратный single-slot профиль извлечён из `KONOR_ds18b20/src/bootloader.c`.
|
||||
Прямые операции с регистрами адаптированы к современному STM32CubeF1 HAL;
|
||||
UART SETGUI parser исходного проекта в этот CAN-порт не входит.
|
||||
|
||||
| Область | Начало | Конец, исключён | Размер |
|
||||
|---|---|---|---:|
|
||||
| Загрузчик | `0x08000000` | `0x08003000` | 12 КиБ |
|
||||
| Приложение | `0x08003000` | `0x08010000` | 52 КиБ |
|
||||
| SRAM для MSP | `0x20000000` | `0x20005000` | 20 КиБ |
|
||||
|
||||
MSP может совпадать с верхней границей SRAM, но не с нижней; выравнивание —
|
||||
восемь байт. Страница Flash — 1024 байта, единица записи — полуслово 16 бит.
|
||||
Профиль предназначен для штатных 64 КиБ Flash STM32F103C8, без использования
|
||||
недокументированного расширения памяти.
|
||||
|
||||
Подключайте `pcan_boot_stm32f103.c`, общий `src/pcan_boot.c` и сохраните соседний
|
||||
каталог `stm32-common`. STM32CubeF1 HAL должен включать CAN и Flash/FlashEx.
|
||||
Для определения CMSIS обычно используется `STM32F103xB`; физический профиль
|
||||
этого порта остаётся C8 с 64 КиБ.
|
||||
|
||||
Проект настраивает CAN1 и GPIO: исходная плата использует PA11 RX, PA12 TX,
|
||||
500 кбит/с, DeviceType=7 / DeviceID=13. Значения адреса задавайте через
|
||||
`pcan_boot_config_t` и согласуйте с host-клиентом. На стороне Flash всегда
|
||||
используется логический слот A.
|
||||
|
||||
Вызывайте `pcan_boot_stm32f103_init` после `HAL_CAN_Init`, затем
|
||||
`pcan_boot_stm32f103_poll` из основного цикла. Linker приложения и VTOR должны
|
||||
указывать на `0x08003000`. В загрузчике зарезервируйте весь диапазон до этого
|
||||
адреса и проверьте размер итогового образа по MAP: HAL-сборка может оказаться
|
||||
крупнее исходного загрузчика с прямым доступом к регистрам.
|
||||
|
||||
[Общая инструкция, пример, ограничения COMMIT и запуска после reset](../stm32-common/README.md).
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "boot_profile.h" /* Сохраняет карту памяти, BKP-запросы и CAN-адрес исходного KONOR. */
|
||||
#include "../../include/pcan_boot.h" /* Использует единственную текущую машину состояний ProtoCAN из templates. */
|
||||
#include "../../../rs485-boot/include/rs485_boot.h" /* Повторно использует общий SETGUI v1 UART-автомат вместо локальной копии parser/CRC. */
|
||||
#include "stm32f10x.h" /* Работает с существующим CMSIS/StdPeriph проекта без добавления HAL. */
|
||||
#include <string.h> /* Предоставляет безопасное копирование коротких хвостов Flash. */
|
||||
#define NATIVE_APP_BASE BOOT_APP_ADDRESS /* Выбирает начало приложения после 12 КиБ загрузчика. */
|
||||
#define NATIVE_APP_SIZE (BOOT_FLASH_END - BOOT_APP_ADDRESS) /* Ограничивает обновление 52 КиБ штатной Flash C8. */
|
||||
#define NATIVE_RAM_END 0x20005000UL /* Начальный MSP допускается до верхней границы 20 КиБ SRAM. */
|
||||
#define NATIVE_PROGRAM_UNIT 2U /* Контроллер Flash STM32F103 записывает полуслова по два байта. */
|
||||
#define NATIVE_PPRE1_SHIFT 8U /* STM32F1 размещает делитель APB1 в RCC_CFGR[10:8]. */
|
||||
static pcan_boot_t boot; static volatile uint32_t ticks_ms; /* Хранит CAN-сессию и монотонный счётчик SysTick загрузчика. */
|
||||
static rs485_boot_t uart_boot; /* Хранит UART parser, повторяемую запись блоков и статус общего SETGUI v1-автомата. */
|
||||
static bool native_wait(void) /* Ограничивает ожидание Flash и проверяет аппаратные ошибки программирования. */
|
||||
{ /* На однобанковой Flash CPU может останавливаться до завершения операции. */
|
||||
uint32_t guard = SystemCoreClock; /* Конечный бюджет проверок численно равен HCLK в Гц; это число итераций CPU, а не аппаратный тайм-аут. */
|
||||
while ((FLASH->SR & FLASH_SR_BSY) != 0U && guard != 0U) --guard; /* Не продолжает запись до освобождения контроллера Flash. */
|
||||
return (FLASH->SR & (FLASH_SR_BSY | FLASH_SR_PGERR | FLASH_SR_WRPRTERR)) == 0U; /* Ошибки записи или защиты делают аппаратную операцию неуспешной. */
|
||||
} /* Завершает ожидание Flash с аппаратной проверкой. */
|
||||
static bool native_unlock(void) /* Открывает Flash для одной ограниченной операции. */
|
||||
{ /* Не меняет Option Bytes или защиту загрузчика. */
|
||||
if ((FLASH->SR & FLASH_SR_BSY) != 0U) return false; /* Не посылает ключи занятому контроллеру. */
|
||||
FLASH->SR = FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_SR_EOP; /* Очищает завершение и прежние ошибки записью W1C. */
|
||||
if ((FLASH->CR & FLASH_CR_LOCK) != 0U) { FLASH->KEYR = 0x45670123UL; FLASH->KEYR = 0xCDEF89ABUL; } /* Использует обязательную последовательность ключей STM32F1 из RM0008. */
|
||||
return (FLASH->CR & FLASH_CR_LOCK) == 0U; /* Проверяет фактическое снятие аппаратной блокировки. */
|
||||
} /* Завершает разблокировку без сохранения разрешения между командами. */
|
||||
static void native_lock(void) { FLASH->CR = FLASH_CR_LOCK; } /* Снимает PG/PER и восстанавливает LOCK даже после обработанной ошибки. */
|
||||
static bool native_program(uint32_t address, uint32_t value) /* Записывает и перечитывает одно выровненное полуслово. */
|
||||
{ /* Границы и выравнивание заранее проверяет общий writer. */
|
||||
volatile uint16_t *destination = (volatile uint16_t *)(uintptr_t)address; /* Выбирает аппаратную 16-битную операцию F103. */
|
||||
FLASH->SR = FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_SR_EOP; FLASH->CR = FLASH_CR_PG; /* Очищает прежний результат и разрешает программирование полуслова. */
|
||||
*destination = (uint16_t)value; /* Передаёт младшие 16 бит, включая FF-дополнение неполного хвоста. */
|
||||
if (!native_wait()) return false; /* Не подтверждает запись при BUSY или аппаратной ошибке. */
|
||||
FLASH->CR = 0U; return *destination == (uint16_t)value; /* Убирает PG и проверяет реально прочитанное полуслово. */
|
||||
} /* Завершает одну аппаратную запись. */
|
||||
static bool native_erase(uint32_t size) /* Стирает страницы приложения, не пересекая область загрузчика. */
|
||||
{ /* Общая геометрия страницы задаётся единственным профилем F103C8. */
|
||||
uint32_t address, end; bool ok = true; /* Хранит исключённый конец стирания и результат до блокировки Flash. */
|
||||
if (size < 8U || size > NATIVE_APP_SIZE) return false; /* Не стирает Flash для пустого или слишком большого образа. */
|
||||
end = BOOT_APP_ADDRESS + ((size + BOOT_PAGE_SIZE - 1U) & ~(BOOT_PAGE_SIZE - 1U)); /* Округляет конец вверх к странице 1024 байта внутри профиля. */
|
||||
if (end > BOOT_FLASH_END || !native_unlock()) return false; /* Проверяет округлённую верхнюю границу до первого стирания. */
|
||||
for (address = BOOT_APP_ADDRESS; address < end; address += BOOT_PAGE_SIZE) { /* Обходит только страницы, покрывающие полезный образ. */
|
||||
FLASH->SR = FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_SR_EOP; FLASH->CR = FLASH_CR_PER; FLASH->AR = address; FLASH->CR |= FLASH_CR_STRT; /* Запускает erase одной конкретной страницы вместо mass erase. */
|
||||
if (!native_wait()) { ok = false; break; } /* Прекращает стирание на первой аппаратной ошибке. */
|
||||
} /* Завершает ограниченный диапазон страниц. */
|
||||
native_lock(); return ok; /* Обязательно закрывает Flash после успешного или прерванного erase. */
|
||||
} /* Завершает аппаратное стирание F1. */
|
||||
static void native_gpio_init(void) /* Сохраняет исходную разводку CAN1 KONOR: PA11 RX и PA12 TX. */
|
||||
{ /* UART на PA9/PA10 остаётся независимым загрузочным транспортом. */
|
||||
uint32_t value; RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; /* Включает GPIOA, AFIO и CAN1 в соответствующих APB-доменах. */
|
||||
value = GPIOA->CRH; value &= ~((0xFUL << 12U) | (0xFUL << 16U)); value |= (0x8UL << 12U) | (0xBUL << 16U); GPIOA->CRH = value; /* Настраивает PA11 как вход с подтяжкой, PA12 как AF push-pull согласно RM0008. */
|
||||
GPIOA->BSRR = 1UL << 11U; /* Выбирает подтяжку CAN RX вверх, соответствующую рецессивному состоянию неподключённой линии. */
|
||||
} /* Завершает настройку физического CAN-порта. */
|
||||
static void native_peripheral_stop(void) { USART1->CR1 = 0U; RCC->APB1RSTR |= RCC_APB1RSTR_CAN1RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_CAN1RST; } /* Отключает загрузочный USART и сбрасывает CAN перед handoff. */
|
||||
#include "../stm32-common/boot_native.inc" /* Подключает общий транспорт, single-slot callbacks, CRC readback и безопасный переход MSP. */
|
||||
static bool uart_transmit(void *user, const uint8_t *data, uint16_t length) /* Отправляет кадры SETGUI v1 с ожиданием последнего стоп-бита. */
|
||||
{ /* Использует прежнюю скорость USART1 512000 бод без управления RS-485 DE. */
|
||||
uint16_t index; (void)user; /* UART-автомат использует единственный статический аппаратный порт. */
|
||||
for (index = 0U; index < length; ++index) { while ((USART1->SR & USART_SR_TXE) == 0U) {} USART1->DR = data[index]; } /* Передаёт только объявленные байты полного кадра. */
|
||||
while ((USART1->SR & USART_SR_TC) == 0U) {} return true; /* Дожидается физического завершения ответа перед возможным reset. */
|
||||
} /* Завершает аппаратную передачу UART. */
|
||||
static bool uart_erase(void *user, uint32_t size) { (void)user; return boot.state == PCAN_BOOT_STATE_IDLE && native_erase(size); } /* Не позволяет UART стереть Flash во время открытой CAN-сессии. */
|
||||
static bool uart_write(void *user, uint32_t offset, const uint8_t *data, uint16_t length) { (void)user; return native_write_bytes(uart_boot.image_size, offset, data, length); } /* Использует ту же проверку диапазонов и аппаратную запись, что CAN. */
|
||||
static bool uart_read(void *user, uint32_t offset, uint8_t *data, uint16_t length) /* Позволяет общему UART-автомату подтвердить повторно присланный блок. */
|
||||
{ /* Проверяет размер до сложения адреса, исключая переполнение. */
|
||||
(void)user; if (data == NULL || offset > NATIVE_APP_SIZE || length > NATIVE_APP_SIZE - offset) return false; /* Не читает за областью приложения. */
|
||||
(void)memcpy(data, (const void *)(uintptr_t)(NATIVE_APP_BASE + offset), length); return true; /* Копирует только объявленный безопасный диапазон Flash. */
|
||||
} /* Завершает UART readback для повторов передачи. */
|
||||
static bool uart_image_valid(void *user, uint32_t size, uint32_t crc) { (void)user; return native_image_valid(size, crc); } /* Перед END использует тот же CRC Flash и проверку векторов, что CAN VERIFY. */
|
||||
static void uart_init(void) /* Инициализирует существующий TTL UART и общий SETGUI v1-автомат. */
|
||||
{ /* Локальный Proto parser исходного проекта загрузчику больше не нужен. */
|
||||
uint32_t value; rs485_boot_config_t config = {BOOT_APP_ADDRESS, NATIVE_APP_SIZE, RS485_BOOT_MAX_BLOCK}; rs485_boot_port_t callbacks = {0}; /* Сохраняет прежний wire-протокол и предел 504 полезных байта на DATA. */
|
||||
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN; value = GPIOA->CRH; value &= ~((0xFUL << 4U) | (0xFUL << 8U)); value |= (0xBUL << 4U) | (0x4UL << 8U); GPIOA->CRH = value; /* Настраивает PA9 TX AF-PP и PA10 RX floating, как исходный KONOR. */
|
||||
USART1->BRR = (SystemCoreClock + 256000UL) / 512000UL; USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; /* При штатном APB2=HCLK сохраняет округлённую скорость 512000 бод, 8-N-1. */
|
||||
callbacks.transmit = uart_transmit; callbacks.erase = uart_erase; callbacks.write = uart_write; callbacks.read = uart_read; callbacks.image_valid = uart_image_valid; callbacks.reboot = native_reboot; /* Подключает аппаратные операции к общему UART-автомату. */
|
||||
(void)rs485_boot_init(&uart_boot, &config, &callbacks, NULL); /* Инициализирует parser и RAM-сессию отдельного транспорта. */
|
||||
} /* Завершает настройку UART без локальной копии codec. */
|
||||
static void uart_poll(void) /* Обслуживает UART из главного цикла без прерываний и выделения памяти. */
|
||||
{ /* Во время CAN-сессии UART BEGIN отвергается callback стирания. */
|
||||
uint32_t status = USART1->SR; uint8_t byte; /* Сохраняет статус до чтения регистра данных. */
|
||||
if ((status & (USART_SR_ORE | USART_SR_NE | USART_SR_FE | USART_SR_PE)) != 0U) { byte = (uint8_t)USART1->DR; (void)byte; } /* Снимает аппаратные ошибки чтением SR/DR и отбрасывает повреждённый байт. */
|
||||
else if ((status & USART_SR_RXNE) != 0U) { byte = (uint8_t)USART1->DR; rs485_boot_feed(&uart_boot, &byte, 1U); } /* Передаёт исправный байт общему parser с прежним кадрированием A5 5A. */
|
||||
} /* Завершает неблокирующий приём UART. */
|
||||
int main(void) /* Самостоятельная точка входа общего загрузчика KONOR для Keil/GCC. */
|
||||
{ /* Тактирование и startup остаются в исходном проекте и выполняются до main. */
|
||||
uint16_t request; bool can_ready; /* Хранит одноразовое назначение reset и результат включения CAN. */
|
||||
RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; PWR->CR |= PWR_CR_DBP; request = (uint16_t)BKP->DR1; BKP->DR1 = 0U; /* Потребляет совместимый BKP-запрос ровно один раз. */
|
||||
if (request == BOOT_REQUEST_SYSTEM_MAGIC) { /* Сохраняет исходную подготовку периферии перед переходом в заводской UART-загрузчик. */
|
||||
__disable_irq(); RCC->APB1RSTR = 0U; RCC->APB2RSTR = 0U; RCC->AHBENR = 0x00000014UL; RCC->APB2ENR = 0U; RCC->APB1ENR = 0U; /* Как исходный KONOR, оставляет только AHB Flash/SRAM и отключает внешнюю APB-периферию перед System Memory. */
|
||||
native_jump(0x1FFFF000UL); /* AN2606: фиксированное начало System Memory STM32F103 medium density, дальнейший переход выполняется без доступа к старому MSP. */
|
||||
} /* Завершает совместимую ветку заводского загрузчика. */
|
||||
if (request != BOOT_REQUEST_FIRMWARE_MAGIC && native_vectors_valid(NATIVE_APP_SIZE)) native_jump(BOOT_APP_ADDRESS); /* Сохраняет немедленный обычный запуск KONOR при допустимых векторах приложения. */
|
||||
SystemCoreClockUpdate(); if (SysTick_Config(SystemCoreClock / 1000U) != 0U || !native_init()) for (;;) {} /* Создаёт миллисекундный tick и проверяет текущую конфигурацию ProtoCAN до обработки команд. */
|
||||
uart_init(); can_ready = native_can_init(); /* При отказе CAN сохраняет доступный UART-канал восстановления. */
|
||||
for (;;) { uart_poll(); if (can_ready && uart_boot.state != RS485_BOOT_RECEIVING) native_can_poll(); } /* Не позволяет двум транспортам одновременно изменять одну область Flash. */
|
||||
} /* Завершает самостоятельный загрузчик; управление остаётся в сервисном цикле до успешного обновления. */
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef PCAN_BOOT_F103_PROFILE_H /* Согласует загрузчик и приложение KONOR с одной картой памяти. */
|
||||
#define PCAN_BOOT_F103_PROFILE_H /* Исключает повторное определение аппаратных констант. */
|
||||
#define BOOT_APP_ADDRESS 0x08003000UL /* Приложение KONOR начинается после 12 КиБ защищаемого загрузчика. */
|
||||
#define BOOT_FLASH_END 0x08010000UL /* Верхняя исключённая граница штатных 64 КиБ Flash STM32F103C8. */
|
||||
#define BOOT_PAGE_SIZE 1024UL /* Средняя плотность STM32F103C8 использует страницы Flash по 1024 байта. */
|
||||
#define BOOT_REQUEST_FIRMWARE_MAGIC 0xB007U /* Сохраняет совместимость существующего запроса обновления в BKP DR1. */
|
||||
#define BOOT_REQUEST_SYSTEM_MAGIC 0xB315U /* Сохраняет запрос перехода в заводской UART-загрузчик AN3155. */
|
||||
#define PCAN_BOOT_DEVICE_TYPE 7U /* Адрес исходного загрузчика KONOR: DeviceType=7. */
|
||||
#define PCAN_BOOT_DEVICE_ID 13U /* Адрес исходного загрузчика KONOR: DeviceID=13. */
|
||||
#define PCAN_BOOT_PRODUCT_TYPE 0U /* Исходный ProductType KONOR; должен совпадать с manifest клиента. */
|
||||
#define PCAN_BOOT_HW_REVISION 0U /* Исходная ревизия платы, согласованная с диапазоном HW manifest. */
|
||||
#define PCAN_BOOT_FW_VERSION 0U /* Версия самого загрузчика KONOR; версию приложения сообщает приложение. */
|
||||
#define PCAN_BOOT_ACK_WINDOW 16U /* Подтверждает каждые 16 блоков ProtoCAN по восемь байт. */
|
||||
#define PCAN_BOOT_CAN_BITRATE 500000UL /* Исходная скорость CAN-загрузчика, бит в секунду. */
|
||||
void Boot_RequestFirmware(void); /* Записывает запрос обновления в BKP DR1 и выполняет программный reset. */
|
||||
void Boot_RequestSystemMemory(void); /* Записывает запрос AN3155 в BKP DR1 и выполняет программный reset. */
|
||||
#endif /* Завершает профиль STM32F103C8 для загрузчика и приложения. */
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "boot_profile.h" /* Использует ту же карту памяти и magic, что общий загрузчик F103. */
|
||||
#include "stm32f10x.h" /* Сохраняет совместимость KONOR с CMSIS/StdPeriph без зависимости от HAL. */
|
||||
static void request_boot(uint16_t magic) /* Сохраняет одноразовый запрос через программный reset. */
|
||||
{ /* BKP DR1 существует в backup-домене STM32F103 и не требует резервирования основной SRAM. */
|
||||
RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; /* Разрешает тактирование PWR и backup-регистров. */
|
||||
PWR->CR |= PWR_CR_DBP; /* Разрешает запись в защищённый backup-домен. */
|
||||
BKP->DR1 = magic; /* Записывает совместимый код назначения следующего запуска. */
|
||||
__DSB(); /* Гарантирует завершение записи до запроса системного сброса. */
|
||||
NVIC_SystemReset(); /* Следующий startup потребляет запрос и выбирает загрузчик. */
|
||||
} /* Завершает общую аппаратную последовательность входа. */
|
||||
void Boot_RequestFirmware(void) { request_boot(BOOT_REQUEST_FIRMWARE_MAGIC); } /* Сохраняет прежний API приложения KONOR для обновления CAN/UART. */
|
||||
void Boot_RequestSystemMemory(void) { request_boot(BOOT_REQUEST_SYSTEM_MAGIC); } /* Сохраняет прежний API меню для заводского UART-загрузчика STM32. */
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "pcan_boot_stm32f103.h" /* Подключает HAL STM32F1 и публичный профиль KONOR. */
|
||||
#include <string.h> /* Предоставляет инициализацию структур и чтение невыровненных октетов. */
|
||||
#define STM32_BOOT_INIT pcan_boot_stm32f103_init /* Экспортирует семейное имя общей процедуры настройки. */
|
||||
#define STM32_BOOT_POLL pcan_boot_stm32f103_poll /* Экспортирует семейное имя обслуживания FIFO и таймеров. */
|
||||
#define STM32_BOOT_VECTORS pcan_boot_stm32f103_vectors_valid /* Экспортирует проверку векторов именно этого профиля. */
|
||||
#define STM32_BOOT_BASE PCAN_BOOT_F103_APP_ADDRESS /* Сохраняет адрес приложения исходной прошивки KONOR. */
|
||||
#define STM32_BOOT_END PCAN_BOOT_F103_FLASH_END /* Ограничивает erase и write штатными 64 КиБ Flash. */
|
||||
#define STM32_BOOT_RAM_END 0x20005000UL /* STM32F103C8 имеет 20 КиБ SRAM; MSP может указывать сразу за последним байтом. */
|
||||
#define STM32_BOOT_UNIT 2U /* Flash STM32F103 программируется полусловами по два байта. */
|
||||
#define STM32_BOOT_PROGRAM FLASH_TYPEPROGRAM_HALFWORD /* Выбирает штатный HAL-режим записи полуслова. */
|
||||
#define STM32_BOOT_SYNC() ((void)0) /* F103 не имеет Flash data cache, требующего сброса перед readback. */
|
||||
static bool erase_region(pcan_boot_stm32_t *port, uint32_t size) /* Стирает только страницы, покрывающие проверенный размер образа. */
|
||||
{ /* Вызывается общим кодом после HAL_FLASH_Unlock и проверки границ. */
|
||||
FLASH_EraseInitTypeDef erase = {0}; /* Исключает неопределённые поля семейной структуры HAL. */
|
||||
uint32_t error = 0U; /* Получает адрес страницы, на которой HAL обнаружил ошибку. */
|
||||
(void)port; /* F1 стирает выбранные страницы одной синхронной операцией HAL. */
|
||||
erase.TypeErase = FLASH_TYPEERASE_PAGES; /* Не разрешает массовое стирание, затрагивающее загрузчик. */
|
||||
erase.PageAddress = STM32_BOOT_BASE; /* Начинает стирание с первой страницы приложения. */
|
||||
erase.NbPages = (size + 1023U) / 1024U; /* Округляет вверх к странице 1 КиБ STM32F103C8, как в исходном KONOR. */
|
||||
return HAL_FLASHEx_Erase(&erase, &error) == HAL_OK; /* Прерывает обновление при ошибке или тайм-ауте Flash. */
|
||||
} /* Завершает аппаратную реализацию стирания F1. */
|
||||
#include "../stm32-common/pcan_boot_stm32.inc" /* Собирает общий transport/readback с геометрией и HAL этого семейства. */
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef PCAN_BOOT_STM32F103_H /* Исключает повторное объявление порта STM32F103C8. */
|
||||
#define PCAN_BOOT_STM32F103_H /* Обозначает активный интерфейс аппаратного профиля. */
|
||||
#include "stm32f1xx_hal.h" /* Получает тип CAN_HandleTypeDef и драйверы Flash из STM32CubeF1. */
|
||||
#include "boot_profile.h" /* Использует ту же аппаратную карту, что подключённый CMSIS-загрузчик KONOR. */
|
||||
#include "../stm32-common/pcan_boot_stm32.h" /* Подключает состояние и API переносимого автомата. */
|
||||
#define PCAN_BOOT_F103_APP_ADDRESS BOOT_APP_ADDRESS /* Сохраняет публичное HAL-имя адреса приложения без дублирования числа 0x08003000. */
|
||||
#define PCAN_BOOT_F103_FLASH_END BOOT_FLASH_END /* Сохраняет публичное HAL-имя верхней границы 64 КиБ Flash из общего профиля. */
|
||||
bool pcan_boot_stm32f103_init(pcan_boot_stm32_t *port, CAN_HandleTypeDef *can, const pcan_boot_config_t *config); /* Назначает single-slot callbacks, фильтры 0/1 в FIFO0 и запускает заранее настроенный CAN1. */
|
||||
void pcan_boot_stm32f103_poll(pcan_boot_stm32_t *port); /* Обслуживает до трёх кадров FIFO0 и таймеры ядра из основного цикла. */
|
||||
bool pcan_boot_stm32f103_vectors_valid(void); /* Проверяет MSP и Thumb-вектор в пределах профиля; CRC установленного образа не подтверждает. */
|
||||
#endif /* Завершает интерфейс профиля STM32F103C8. */
|
||||
@@ -0,0 +1,51 @@
|
||||
# STM32F407VE: ProtoCAN Boot v1
|
||||
|
||||
## Подключённый climate
|
||||
|
||||
`boot_main.c` содержит извлечённый CMSIS-загрузчик, `boot_control_hal.c/.h` —
|
||||
адресный ENTER_BOOT из рабочего приложения. `boot_profile.h` определяет карту
|
||||
Flash и SRAM-запрос; идентичность прибора остаётся в `pcan_boot_config.h` проекта.
|
||||
Исходные `Bootloader/bootloader_f407.c`, `Libraries/ProtoCanBoot/Src/pcan_boot.c`
|
||||
и `Core/Src/can_boot_control.c` стали include-обёртками общего submodule.
|
||||
Старый include API также перенаправлен в `templates`.
|
||||
|
||||
BOOT linker исключает последние восемь байт SRAM: запрос хранится в
|
||||
`0x2001FFFC`, а initial stack не может затереть его до main. Приложение climate
|
||||
уже использует нижние 112 КиБ SRAM и не перекрывает запрос. Обычный reset
|
||||
сохраняет десятисекундное окно обновления; TX ACK перед REBOOT проверяется.
|
||||
Native boot main и reusable HAL `.c` ниже — отдельные варианты сборки.
|
||||
|
||||
## Reusable HAL-порт
|
||||
|
||||
Аппаратный single-slot профиль извлечён из
|
||||
`climate_control_f407vet6_f4/Bootloader/bootloader_f407.c` и
|
||||
`Libraries/ProtoCanBoot/Inc/pcan_boot_config.h` проекта climate.
|
||||
Операции с регистрами адаптированы к современному STM32CubeF4 HAL.
|
||||
|
||||
| Область | Начало | Конец, исключён | Размер |
|
||||
|---|---|---|---:|
|
||||
| Загрузчик: секторы 0/1 | `0x08000000` | `0x08008000` | 32 КиБ |
|
||||
| Приложение: секторы 2..5 | `0x08008000` | `0x08040000` | 224 КиБ |
|
||||
| EEPROM-кольцо: секторы 6/7 | `0x08040000` | `0x08080000` | 256 КиБ |
|
||||
| Основная SRAM для MSP | `0x20000000` | `0x20020000` | 128 КиБ |
|
||||
|
||||
Размеры стираемых секторов 2/3/4/5: 16/16/64/128 КиБ. Запись выполняется
|
||||
словами 32 бита, с `FLASH_VOLTAGE_RANGE_3`; VDD платы должно быть 2,7..3,6 В.
|
||||
Порт не стирает секторы 0/1/6/7. CCM не используется как область допустимого MSP.
|
||||
|
||||
Подключайте `pcan_boot_stm32f407.c`, общий `src/pcan_boot.c` и сохраните соседний
|
||||
каталог `stm32-common`. В CMSIS выберите `STM32F407xx`, в HAL включите CAN и
|
||||
Flash/FlashEx. Проект настраивает CAN1, скорость и GPIO: исходная плата использует
|
||||
PD0 RX, PD1 TX, AF9, 500 кбит/с, DeviceType=7 / DeviceID=3.
|
||||
|
||||
Вызывайте `pcan_boot_stm32f407_init` после `HAL_CAN_Init`, затем
|
||||
`pcan_boot_stm32f407_poll` из основного цикла. Адрес прибора, Product/HW/FW,
|
||||
окно ACK и тайм-аут сессии задаются через `pcan_boot_config_t` по именам полей.
|
||||
Инициализация принудительно задаёт логический слот A и ёмкость 224 КиБ.
|
||||
|
||||
Linker приложения и VTOR должны указывать на `0x08008000`; загрузчик должен
|
||||
помещаться в 32 КиБ до этого адреса. Это исходная CAN-разметка climate:
|
||||
USB SET v2-порт F407 из `set-protocol` использует другую карту памяти,
|
||||
поэтому его linker и готовые USB-образы нельзя подключать без адаптации.
|
||||
|
||||
[Общая инструкция, пример, ограничения COMMIT и запуска после reset](../stm32-common/README.md).
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "boot_control_hal.h" /* Подключает совместимый API приложения climate. */
|
||||
#include "boot_profile.h" /* Получает общий адрес SRAM-запроса и карту Flash. */
|
||||
#include "pcan_boot_config.h" /* Идентичность прибора остаётся настройкой проекта, общей для boot и app. */
|
||||
HAL_StatusTypeDef CanBootControl_Start(CAN_HandleTypeDef *can) /* Сохраняет прежнюю настройку выделенного CAN приложения. */
|
||||
{ /* Новые приложения с DeviceCan используют только Handle и собственные адресные фильтры. */
|
||||
CAN_FilterTypeDef filter = {0}; /* Исключает неопределённые поля аппаратной конфигурации. */
|
||||
if (can == NULL) return HAL_ERROR; /* Не обращается к отсутствующему HAL handle. */
|
||||
filter.FilterBank = 0U; filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_32BIT; /* Сохраняет широкую фильтрацию старого выделенного канала; адрес проверяется программно. */
|
||||
filter.FilterFIFOAssignment = CAN_RX_FIFO0; filter.FilterActivation = ENABLE; filter.SlaveStartFilterBank = 14U; /* Использует FIFO0 и штатное разделение банков CAN1/CAN2. */
|
||||
if (HAL_CAN_ConfigFilter(can, &filter) != HAL_OK) return HAL_ERROR; /* Не запускает CAN с неподтверждённым фильтром. */
|
||||
return HAL_CAN_Start(can); /* Возвращает вызывающему приложению фактический результат запуска. */
|
||||
} /* Завершает совместимую настройку транспорта приложения. */
|
||||
void CanBootControl_Poll(CAN_HandleTypeDef *can) /* Поддерживает старые приложения без отдельного распределителя входящих кадров. */
|
||||
{ /* Приём ограничен одним кадром, чтобы не блокировать главный цикл. */
|
||||
CAN_RxHeaderTypeDef header; uint8_t data[8]; /* Выделяет полный буфер classic CAN независимо от входящего DLC. */
|
||||
if (can != NULL && HAL_CAN_GetRxFifoFillLevel(can, CAN_RX_FIFO0) != 0U && HAL_CAN_GetRxMessage(can, CAN_RX_FIFO0, &header, data) == HAL_OK) (void)CanBootControl_Handle(&header, data); /* Передаёт только успешно принятый кадр общему обработчику. */
|
||||
} /* Завершает опрос канала совместимости. */
|
||||
uint8_t CanBootControl_Handle(const CAN_RxHeaderTypeDef *header, const uint8_t data[8]) /* Запускает обновление только для собственного адреса и ненулевой сессии. */
|
||||
{ /* Ответ ENTER_BOOT отправляет уже загрузчик после повторного запроса клиента. */
|
||||
uint32_t id; /* Сохраняет проверенный расширенный CAN ID запроса. */
|
||||
(void)data; /* ENTER_BOOT не содержит payload и не должен читать входной буфер. */
|
||||
if (header == NULL || header->IDE != CAN_ID_EXT || header->RTR != CAN_RTR_DATA || header->DLC != 0U || header->ExtId > 0x1FFFFFFFUL) return 0U; /* Отвергает стандартные, remote и ошибочно дополненные запросы. */
|
||||
id = header->ExtId; /* Извлекает идентификатор только после проверки внешнего заголовка. */
|
||||
if (((id >> 27U) & 1U) != 0U || ((id >> 24U) & 7U) != PCAN_BOOT_DEVICE_TYPE || ((id >> 20U) & 15U) != PCAN_BOOT_DEVICE_ID || ((id >> 16U) & 15U) != 9U || (id & 255U) != 2U || ((id >> 8U) & 255U) == 0U) return 0U; /* Сопоставляет Route=0, адрес, BOOT_CONTROL=9, ENTER_BOOT=2 и SessionID != 0. */
|
||||
*((volatile uint32_t *)(uintptr_t)PCAN_BOOT_REQUEST_ADDRESS) = PCAN_BOOT_REQUEST_MAGIC; /* Сохраняет одноразовый запрос в слове SRAM, исключённом из linker-стека. */
|
||||
__DSB(); /* Дожидается записи SRAM перед системным reset. */
|
||||
NVIC_SystemReset(); /* Передаёт управление общему загрузчику без подтверждения из приложения. */
|
||||
return 1U; /* Позволяет тестовой модели и старому диспетчеру отметить обработанный запрос. */
|
||||
} /* Завершает безопасную проверку входа в загрузчик. */
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef PCAN_BOOT_F407_CONTROL_HAL_H /* Защищает совместимый API входа climate в общий загрузчик. */
|
||||
#define PCAN_BOOT_F407_CONTROL_HAL_H /* Обозначает включение адаптера рабочего приложения. */
|
||||
#include "stm32f4xx_hal.h" /* Приложение climate передаёт штатные заголовки и handle HAL CAN. */
|
||||
HAL_StatusTypeDef CanBootControl_Start(CAN_HandleTypeDef *can); /* Сохраняет прежний API выделенного CAN-канала приложения. */
|
||||
void CanBootControl_Poll(CAN_HandleTypeDef *can); /* Проверяет один входящий кадр в старых проектах без общего CAN-диспетчера. */
|
||||
uint8_t CanBootControl_Handle(const CAN_RxHeaderTypeDef *header, const uint8_t data[8]); /* Обрабатывает адресный ENTER_BOOT из существующего диспетчера DeviceCan. */
|
||||
#endif /* Завершает интерфейс адаптера приложения F407. */
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "boot_profile.h" /* Сохраняет карту Flash climate и зарезервированное SRAM-слово запроса. */
|
||||
#include "pcan_boot_config.h" /* Идентичность прибора и окно ACK остаются конфигурацией исходного проекта. */
|
||||
#include "../../include/pcan_boot.h" /* Подключает единственную текущую state machine ProtoCAN из templates. */
|
||||
#include "stm32f407xx.h" /* Сохраняет лёгкую CMSIS-сборку загрузчика без HAL-драйверов. */
|
||||
#include <string.h> /* Предоставляет безопасное формирование слов из коротких хвостов CAN. */
|
||||
#define NATIVE_APP_BASE PCAN_BOOT_APP_ADDRESS /* Приложение climate начинается с физического сектора 2. */
|
||||
#define NATIVE_APP_SIZE PCAN_BOOT_APP_SIZE /* Ограничивает прошивку секторами 2..5, сохраняя EEPROM-кольцо. */
|
||||
#define NATIVE_RAM_END 0x20020000UL /* Физическая верхняя граница 128 КиБ SRAM сохраняет совместимость векторов ранее установленных приложений. */
|
||||
#define NATIVE_PROGRAM_UNIT 4U /* F407 программирует слова x32 при VDD 2,7..3,6 В. */
|
||||
#define NATIVE_PPRE1_SHIFT 10U /* STM32F4 размещает делитель APB1 в RCC_CFGR[12:10]. */
|
||||
#define NATIVE_FLASH_ERRORS (FLASH_SR_PGSERR | FLASH_SR_PGPERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR) /* Проверяет последовательность, разрядность, выравнивание и аппаратную защиту Flash по RM0090. */
|
||||
static pcan_boot_t boot; static volatile uint32_t ticks_ms; /* Хранит единственный автомат CAN и миллисекундные часы загрузчика. */
|
||||
static bool native_wait(void) /* Ограничивает аппаратное ожидание Flash без бесконечной блокировки. */
|
||||
{ /* Код продолжит исполняться после аппаратного Flash stall на однобанковом F407. */
|
||||
uint32_t guard = SystemCoreClock; while ((FLASH->SR & FLASH_SR_BSY) != 0U && guard != 0U) --guard; /* Использует конечный бюджет проверок в единицах итераций текущего CPU. */
|
||||
return (FLASH->SR & (FLASH_SR_BSY | NATIVE_FLASH_ERRORS)) == 0U; /* Не подтверждает операцию при BUSY или любой выбранной аппаратной ошибке. */
|
||||
} /* Завершает проверку готовности Flash. */
|
||||
static bool native_unlock(void) /* Открывает Flash только для текущей операции загрузчика. */
|
||||
{ /* Геометрия профиля исключает массовое стирание и запись Option Bytes. */
|
||||
if ((FLASH->SR & FLASH_SR_BSY) != 0U) { return false; } FLASH->SR = NATIVE_FLASH_ERRORS; /* Не посылает ключи занятой Flash и очищает предыдущие ошибки W1C. */
|
||||
if ((FLASH->CR & FLASH_CR_LOCK) != 0U) { FLASH->KEYR = 0x45670123UL; FLASH->KEYR = 0xCDEF89ABUL; } /* Использует штатную последовательность ключей RM0090. */
|
||||
return (FLASH->CR & FLASH_CR_LOCK) == 0U; /* Проверяет фактическое снятие блокировки Flash. */
|
||||
} /* Завершает разблокировку контроллера Flash. */
|
||||
static void native_lock(void) { FLASH->CR = FLASH_CR_LOCK; } /* Выключает PG/SER и восстанавливает LOCK после успеха и обработанных отказов. */
|
||||
static void native_sync_cache(void) /* Убирает устаревшие строки Flash cache перед readback соседнего записанного слова. */
|
||||
{ /* Сохраняет исходное включение instruction/data cache и остальные настройки ACR. */
|
||||
uint32_t saved = FLASH->ACR; FLASH->ACR = saved & ~(FLASH_ACR_ICEN | FLASH_ACR_DCEN); /* Отключает кэши перед их аппаратным сбросом. */
|
||||
FLASH->ACR |= FLASH_ACR_ICRST | FLASH_ACR_DCRST; FLASH->ACR &= ~(FLASH_ACR_ICRST | FLASH_ACR_DCRST); FLASH->ACR = saved; /* Сбрасывает обе cache-памяти и возвращает прежние enable/latency/prefetch настройки. */
|
||||
__DSB(); __ISB(); /* Синхронизирует последующие чтения Flash и исполняемый поток после reset кэшей. */
|
||||
} /* Завершает синхронизацию Flash cache без HAL. */
|
||||
static bool native_program(uint32_t address, uint32_t value) /* Программирует и перечитывает одно слово x32. */
|
||||
{ /* Границы и выравнивание проверяет общий writer до вызова. */
|
||||
bool ok; FLASH->SR = NATIVE_FLASH_ERRORS; FLASH->CR = FLASH_CR_PG | (2UL << FLASH_CR_PSIZE_Pos); /* Выбирает PSIZE=2, то есть 32-битную запись при VDD 2,7..3,6 В. */
|
||||
*(volatile uint32_t *)(uintptr_t)address = value; ok = native_wait(); FLASH->CR &= ~FLASH_CR_PG; native_sync_cache(); /* Дожидается завершения записи и сбрасывает устаревшие строки cache перед чтением. */
|
||||
return ok && *(const volatile uint32_t *)(uintptr_t)address == value; /* Проверяет аппаратные флаги и реально прочитанное значение, включая дополнение FF. */
|
||||
} /* Завершает одну аппаратную запись F407. */
|
||||
static bool native_erase(uint32_t size) /* Стирает только используемые секторы 2..5 приложения climate. */
|
||||
{ /* EEPROM-кольцо 6/7 и загрузочные 0/1 не включаются в таблицу разрешённых секторов. */
|
||||
static const uint32_t starts[4] = {0x08008000UL, 0x0800C000UL, 0x08010000UL, 0x08020000UL}; /* RM0090: начала секторов 2,3,4,5 объёмом 16,16,64,128 КиБ. */
|
||||
uint32_t index; bool ok = true; /* Хранит текущий сектор и результат до восстановления LOCK. */
|
||||
if (size < 8U || size > NATIVE_APP_SIZE || !native_unlock()) return false; /* Отвергает неподдерживаемый размер до первого изменения Flash. */
|
||||
for (index = 0U; index < 4U && starts[index] < NATIVE_APP_BASE + size; ++index) { /* Не стирает следующий сектор, когда образ заканчивается точно на его границе. */
|
||||
(void)pcan_boot_report_busy(&boot, PCAN_BOOT_CMD_ERASE, ticks_ms); FLASH->SR = NATIVE_FLASH_ERRORS; /* Продлевает сессию и сообщает прогресс между длительными стираниями. */
|
||||
FLASH->CR = FLASH_CR_SER | (2UL << FLASH_CR_PSIZE_Pos) | ((index + 2U) << FLASH_CR_SNB_Pos); FLASH->CR |= FLASH_CR_STRT; /* Запускает erase конкретного сектора с разрядностью x32. */
|
||||
if (!native_wait()) { ok = false; break; } /* Прерывает обновление на первом аппаратном отказе. */
|
||||
} /* Завершает обход разрешённых секторов. */
|
||||
native_lock(); native_sync_cache(); return ok; /* Восстанавливает LOCK и обновляет cache как после успеха, так и после обработанного отказа. */
|
||||
} /* Завершает аппаратное стирание F407. */
|
||||
static void native_gpio_init(void) /* Сохраняет исходный CAN1 climate на PD0/PD1 AF9. */
|
||||
{ /* Тактирование APB1 определяется общим кодом из фактического RCC_CFGR. */
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; /* Разрешает аппаратные такты GPIOD и CAN1. */
|
||||
GPIOD->MODER = (GPIOD->MODER & ~15UL) | 10UL; GPIOD->PUPDR = (GPIOD->PUPDR & ~3UL) | 1UL; /* Назначает PD0/PD1 альтернативной функцией и подтягивает RX к рецессивному уровню. */
|
||||
GPIOD->OSPEEDR |= 15UL; GPIOD->AFR[0] = (GPIOD->AFR[0] & ~255UL) | 0x99UL; /* Выбирает высокую скорость GPIO и AF9 обоих выводов CAN1 согласно datasheet F407. */
|
||||
} /* Завершает настройку физического CAN-порта. */
|
||||
static void native_peripheral_stop(void) { RCC->APB1RSTR |= RCC_APB1RSTR_CAN1RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_CAN1RST; } /* Сбрасывает CAN1 перед handoff без изменения памяти приложения и EEPROM. */
|
||||
#include "../stm32-common/boot_native.inc" /* Подключает общий ProtoCAN transport, CRC, callbacks и безопасную замену MSP. */
|
||||
int main(void) /* Самостоятельный загрузчик climate, подключаемый тонкой обёрткой исходного проекта. */
|
||||
{ /* Сборка PCAN_BOOTLOADER_BUILD сохраняет VTOR startup в начале Flash. */
|
||||
bool requested = *(volatile uint32_t *)(uintptr_t)PCAN_BOOT_REQUEST_ADDRESS == PCAN_BOOT_REQUEST_MAGIC; /* Читает запрос из отдельного слова SRAM, не используемого стеком или ZI. */
|
||||
*(volatile uint32_t *)(uintptr_t)PCAN_BOOT_REQUEST_ADDRESS = 0U; /* Потребляет запрос ровно один раз до запуска интерфейсов. */
|
||||
SystemCoreClockUpdate(); if (SysTick_Config(SystemCoreClock / 1000U) != 0U || !native_init()) for (;;) {} /* Использует фактический HCLK и создаёт millisecond tick до CAN-тайм-аутов. */
|
||||
while (!native_can_init()) { if (!requested && native_vectors_valid(NATIVE_APP_SIZE)) native_jump(NATIVE_APP_BASE); native_peripheral_stop(); } /* При недоступной шине сохраняет обычный запуск установленного приложения, а по запросу остаётся восстанавливаемым. */
|
||||
if (!requested && native_vectors_valid(NATIVE_APP_SIZE)) { /* Сохраняет исходное десятисекундное окно входа в обновление после обычного reset. */
|
||||
uint32_t started = ticks_ms; while ((uint32_t)(ticks_ms - started) < 10000U) { native_can_poll(); if (boot.state != PCAN_BOOT_STATE_IDLE) { requested = true; break; } } /* Обслуживает discovery и команды без ложного входа по одному IDENTIFY. */
|
||||
if (!requested) native_jump(NATIVE_APP_BASE); /* Запускает приложение после окна, используя безопасный naked handoff. */
|
||||
} /* Завершает политику обычного запуска. */
|
||||
for (;;) native_can_poll(); /* Остаётся в общем ProtoCAN-автомате до подтверждённого COMMIT/REBOOT. */
|
||||
} /* Завершает самостоятельный CMSIS-загрузчик F407. */
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef PCAN_BOOT_F407_PROFILE_H /* Согласует нативный загрузчик climate и приложение с общей аппаратной разметкой. */
|
||||
#define PCAN_BOOT_F407_PROFILE_H /* Предотвращает повторное объявление адресов и magic. */
|
||||
#define PCAN_BOOT_APP_ADDRESS 0x08008000UL /* climate: секторы 0/1 общей ёмкостью 32 КиБ принадлежат загрузчику. */
|
||||
#define PCAN_BOOT_FLASH_END 0x08040000UL /* climate: начало EEPROM-кольца в секторе 6 исключено из приложения. */
|
||||
#define PCAN_BOOT_APP_SIZE (PCAN_BOOT_FLASH_END - PCAN_BOOT_APP_ADDRESS) /* Разрешённая ёмкость приложения составляет 224 КиБ в секторах 2..5. */
|
||||
#define PCAN_BOOT_REQUEST_MAGIC 0x5043414EUL /* Сохраняет совместимый маркер PCAN одноразового запроса обновления. */
|
||||
#define PCAN_BOOT_REQUEST_ADDRESS 0x2001FFFCUL /* Последнее слово основной SRAM F407; linker приложения и загрузчика должен исключать эти четыре байта. */
|
||||
#endif /* Завершает общую аппаратную часть конфигурации climate. */
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "pcan_boot_stm32f407.h" /* Подключает HAL STM32F4 и публичный профиль climate. */
|
||||
#include <string.h> /* Предоставляет инициализацию структур и чтение невыровненных октетов. */
|
||||
#define STM32_BOOT_INIT pcan_boot_stm32f407_init /* Экспортирует семейное имя общей процедуры настройки. */
|
||||
#define STM32_BOOT_POLL pcan_boot_stm32f407_poll /* Экспортирует семейное имя обслуживания FIFO и таймеров. */
|
||||
#define STM32_BOOT_VECTORS pcan_boot_stm32f407_vectors_valid /* Экспортирует проверку векторов именно этого профиля. */
|
||||
#define STM32_BOOT_BASE PCAN_BOOT_F407_APP_ADDRESS /* Сохраняет адрес приложения исходного загрузчика climate. */
|
||||
#define STM32_BOOT_END PCAN_BOOT_F407_FLASH_END /* Не разрешает запись в EEPROM-кольцо секторов 6 и 7. */
|
||||
#define STM32_BOOT_RAM_END 0x20020000UL /* STM32F407VE: верхняя исключённая граница основной SRAM объёмом 128 КиБ. */
|
||||
#define STM32_BOOT_UNIT 4U /* Исходный F407-порт использует запись 32-битных слов при VDD 2,7..3,6 В. */
|
||||
#define STM32_BOOT_PROGRAM FLASH_TYPEPROGRAM_WORD /* Согласует разрядность программирования с VOLTAGE_RANGE_3. */
|
||||
#define STM32_BOOT_SYNC() FLASH_FlushCaches() /* HAL сохраняет состояние cache enable и сбрасывает устаревшие строки перед readback соседних записанных слов. */
|
||||
static bool erase_region(pcan_boot_stm32_t *port, uint32_t size) /* Стирает последовательно только используемые секторы приложения. */
|
||||
{ /* Общий код уже проверил размер и разблокировал Flash. */
|
||||
static const uint32_t starts[] = {0x08008000UL, 0x0800C000UL, 0x08010000UL, 0x08020000UL}; /* RM0090: начала секторов 2..5 размером 16,16,64,128 КиБ. */
|
||||
uint32_t index; /* Индекс сектора в разрешённой области приложения. */
|
||||
for (index = 0U; index < 4U && starts[index] < STM32_BOOT_BASE + size; ++index) { /* Не стирает следующий сектор, если образ закончился на его границе. */
|
||||
FLASH_EraseInitTypeDef erase = {0}; /* Обнуляет все поля аппаратной команды перед выбором сектора. */
|
||||
uint32_t error = 0U; /* Получает номер сектора с ошибкой от HAL. */
|
||||
(void)pcan_boot_report_busy(&port->boot, PCAN_BOOT_CMD_ERASE, HAL_GetTick()); /* Уведомляет клиента между длительными синхронными стираниями и продлевает сессию. */
|
||||
erase.TypeErase = FLASH_TYPEERASE_SECTORS; /* Исключает массовое стирание загрузчика и EEPROM-кольца. */
|
||||
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; /* Требует VDD 2,7..3,6 В для выбранного программирования x32. */
|
||||
erase.Sector = FLASH_SECTOR_2 + index; /* Разрешает только физические секторы 2,3,4,5 STM32F407. */
|
||||
erase.NbSectors = 1U; /* Между секторами возвращает управление для ответа BUSY. */
|
||||
if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) return false; /* Прекращает операцию на первой аппаратной ошибке. */
|
||||
} /* Завершает обход секторов, покрывающих фактический образ. */
|
||||
return true; /* Все выбранные секторы успешно стёрты. */
|
||||
} /* Завершает аппаратную реализацию стирания F4. */
|
||||
#include "../stm32-common/pcan_boot_stm32.inc" /* Собирает общий transport/readback с геометрией и HAL этого семейства. */
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef PCAN_BOOT_STM32F407_H /* Исключает повторное объявление порта STM32F407VE. */
|
||||
#define PCAN_BOOT_STM32F407_H /* Обозначает активный интерфейс аппаратного профиля. */
|
||||
#include "stm32f4xx_hal.h" /* Получает тип CAN_HandleTypeDef и драйверы Flash из STM32CubeF4. */
|
||||
#include "boot_profile.h" /* Использует ту же аппаратную карту, что приложение и CMSIS-загрузчик climate. */
|
||||
#include "../stm32-common/pcan_boot_stm32.h" /* Подключает состояние и API переносимого автомата. */
|
||||
#define PCAN_BOOT_F407_APP_ADDRESS PCAN_BOOT_APP_ADDRESS /* Сохраняет публичное HAL-имя начала приложения без дублирования аппаратного адреса. */
|
||||
#define PCAN_BOOT_F407_FLASH_END PCAN_BOOT_FLASH_END /* Сохраняет публичное HAL-имя границы приложения перед EEPROM-кольцом секторов 6/7. */
|
||||
bool pcan_boot_stm32f407_init(pcan_boot_stm32_t *port, CAN_HandleTypeDef *can, const pcan_boot_config_t *config); /* Назначает single-slot callbacks, фильтры 0/1 в FIFO0 и запускает заранее настроенный CAN1. */
|
||||
void pcan_boot_stm32f407_poll(pcan_boot_stm32_t *port); /* Обслуживает до трёх кадров FIFO0 и таймеры ядра из основного цикла. */
|
||||
bool pcan_boot_stm32f407_vectors_valid(void); /* Проверяет MSP и Thumb-вектор в пределах профиля; CRC установленного образа не подтверждает. */
|
||||
#endif /* Завершает интерфейс профиля STM32F407VE. */
|
||||
@@ -0,0 +1,34 @@
|
||||
# Licenses
|
||||
|
||||
## candleLight Firmware Sources
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Hubert Denkmair
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
## STM32 HAL
|
||||
The HAL is distributed under a non-restrictive BSD (Berkeley Software
|
||||
Distribution) license.
|
||||
|
||||
## STM32 USB Library
|
||||
Code from the STM32 USB library is licensed
|
||||
under ST STMicroelectronics
|
||||
[Ultimate Liberty license SLA0044] (www.st.com/SLA0044)
|
||||
@@ -0,0 +1,63 @@
|
||||
# STM32G474VET6: ProtoCAN Boot v1 / CANdle
|
||||
|
||||
Исходный проект: `https://git.rd12.ru/setcorp/CANdle_stm32.git`, commit
|
||||
`61a93c5`. Аппаратный код извлечён из `src/boot/g474_can_bootloader.c`,
|
||||
`src/can_boot_control.c` и `src/boot/g474_boot_startup.c`; локальная CAN state
|
||||
machine заменена единым `src/pcan_boot.c`. Лицензия исходного проекта сохранена
|
||||
в [`LICENSE.source.md`](LICENSE.source.md).
|
||||
|
||||
| Область | Начало | Конец, исключён | Размер |
|
||||
|---|---|---|---:|
|
||||
| Загрузчик | `0x08000000` | `0x08008000` | 32 КиБ |
|
||||
| Single-slot приложение | `0x08008000` | `0x08080000` | 480 КиБ |
|
||||
| RAM проекта | `0x20000000` | `0x20013FF8` | 80 КиБ − 8 байт |
|
||||
| Одноразовый запрос CANB | `0x20013FFC` | `0x20014000` | 4 байта |
|
||||
|
||||
`boot_profile.h` содержит единые адреса/magic и совместимый API приложения.
|
||||
Валидатор векторов допускает прежний MSP вплоть до физической границы
|
||||
`0x20014000`; новые linker/startup исключают последние восемь байт, чтобы
|
||||
не затереть запрос до чтения в main. RAM-COMMIT не сохраняется после питания.
|
||||
|
||||
## Состав и подключение
|
||||
|
||||
- `pcan_boot_stm32g474.c/.h` — reusable HAL FDCAN/Flash-порт с single-slot,
|
||||
независимым CRC readback, doubleword записью и проверкой TXBTO перед reset.
|
||||
- `boot_main.c` — самостоятельный CAN/UART-загрузчик CANdle, без собственной
|
||||
реализации wire-протокола; использует `pcan_boot` и общий `rs485-boot`.
|
||||
- `boot_startup.c` — исходная схема Keil/GCC startup с безопасным initial MSP.
|
||||
- `boot_control.c` — вход из приложения по адресному ENTER_BOOT или UART BEGIN,
|
||||
совместимый UART PING/ACK и исходный USART1 IRQ.
|
||||
|
||||
В проекте CANdle старые файлы стали include-обёртками из submodule `templates`.
|
||||
BOOT Keil и CMake компилируют дополнительно `pcan_boot.c` и
|
||||
`pcan_boot_stm32g474.c`. Приложение использует общий `boot_control.c` без
|
||||
подключения загрузочной state machine к USB–CAN приложению.
|
||||
|
||||
Board-проект предоставляет `device.h`, `device_sysclock_config`,
|
||||
`HAL_FDCAN_MspInit`, CMSIS/HAL и linker. Исходная плата использует CPU 144 МГц,
|
||||
FDCAN kernel 48 МГц, FDCAN1 PB8/PB9 AF9, classic CAN 500 кбит/с,
|
||||
DeviceType=7 / DeviceID=15. UART — PA9/PA10 AF7, 512000 бод, TTL без DE.
|
||||
Для reusable API вызовите `HAL_FDCAN_Init` с двумя extended-фильтрами,
|
||||
затем `pcan_boot_stm32g474_init`, затем регулярно `poll` из main.
|
||||
FDCAN и его TX FIFO должны принадлежать этому порту, без конкурирующих TX IRQ.
|
||||
|
||||
## Flash и протокол
|
||||
|
||||
Поддерживается реальная плотность 512 КиБ. DBANK=1 использует два банка по
|
||||
256 КиБ и страницы 2 КиБ; DBANK=0 — один банк 512 КиБ и страницы 4 КиБ.
|
||||
Номера страниц считаются отдельно для каждого банка, с проверкой всей
|
||||
округлённой области до erase. BFB2 и SYSCFG FB_MODE bank swap отвергаются;
|
||||
Option Bytes не изменяются. Другие плотности требуют собственного профиля.
|
||||
|
||||
Flash программируется doubleword по восемь байт. Неполный doubleword
|
||||
дополняется FF только в конце образа; повторная запись ECC-слова в середине
|
||||
потока не допускается. UART использует тот же writer, что CAN DATA.
|
||||
VERIFY перечитывает Flash, COMMIT повторяет проверку, REBOOT возможен только
|
||||
после COMMIT и физически подтверждённого TX. Кодирование DLC определяется
|
||||
константами используемой версии HAL; старый сдвиг 16 и новый сдвиг 0 не смешиваются.
|
||||
|
||||
Стирание синхронное. Клиент должен допускать длительное Flash stall; BUSY до
|
||||
и после erase поддерживает сессию. Постоянного manifest, A/B-rollback, resume
|
||||
после питания и аутентификации в исходном single-slot профиле нет.
|
||||
|
||||
[Подключённые проекты и проверки Keil/CMake](../../projects/STM32.md).
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "boot_profile.h" /* Получает единственный источник адресов, magic и функций входа CANdle G474. */
|
||||
#include "stm32g4xx.h" /* Использует исходный CMSIS-порт USART/SRAM без зависимости приложения от загрузочного main. */
|
||||
static volatile bool request; /* IRQ либо CAN-диспетчер отмечает запрос, который основной цикл может безопасно обработать. */
|
||||
static uint8_t frame[28], used; /* Кадр BEGIN содержит 8 байт заголовка, 16 байт payload и 4 байта CRC, всего 28 байт. */
|
||||
static uint32_t read32(const uint8_t *data) { return (uint32_t)data[0] | ((uint32_t)data[1] << 8U) | ((uint32_t)data[2] << 16U) | ((uint32_t)data[3] << 24U); } /* Извлекает little-endian CRC без невыровненного чтения. */
|
||||
static uint32_t crc32(const uint8_t *data, uint8_t length) /* Малый app-only codec сохраняет отсутствие зависимости приложения от boot-автомата. */
|
||||
{ /* Формат PING/BEGIN совпадает с SETGUI v1, исходный полином IEEE отражённый. */
|
||||
uint32_t crc = 0xFFFFFFFFUL; uint8_t index, bit; /* Начинает IEEE CRC32 с инверсного состояния, как исходное приложение CANdle. */
|
||||
for (index = 0U; index < length; ++index) { crc ^= data[index]; for (bit = 0U; bit < 8U; ++bit) crc = (crc >> 1U) ^ ((crc & 1U) != 0U ? 0xEDB88320UL : 0U); } /* Обрабатывает только проверенные октеты и восемь бит каждого байта. */
|
||||
return ~crc; /* Выполняет финальный XOR, совместимый с zlib и загрузчиком SETGUI v1. */
|
||||
} /* Завершает совместимый малый CRC приложения. */
|
||||
void can_boot_observe(uint32_t id, bool extended, uint8_t dlc) /* Не переводит приложение в обновление по чужому либо невалидному кадру. */
|
||||
{ /* Переход откладывается до вызова can_boot_enter из существующего main. */
|
||||
if (extended && dlc == 0U && id <= 0x1FFFFFFFUL && ((id >> 27U) & 1U) == 0U && ((id >> 24U) & 7U) == CAN_BOOT_DEVICE_TYPE && ((id >> 20U) & 15U) == CAN_BOOT_DEVICE_ID && ((id >> 16U) & 15U) == 9U && (id & 255U) == 2U && ((id >> 8U) & 255U) != 0U) request = true; /* Требует Route=0, собственный адрес, BOOT_CONTROL/ENTER_BOOT и ненулевую сессию. */
|
||||
} /* Завершает наблюдение входящего CAN без чтения отсутствующего payload. */
|
||||
bool can_boot_requested(void) { return request; } /* Сохраняет прежний API опроса приложения CANdle. */
|
||||
void can_boot_enter(void) { *(volatile uint32_t *)(uintptr_t)CAN_BOOT_REQUEST_ADDRESS = CAN_BOOT_REQUEST_MAGIC; __DSB(); NVIC_SystemReset(); } /* Записывает CANB в зарезервированное SRAM-слово до системного сброса. */
|
||||
static void process_frame(void) /* Принимает только проверенные короткие PING и BEGIN приложения. */
|
||||
{ /* Полный firmware flow после BEGIN обслуживает уже общий загрузчик из templates. */
|
||||
uint8_t payload = frame[7]; uint32_t checksum; /* Размер payload предварительно ограничен шестнадцатью байтами. */
|
||||
if (frame[2] != 1U || read32(frame + 8U + payload) != crc32(frame + 2U, (uint8_t)(6U + payload))) { return; } /* Не выполняет reset по кадру другой версии либо повреждённому CRC. */
|
||||
if (frame[3] == 0x0BU && payload == 16U) { can_boot_enter(); return; } /* Сохраняет существующий SETGUI FIRMWARE_BEGIN как запрос входа в загрузчик. */
|
||||
if (frame[3] == 1U && payload == 0U) { /* PING подтверждает наличие UART приложения без входа в обновление. */
|
||||
uint8_t reply[15] = {0xA5U, 0x5AU, 1U, 0x81U, frame[4], frame[5], 0U, 3U, 0U, 0U, 1U}; uint8_t index; /* Формирует прежний ACK PING с номером входящего запроса и трёхбайтовым результатом. */
|
||||
checksum = crc32(reply + 2U, 9U); for (index = 0U; index < 4U; ++index) reply[11U + index] = (uint8_t)(checksum >> (index * 8U)); /* Добавляет CRC32 LE после девяти защищаемых байтов ver/header/payload. */
|
||||
for (index = 0U; index < sizeof(reply); ++index) { while ((USART1->ISR & USART_ISR_TXE_TXFNF) == 0U) {} USART1->TDR = reply[index]; } /* Отправляет полный совместимый ответ USART1. */
|
||||
while ((USART1->ISR & USART_ISR_TC) == 0U) {} USART1->ICR = USART_ICR_TCCF; /* Дожидается последнего стоп-бита и очищает флаг завершения передачи. */
|
||||
} /* Остальные сообщения приложения не относятся к входу в boot и игнорируются. */
|
||||
} /* Завершает обработку валидного короткого кадра приложения. */
|
||||
void can_boot_uart_init(void) /* Сохраняет исходную разводку и IRQ-приём UART CANdle G474. */
|
||||
{ /* Этот код компилируется только в работающем приложении с CONFIG_CAN_BOOT_APP. */
|
||||
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* Включает GPIOA и USART1 в соответствующих clock-доменах G474. */
|
||||
GPIOA->MODER = (GPIOA->MODER & ~((3UL << 18U) | (3UL << 20U))) | (2UL << 18U) | (2UL << 20U); /* Переводит PA9 и PA10 в режим альтернативной функции. */
|
||||
GPIOA->AFR[1] = (GPIOA->AFR[1] & ~((15UL << 4U) | (15UL << 8U))) | (7UL << 4U) | (7UL << 8U); /* Назначает AF7 USART1 TX/RX согласно исходной плате CANdle. */
|
||||
USART1->BRR = (SystemCoreClock + 256000UL) / 512000UL; USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE | USART_CR1_UE; /* Сохраняет 512000 бод при исходном APB2=HCLK и приём по RXNE IRQ. */
|
||||
NVIC_SetPriority(USART1_IRQn, 2U); NVIC_EnableIRQ(USART1_IRQn); used = 0U; /* Сохраняет приоритет 2 исходного UART и очищает parser перед приёмом. */
|
||||
} /* Завершает настройку входа приложения через UART. */
|
||||
void can_boot_uart_poll(void) /* Принимает один октет, проверяет длину и синхронизацию до записи в малый буфер. */
|
||||
{ /* Длина 28 байт покрывает только PING и BEGIN; больших команд здесь нет. */
|
||||
uint32_t status = USART1->ISR; uint8_t byte, total; /* Сохраняет флаги USART до чтения RDR. */
|
||||
if ((status & (USART_ISR_ORE | USART_ISR_FE | USART_ISR_NE)) != 0U) { USART1->ICR = USART_ICR_ORECF | USART_ICR_FECF | USART_ICR_NECF; used = 0U; return; } /* Сбрасывает parser после аппаратной потери либо повреждения байта. */
|
||||
if ((status & USART_ISR_RXNE_RXFNE) == 0U) { return; } byte = (uint8_t)USART1->RDR; /* Не читает RDR без доступного октета. */
|
||||
if (used == 0U && byte != 0xA5U) { return; } if (used == 1U && byte != 0x5AU) { used = byte == 0xA5U ? 1U : 0U; return; } /* Ищет двухбайтовую сигнатуру A5 5A с сохранением возможного начала следующего кадра. */
|
||||
frame[used++] = byte; if (used < 8U) { return; } /* Накапливает только ограниченный буфер и ждёт полного восьмибайтового заголовка. */
|
||||
if (frame[6] != 0U || frame[7] > 16U) { used = 0U; return; } /* Запрещает длину больше шестнадцати байтов до дальнейшего накопления. */
|
||||
total = (uint8_t)(12U + frame[7]); if (used < total) { return; } process_frame(); used = 0U; /* Передаёт полный header/payload/CRC обработчику и начинает следующий короткий кадр. */
|
||||
} /* Завершает безопасный parser приложения. */
|
||||
void USART1_IRQHandler(void) { can_boot_uart_poll(); } /* Сохраняет прежний экспорт IRQ, используемый startup приложения CANdle. */
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "pcan_boot_stm32g474.h" /* Подключает FDCAN/Flash порт и единственную ProtoCAN state machine templates. */
|
||||
#include "../../../rs485-boot/include/rs485_boot.h" /* Повторно использует общий SETGUI v1 UART updater. */
|
||||
#include "device.h" /* Тактирование и GPIO FDCAN остаются аппаратной привязкой исходного board-проекта. */
|
||||
#include <string.h> /* Копирует проверенные диапазоны Flash для повторов UART-блоков. */
|
||||
static FDCAN_HandleTypeDef can; static pcan_boot_stm32_t updater; static rs485_boot_t uart_boot; static bool can_started; /* Сохраняет состояния двух транспортов и факт успешного аппаратного запуска FDCAN. */
|
||||
static __attribute__((naked, noreturn)) void enter_app(uint32_t sp __attribute__((unused)), uint32_t pc __attribute__((unused))) /* Не обращается к старому C-стеку после замены MSP. */
|
||||
{ /* AAPCS передаёт исходный MSP приложения в r0 и Thumb entry в r1. */
|
||||
__asm volatile("msr msp, r0\n" "movs r2, #0\n" "msr control, r2\n" "dsb\n" "isb\n" "cpsie i\n" "bx r1\n"); /* Согласует привилегированный режим и конвейер до безвозвратного перехода по новому вектору. */
|
||||
} /* Завершает naked handoff без эпилога C. */
|
||||
static void jump_app(void) /* Отключает загрузочные интерфейсы перед запуском установленного приложения. */
|
||||
{ /* Структурная проверка векторов выполнена вызывающим main. */
|
||||
const uint32_t *vectors = (const uint32_t *)(uintptr_t)CAN_BOOT_APP_ADDRESS; uint32_t sp = vectors[0], pc = vectors[1], index; /* Сохраняет новые значения до отключения прерываний и смены стека. */
|
||||
__disable_irq(); SysTick->CTRL = 0U; USART1->CR1 = 0U; if (can_started) (void)HAL_FDCAN_Stop(&can); /* Не допускает работы загрузочного UART/CAN и SysTick после handoff. */
|
||||
for (index = 0U; index < 8U; ++index) { NVIC->ICER[index] = 0xFFFFFFFFUL; NVIC->ICPR[index] = 0xFFFFFFFFUL; } /* Снимает внешние enable/pending IRQ записью W1C. */
|
||||
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk; SCB->VTOR = CAN_BOOT_APP_ADDRESS; /* Очищает системные pending IRQ и согласует VTOR с linker приложения. */
|
||||
enter_app(sp, pc); /* Передаёт управление без обращения к старому C-стеку. */
|
||||
} /* Завершает аппаратную подготовку запуска приложения. */
|
||||
static bool uart_transmit(void *user, const uint8_t *data, uint16_t length) /* Передаёт совместимые ответы SETGUI v1 через USART1. */
|
||||
{ /* Транспорт TTL не управляет DE внешнего RS-485 трансивера. */
|
||||
uint16_t index; (void)user; for (index = 0U; index < length; ++index) { while ((USART1->ISR & USART_ISR_TXE_TXFNF) == 0U) {} USART1->TDR = data[index]; } /* Передаёт полный сформированный кадр без чтения за его длиной. */
|
||||
while ((USART1->ISR & USART_ISR_TC) == 0U) {} USART1->ICR = USART_ICR_TCCF; return true; /* Дожидается последнего стоп-бита до возможного reset после END. */
|
||||
} /* Завершает синхронную UART передачу. */
|
||||
static bool uart_erase(void *user, uint32_t size) { (void)user; return updater.boot.state == PCAN_BOOT_STATE_IDLE && pcan_boot_stm32g474_erase(size); } /* Не позволяет UART стереть Flash при открытой CAN-сессии. */
|
||||
static bool uart_write(void *user, uint32_t offset, const uint8_t *data, uint16_t length) { (void)user; return pcan_boot_stm32g474_write(uart_boot.image_size, offset, data, length); } /* Использует тот же doubleword writer, что CAN DATA. */
|
||||
static bool uart_read(void *user, uint32_t offset, uint8_t *data, uint16_t length) /* Подтверждает повторы UART-блоков чтением сохранённых данных. */
|
||||
{ /* Проверяет размеры вычитанием, чтобы не переполнить адресное сложение. */
|
||||
(void)user; if (data == NULL || offset > CAN_BOOT_APP_SIZE || length > CAN_BOOT_APP_SIZE - offset) { return false; } /* Не допускает чтение за пределами 480-КиБ области. */
|
||||
(void)memcpy(data, (const void *)(uintptr_t)(CAN_BOOT_APP_ADDRESS + offset), length); return true; /* Возвращает только проверенный диапазон Flash. */
|
||||
} /* Завершает readback повторного UART-запроса. */
|
||||
static bool uart_valid(void *user, uint32_t size, uint32_t crc) { (void)user; return pcan_boot_stm32g474_image_valid(size, crc); } /* Перед END проверяет векторы и настоящий CRC Flash общим портом. */
|
||||
static void uart_reboot(void *user) { (void)user; NVIC_SystemReset(); } /* UART callback вызывается после физически завершённого ответа END. */
|
||||
static void uart_init(void) /* Сохраняет исходные PA9/PA10 AF7, 512000 бод и общий SETGUI v1 updater. */
|
||||
{ /* Аппаратные частоты уже настроены board-функцией device_sysclock_config. */
|
||||
GPIO_InitTypeDef gpio = {0}; rs485_boot_config_t config = {CAN_BOOT_APP_ADDRESS, CAN_BOOT_APP_SIZE, RS485_BOOT_MAX_BLOCK}; rs485_boot_port_t callbacks = {0}; /* Полностью определяет структуры текущих HAL и UART API. */
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART1_CLK_ENABLE(); gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; gpio.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &gpio); /* Назначает исходные TX/RX выводы без изменения CAN PB8/PB9. */
|
||||
USART1->BRR = (HAL_RCC_GetPCLK2Freq() + 256000UL) / 512000UL; USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_FIFOEN | USART_CR1_UE; /* Рассчитывает скорость от фактического PCLK2 и включает polling UART с FIFO. */
|
||||
callbacks.transmit = uart_transmit; callbacks.erase = uart_erase; callbacks.write = uart_write; callbacks.read = uart_read; callbacks.image_valid = uart_valid; callbacks.reboot = uart_reboot; /* Подключает общий Flash writer и проверку образа вместо локальной реализации UART flow. */
|
||||
(void)rs485_boot_init(&uart_boot, &config, &callbacks, NULL); /* Создаёт отдельную RAM-сессию UART с прежним wire-протоколом. */
|
||||
} /* Завершает настройку UART восстановления. */
|
||||
static bool fdcan_init(void) /* Сохраняет исходные 48 МГц kernel clock и скорость classic CAN 500 кбит/с. */
|
||||
{ /* GPIO FDCAN PB8/PB9 AF9 включает board-функция HAL_FDCAN_MspInit. */
|
||||
pcan_boot_config_t config = {0}; can.Instance = FDCAN1; can.Init.ClockDivider = FDCAN_CLOCK_DIV1; can.Init.FrameFormat = FDCAN_FRAME_CLASSIC; can.Init.Mode = FDCAN_MODE_NORMAL; can.Init.AutoRetransmission = ENABLE; /* Выбирает единственный выделенный FDCAN1 без CAN FD/BRS и с автоматическим повтором TX. */
|
||||
can.Init.NominalPrescaler = 6U; can.Init.NominalSyncJumpWidth = 2U; can.Init.NominalTimeSeg1 = 13U; can.Init.NominalTimeSeg2 = 2U; /* 48 МГц / 6 / (1+13+2) дают ровно 500000 бит/с. */
|
||||
can.Init.DataPrescaler = 6U; can.Init.DataSyncJumpWidth = 2U; can.Init.DataTimeSeg1 = 13U; can.Init.DataTimeSeg2 = 2U; /* Полностью инициализирует data timing, хотя в classic режиме он не используется. */
|
||||
can.Init.StdFiltersNbr = 0U; can.Init.ExtFiltersNbr = 2U; can.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; /* Выделяет адресный и broadcast-discovery фильтры общему порту. */
|
||||
if (HAL_FDCAN_Init(&can) != HAL_OK) { return false; } config.device_type = CAN_BOOT_DEVICE_TYPE; config.device_id = CAN_BOOT_DEVICE_ID; config.ack_window = 16U; config.session_timeout_ms = 30000UL; /* Сохраняет исходный адрес 7:15 и окно ACK, добавляя тайм-аут RAM-сессии 30 секунд. */
|
||||
return pcan_boot_stm32g474_init(&updater, &can, &config); /* Инициализирует единую state machine ProtoCAN и проверяет геометрию Flash. */
|
||||
} /* Завершает настройку FDCAN без локальной копии CAN-протокола. */
|
||||
static void service_interfaces(void) /* Обслуживает оба транспорта без одновременной записи одной Flash-области. */
|
||||
{ /* Каждый вызов ограничивает обработку UART шестьюдесятью четырьмя октетами. */
|
||||
uint32_t count; for (count = 0U; count < 64U; ++count) { /* Не позволяет постоянному UART-потоку захватить главный цикл целиком. */
|
||||
uint32_t status = USART1->ISR; uint8_t byte; if ((status & (USART_ISR_ORE | USART_ISR_FE | USART_ISR_NE)) != 0U) USART1->ICR = USART_ICR_ORECF | USART_ICR_FECF | USART_ICR_NECF; /* Снимает аппаратные ошибки USART перед дальнейшим приёмом. */
|
||||
if ((status & USART_ISR_RXNE_RXFNE) == 0U) { break; } byte = (uint8_t)USART1->RDR; rs485_boot_feed(&uart_boot, &byte, 1U); /* Передаёт доступный октет общему UART parser. */
|
||||
} /* Завершает ограниченный пакет UART. */
|
||||
if (can_started && uart_boot.state != RS485_BOOT_RECEIVING) pcan_boot_stm32g474_poll(&updater); /* Во время записи UART запрещает второй транспорт, в остальных состояниях обслуживает CAN и discovery. */
|
||||
} /* Завершает мультиплексирование загрузочных интерфейсов. */
|
||||
int main(void) /* Подключает исходный board startup CANdle к общим загрузочным библиотекам templates. */
|
||||
{ /* Linker и initial MSP должны исключать последние восемь байт SRAM со словом запроса. */
|
||||
bool requested = *(volatile uint32_t *)(uintptr_t)CAN_BOOT_REQUEST_ADDRESS == CAN_BOOT_REQUEST_MAGIC; *(volatile uint32_t *)(uintptr_t)CAN_BOOT_REQUEST_ADDRESS = 0U; /* Потребляет сохранённый запрос CANB до настройки периферии. */
|
||||
HAL_Init(); device_sysclock_config(); uart_init(); can_started = fdcan_init(); /* Сохраняет исходную последовательность clock/GPIO/UART/FDCAN и доступность UART при отказе CAN. */
|
||||
if (!requested && pcan_boot_stm32g474_vectors_valid(CAN_BOOT_APP_SIZE)) { /* Сохраняет исходное десятисекундное окно входа в обновление. */
|
||||
uint32_t started = HAL_GetTick(); while ((uint32_t)(HAL_GetTick() - started) < 10000U) { service_interfaces(); if (updater.boot.state != PCAN_BOOT_STATE_IDLE || uart_boot.state != RS485_BOOT_IDLE) { requested = true; break; } } /* Обслуживает UART/CAN с учётом переполнения tick, без входа в обновление по одному discovery. */
|
||||
if (!requested) jump_app(); /* Передаёт управление установленному приложению после окна ожидания. */
|
||||
} /* Завершает обычную политику запуска без постоянного manifest. */
|
||||
for (;;) service_interfaces(); /* Остаётся доступным для адресного обновления и восстановления через UART. */
|
||||
} /* Завершает общий boot main STM32G474VET6. */
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef PCAN_BOOT_G474_PROFILE_H /* Согласует приложение CANdle и самостоятельный CAN/UART-загрузчик. */
|
||||
#define PCAN_BOOT_G474_PROFILE_H /* Исключает повторное определение общей аппаратной конфигурации. */
|
||||
#include <stdbool.h> /* Объявляет булевы признаки расширенного кадра и запроса обновления. */
|
||||
#include <stdint.h> /* Объявляет wire-типы CAN ID и DLC публичного приложения. */
|
||||
#define CAN_BOOT_APP_ADDRESS 0x08008000UL /* Исходный CANdle G474 резервирует 32 КиБ Flash под загрузчик. */
|
||||
#define CAN_BOOT_FLASH_END 0x08080000UL /* Верхняя исключённая граница 512 КиБ Flash STM32G474VET6. */
|
||||
#define CAN_BOOT_APP_SIZE (CAN_BOOT_FLASH_END - CAN_BOOT_APP_ADDRESS) /* Единственный слот приложения содержит 480 КиБ. */
|
||||
#define CAN_BOOT_REQUEST_ADDRESS 0x20013FFCUL /* Исходный запрос CANB хранится в последнем слове используемых 80 КиБ SRAM; linker исключает последние восемь байт. */
|
||||
#define CAN_BOOT_REQUEST_MAGIC 0x43414E42UL /* Сохраняет совместимый маркер CANB для входа из работающего приложения. */
|
||||
#define CAN_BOOT_DEVICE_TYPE 7U /* Сохраняет исходный тип устройства CANdle G474 в ProtoCAN. */
|
||||
#define CAN_BOOT_DEVICE_ID 15U /* Сохраняет исходный адрес устройства CANdle G474. */
|
||||
void can_boot_observe(uint32_t can_id, bool extended, uint8_t dlc); /* Отмечает адресный ENTER_BOOT из общего CAN-диспетчера приложения. */
|
||||
bool can_boot_requested(void); /* Возвращает отложенный запрос, чтобы main приложения выполнил reset из безопасного контекста. */
|
||||
void can_boot_enter(void); /* Сохраняет одноразовый запрос в SRAM и вызывает системный reset. */
|
||||
void can_boot_uart_init(void); /* Настраивает исходный USART1 PA9/PA10, 512000 бод, и приём через IRQ. */
|
||||
void can_boot_uart_poll(void); /* Разбирает один байт PING/BEGIN SETGUI v1 в работающем приложении. */
|
||||
#endif /* Завершает профиль и совместимый API входа CANdle G474. */
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "boot_profile.h" /* Использует общий адресный профиль CANdle G474 и исключённое слово SRAM-запроса. */
|
||||
#include "stm32g4xx.h" /* Получает CMSIS регистры и SystemInit выбранного STM32G474. */
|
||||
#include "stm32g4xx_hal.h" /* Связывает SysTick самостоятельного загрузчика с HAL_GetTick. */
|
||||
extern uint32_t __StackTop, __data_start, __data_end, __data_source, __bss_start, __bss_end; /* GNU linker задаёт границы runtime-инициализации и вершину отдельного стека. */
|
||||
extern int main(void); /* Передаёт управление общему boot main после инициализации C runtime. */
|
||||
void SysTick_Handler(void); /* Объявляет публичное имя обработчика до определения, как требует standalone startup. */
|
||||
__attribute__((noreturn)) void Reset_Handler(void); /* Объявляет безвозвратную аппаратную точку входа GNU/Keil. */
|
||||
static __attribute__((noreturn)) void fault(void) { for (;;) {} } /* Не выполняет произвольный переход при необработанном IRQ либо исключении загрузчика. */
|
||||
void SysTick_Handler(void) { HAL_IncTick(); } /* Обеспечивает реальные миллисекундные тайм-ауты UART/FDCAN/HAL. */
|
||||
__attribute__((noreturn)) void Reset_Handler(void) /* Сохраняет совместимость исходного startup с Arm Compiler 6 и GNU toolchain. */
|
||||
{ /* На Arm Compiler runtime и scatter-loading предоставляет библиотечная функция __main. */
|
||||
#if defined(__ARMCC_VERSION) /* Выбирает штатный C runtime Keil, не дублируя ZI/RW инициализацию. */
|
||||
extern void __main(void); SystemInit(); __main(); /* Настраивает базовый MCU и запускает runtime перед main. */
|
||||
#else /* GNU-вариант использует символы исходного linker script CANdle. */
|
||||
uint32_t *source = &__data_source, *destination = &__data_start; /* Сохраняет Flash-источник и SRAM-назначение инициализированных данных. */
|
||||
while (destination < &__data_end) *destination++ = *source++; /* Копирует только объявленную секцию data, не затрагивая исключённый SRAM-маркер. */
|
||||
for (destination = &__bss_start; destination < &__bss_end; ++destination) *destination = 0U; /* Обнуляет ZI только внутри RAM-региона, согласованного с linker. */
|
||||
SystemInit(); (void)main(); /* Запускает базовую конфигурацию MCU и общую загрузочную программу. */
|
||||
#endif /* Завершает выбор C runtime для двух поддерживаемых toolchain. */
|
||||
for (;;) {} /* Не допускает возврат из main в несуществующий вызывающий код reset. */
|
||||
} /* Завершает Reset_Handler самостоятельного загрузчика. */
|
||||
typedef void (*vector_t)(void); /* Описывает Thumb-обработчик аппаратного исключения Cortex-M4. */
|
||||
static __attribute__((used, section(".vectors"))) const vector_t vectors[118] = { /* Сохраняет таблицу из 118 записей в начале 32-КиБ загрузочной Flash без ненужного внешнего символа. */
|
||||
#if defined(__ARMCC_VERSION) /* Keil получает fixed MSP, совпадающий с ARM_LIB_STACK в общем scatter script. */
|
||||
(vector_t)0x20013FF8UL, Reset_Handler, fault, fault, /* Резервирует последние восемь байт SRAM, чтобы пролог main не уничтожил маркер CANB до чтения. */
|
||||
#else /* GNU linker вычисляет вершину стека по RAM_SIZE, также уменьшенному на восемь байт. */
|
||||
(vector_t)&__StackTop, Reset_Handler, fault, fault, /* Использует linker-определение без жёсткого адреса для GNU сборки. */
|
||||
#endif /* Завершает выбор совместимого initial MSP. */
|
||||
[4 ... 14] = fault, [15] = SysTick_Handler, [16 ... 117] = fault /* Разрешает только SysTick; загрузочные UART/FDCAN обслуживаются опросом. */
|
||||
}; /* Завершает извлечённую таблицу векторов CANdle G474. */
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "pcan_boot_stm32g474.h" /* Подключает семейные HAL API, карту памяти и состояние общего автомата. */
|
||||
#include <string.h> /* Предоставляет безопасную упаковку doubleword и коротких CAN-ответов. */
|
||||
static const uint32_t dlc_values[9] = {FDCAN_DLC_BYTES_0, FDCAN_DLC_BYTES_1, FDCAN_DLC_BYTES_2, FDCAN_DLC_BYTES_3, FDCAN_DLC_BYTES_4, FDCAN_DLC_BYTES_5, FDCAN_DLC_BYTES_6, FDCAN_DLC_BYTES_7, FDCAN_DLC_BYTES_8}; /* Изолирует различное кодирование DLC в старых и новых версиях HAL, не предполагая сдвиг 0 либо 16. */
|
||||
static bool geometry(uint32_t *page_size, uint32_t *bank_size) /* Не позволяет неподдерживаемой геометрии изменить загрузочную область. */
|
||||
{ /* Профиль предназначен только для STM32G474VET6 с 512 КиБ Flash. */
|
||||
bool dual = (FLASH->OPTR & FLASH_OPTR_DBANK) != 0U; /* DBANK выбирает две страницы по 2 КиБ либо один банк со страницами по 4 КиБ. */
|
||||
__HAL_RCC_SYSCFG_CLK_ENABLE(); /* Гарантирует доступность MEMRMP перед проверкой bank swap даже вне board-функции CANdle. */
|
||||
if (*(const uint16_t *)(uintptr_t)FLASHSIZE_BASE != 512U || (FLASH->OPTR & FLASH_OPTR_BFB2) != 0U || (SYSCFG->MEMRMP & SYSCFG_MEMRMP_FB_MODE) != 0U) { return false; } /* Требует известную физическую ёмкость и прямое соответствие логических адресов физическим банкам. */
|
||||
*page_size = dual ? 2048U : 4096U; *bank_size = dual ? 262144UL : 524288UL; /* RM0440: страницы 2/4 КиБ и банки 256/512 КиБ соответственно режиму DBANK. */
|
||||
return true; /* Обе исходные конфигурации DBANK поддерживаются без изменения Option Bytes. */
|
||||
} /* Завершает проверку геометрии перед erase и program. */
|
||||
bool pcan_boot_stm32g474_vectors_valid(uint32_t size) /* Проверяет только допустимость таблицы векторов. */
|
||||
{ /* Используемая SRAM 80 КиБ соответствует исходному linker CANdle G474. */
|
||||
const uint32_t *vectors = (const uint32_t *)(uintptr_t)CAN_BOOT_APP_ADDRESS; uint32_t sp, pc; /* Два первых слова содержат MSP и Thumb reset handler. */
|
||||
if (size < 8U || size > CAN_BOOT_APP_SIZE) { return false; } sp = vectors[0]; pc = vectors[1]; /* Проверяет размер до чтения аппаратно отображённой Flash. */
|
||||
return sp > 0x20000000UL && sp <= 0x20014000UL && (sp & 7U) == 0U && (pc & 1U) != 0U && (pc & ~1UL) >= CAN_BOOT_APP_ADDRESS && (pc & ~1UL) < CAN_BOOT_APP_ADDRESS + size; /* Не принимает стек вне SRAM, невыровненный MSP или reset vector за полезным образом. */
|
||||
} /* Завершает структурную проверку без чтения остальной Flash. */
|
||||
bool pcan_boot_stm32g474_image_valid(uint32_t size, uint32_t crc) /* Подтверждает реальную Flash перед VERIFY, COMMIT или UART END. */
|
||||
{ /* Полезная длина не включает дополнение FF последнего doubleword. */
|
||||
return pcan_boot_stm32g474_vectors_valid(size) && pcan_boot_crc32_update(0U, (const uint8_t *)(uintptr_t)CAN_BOOT_APP_ADDRESS, size) == crc; /* Использует общий полином ProtoCAN вместо локальной копии CAN-CRC. */
|
||||
} /* Завершает независимую проверку сохранённого образа. */
|
||||
bool pcan_boot_stm32g474_erase(uint32_t size) /* Стирает страницы 480-КиБ single-slot области без затрагивания загрузчика. */
|
||||
{ /* Bank swap отвергается, а настройки Option Bytes остаются владельцу устройства. */
|
||||
uint32_t page_size, bank_size, address, end; bool ok = true; /* Сохраняет аппаратную геометрию и результат до восстановления LOCK. */
|
||||
if (size < 8U || size > CAN_BOOT_APP_SIZE || !geometry(&page_size, &bank_size)) { return false; } /* Проверяет полезную длину и физическую карту до разблокировки Flash. */
|
||||
end = CAN_BOOT_APP_ADDRESS + ((size + page_size - 1U) & ~(page_size - 1U)); /* Округляет конец вверх к странице текущего DBANK-режима. */
|
||||
if (end > CAN_BOOT_FLASH_END || HAL_FLASH_Unlock() != HAL_OK) { return false; } /* Не стирает Flash за физической ёмкостью профиля. */
|
||||
for (address = CAN_BOOT_APP_ADDRESS; address < end; address += page_size) { /* Обходит только страницы, покрывающие полезный образ. */
|
||||
FLASH_EraseInitTypeDef erase = {0}; uint32_t error = 0U, offset = address - FLASH_BASE; /* Определяет физическое положение страницы относительно начала Flash. */
|
||||
erase.TypeErase = FLASH_TYPEERASE_PAGES; erase.Banks = offset >= bank_size ? FLASH_BANK_2 : FLASH_BANK_1; /* Разрешает адресовать второй банк только в поддерживаемой dual-bank карте. */
|
||||
erase.Page = (offset % bank_size) / page_size; erase.NbPages = 1U; /* Номер страницы считается отдельно внутри физического банка. */
|
||||
if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) { ok = false; break; } /* Прерывает стирание при тайм-ауте, защите либо аппаратной ошибке страницы. */
|
||||
} /* Завершает ограниченный обход страниц. */
|
||||
if (HAL_FLASH_Lock() != HAL_OK) { ok = false; } return ok; /* Обязательно восстанавливает блокировку и не подтверждает отказ LOCK. */
|
||||
} /* Завершает стирание с поддержкой single/dual bank STM32G474VE. */
|
||||
bool pcan_boot_stm32g474_write(uint32_t image_size, uint32_t offset, const uint8_t *data, uint16_t length) /* Записывает выровненные doubleword без повторного программирования ECC-слова. */
|
||||
{ /* Один writer используется обоими загрузочными транспортами. */
|
||||
uint32_t page_size, bank_size, done = 0U; bool ok = true; /* Хранит проверенную геометрию и фактическое смещение полезных байтов. */
|
||||
if (image_size < 8U || image_size > CAN_BOOT_APP_SIZE || data == NULL || length == 0U || offset > image_size || length > image_size - offset || (offset & 7U) != 0U || !geometry(&page_size, &bank_size)) { return false; } /* Проверяет адреса вычитанием, исключая переполнение и неизвестную физическую геометрию. */
|
||||
if ((length & 7U) != 0U && offset + length != image_size) { return false; } /* Неполное 64-битное слово допускается только в конце полезного образа. */
|
||||
if (HAL_FLASH_Unlock() != HAL_OK) { return false; } /* Не начинает запись при отказе аппаратной разблокировки. */
|
||||
while (done < length) { /* Разбивает UART/CAN payload на аппаратные doubleword по восемь байт. */
|
||||
uint64_t value = UINT64_MAX; uint32_t count = length - done, address = CAN_BOOT_APP_ADDRESS + offset + done; /* Значение FF сохраняет незаданные хвостовые октеты стёртыми. */
|
||||
if (count > 8U) { count = 8U; } (void)memcpy(&value, data + done, count); /* Не читает за полезной длиной неполного последнего блока. */
|
||||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, value) != HAL_OK || memcmp((const void *)(uintptr_t)address, &value, 8U) != 0) { ok = false; break; } /* HAL G4 синхронизирует Flash cache, а порт проверяет аппаратную запись и весь doubleword readback. */
|
||||
done += count; /* Продвигается только на число полезных октетов, не включая FF в CRC. */
|
||||
} /* Завершает запись ограниченного payload. */
|
||||
if (HAL_FLASH_Lock() != HAL_OK) { ok = false; } return ok; /* Восстанавливает аппаратную блокировку после успеха и отказов записи. */
|
||||
} /* Завершает защищённое программирование G474. */
|
||||
static bool send_frame(void *user, uint32_t id, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Подтверждает физическую передачу FDCAN, включая ответ перед reset. */
|
||||
{ /* Порт владеет выделенным FDCAN1 без конкурирующих отправителей или TX IRQ. */
|
||||
pcan_boot_stm32_t *port = user; FDCAN_HandleTypeDef *can = port->can; FDCAN_TxHeaderTypeDef header = {0}; /* Получает handle и обнуляет все поля аппаратного заголовка. */
|
||||
uint8_t bytes[8] = {0}; uint32_t started = HAL_GetTick(), buffer; /* Не позволяет HAL читать за коротким служебным payload. */
|
||||
if (length > 8U || id > 0x1FFFFFFFUL || (length != 0U && data == NULL)) { return false; } if (length != 0U) (void)memcpy(bytes, data, length); /* Проверяет внешний размер до копирования в полный classic CAN-буфер. */
|
||||
while (HAL_FDCAN_GetTxFifoFreeLevel(can) == 0U) if ((uint32_t)(HAL_GetTick() - started) >= 100U) { return false; } /* Ограничивает ожидание места в TX FIFO ста миллисекундами. */
|
||||
header.Identifier = id; header.IdType = FDCAN_EXTENDED_ID; header.TxFrameType = FDCAN_DATA_FRAME; header.DataLength = dlc_values[length]; /* Формирует расширенный адресный кадр с совместимым кодированием DLC текущего HAL. */
|
||||
header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; header.BitRateSwitch = FDCAN_BRS_OFF; header.FDFormat = FDCAN_CLASSIC_CAN; header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; /* ProtoCAN Boot использует classic CAN без BRS и не требует отдельного TX Event FIFO. */
|
||||
if (HAL_FDCAN_AddMessageToTxFifoQ(can, &header, bytes) != HAL_OK) { return false; } buffer = HAL_FDCAN_GetLatestTxFifoQRequestBuffer(can); /* Запоминает bitmask именно поставленного в очередь TX-буфера. */
|
||||
while (HAL_FDCAN_IsTxBufferMessagePending(can, buffer) != 0U) if ((uint32_t)(HAL_GetTick() - started) >= 100U) { (void)HAL_FDCAN_AbortTxRequest(can, buffer); return false; } /* Отменяет неподтверждённый TX вместо перезапуска MCU при отсутствии ACK шины. */
|
||||
return buffer != 0U && (can->Instance->TXBTO & buffer) != 0U; /* Отличает завершённую передачу от abort по аппаратному флагу transmission occurred. */
|
||||
} /* Завершает надёжную отправку ответа FDCAN. */
|
||||
static bool erase_slot(void *user, pcan_boot_byte_t slot) /* Подключает страницу Flash к единственному логическому слоту A. */
|
||||
{ /* Разрешение reset сбрасывается до изменения Flash. */
|
||||
pcan_boot_stm32_t *port = user; bool ok; if (slot != 0U) { return false; } port->ready = false; port->written = 0U; /* Не допускает A/B-адресацию и прежний COMMIT после начала новой записи. */
|
||||
(void)pcan_boot_report_busy(&port->boot, PCAN_BOOT_CMD_ERASE, HAL_GetTick()); ok = pcan_boot_stm32g474_erase(port->boot.manifest.image_size); /* Предупреждает клиента перед синхронным стиранием выбранных страниц. */
|
||||
if (ok) { (void)pcan_boot_report_busy(&port->boot, PCAN_BOOT_CMD_ERASE, HAL_GetTick()); } return ok; /* Продлевает сессию после завершения длительной Flash-операции. */
|
||||
} /* Завершает callback стирания G474. */
|
||||
static bool write_slot(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Подключает защищённый writer к общему CAN-автомату. */
|
||||
{ /* Общий автомат проверяет номера восьмибайтовых блоков, порт повторно проверяет фактическое смещение. */
|
||||
pcan_boot_stm32_t *port = user; if (slot != 0U || offset != port->written || length > 8U || !pcan_boot_stm32g474_write(port->boot.manifest.image_size, offset, data, length)) { return false; } /* Не подтверждает неправильный слот, порядок или отказ Flash. */
|
||||
port->written += length; return true; /* Считает только полностью записанные полезные октеты. */
|
||||
} /* Завершает callback записи G474. */
|
||||
static pcan_boot_status_t verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) /* Отличает неверный образ от ошибки readback CRC. */
|
||||
{ /* Сохраняет точные статусы текущего общего API. */
|
||||
pcan_boot_stm32_t *port = user; if (slot != 0U || manifest == NULL || port->written != manifest->image_size || !pcan_boot_stm32g474_vectors_valid(manifest->image_size)) return PCAN_BOOT_STATUS_INVALID_SIZE; /* Не принимает неполную запись или reset handler вне полезного образа. */
|
||||
return pcan_boot_stm32g474_image_valid(manifest->image_size, manifest->image_crc32) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_CRC_ERROR; /* Перечитывает реальную Flash вместо доверия CRC потока. */
|
||||
} /* Завершает VERIFY G474. */
|
||||
static bool commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) { pcan_boot_stm32_t *port = user; port->ready = verify(user, slot, manifest) == PCAN_BOOT_STATUS_OK; return port->ready; } /* Активирует проверенный образ только в RAM, без имитации постоянных A/B-метаданных. */
|
||||
static bool can_reboot(void *user) { pcan_boot_stm32_t *port = user; return port->ready && port->boot.state == PCAN_BOOT_STATE_COMMITTED; } /* Разрешает протокольный reset только после VERIFY и COMMIT. */
|
||||
static void reboot(void *user) { if (can_reboot(user)) NVIC_SystemReset(); } /* Общий автомат вызывает reset только после подтверждённого TX ответа. */
|
||||
bool pcan_boot_stm32g474_init(pcan_boot_stm32_t *port, FDCAN_HandleTypeDef *can, const pcan_boot_config_t *config) /* Настраивает current ProtoCAN Boot v1 поверх подготовленного FDCAN1. */
|
||||
{ /* GPIO, kernel clock 48 МГц и HAL_FDCAN_Init остаются в board-проекте. */
|
||||
pcan_boot_config_t single; pcan_boot_port_t callbacks = {0}; FDCAN_FilterTypeDef filter = {0}; uint32_t page_size, bank_size; /* Подготавливает полностью определённые структуры текущего API и HAL. */
|
||||
if (port == NULL || can == NULL || config == NULL || can->Instance != FDCAN1 || can->Init.ExtFiltersNbr < 2U || !geometry(&page_size, &bank_size)) { return false; } /* Не запускает порт без двух выделенных extended-фильтров или поддерживаемой Flash-геометрии. */
|
||||
single = *config; single.active_slot = 0U; single.slot_count = 1U; single.max_image_size = CAN_BOOT_APP_SIZE; /* Принудительно сохраняет исходный single-slot профиль 480 КиБ. */
|
||||
callbacks.send = send_frame; callbacks.erase_slot = erase_slot; callbacks.write_slot = write_slot; callbacks.verify_status = verify; callbacks.set_pending_slot = commit; callbacks.can_reboot = can_reboot; callbacks.reboot = reboot; /* Подключает Flash, readback и согласованную политику reset к единственному общему автомату. */
|
||||
(void)memset(port, 0, sizeof(*port)); port->can = can; if (!pcan_boot_init(&port->boot, &single, &callbacks, port)) { port->can = NULL; return false; } /* Делает неуспешно настроенный порт безопасным для последующего poll. */
|
||||
filter.IdType = FDCAN_EXTENDED_ID; filter.FilterIndex = 0U; filter.FilterType = FDCAN_FILTER_MASK; filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* Первый аппаратный фильтр обслуживает все сообщения собственного адреса. */
|
||||
filter.FilterID1 = pcan_boot_make_id(0U, 0U, single.device_type, single.device_id, 0U, 0U); filter.FilterID2 = 0x0FF00000UL; /* Не маскирует приоритет; проверяет только Route/Type/ID. */
|
||||
if (HAL_FDCAN_ConfigFilter(can, &filter) != HAL_OK) { port->can = NULL; return false; } /* Не использует CAN с неподтверждённым адресным фильтром. */
|
||||
filter.FilterIndex = 1U; filter.FilterID1 = pcan_boot_make_id(0U, 0U, 7U, 15U, PCAN_BOOT_MSG_DISCOVERY, 0U); filter.FilterID2 = 0x0FFFFFFFUL; /* Второй фильтр допускает только штатный broadcast discovery, исключая broadcast-запись. */
|
||||
if (HAL_FDCAN_ConfigFilter(can, &filter) != HAL_OK || HAL_FDCAN_ConfigGlobalFilter(can, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE) != HAL_OK || HAL_FDCAN_Start(can) != HAL_OK) { port->can = NULL; return false; } /* Отвергает прочие standard/extended/remote кадры и проверяет аппаратный запуск. */
|
||||
return true; /* Flash-профиль, оба фильтра и FDCAN готовы к общему автомату. */
|
||||
} /* Завершает инициализацию reusable G474-порта. */
|
||||
void pcan_boot_stm32g474_poll(pcan_boot_stm32_t *port) /* Обслуживает транспорт и таймеры ядра из основного цикла. */
|
||||
{ /* Не вызывается из IRQ из-за синхронных операций Flash. */
|
||||
FDCAN_HandleTypeDef *can; uint32_t count; if (port == NULL || port->can == NULL) { return; } can = port->can; pcan_boot_poll(&port->boot, HAL_GetTick()); /* Проверяет состояние до доступа к handle и обновляет часы без входящего кадра. */
|
||||
for (count = 0U; count < 3U && HAL_FDCAN_GetRxFifoFillLevel(can, FDCAN_RX_FIFO0) != 0U; ++count) { /* Ограничивает время обработки нагруженного CAN на одну итерацию main. */
|
||||
FDCAN_RxHeaderTypeDef header; uint8_t data[64], length; /* Защищает память даже при ошибочно принятом CAN FD payload до 64 байт. */
|
||||
if (HAL_FDCAN_GetRxMessage(can, FDCAN_RX_FIFO0, &header, data) != HAL_OK) break; /* Не декодирует неуспешно принятый аппаратный кадр. */
|
||||
for (length = 0U; length <= 8U && header.DataLength != dlc_values[length]; ++length) {} /* Декодирует только classic DLC через значения текущего HAL. */
|
||||
if (length <= 8U && header.IdType == FDCAN_EXTENDED_ID && header.RxFrameType == FDCAN_DATA_FRAME && header.FDFormat == FDCAN_CLASSIC_CAN && header.Identifier <= 0x1FFFFFFFUL) { pcan_boot_poll(&port->boot, HAL_GetTick()); (void)pcan_boot_process(&port->boot, header.Identifier, data, length); } /* Проверяет внешний формат и передаёт полный актуальный ProtoCAN flow единому ядру. */
|
||||
} /* Завершает ограниченный пакет RX-кадров. */
|
||||
} /* Завершает обслуживание reusable G474-порта. */
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef PCAN_BOOT_STM32G474_H /* Защищает HAL FDCAN-порт от повторного включения. */
|
||||
#define PCAN_BOOT_STM32G474_H /* Обозначает активный интерфейс профиля STM32G474VET6. */
|
||||
#include "stm32g4xx_hal.h" /* Подключает реальные типы FDCAN/Flash STM32CubeG4. */
|
||||
#include "boot_profile.h" /* Получает аппаратные границы и адрес CAN исходного CANdle G474. */
|
||||
#include "../stm32-common/pcan_boot_stm32.h" /* Использует общее состояние single-slot порта и актуальный API ядра. */
|
||||
bool pcan_boot_stm32g474_init(pcan_boot_stm32_t *port, FDCAN_HandleTypeDef *can, const pcan_boot_config_t *config); /* Подключает готовый FDCAN1 с двумя extended-фильтрами, запускает адресный ProtoCAN Boot v1. */
|
||||
void pcan_boot_stm32g474_poll(pcan_boot_stm32_t *port); /* Обслуживает ограниченный пакет кадров и таймеры ядра из основного цикла. */
|
||||
bool pcan_boot_stm32g474_erase(uint32_t size); /* Стирает страницы приложения при поддерживаемой геометрии 512 КиБ и отключённой перестановке банков. */
|
||||
bool pcan_boot_stm32g474_write(uint32_t image_size, uint32_t offset, const uint8_t *data, uint16_t length); /* Позволяет CAN и UART использовать один doubleword writer с проверкой полезной длины. */
|
||||
bool pcan_boot_stm32g474_image_valid(uint32_t size, uint32_t crc); /* Проверяет readback CRC, MSP и reset vector внутри фактической длины образа. */
|
||||
bool pcan_boot_stm32g474_vectors_valid(uint32_t size); /* Проверяет только векторы; не доказывает целостность после внешнего reset. */
|
||||
#endif /* Завершает интерфейс G474 single-slot порта. */
|
||||
@@ -0,0 +1,15 @@
|
||||
# F2812 / AM29LV800BT
|
||||
|
||||
Самостоятельный проект, карта NOR, параметры CAN, установка и ограничения
|
||||
описаны в [общей инструкции CCS](../../projects/README.md).
|
||||
|
||||
- `boot2812.h` — физические CPU word-адреса и отдельная совместимость F2812;
|
||||
- `nor_flash.c` — x16 AMD driver с ID, ограниченными program/sector erase,
|
||||
DQ7/DQ5 polling, readback и BUSY;
|
||||
- `boot_store.c` — упаковка октетов, проверка всей SCI8-таблицы, CRC32,
|
||||
публикация manifest последними magic-словами, проверенная загрузка RAM;
|
||||
- `projects/ccs/f2812/board.c` — подтверждённые линии CPLD и XINTF ПМ67;
|
||||
- `projects/ccs/f2812/main.c` — независимые eCAN/IRQ/очередь/main F2812.
|
||||
|
||||
Протокол и CRC повторно используются из `src/pcan_boot.c`. EEPROM отсутствует;
|
||||
код F28335 и его A/B-журнал не подключаются к этому проекту.
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef BOOT2812_H /* Исключает повторное объявление профиля NOR-загрузчика F2812. */
|
||||
#define BOOT2812_H /* Отделяет основной контроллер ПМ67 от периферийного F28335. */
|
||||
#include "../../include/pcan_boot.h" /* Использует существующий ProtoCAN codec и представление октетов C28x. */
|
||||
#define BOOT2812_NOR_BASE 0x100000UL /* CPU word-адрес AM29LV800BT взят из START_ADR_FLASH действующего flash_tools.h платы. */
|
||||
#define BOOT2812_IMAGE_WORD 0x160000UL /* Новый CAN-образ занимает три полных верхних обычных сектора, выше старого образа и данных FPGA. */
|
||||
#define BOOT2812_IMAGE_BYTES 0x30000UL /* Три сектора по 0x8000 слов дают 196608 октетов полезного SCI8. */
|
||||
#define BOOT2812_META_WORD 0x17E000UL /* Последний top-boot сектор начинается после областей 16K/4K/4K слов и содержит 8192 слова. */
|
||||
#define BOOT2812_PRODUCT 0x2812U /* Отдельный Product Type запрещает запись образа F28335 в основной контроллер F2812. */
|
||||
#define BOOT2812_DEVICE_TYPE 3U /* Тип загрузчика остаётся в том же адресном классе ProtoCAN, что F28335. */
|
||||
#define BOOT2812_DEVICE_ID 6U /* Отдельный ID 6 исключает совпадение с существующим F28335 на адресе 3/7. */
|
||||
#define BOOT2812_RECOVERY_MS 3000UL /* После reset загрузчик три секунды принимает ENTER_BOOT до автоматического запуска сохранённого образа. */
|
||||
unsigned long Boot2812_Now(void); /* Возвращает монотонные миллисекунды Timer1 для CAN и ограниченных ожиданий NOR. */
|
||||
void Boot2812_Busy(void); /* Во время долгого стирания сообщает BUSY через общий codec и обслуживает аппаратный TX ACK. */
|
||||
void Boot2812_BoardInit(void); /* Настраивает действующие линии CPLD и XINTF платы ПМ67. */
|
||||
bool Boot2812_NorIdentify(void); /* До первого erase требует AMD manufacturer 0001 и top-boot device 22DA. */
|
||||
unsigned int Boot2812_NorRead(unsigned long word); /* Читает одно 16-битное слово по CPU-адресу внешней NOR. */
|
||||
bool Boot2812_NorProgram(unsigned long word, unsigned int value); /* Программирует только зарезервированные CAN-области и проверяет прочитанный результат. */
|
||||
bool Boot2812_NorErase(unsigned long word); /* Принимает только начала трёх секторов образа и одного сектора метаданных. */
|
||||
bool Boot2812_Erase(void *user, pcan_boot_byte_t slot); /* Удаляет публикацию до стирания единственного CAN-слота. */
|
||||
bool Boot2812_Write(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length); /* Сохраняет октеты ProtoCAN в x16 NOR младшим байтом вперёд. */
|
||||
pcan_boot_status_t Boot2812_Verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image); /* Проверяет CRC прочитанной NOR и все CPU-адреса SCI8 до COMMIT. */
|
||||
bool Boot2812_Commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image); /* Публикует manifest только после повторной проверки всей внешней Flash. */
|
||||
bool Boot2812_Load(uint32_t *entry); /* Читает действующий manifest, проверяет образ и переносит только разрешённую RAM. */
|
||||
#endif /* Завершает интерфейс самостоятельного NOR-порта F2812. */
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "boot2812.h" /* Использует отдельные адреса и совместимость внешней NOR F2812. */
|
||||
static unsigned int octet(uint32_t offset) /* Читает октет файла SCI8 из x16 NOR, где младший октет хранится первым. */
|
||||
{ /* Поток CAN остаётся независимым от sizeof(char) на C28x. */
|
||||
unsigned int word = Boot2812_NorRead(BOOT2812_IMAGE_WORD + offset / 2UL); /* Преобразует смещение в октетах в CPU word-адрес слота. */
|
||||
return (offset & 1UL) ? word >> 8 : word & 255U; /* Передаёт младшие восемь бит октета без знакового расширения. */
|
||||
} /* Все дальнейшие поля таблицы собираются из того же порядка октетов. */
|
||||
static unsigned int u16(uint32_t offset) /* Восстанавливает слово TI boot table в little endian. */
|
||||
{ /* Вызывается только после проверки достаточной длины соответствующего поля. */
|
||||
return octet(offset) | (octet(offset + 1UL) << 8); /* Два октета соответствуют одному слову C28x. */
|
||||
} /* Завершает чтение длины секции или данных программы. */
|
||||
static uint32_t address_at(uint32_t offset) /* Адрес TI SCI8 содержит старшее 16-битное слово перед младшим. */
|
||||
{ /* Этот порядок отличается от обычного u32 little endian метаданных ProtoCAN. */
|
||||
return ((uint32_t)u16(offset) << 16) | u16(offset + 2UL); /* Внутри каждого слова по-прежнему сохраняется little endian. */
|
||||
} /* Возвращает CPU-адрес, измеряемый в словах. */
|
||||
static bool allowed(uint32_t first, uint32_t end) /* Разрешает только реальную RAM приложения и исключает RAM работающего загрузчика. */
|
||||
{ /* Новое приложение не должно загружать инициализированные секции в L1 или RAM InitFlash до перехода в runtime. */
|
||||
return end > first && ((first >= 0x8000UL && end <= 0x8F00UL) || (first >= 0x3F8000UL && end <= 0x3FA000UL) || (first >= 0x80000UL && end <= 0x100000UL)); /* L0 до ramfuncs, полный H0 и внешняя SRAM зоны 2; периферия, NOR, стек и внутренние Flash запрещены. */
|
||||
} /* Проверки входа и всех секций выполняются до первой записи приложения в RAM. */
|
||||
static bool table(uint32_t size, bool copy, uint32_t *entry) /* Проверяет целиком SCI8 и при повторном проходе переносит данные в RAM. */
|
||||
{ /* Устойчивость загрузчика не зависит от адресов, заявленных принимаемым файлом. */
|
||||
uint32_t starts[64], ends[64], offset = 22UL, address, end; /* Лимит 64 секции ограничивает стек и число сравнений перекрытий. */
|
||||
unsigned int sections = 0U, count, i; bool entry_loaded = false; /* Требует, чтобы точка входа реально находилась среди загружаемых слов. */
|
||||
if (size < 24UL || (size & 1UL) || u16(0UL) != 0x08AAU) return false; /* Ключ SCI8 AA08, чётный размер и терминатор обязательны. */
|
||||
*entry = address_at(18UL); /* После ключа и восьми резервных слов находится CPU word-адрес C runtime. */
|
||||
while (offset + 2UL <= size) { /* Поле count читается только при наличии обоих октетов. */
|
||||
count = u16(offset); offset += 2UL; /* Размер секции выражен в 16-битных словах, а не октетах файла. */
|
||||
if (!count) return offset == size && entry_loaded; /* Запрещает хвост, незавершённую таблицу и entry вне payload. */
|
||||
if (sections == 64U || size - offset < 4UL) return false; /* Не допускает переполнения ограниченных массивов или усечённого адреса. */
|
||||
address = address_at(offset); offset += 4UL; end = address + count; /* Восстанавливает исключающую верхнюю границу CPU-диапазона секции. */
|
||||
if (!allowed(address, end) || (uint32_t)count * 2UL > size - offset) return false; /* Проверяет адреса и полный payload до обращения к RAM назначения. */
|
||||
for (i = 0; i < sections; ++i) if (address < ends[i] && starts[i] < end) return false; /* Даже правильно переданная CRC не разрешает две секции поверх одного слова. */
|
||||
starts[sections] = address; ends[sections++] = end; /* Сохраняет уже проверенный диапазон для следующих секций. */
|
||||
if (*entry >= address && *entry < end) entry_loaded = true; /* Подтверждает наличие исполняемого слова по адресу входа. */
|
||||
if (copy) { /* Этот проход выполняется только после полной read-only проверки всего образа. */
|
||||
for (i = 0; i < count; ++i) { /* Переносит ровно заявленные и предварительно проверенные слова секции. */
|
||||
#if defined(BOOT2812_HOST) /* Аппаратная модель проверяет тот же диапазон без записи по адресам процесса ПК. */
|
||||
extern void NorModel_LoadWord(uint32_t target, unsigned int value); /* Модель сохраняет каждое слово переноса для независимой проверки. */
|
||||
NorModel_LoadWord(address + i, u16(offset + (uint32_t)i * 2UL)); /* Подменяет только шину RAM назначения. */
|
||||
#else /* На F2812 CPU-адреса уже выражены в 16-битных словах. */
|
||||
*(volatile unsigned int *)(address + i) = u16(offset + (uint32_t)i * 2UL); /* Записывает секцию только в разрешённую RAM приложения. */
|
||||
#endif /* Завершает аппаратную запись либо её host-модель. */
|
||||
} /* Завершает перенос текущей секции. */
|
||||
} /* После проверочного прохода RAM остаётся нетронутой. */
|
||||
offset += (uint32_t)count * 2UL; /* Пропускает полезные октеты до следующего count. */
|
||||
} /* Исчерпание файла без терминатора означает неполный образ. */
|
||||
return false; /* Не запускает незавершённую таблицу. */
|
||||
} /* Образ с ошибкой не публикуется как запускаемый. */
|
||||
bool Boot2812_Erase(void *user, pcan_boot_byte_t slot) /* Реализует single-slot подготовку внешней NOR. */
|
||||
{ /* Существующие legacy-образы и FPGA ниже 160000 не входят в новый CAN-раздел. */
|
||||
unsigned long sector; (void)user; /* Перебирает только три полных обычных сектора нового образа. */
|
||||
if (slot || !Boot2812_NorIdentify() || !Boot2812_NorErase(BOOT2812_META_WORD)) return false; /* Сначала удаляет marker старого CAN-образа; при обрыве стирания сохраняется recovery. */
|
||||
for (sector = BOOT2812_IMAGE_WORD; sector < BOOT2812_IMAGE_WORD + BOOT2812_IMAGE_BYTES / 2UL; sector += 0x8000UL) if (!Boot2812_NorErase(sector)) return false; /* Геометрия x16 допускает erase только по границам обычных секторов 32K слов. */
|
||||
return true; /* Ядро ProtoCAN разрешает DATA только после полного успешного стирания слота. */
|
||||
} /* EEPROM-операции и несуществующий второй слот отсутствуют. */
|
||||
bool Boot2812_Write(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Упаковывает поступившие октеты в x16 слова NOR. */
|
||||
{ /* В одном вызове записывается не более одного classic CAN кадра. */
|
||||
unsigned int i, word; (void)user; /* Счётчик относится к октетам кадра, адрес NOR отдельно делится на два. */
|
||||
if (slot || !length || length > 8U || (offset & 1UL) || offset > BOOT2812_IMAGE_BYTES || length > BOOT2812_IMAGE_BYTES - offset) return false; /* Не допускает выхода из слота либо продолжения слова с неверного октета. */
|
||||
for (i = 0; i < length; i += 2U) { /* Последовательные 8-октетные блоки начинаются на границе x16 слова. */
|
||||
word = (data[i] & 255U) | ((i + 1U < length ? data[i + 1U] & 255U : 255U) << 8); /* Неполное последнее слово сохраняет FF в незанятом старшем октете. */
|
||||
if (!Boot2812_NorProgram(BOOT2812_IMAGE_WORD + (offset + i) / 2UL, word)) return false; /* Аппаратный драйвер проверяет и допустимый адрес, и результат записи каждого слова. */
|
||||
} /* Завершает сохранение текущего CAN-блока. */
|
||||
return true; /* ACK означает проверенную запись, а не только получение кадра. */
|
||||
} /* CRC ядра продвигается только после успешного возврата этого callback. */
|
||||
pcan_boot_status_t Boot2812_Verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image) /* Перепроверяет CRC непосредственно из сохранённой NOR. */
|
||||
{ /* Потоковая CRC при приёме не заменяет аппаратный readback полного файла. */
|
||||
uint32_t offset, crc = 0UL, entry; pcan_boot_byte_t bytes[32]; unsigned int count, i; (void)user; /* Небольшой буфер не требует размещать весь образ во внутренней RAM F2812. */
|
||||
if (slot || image->image_size < 24UL || image->image_size > BOOT2812_IMAGE_BYTES || (image->image_size & 1UL)) return PCAN_BOOT_STATUS_INVALID_SIZE; /* SCI8 должен целиком помещаться в выделенных трёх секторах и содержать целые слова. */
|
||||
if (image->product_type != BOOT2812_PRODUCT || image->hardware_revision_min > 1U || image->hardware_revision_max < 1U) return PCAN_BOOT_STATUS_WRONG_DEVICE; /* Образ F28335 или несовместимого изделия не становится загрузочным. */
|
||||
for (offset = 0; offset < image->image_size; offset += count) { /* Проверяет каждый фактически сохранённый октет ровно один раз. */
|
||||
count = image->image_size - offset < 32UL ? (unsigned int)(image->image_size - offset) : 32U; /* Последний буфер ограничен реальной длиной без FF-дополнения CAN. */
|
||||
for (i = 0; i < count; ++i) bytes[i] = octet(offset + i); /* Читает данные из NOR, а не из приёмной очереди CAN. */
|
||||
crc = pcan_boot_crc32_update(crc, bytes, count); /* Использует общий CRC32 ProtoCAN без дублирования полинома в аппаратном порту. */
|
||||
Boot2812_Busy(); /* Большой файл сохраняет ответы клиента при длинной проверке на реальном CPU. */
|
||||
} /* Завершает вычисление CRC readback. */
|
||||
if (crc != image->image_crc32) return PCAN_BOOT_STATUS_CRC_ERROR; /* Различает повреждение памяти и ошибку структуры SCI8. */
|
||||
return table(image->image_size, false, &entry) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_INVALID_STATE; /* Все адреса, перекрытия, терминатор и entry проверяются до COMMIT. */
|
||||
} /* До успеха verifier никакие слова приложения не переносятся в RAM. */
|
||||
bool Boot2812_Commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image) /* Публикует загрузочный manifest последней операцией после readback. */
|
||||
{ /* Формат из 12 слов фиксирован и не зависит от sizeof C-структуры. */
|
||||
unsigned int words[12], i; /* Два magic-слова, версия формата, размер, CRC, версия FW и контроль manifest. */
|
||||
pcan_boot_byte_t bytes[20]; uint32_t crc; /* CRC первых десяти слов защищает размер и контрольную сумму самого образа. */
|
||||
if (Boot2812_Verify(user, slot, image) != PCAN_BOOT_STATUS_OK) return false; /* Не публикует файл, испорченный после предыдущего VERIFY. */
|
||||
words[0] = 0x2812U; words[1] = 0xCA7EU; words[2] = 1U; words[3] = 0U; /* Magic 2812/CA7E и версия 1 отличают новый CAN manifest от прежних данных платы. */
|
||||
words[4] = (unsigned int)(image->image_size & 0xFFFFUL); words[5] = (unsigned int)(image->image_size >> 16); /* Длина выражена октетами и хранится младшим словом первой. */
|
||||
words[6] = (unsigned int)(image->image_crc32 & 0xFFFFUL); words[7] = (unsigned int)(image->image_crc32 >> 16); /* Сохраняет точную CRC всего полученного файла. */
|
||||
words[8] = (unsigned int)(image->firmware_version & 0xFFFFUL); words[9] = (unsigned int)(image->firmware_version >> 16); /* Версия кандидата не подменяется версией постоянного загрузчика. */
|
||||
for (i = 0; i < 10U; ++i) { bytes[2U * i] = words[i] & 255U; bytes[2U * i + 1U] = words[i] >> 8; } /* Wire-представление manifest использует октеты независимо от ширины C28x char. */
|
||||
crc = pcan_boot_crc32_update(0UL, bytes, 20U); words[10] = (unsigned int)(crc & 0xFFFFUL); words[11] = (unsigned int)(crc >> 16); /* CRC manifest не позволяет принять частично записанные поля размера и CRC образа. */
|
||||
for (i = 2U; i < 12U; ++i) if (!Boot2812_NorProgram(BOOT2812_META_WORD + i, words[i])) return false; /* Сначала записывает все поля, пока оба magic-слова ещё равны FF. */
|
||||
if (!Boot2812_NorProgram(BOOT2812_META_WORD + 1UL, words[1])) return false; /* Второе magic-слово фиксируется после полного тела manifest. */
|
||||
return Boot2812_NorProgram(BOOT2812_META_WORD, words[0]); /* Первое magic записывается последним: потеря питания раньше оставляет recovery. */
|
||||
} /* Повтор COMMIT обрабатывает идемпотентный автомат общего ядра. */
|
||||
bool Boot2812_Load(uint32_t *entry) /* Проверяет опубликованный образ после reset перед переносом в RAM приложения. */
|
||||
{ /* Пустой или повреждённый manifest оставляет прибор в постоянном CAN recovery. */
|
||||
unsigned int words[12], i; pcan_boot_byte_t bytes[20]; uint32_t crc; pcan_boot_manifest_t image; /* Сериализованные поля читаются явно, без memcpy по ABI структуры. */
|
||||
for (i = 0; i < 12U; ++i) words[i] = Boot2812_NorRead(BOOT2812_META_WORD + i); /* Читает только фиксированные 24 октета manifest. */
|
||||
if (words[0] != 0x2812U || words[1] != 0xCA7EU || words[2] != 1U || words[3]) return false; /* Не интерпретирует старые данные Flash либо неполный commit как запускаемый файл. */
|
||||
for (i = 0; i < 10U; ++i) { bytes[2U * i] = words[i] & 255U; bytes[2U * i + 1U] = words[i] >> 8; } /* Восстанавливает то же октетное представление, что защищалось при COMMIT. */
|
||||
crc = pcan_boot_crc32_update(0UL, bytes, 20U); /* Проверяет метаданные до использования объявленной ими длины. */
|
||||
if (crc != ((uint32_t)words[10] | ((uint32_t)words[11] << 16))) return false; /* Повреждённый размер не может вывести чтение за пределы CAN-слота. */
|
||||
image.image_size = (uint32_t)words[4] | ((uint32_t)words[5] << 16); image.image_crc32 = (uint32_t)words[6] | ((uint32_t)words[7] << 16); /* Размер и CRC файла восстанавливаются только после успешной CRC manifest. */
|
||||
image.firmware_version = (uint32_t)words[8] | ((uint32_t)words[9] << 16); image.product_type = BOOT2812_PRODUCT; image.hardware_revision_min = image.hardware_revision_max = 1U; /* Профиль образа был проверен до публикации manifest. */
|
||||
return Boot2812_Verify(0, 0U, &image) == PCAN_BOOT_STATUS_OK && table(image.image_size, true, entry); /* Сначала полный read-only проход, затем перенос и только после успеха разрешение вызова entry. */
|
||||
} /* Не загружает старые flat BIN автоматически: новый проект использует проверяемую SCI8-таблицу. */
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "boot2812.h" /* Ограничивает аппаратный драйвер разделами обновления конкретного профиля ПМ67. */
|
||||
#if defined(BOOT2812_HOST) /* Host-модель воспроизводит команды NOR без обращения к физическим CPU-адресам. */
|
||||
extern unsigned int NorModel_Read(unsigned long word); /* Модель отдаёт данные, ID и аппаратные биты готовности чипа. */
|
||||
extern void NorModel_Write(unsigned long word, unsigned int value); /* Модель принимает настоящую последовательность unlock/program/erase драйвера. */
|
||||
#define NOR_READ(word) NorModel_Read(word) /* Подменяет только чтение шины, сохраняя алгоритм драйвера. */
|
||||
#define NOR_WRITE(word, value) NorModel_Write(word, value) /* Подменяет физическую запись при проверке границ и отказов. */
|
||||
#else /* Production использует 16-битную параллельную XINTF-шину. */
|
||||
#define NOR_READ(word) (*(volatile unsigned int *)(word)) /* Один CPU-адрес C28x соответствует одному x16 слову AM29LV800BT. */
|
||||
#define NOR_WRITE(word, value) (*(volatile unsigned int *)(word) = (value)) /* Volatile сохраняет каждый командный цикл AMD в исходном порядке. */
|
||||
#endif /* Завершает выбор физической шины либо модели. */
|
||||
static void reset_read(void) /* Выводит чип из autoselect либо неудачной операции в обычный режим чтения. */
|
||||
{ /* Reset не стирает слова памяти и допустим по любому адресу чипа. */
|
||||
NOR_WRITE(BOOT2812_NOR_BASE, 0x00F0U); /* F0 — Read/Reset из таблицы команд AMD Am29LV800B. */
|
||||
} /* Все ошибки возвращают NOR в состояние, пригодное для следующей recovery-сессии. */
|
||||
static void unlock(unsigned int command) /* Выполняет общий префикс x16-команд согласно datasheet AMD. */
|
||||
{ /* Адреса ниже являются смещениями в словах, а не октетными AAA/555 режима x8. */
|
||||
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, 0x00AAU); /* Первый unlock-цикл AA по смещению 555h в режиме x16. */
|
||||
NOR_WRITE(BOOT2812_NOR_BASE + 0x2AAUL, 0x0055U); /* Второй unlock-цикл 55 по смещению 2AAh. */
|
||||
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, command); /* Третий цикл выбирает autoselect, программирование либо начало стирания. */
|
||||
} /* Последующий цикл данных или адрес сектора выполняет вызывающая операция. */
|
||||
unsigned int Boot2812_NorRead(unsigned long word) /* Читает только физический диапазон установленной NOR ёмкостью 1 МиБ. */
|
||||
{ /* Верхняя исключающая граница равна BASE + 512K слов. */
|
||||
return word >= BOOT2812_NOR_BASE && word < BOOT2812_NOR_BASE + 0x80000UL ? NOR_READ(word) : 0xFFFFU; /* Вне чипа не создаёт шинного чтения по неизвестному адресу. */
|
||||
} /* Возвращает полное x16 слово без перестановки октетов. */
|
||||
bool Boot2812_NorIdentify(void) /* Идентификация обязательна перед разрешением изменяющих операций. */
|
||||
{ /* Не подменяет top-boot геометрию похожим bottom-boot или NAND-чипом. */
|
||||
unsigned int maker, device; /* Хранит два слова autoselect, прежде чем вернуть чип к чтению. */
|
||||
reset_read(); unlock(0x0090U); /* 90h включает чтение manufacturer/device ID после обычного unlock. */
|
||||
maker = NOR_READ(BOOT2812_NOR_BASE); device = NOR_READ(BOOT2812_NOR_BASE + 1UL); /* AMD: offset 0 содержит 0001, offset 1 — 22DA для Am29LV800BT x16. */
|
||||
reset_read(); /* Обычные чтения образа после проверки не должны видеть таблицу autoselect. */
|
||||
return maker == 0x0001U && device == 0x22DAU; /* Отличающийся чип получает отказ до первого sector erase. */
|
||||
} /* Не выполняет разрушительных пробных записей для определения типа памяти. */
|
||||
static bool wait_word(unsigned long word, unsigned int expected, unsigned long timeout) /* Ограничивает DQ7/DQ5 polling временем и числом проходов. */
|
||||
{ /* DQ7 отражает готовность программируемого старшего бита; DQ5 требует завершающего повторного чтения. */
|
||||
unsigned long started = Boot2812_Now(), budget = 50000000UL; /* Итерационный предел обеспечивает выход даже при остановленном Timer1. */
|
||||
unsigned int value; /* Снимок status считывается один раз на основной проход. */
|
||||
do { /* Не вызывает никаких функций из программируемой внешней NOR: код загрузчика находится во внутренней Flash. */
|
||||
value = NOR_READ(word); /* Читает именно адрес программирования либо начало стираемого сектора. */
|
||||
if ((value & 0x0080U) == (expected & 0x0080U)) return NOR_READ(word) == expected; /* После совпадения DQ7 подтверждает все 16 бит результата. */
|
||||
if (value & 0x0020U) { /* DQ5 сообщает превышение внутреннего времени алгоритма AMD. */
|
||||
if ((NOR_READ(word) & 0x0080U) == (expected & 0x0080U)) return NOR_READ(word) == expected; /* Повтор исключает нормальное завершение операции одновременно с чтением DQ5. */
|
||||
break; /* После подтверждённой ошибки больше не ждёт неопределённое время. */
|
||||
} /* Завершает трактовку аппаратного статуса текущего прохода. */
|
||||
Boot2812_Busy(); /* Долгое стирание продолжает давать клиенту живой ответ и не блокирует CAN ISR. */
|
||||
} while (--budget && Boot2812_Now() - started < timeout); /* Беззнаковое вычитание сохраняет корректность при переполнении миллисекунд. */
|
||||
reset_read(); /* Отказ возвращает чип в режим чтения перед следующей recovery-попыткой. */
|
||||
return false; /* Не сообщает успех по одному окончанию тайм-аута. */
|
||||
} /* Завершает ограниченное ожидание аппаратной операции. */
|
||||
bool Boot2812_NorProgram(unsigned long word, unsigned int value) /* Программирует только выделенный CAN-образ либо manifest. */
|
||||
{ /* Старые данные приложения, FPGA и остальные сектора чипа не входят в разрешённые диапазоны. */
|
||||
unsigned int before; /* Предварительное чтение исключает попытку перевода NOR-бита из нуля в единицу. */
|
||||
if (!((word >= BOOT2812_IMAGE_WORD && word < BOOT2812_IMAGE_WORD + BOOT2812_IMAGE_BYTES / 2UL) || (word >= BOOT2812_META_WORD && word < BOOT2812_META_WORD + 0x2000UL))) return false; /* Проверяет word-адрес до любого unlock или командного цикла. */
|
||||
before = NOR_READ(word); /* Проверяет текущее содержимое перед началом операции. */
|
||||
if (before == value) return true; /* Повтор одинакового блока не выполняет лишнее программирование. */
|
||||
if ((before & value) != value) return false; /* Бит 0→1 требует отдельного sector erase и не должен маскироваться чтением старого слова. */
|
||||
unlock(0x00A0U); NOR_WRITE(word, value); /* A0 запускает word program; следующий цикл содержит адрес и полные 16 бит данных. */
|
||||
return wait_word(word, value, 100UL); /* Сто миллисекунд дают ограниченный запас для программирования одного слова. */
|
||||
} /* Каждый успех означает фактическое совпадение прочитанного слова. */
|
||||
bool Boot2812_NorErase(unsigned long word) /* Не принимает произвольный адрес или команду полного стирания чипа. */
|
||||
{ /* Обновление ограничено секторами 12/13/14 и последним top-boot сектором 18. */
|
||||
unsigned long i, count = word == BOOT2812_META_WORD ? 0x2000UL : 0x8000UL; /* AM29LV800BT: обычный сектор 32K слов, последний boot-сектор 8K слов. */
|
||||
if (!(word == BOOT2812_IMAGE_WORD || word == BOOT2812_IMAGE_WORD + 0x8000UL || word == BOOT2812_IMAGE_WORD + 0x10000UL || word == BOOT2812_META_WORD)) return false; /* Только точные разрешённые начала секторов исключают округление в соседнюю область. */
|
||||
unlock(0x0080U); /* 80h начинает шеститактовую последовательность Sector Erase AMD. */
|
||||
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, 0x00AAU); NOR_WRITE(BOOT2812_NOR_BASE + 0x2AAUL, 0x0055U); /* Повторяет вторую unlock-пару перед выбором сектора. */
|
||||
NOR_WRITE(word, 0x0030U); /* 30h по sector address выбирает один сектор; команда Chip Erase 10h отсутствует. */
|
||||
if (!wait_word(word, 0xFFFFU, 20000UL)) return false; /* Ограничивает ожидание двадцатью секундами с периодическими BUSY-ответами. */
|
||||
for (i = 0; i < count; ++i) if (NOR_READ(word + i) != 0xFFFFU) return false; /* Проверяет всю стёртую область, а не только её первое слово. */
|
||||
return true; /* Разрешает запись лишь после полного аппаратного readback сектора. */
|
||||
} /* При сбое сохранённый manifest уже недействителен, загрузчик остаётся в recovery. */
|
||||
@@ -0,0 +1,207 @@
|
||||
# F28335 + M95M02: CAN, отдельный загрузчик и A/B
|
||||
|
||||
Реализация состоит из Flash-загрузчика `projects/ccs/f28335`, переносимого
|
||||
`f28335_boot_store.c`, аппаратного `f28335_boot_hw.c` и адаптера
|
||||
`f28335_can_boot.c`. Приложение подключает общий `f28335_can_application.c`
|
||||
прямой ссылкой linked resource из templates в CCS Debug/Release.
|
||||
В сборке `EEPROM_M95M02=0` CAN Boot отключён. Для установленной M95M02 — значение 1.
|
||||
|
||||
## Адрес и формат
|
||||
|
||||
Адрес определяется физическим номером **PERIPH 0–31** по новой Excel-таблице:
|
||||
Type = 1 + PERIPH / 16 (целая часть), Device ID = PERIPH mod 16.
|
||||
**УМП1 — 1/5, УМП2 — 1/6, задатчик — 1/8**. Product **0x2833**, HW **1**
|
||||
описывают существующую аппаратную совместимость F28335 и не являются CAN-адресом.
|
||||
Имена паспорта для назначенных плат — `UMP1`, `UMP2`, `Zadatchik`.
|
||||
Приложение передаёт Mode в `F28335CanBoot_Init(Mode)`. Загрузчик независимо
|
||||
читает те же активные низким уровнем GPIO21/20/22/51 с весами 1/2/4/8 и базой 1.
|
||||
Поэтому существующие перемычки ПМ35 задают номера 1–16; номера 0 и 17–31
|
||||
поддерживаются адресным контрактом, но требуют другого источника физического номера.
|
||||
Общий адрес 3/7 больше не принимается. Чужие адреса отбрасываются аппаратным
|
||||
mailbox, ISR и ядром до записи EEPROM, включая данные A/B и управляющие команды.
|
||||
В приложении FW берётся из `firmware_version.h`, формат `major<<24 | minor<<16 | patch`;
|
||||
для версии 1.1.2 это `0x01010002`. Загрузчик сообщает версию подтверждённого образа
|
||||
из журнала, при отсутствии такого образа — 0. Версия кандидата передаётся в BEGIN_COMPAT.
|
||||
CAN **250000 бит/с**, Extended ID, окно **16 блоков по 8 октетов**.
|
||||
Для задатчика и session=1: ENTER_BOOT `0x11890102`, STATUS `0x198C0102`.
|
||||
|
||||
## Общий запуск Flash и резервной EEPROM
|
||||
|
||||
Сектор A и аппаратный boot `0x33FFF6` принадлежат постоянному загрузчику.
|
||||
Flash-приложение использует `[0x300000,0x338000)`: первые 16 слов — описание
|
||||
FAB1, остальные — собственные LOAD-секции. `f28335_flash_app.c` проверяет
|
||||
magic/version, entry, границы, инверсию entry, маркеры и CRC32 IEEE всего
|
||||
диапазона после заголовка, включая промежутки, заполненные `0xFFFF`.
|
||||
Python-контракт `python/protocan/f28335_flash.py` заполняет зарезервированные
|
||||
32 октета COFF по смещению, подтверждённому TI OFD, после линковки.
|
||||
|
||||
Загрузчик даёт трёхсекундное окно CAN, затем запускает исправную внутреннюю
|
||||
Flash в приоритете. Отсутствующая/повреждённая Flash ведёт к прежнему выбору
|
||||
EEPROM A/B с trial/CONFIRM/rollback. ENTER_BOOT удерживает CAN recovery.
|
||||
Handoff running=`0xFE` обозначает проверенную внутреннюю Flash; 0/1 — EEPROM
|
||||
A/B, `0xFF` — отсутствие запущенного приложения. Flash-приложение обслуживает
|
||||
PING и ENTER_BOOT, но не подменяет подтверждение EEPROM-кандидата.
|
||||
Обновление EEPROM по CAN не меняет приоритет исправной Flash после reset.
|
||||
|
||||
RS Flash должен стирать только B–H, публиковать FAB1 после проверки остальных
|
||||
данных и запрещать любые адреса сектора A. В SETGUI соответствующие JTAG
|
||||
маски задаются явными TI `FlashSectorA..H`: A для установки boot, B–H для
|
||||
приложения. Полное стирание чипа внешним программатором не ограничивается linker.
|
||||
Старая самостоятельная Flash-сборка с codestart `0x33FFF6` несовместима с этим
|
||||
размещением и не должна передаваться как обновление приложения.
|
||||
|
||||
Образ — SCI8 BIN (`AA 08`), чётная длина от 24 до **130560 байт** включительно.
|
||||
Числа в протоколе little endian. Адреса внутри SCI8: сначала старшее 16-битное
|
||||
слово, затем младшее; каждое слово little endian. В C28x `char` 16-битный,
|
||||
поэтому `pcan_boot_byte_t` хранит октет в младших 8 битах.
|
||||
|
||||
## Разметка EEPROM, байтовые адреса
|
||||
|
||||
| Область | Начало | Конец включительно | Размер |
|
||||
|---|---:|---:|---:|
|
||||
| Журнал 0 | 0x00000 | 0x000FF | 256 |
|
||||
| Журнал 1 | 0x00100 | 0x001FF | 256 |
|
||||
| Slot A | 0x00200 | 0x1FFFF | 130560 |
|
||||
| Slot B | 0x20000 | 0x3FDFF | 130560 |
|
||||
| Резерв | 0x3FE00 | 0x3FEFB | 252 |
|
||||
| Параметры | 0x3FEFC | 0x3FFFD | 258 |
|
||||
| Последнее слово | 0x3FFFE | 0x3FFFF | 2 |
|
||||
|
||||
В каждой странице журнала первые 64 октета содержат magic `FAB2`, версию 1,
|
||||
sequence, active/pending/attempted, два manifest, CRC32 первых 56 октетов и
|
||||
commit marker в октетах 60..63. Сначала обнуляется marker запасной страницы,
|
||||
затем записывается тело с проверкой чтением, последним — marker. Предыдущая
|
||||
страница остаётся действующей до полной публикации новой. Выбор sequence
|
||||
учитывает переполнение u32. После ошибки публикации журнал перечитывается;
|
||||
ошибка чтения блокирует последующие записи.
|
||||
|
||||
## Сценарий обновления и отката
|
||||
|
||||
1. Приложение принимает ENTER_BOOT, останавливает управление нагрузкой, выставляет 4 мА и выдерживает
|
||||
секундный STOP, затем сохраняет session в NOINIT RAM 0x50..0x57. Watchdog выполняет reset.
|
||||
ACK ENTER_BOOT отправляет уже Flash-загрузчик; приложение ничего не стирает.
|
||||
2. BEGIN_IMAGE/BEGIN_COMPAT проверяют размер, Product, HW и сессию. Неактивный
|
||||
слот выбирается автоматически; при первом образе это A. ERASE инвалидирует
|
||||
только его manifest. Полного стирания EEPROM не требуется.
|
||||
3. Последовательные DATA_A/B записываются с немедленным readback. VERIFY
|
||||
проверяет потоковый CRC32, CRC32 EEPROM и полную таблицу SCI8.
|
||||
Разрешена загрузка только в CPU word RAM `[0x8000,0xE000)`: максимум 64 секции,
|
||||
без пересечений, усечений и хвостовых данных; entry должен попасть в секцию.
|
||||
Запись в Flash, периферию, стек, RAM загрузчика и handoff запрещена.
|
||||
4. COMMIT повторно проверяет образ и публикует pending в журнале. Подтверждённый
|
||||
слот сохраняется. Повтор COMMIT безопасен. REBOOT выполняется после физического
|
||||
CAN ACK ответа. Потеря ACK/тайм-аут передачи отменяет запрос reset.
|
||||
5. Перед первым запуском кандидата загрузчик сохраняет attempted=1, затем
|
||||
проверяет и копирует SCI8 в RAM. Следующий reset без CONFIRM сбрасывает pending
|
||||
и запускает прежний active. Если прежнего образа нет — остаётся CAN recovery.
|
||||
6. Новое приложение включает watchdog в начале запуска. До подтверждения
|
||||
прерывания не кормят watchdog вместо основного цикла. После READY, 100 проходов
|
||||
main и 5 секунд выполняется автоматический CONFIRM. Неудача сохранения
|
||||
подтверждения через 10 секунд прекращает кормление watchdog, вызывая откат.
|
||||
Адресная команда CONFIRM с ненулевой session также разрешена после READY.
|
||||
CONFIRM в загрузчике и до первого запуска кандидата отклоняется.
|
||||
|
||||
PING, IDENTIFY и паспорт доступны без ENTER_BOOT. QUERY_PROGRESS/ABORT привязаны
|
||||
к сессии. Сессия в RAM истекает через 30 секунд без подходящих команд/данных.
|
||||
После reset передачу начинают заново; параметры и подтверждённый образ остаются.
|
||||
ABORT после COMMIT не отменяет опубликованный pending. Для его пробного запуска
|
||||
можно открыть новую сессию и отправить REBOOT. Pending запрещено перезаписывать.
|
||||
Повторы ENTER_BOOT, метаданных, ERASE до первого блока, VERIFY и COMMIT учитывают
|
||||
потерю ответа. Broadcast никогда не разрешает запись.
|
||||
|
||||
## Сервисные команды
|
||||
|
||||
Запрос: MsgType=9, Body=`session<<8 | command`. IDENTIFY/PING/паспорт не требуют сессии.
|
||||
Ответ паспорта: MsgType=D, Body=`page<<8 | command`, DLC=8.
|
||||
|
||||
| Код | Имя | DLC запроса | Ответ |
|
||||
|---:|---|---:|---|
|
||||
| 01 | IDENTIFY | 0 | Product:u16, HW:u8, protocol:u8, FW:u32; Body=1 |
|
||||
| 0C | PING | 0 | protocol, state, active, target, session, slot_count, last_status, 0 |
|
||||
| 0D | CAPABILITIES | 0 | flags:u32, slot_capacity:u32 |
|
||||
| 0E | DEVICE_NAME | 1 (page) | 8 ASCII-октетов имени; последний фрагмент с NUL |
|
||||
| 0F | MEMORY_INFO | 1 (page) | Страницы ниже |
|
||||
|
||||
MEMORY_INFO 0: EEPROM bytes:u32, page bytes:u16, mode:u8 (0 app/1 boot), running:u8.
|
||||
Страница 1: адреса A:u32, B:u32. Страница 2: active, pending, attempted, storage_healthy,
|
||||
session_timeout_ms:u32. Страница 3: parameters_offset:u32, parameters_bytes:u32.
|
||||
Слот FF означает отсутствие образа/запущенного приложения.
|
||||
CAPABILITIES bits 0..11: ping, discovery, identify, name, memory, A/B, confirm,
|
||||
rollback, separate loader, session timeout, readback, SCI8 validation.
|
||||
Приложение, запущенное старым SPI ROM без handoff загрузчика, сообщает только
|
||||
базовые сервисы (0x1F) и отклоняет ENTER_BOOT.
|
||||
|
||||
Обнаружение: MsgType=D, Body=0, DLC=0, Route=0. Адресный запрос или общий
|
||||
DeviceType=7/DeviceID=15. Ответ IDENTIFY приходит с адреса прибора через
|
||||
`2*(1+16*DeviceType+DeviceID)` мс. При нескольких одинаковых адресах конфликт
|
||||
остаётся: каждому физическому устройству нужен уникальный адрес.
|
||||
Python API: `protocan.can_boot.CanBootServices` — запросы и разбор паспорта;
|
||||
`CanBootTransfer` — загрузка. Кнопка «Пинг» доступна в режиме ProtoCAN Boot
|
||||
окна прошивки SETGUI. Для ENTER/VERIFY/COMMIT клиент ждёт до 15 секунд.
|
||||
|
||||
## Аппаратная интеграция
|
||||
|
||||
MBOX3 — адресные входящие Boot; MBOX4 — ответы; MBOX6 — точный broadcast discovery.
|
||||
MBOX0..2 сохраняют legacy/UMP, MBOX5 загрузчик не использует. ISR помещает сообщения
|
||||
в очередь на 31 кадр. Чужие адреса, RTR и неподходящие типы отбрасываются до очереди.
|
||||
При переполнении/потере mailbox сессия отменяется; повторная запись начинается заново.
|
||||
SPI-драйвер загрузчика имеет ограничение ожидания байта и WIP, 24-битный адрес,
|
||||
разбиение по страницам 256 байт и восстановление конфигурации SPI приложения.
|
||||
Он работает только при остановленном Timer2. Обычный Seeprom_write приложения
|
||||
ограничен областью параметров; старый RS EXTEND 0x44 запрещён для A/B.
|
||||
|
||||
Загрузчик исполняется из внутренней Flash A 0x338000..0x33FF7F, entry 0x33FFF6.
|
||||
Его RAM — 0xE000..0xFFFF; стек — 0x400..0x7FF. Приложение M95M02 использует
|
||||
0x8000..0xD7FF для кода, 0xD800..0xDFFF для констант/инициализации,
|
||||
0xE000..0xFFFF для .ebss после перехода в C runtime. Linker резервирует
|
||||
NOINIT handoff в обоих проектах. CAN не содержит операций записи внутренней Flash.
|
||||
|
||||
## Первичная установка
|
||||
|
||||
1. Собрать приложение с EEPROM_M95M02=1 и обновлённым F28335.cmd; получить SCI8 BIN.
|
||||
Старые BIN/OUT автоматически не заменены. Приложение должно содержать интеграцию
|
||||
READY/CONFIRM, иначе на следующем reset оно останется неподтверждённым кандидатом.
|
||||
2. Собрать отдельный загрузчик TI C2000 COFF compiler:
|
||||
`python c/protocan-boot/projects/build.py --device f28335 --compiler <путь-к-ti-cgt-c2000>`.
|
||||
Результат — `projects/ccs/f28335/Debug/F28335_CAN_AB.out` и MAP.
|
||||
3. Установить обновлённый OUT через JTAG и выбрать аппаратный Flash boot
|
||||
согласно схеме платы. При переходе с общего адреса 3/7 обновить и приложение,
|
||||
и загрузчик: старый загрузчик не перейдёт на новый адрес после reset сам.
|
||||
В SETCAN выбрать «Прошивка → CAN / ProtoCAN Boot → Устройство PERIPH»
|
||||
и нужное назначение. Не загружать поверх загрузчика старый application OUT с
|
||||
секцией codestart во Flash.
|
||||
4. После reset пустая EEPROM или старая разметка автоматически ведут в CAN recovery.
|
||||
Передать новое приложение через ProtoCAN Boot, дождаться пробного запуска и
|
||||
проверить MEMORY_INFO page 2: pending=FF, active=A. Затем проверить обновление в B.
|
||||
|
||||
Старая SPI-ROM разметка с AA08 по адресу 0 несовместима с новым журналом.
|
||||
Первое обновление заменяет её; параметры по прежнему адресу сохраняются.
|
||||
Загрузчик не выполняет скрытую миграцию и не меняет boot pins программно.
|
||||
|
||||
## Проверки и границы проверенного
|
||||
|
||||
`python c/protocan-boot/tests/run_tests.py` (переменная CC — clang/gcc) проверяет
|
||||
ядро, приложение/загрузчик, wide-octet режим, журнал, обрыв публикации на каждом
|
||||
из 69 возможных положений, пробный запуск, CONFIRM, откат, SCI8 и границы памяти.
|
||||
Python-тесты проверяют клиентские запросы, паспорт, фильтрацию ответов и лимит A/B.
|
||||
Проверка синтаксиса использует настоящие DSP2833x headers. Импорт и сборка TI/CCS,
|
||||
раскладка MAP и реальные COFF LOAD-адреса проверены; физические CAN/SPI/reset
|
||||
на плате требуют стендовой проверки. Отдельные проекты и команды сборки описаны
|
||||
в [инструкции CCS](../../projects/README.md).
|
||||
|
||||
CRC32 защищает от повреждения, криптографической подписи и anti-rollback по
|
||||
версии в этом порту нет. Удалённый CONFIRM подтверждает готовность приложения,
|
||||
но не заменяет проверку датчиков/силовой части на стенде.
|
||||
|
||||
Аппаратный формат SPI boot описан в [TI Boot ROM Reference Guide](https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/171/2742.Boot-ROM.pdf).
|
||||
В A/B-схеме ROM выбирает Flash, а разбор EEPROM выполняет этот загрузчик.
|
||||
|
||||
Проект предоставляет `f28335_can_application_config.h` в include paths.
|
||||
Он задаёт `F28335_CAN_BOOT_APPLICATION_ENABLED` (0/1) и при включённом модуле
|
||||
`F28335_CAN_BOOT_FIRMWARE_VERSION` из версии приложения. В 167 это ручной
|
||||
`firmware_version.h`; профиль включён только при M95M02 без `FLASH_BOOT`.
|
||||
Локальный `Source/Internal/can_boot.c` содержит только `F28335Boot_Quiesce`
|
||||
со штатной остановкой нагрузки. Общие `.c` повторно включать в него не нужно:
|
||||
они компилируются единственным linked resource общей библиотеки.
|
||||
Постоянный Flash-загрузчик по-прежнему собирается отдельным проектом
|
||||
`projects/ccs/f28335` и не линкуется в RAM-приложение.
|
||||
@@ -0,0 +1,181 @@
|
||||
/* Shared by the application and the separate Flash bootloader. */
|
||||
/* Аппаратный слой: общая RAM между reset, watchdog и SPI EEPROM.
|
||||
* Один исходник компилируется и в приложение, и в отдельный Flash-загрузчик.
|
||||
* В отличие от store.c этот файл требует настоящих регистров F28335. */
|
||||
#include "DSP2833x_Device.h"
|
||||
#include "f28335_boot_platform.h"
|
||||
#include "f28335_can_boot.h"
|
||||
|
||||
/* Must be NOINIT in BOTH linkers, outside ROM scratch and application payload. */
|
||||
#pragma DATA_SECTION(boot_handoff,".boot_shared")
|
||||
/* 8 слов NOINIT по адресу 0x50, одинаково зарезервированных обоими linker-файлами.
|
||||
* [0..1] magic; [2] запрошенная session; [3] запущенный слот; [4..6] служебные
|
||||
* поля; [7] XOR-контроль. Это кратковременная передача контекста через watchdog
|
||||
* reset, а не замена энергонезависимого журнала EEPROM. */
|
||||
static volatile unsigned int boot_handoff[8];
|
||||
/* Проверяем magic и XOR прежде, чем трактовать RAM как запрос входа в boot.
|
||||
* После включения питания содержимое NOINIT может быть произвольным. */
|
||||
static unsigned int handoff_valid(void)
|
||||
{
|
||||
unsigned int i,sum=0;
|
||||
for(i=0;i<8;++i) sum^=boot_handoff[i];
|
||||
return boot_handoff[0]==0xB283 && boot_handoff[1]==0x4D7C && sum==0xA95B;
|
||||
}
|
||||
/* Сначала снимаем признак действительности, затем заполняем поля и контроль.
|
||||
* Первый magic записываем последним; volatile сохраняет обращения к общей RAM.
|
||||
* request=0 означает обычный запуск, ненулевая session — явный ENTER_BOOT. */
|
||||
static void handoff_set(unsigned int request,unsigned int running)
|
||||
{
|
||||
unsigned int i,sum=0;
|
||||
boot_handoff[0]=0; boot_handoff[1]=0x4D7C;
|
||||
boot_handoff[2]=request; boot_handoff[3]=running;
|
||||
boot_handoff[4]=0xF335; boot_handoff[5]=0x0CCA; boot_handoff[6]=1;
|
||||
sum=0xB283;
|
||||
for(i=1;i<7;++i) sum^=boot_handoff[i];
|
||||
boot_handoff[7]=sum^0xA95B;
|
||||
boot_handoff[0]=0xB283; /* publish last */
|
||||
}
|
||||
/* Приложение узнаёт свой слот из handoff, а не предполагает, что оно active:
|
||||
* во время пробного запуска выполняется pending, active ещё указывает на старый образ. */
|
||||
unsigned int F28335Boot_RunningSlot(void)
|
||||
{ return handoff_valid()?boot_handoff[3]:F28335_AB_NONE; }
|
||||
unsigned int F28335Boot_RequestedSession(void)
|
||||
{ return handoff_valid() && boot_handoff[2]<256?boot_handoff[2]:0; }
|
||||
/* Одновременно публикуем выбранный слот и потребляем запрос ENTER_BOOT:
|
||||
* он не должен повторяться при следующем обычном reset. */
|
||||
void F28335Boot_SetRunning(unsigned int slot) { handoff_set(0,slot); }
|
||||
/* Timer1 должен давать 1 мс на InterruptCount и в приложении, и в загрузчике.
|
||||
* Тайм-ауты используют разность unsigned-счётчиков для переноса через wrap. */
|
||||
uint32_t F28335Boot_Now(void) { return CpuTimer1.InterruptCount; }
|
||||
/* Вызвать сразу после InitSysCtrl, который штатно отключает watchdog.
|
||||
* Сбрасываем разрешение кормления из ISR до полной инициализации CAN-сервиса.
|
||||
* Для приложения, запущенного нашим загрузчиком, включаем watchdog с делителем. */
|
||||
void F28335Boot_StartupWatchdog(void)
|
||||
{
|
||||
F28335CanBoot_EarlyInit();
|
||||
if(F28335Boot_RunningSlot()<2) {
|
||||
EALLOW; SysCtrlRegs.WDKEY=0x55; SysCtrlRegs.WDKEY=0xAA;
|
||||
SysCtrlRegs.WDCR=0x002F; EDIS;
|
||||
}
|
||||
}
|
||||
/* Платформенный Quiesce останавливает управление выходами перед reset.
|
||||
* Session сохраняется до включения watchdog; ненулевую session загрузчик
|
||||
* использует для отложенного ACK ENTER_BOOT. В бесконечном цикле watchdog
|
||||
* намеренно не обслуживается — выход из функции должен произойти через reset. */
|
||||
void F28335Boot_Reset(unsigned int session)
|
||||
{
|
||||
F28335Boot_Quiesce();
|
||||
DINT;
|
||||
handoff_set(session,F28335_AB_NONE);
|
||||
EALLOW; SysCtrlRegs.WDCR=0x0028; EDIS;
|
||||
for(;;) { }
|
||||
}
|
||||
|
||||
/* Main context only. Legacy Timer2 EEPROM driver must be idle/stopped. Save
|
||||
* its SPI configuration: it changes the character width during transactions. */
|
||||
typedef struct { unsigned int ccr,ctl,brr,tx,rx,ct,pri; } boot_spi_saved;
|
||||
static void spi_cs(unsigned int high)
|
||||
{ if(high) GpioDataRegs.GPASET.bit.GPIO19=1; else GpioDataRegs.GPACLEAR.bit.GPIO19=1; }
|
||||
/* Доступ разрешён только из main при остановленном Timer2 старого SPI-драйвера.
|
||||
* Это проверка занятости в однопоточном приложении, не универсальный mutex.
|
||||
* Сохраняем режим SPI, поскольку штатный драйвер меняет разрядность передачи.
|
||||
* CS сначала поднимаем, затем переключаем SPI на 8-битные транзакции. */
|
||||
static bool spi_take(boot_spi_saved *s)
|
||||
{
|
||||
volatile unsigned int discard;
|
||||
if(!CpuTimer2Regs.TCR.bit.TSS) return false;
|
||||
s->ccr=SpiaRegs.SPICCR.all; s->ctl=SpiaRegs.SPICTL.all; s->brr=SpiaRegs.SPIBRR;
|
||||
s->tx=SpiaRegs.SPIFFTX.all; s->rx=SpiaRegs.SPIFFRX.all;
|
||||
s->ct=SpiaRegs.SPIFFCT.all; s->pri=SpiaRegs.SPIPRI.all;
|
||||
EALLOW;
|
||||
GpioCtrlRegs.GPAMUX2.bit.GPIO16=1; GpioCtrlRegs.GPAMUX2.bit.GPIO17=1;
|
||||
GpioCtrlRegs.GPAMUX2.bit.GPIO18=1; GpioCtrlRegs.GPAMUX2.bit.GPIO19=0;
|
||||
GpioDataRegs.GPASET.bit.GPIO19=1; GpioCtrlRegs.GPADIR.bit.GPIO19=1;
|
||||
GpioCtrlRegs.GPAQSEL2.bit.GPIO17=3;
|
||||
EDIS;
|
||||
SpiaRegs.SPICCR.all=7; SpiaRegs.SPICTL.all=0x0E; /* master, phase=1, IRQ off */
|
||||
SpiaRegs.SPIBRR=CLKMULT*6+1;
|
||||
SpiaRegs.SPIFFTX.all=0x8000; SpiaRegs.SPIFFRX.all=0; SpiaRegs.SPIFFCT.all=0;
|
||||
SpiaRegs.SPIPRI.all=0x10; SpiaRegs.SPICCR.all=0x87;
|
||||
/* Снимаем оставшийся RX/INT_FLAG от прежней операции, чтобы первый обмен
|
||||
* не принял старое содержимое RXBUF за ответ EEPROM. */
|
||||
discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||||
return true;
|
||||
}
|
||||
/* Выход и при успехе, и при ошибке: CS не должен остаться активным.
|
||||
* Настройки возвращаются при удержании SPI в reset; исходный SPISWRESET
|
||||
* восстанавливается последним, после остальных регистров. */
|
||||
static void spi_release(const boot_spi_saved *s)
|
||||
{
|
||||
volatile unsigned int discard;
|
||||
spi_cs(1); discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||||
SpiaRegs.SPICCR.all=s->ccr&0x7FU; SpiaRegs.SPICTL.all=s->ctl;
|
||||
SpiaRegs.SPIBRR=s->brr; SpiaRegs.SPIFFTX.all=s->tx; SpiaRegs.SPIFFRX.all=s->rx;
|
||||
SpiaRegs.SPIFFCT.all=s->ct; SpiaRegs.SPIPRI.all=s->pri; SpiaRegs.SPICCR.all=s->ccr;
|
||||
}
|
||||
/* У F28335 8-битный символ для передачи выравнивается в старшие биты TXBUF.
|
||||
* Полученный октет читается из младших битов RXBUF. Счётчик budget ограничивает
|
||||
* ожидание даже при неработающем Timer1; это число итераций, а не миллисекунды. */
|
||||
static bool spi_octet(unsigned int tx,unsigned int *rx)
|
||||
{
|
||||
uint32_t budget=100000UL;
|
||||
SpiaRegs.SPITXBUF=(tx&255U)<<8;
|
||||
while(!SpiaRegs.SPISTS.bit.INT_FLAG) if(--budget==0) return false;
|
||||
*rx=SpiaRegs.SPIRXBUF&255U; return true;
|
||||
}
|
||||
/* Команда EEPROM и 24-битный байтовый адрес, старший октет адреса первым.
|
||||
* CS удерживается вызывающей функцией на всю команду и полезные данные. */
|
||||
static bool spi_prefix(unsigned int command,uint32_t address)
|
||||
{
|
||||
unsigned int discard;
|
||||
return spi_octet(command,&discard) && spi_octet((unsigned int)(address>>16),&discard) &&
|
||||
spi_octet((unsigned int)(address>>8),&discard) && spi_octet((unsigned int)address,&discard);
|
||||
}
|
||||
/* RDSR (05h): WIP в бите 0 означает незавершённую внутреннюю запись EEPROM.
|
||||
* Ожидание ограничено и 50 мс, и числом опросов на случай остановки tick.
|
||||
* После каждого чтения статуса CS снимается, в том числе при ошибке SPI. */
|
||||
static bool spi_ready(void)
|
||||
{
|
||||
unsigned int status,discard; uint32_t started=F28335Boot_Now(),budget=20000UL;
|
||||
do {
|
||||
spi_cs(0);
|
||||
if(!spi_octet(5,&discard) || !spi_octet(255,&status)) { spi_cs(1); return false; }
|
||||
spi_cs(1);
|
||||
if(!(status&1U)) return true;
|
||||
} while(--budget && (uint32_t)(F28335Boot_Now()-started)<50UL);
|
||||
return false;
|
||||
}
|
||||
/* Читать разрешено всю физическую EEPROM, включая параметры. Проверка
|
||||
* через вычитание защищает от переполнения адреса. spi_release обязателен
|
||||
* после успешного захвата SPI независимо от результата последующего обмена. */
|
||||
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n)
|
||||
{
|
||||
boot_spi_saved saved; unsigned int i,value; bool ok; (void)u;
|
||||
if(address>0x40000UL || n>0x40000UL-address || !spi_take(&saved)) return false;
|
||||
ok=spi_ready();
|
||||
spi_cs(0); if(ok) ok=spi_prefix(3,address);
|
||||
for(i=0;ok && i<n;++i) { ok=spi_octet(255,&value); p[i]=value; }
|
||||
spi_release(&saved); return ok;
|
||||
}
|
||||
/* Нижний аппаратный барьер записи: разрешены журнал и слоты до 0x3FE00.
|
||||
* Резерв, параметры и последнее слово нельзя изменить даже ошибочным вызовом
|
||||
* этой функции. Защита активного слота дополнительно выполняется в store.c. */
|
||||
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n)
|
||||
{
|
||||
boot_spi_saved saved; unsigned int i,chunk,discard; bool ok; (void)u;
|
||||
/* This driver cannot overwrite parameters, even if a caller is defective. */
|
||||
if(address>0x3FE00UL || n>0x3FE00UL-address || !spi_take(&saved)) return false;
|
||||
ok=spi_ready();
|
||||
while(ok && n) {
|
||||
/* Нельзя пересекать страницу одной командой WRITE: EEPROM может завернуть
|
||||
* адрес внутри страницы. Для каждого фрагмента заново выдаём WREN (06h),
|
||||
* WRITE (02h) и ждём снятия WIP. Сравнение readback выполняет верхний слой. */
|
||||
chunk=256U-(unsigned int)(address&255U); if(chunk>n) chunk=n;
|
||||
spi_cs(0); ok=spi_octet(6,&discard); spi_cs(1); /* WREN */
|
||||
spi_cs(0); if(ok) ok=spi_prefix(2,address);
|
||||
for(i=0;ok && i<chunk;++i) ok=spi_octet(p[i],&discard);
|
||||
spi_cs(1); if(ok) ok=spi_ready();
|
||||
n-=chunk; p+=chunk; address+=chunk;
|
||||
}
|
||||
spi_release(&saved); return ok;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef F28335_BOOT_PLATFORM_H
|
||||
#define F28335_BOOT_PLATFORM_H
|
||||
#include "f28335_boot_store.h"
|
||||
#include "f28335_flash_app.h" /* Общий handoff различает EEPROM A/B, защищённое Flash-приложение и неизвестный запуск. */
|
||||
#ifndef F28335_BOOT_LOADER
|
||||
#define F28335_BOOT_LOADER 0
|
||||
#endif
|
||||
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n);
|
||||
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n);
|
||||
uint32_t F28335Boot_Now(void);
|
||||
void F28335Boot_Reset(unsigned int session);
|
||||
unsigned int F28335Boot_RunningSlot(void);
|
||||
unsigned int F28335Boot_RequestedSession(void);
|
||||
void F28335Boot_SetRunning(unsigned int slot);
|
||||
void F28335Boot_StartupWatchdog(void);
|
||||
/* Application hook must stop outputs before reset. No EEPROM operations here. */
|
||||
void F28335Boot_Quiesce(void);
|
||||
#endif
|
||||
@@ -0,0 +1,254 @@
|
||||
/* Хранилище A/B без зависимости от регистров F28335.
|
||||
* Здесь находятся правила сохранения образа при сбое питания и выбора слота.
|
||||
* CAN отвечает за порядок команд; этот слой повторно защищает active/pending
|
||||
* и границы EEPROM, даже если вызывающий код передал неверные параметры.
|
||||
* Адрес EEPROM измеряется в октетах, адрес назначения SCI8 — в словах CPU. */
|
||||
#include "f28335_boot_store.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Формат журнала фиксирован: u32 little endian. Нельзя сохранять C-структуру
|
||||
* через memcpy: выравнивание и sizeof на C28x отличаются от обычного ПК. */
|
||||
static uint32_t bs_get32(const pcan_boot_byte_t *p)
|
||||
{ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); }
|
||||
static void bs_put32(pcan_boot_byte_t *p, uint32_t v)
|
||||
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(8*i))&255U); }
|
||||
/* Потоковый CRC32: первый вызов получает crc=0, следующие — предыдущий результат.
|
||||
* Внутренняя инверсия позволяет считать один CRC по нескольким порциям EEPROM.
|
||||
* Маска 255 исключает старшие биты 16-битного представления октета на C28x.
|
||||
* Это проверка повреждения данных, а не криптографическая подпись. */
|
||||
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n)
|
||||
{ unsigned int i,b; crc=~crc; for(i=0;i<n;++i) { crc^=p[i]&255U;
|
||||
for(b=0;b<8;++b) crc=(crc>>1)^((crc&1U)?0xEDB88320UL:0); } return ~crc; }
|
||||
static uint32_t bs_base(unsigned int slot) { return slot?F28335_AB_SLOT_B:F28335_AB_SLOT_A; }
|
||||
/* Принимаем запись только при совпадении magic, версии, marker и CRC.
|
||||
* Дополнительно проверяем смысл номеров слотов: pending не может быть active.
|
||||
* Наличие правильного marker без правильного CRC не разрешает запуск. */
|
||||
static bool bs_record_valid(const pcan_boot_byte_t *p)
|
||||
{
|
||||
return bs_get32(p)==0x32424146UL && bs_get32(p+4)==1 &&
|
||||
bs_get32(p+60)==0x51AFC03DUL && bs_get32(p+56)==f28335_boot_crc(0,p,56) &&
|
||||
(p[12]<2 || p[12]==F28335_AB_NONE) &&
|
||||
(p[13]<2 || p[13]==F28335_AB_NONE) && p[14]<=1 &&
|
||||
(p[13]==F28335_AB_NONE || p[13]!=p[12]);
|
||||
}
|
||||
/* Карта записи, смещения в октетах:
|
||||
* 0..3 magic FAB2; 4..7 версия; 8..11 sequence; 12 active; 13 pending;
|
||||
* 14 attempted; 15 резерв; 16..35 manifest A; 36..55 manifest B;
|
||||
* 56..59 CRC первых 56 октетов; 60..63 marker завершённой публикации.
|
||||
* Manifest: size, CRC, FW (по u32), Product (u16), HW min/max, 4 октета резерва. */
|
||||
static void bs_decode(f28335_boot_store_t *s, const pcan_boot_byte_t *p)
|
||||
{
|
||||
unsigned int i; s->sequence=bs_get32(p+8); s->active=p[12];
|
||||
s->pending=p[13]; s->attempted=p[14];
|
||||
for(i=0;i<2;++i) {
|
||||
const pcan_boot_byte_t *q=p+16+i*20;
|
||||
s->image[i].image_size=bs_get32(q); s->image[i].image_crc32=bs_get32(q+4);
|
||||
s->image[i].firmware_version=bs_get32(q+8);
|
||||
s->image[i].product_type=(uint16_t)(q[12]|(q[13]<<8));
|
||||
s->image[i].hardware_revision_min=q[14]; s->image[i].hardware_revision_max=q[15];
|
||||
}
|
||||
}
|
||||
/* Читаем обе копии журнала. Нераспознанная разметка допускает CAN recovery
|
||||
* с active=NONE; ошибка чтения запрещает запись, поскольку реальный active неизвестен.
|
||||
* Эта функция не преобразует старый SCI8 по адресу 0 в новую A/B-разметку. */
|
||||
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io)
|
||||
{
|
||||
pcan_boot_byte_t a[64],b[64]; bool va,vb;
|
||||
if(!s || !io || !io->read || !io->write) return false;
|
||||
memset(s,0,sizeof(*s)); s->io=*io; s->active=s->pending=F28335_AB_NONE;
|
||||
if(!io->read(io->user,0,a,64) || !io->read(io->user,256,b,64)) { s->io_fault=true; return false; }
|
||||
va=bs_record_valid(a); vb=bs_record_valid(b);
|
||||
if(!va && !vb) return true;
|
||||
/* Сравнение sequence по модулю 2^32: разность менее половины диапазона
|
||||
* считается более новой записью. При равенстве выбирается страница 1.
|
||||
* Соседние публикации отличаются на единицу, в том числе после переполнения. */
|
||||
s->page=vb && (!va || (uint32_t)(bs_get32(b+8)-bs_get32(a+8))<0x80000000UL)?1:0;
|
||||
bs_decode(s,s->page?b:a); s->valid=true; return true;
|
||||
}
|
||||
/* Публикация выполняется в другую страницу, текущую действующую не трогаем.
|
||||
* Порядок принципиален: обнулить marker -> проверить -> записать тело ->
|
||||
* проверить тело -> записать marker последним -> проверить всю запись.
|
||||
* При обрыве питания загрузчик выбирает прежнюю либо полностью новую запись.
|
||||
* Каждый memcmp учитывает sizeof элемента, поскольку элемент здесь — октет
|
||||
* протокола, но на C28x занимает адресуемое 16-битное слово. */
|
||||
static bool bs_save(f28335_boot_store_t *s)
|
||||
{
|
||||
pcan_boot_byte_t p[64],check[64],invalid[4]={0,0,0,0}; unsigned int i;
|
||||
unsigned int page=s->valid?(s->page^1U):0;
|
||||
uint32_t addr=(uint32_t)page*256;
|
||||
memset(p,0,sizeof(p)); bs_put32(p,0x32424146UL); bs_put32(p+4,1);
|
||||
bs_put32(p+8,s->sequence+1); p[12]=s->active; p[13]=s->pending; p[14]=s->attempted;
|
||||
for(i=0;i<2;++i) {
|
||||
pcan_boot_byte_t *q=p+16+i*20;
|
||||
bs_put32(q,s->image[i].image_size); bs_put32(q+4,s->image[i].image_crc32);
|
||||
bs_put32(q+8,s->image[i].firmware_version);
|
||||
q[12]=s->image[i].product_type&255U; q[13]=(s->image[i].product_type>>8)&255U;
|
||||
q[14]=s->image[i].hardware_revision_min; q[15]=s->image[i].hardware_revision_max;
|
||||
}
|
||||
bs_put32(p+56,f28335_boot_crc(0,p,56)); bs_put32(p+60,0x51AFC03DUL);
|
||||
/* The old page is never erased. A torn write cannot publish a partial record. */
|
||||
if(!s->io.write(s->io.user,addr+60,invalid,4) ||
|
||||
!s->io.read(s->io.user,addr+60,check,4) || memcmp(check,invalid,sizeof(invalid)) ||
|
||||
!s->io.write(s->io.user,addr,p,60) ||
|
||||
!s->io.read(s->io.user,addr,check,60) || memcmp(p,check,60*sizeof(*p)) ||
|
||||
!s->io.write(s->io.user,addr+60,p+60,4) ||
|
||||
!s->io.read(s->io.user,addr,check,64) || memcmp(p,check,sizeof(p))) return false;
|
||||
s->page=page; s->sequence++; s->valid=true; return true;
|
||||
}
|
||||
/* Изменения сначала готовятся в копии состояния next. Рабочее состояние
|
||||
* заменяем только после публикации. Если запись/контрольное чтение сорвались,
|
||||
* перечитываем журнал: marker мог успеть записаться до ошибки readback.
|
||||
* После неудачи чтения io_fault блокирует дальнейшие изменения хранилища. */
|
||||
static bool bs_publish(f28335_boot_store_t *s, f28335_boot_store_t *next)
|
||||
{
|
||||
if(bs_save(next)) { *s=*next; return true; }
|
||||
/* A failed final read may still have committed: reload before any more writes. */
|
||||
{ f28335_boot_io_t io=s->io; if(!f28335_boot_open(s,&io)) {
|
||||
s->valid=false; s->io_fault=true; return false; } }
|
||||
return false;
|
||||
}
|
||||
/* Логический ERASE: обнуляем manifest неактивного слота в журнале.
|
||||
* Саму EEPROM стирать целиком не требуется — каждый байт будет перезаписан.
|
||||
* Active защищён всегда; наличие pending запрещает начало новой записи,
|
||||
* пока кандидат не подтверждён либо не снят при следующем выборе загрузки. */
|
||||
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE) return false;
|
||||
memset(&next.image[slot],0,sizeof(next.image[slot]));
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Записываем не более одного CAN-блока и немедленно сравниваем прочитанное.
|
||||
* Проверка n <= capacity-off выполняется после off <= capacity: это исключает
|
||||
* переполнение суммы off+n и выход из слота в соседний слот или параметры. */
|
||||
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t off,
|
||||
const pcan_boot_byte_t *p, unsigned int n)
|
||||
{
|
||||
pcan_boot_byte_t check[8];
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE || !n || n>8 ||
|
||||
off>F28335_AB_SLOT_BYTES || n>F28335_AB_SLOT_BYTES-off) return false;
|
||||
return s->io.write(s->io.user,bs_base(slot)+off,p,n) &&
|
||||
s->io.read(s->io.user,bs_base(slot)+off,check,n) && !memcmp(check,p,n*sizeof(*p));
|
||||
}
|
||||
/* Слово SCI8 занимает два октета файла, младший октет идёт первым.
|
||||
* Смещение увеличиваем только после успешного чтения; усечённый файл отклоняем. */
|
||||
static bool bs_u16(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint16_t *v)
|
||||
{
|
||||
pcan_boot_byte_t p[2]; if(*off>size || size-*off<2 ||
|
||||
!s->io.read(s->io.user,base+*off,p,2)) return false;
|
||||
*v=(uint16_t)(p[0]|(p[1]<<8)); *off+=2; return true;
|
||||
}
|
||||
/* Особенность таблицы TI: адрес u32 передаётся старшим словом вперёд,
|
||||
* при этом внутри каждого слова остаётся little endian. Не заменять на get32. */
|
||||
static bool bs_addr(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint32_t *v)
|
||||
{
|
||||
uint16_t hi,lo; if(!bs_u16(s,base,size,off,&hi) || !bs_u16(s,base,size,off,&lo)) return false;
|
||||
*v=((uint32_t)hi<<16)|lo; return true;
|
||||
}
|
||||
/* The complete table is validated before any application RAM is written.
|
||||
* Loader RAM is E000..FFFF and M0/M1. Flash, peripherals and XINTF are forbidden. */
|
||||
/* writer=NULL: только разбираем и проверяем таблицу, RAM приложения не меняем.
|
||||
* writer задан: повторно проверяем таблицу и переносим слова в разрешённую RAM.
|
||||
* Массивы starts/ends ограничены MAX_SECTIONS; они нужны для обнаружения
|
||||
* пересекающихся секций, в том числе секций с разным порядком адресов. */
|
||||
static bool bs_table(f28335_boot_store_t *s,unsigned int slot,uint32_t size,
|
||||
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
|
||||
{
|
||||
uint32_t starts[F28335_AB_MAX_SECTIONS],ends[F28335_AB_MAX_SECTIONS];
|
||||
uint32_t off=0,addr,end,base=bs_base(slot); unsigned int sections=0,i;
|
||||
uint16_t count,word; bool entry_loaded=false;
|
||||
if(!bs_u16(s,base,size,&off,&word) || word!=0x08AA) return false;
|
||||
/* После ключа AA 08 пропускаем 16 резервных октетов; далее расположен entry.
|
||||
* Нулевой count завершает таблицу строго на конце файла, без хвостовых данных. */
|
||||
off=18; if(!bs_addr(s,base,size,&off,entry)) return false;
|
||||
for(;;) {
|
||||
if(!bs_u16(s,base,size,&off,&count)) return false;
|
||||
if(!count) return off==size && entry_loaded;
|
||||
if(sections==F28335_AB_MAX_SECTIONS || !bs_addr(s,base,size,&off,&addr)) return false;
|
||||
end=addr+count;
|
||||
/* Разрешён только полуинтервал RAM [0x8000,0xE000) в словах CPU.
|
||||
* Верхняя граница защищает RAM работающего загрузчика; нижняя — стек,
|
||||
* handoff и регистры. Проверка размера payload использует уже октеты файла. */
|
||||
if(addr<0x8000UL || end>0xE000UL || end<=addr ||
|
||||
off>size || (uint32_t)count*2>size-off) return false;
|
||||
for(i=0;i<sections;++i) if(addr<ends[i] && end>starts[i]) return false;
|
||||
starts[sections]=addr; ends[sections++]=end;
|
||||
if(*entry>=addr && *entry<end) entry_loaded=true;
|
||||
if(writer) { for(i=0;i<count;++i) {
|
||||
if(!bs_u16(s,base,size,&off,&word) || !writer(user,addr+i,word)) return false;
|
||||
} } else off+=(uint32_t)count*2;
|
||||
}
|
||||
}
|
||||
/* VERIFY проверяет содержимое самой EEPROM, а не только CRC принятого потока.
|
||||
* Затем проверяется структура SCI8 и попадание entry в загружаемую секцию.
|
||||
* Выходной entry необязателен: COMMIT нужна проверка, но не адрес перехода. */
|
||||
bool f28335_boot_verify(f28335_boot_store_t *s,unsigned int slot,
|
||||
const pcan_boot_manifest_t *m,uint32_t *entry)
|
||||
{
|
||||
uint32_t off,crc=0,local_entry; unsigned int n; pcan_boot_byte_t p[32];
|
||||
if(slot>1 || m->image_size<24 || m->image_size>F28335_AB_SLOT_BYTES ||
|
||||
(m->image_size&1) || m->product_type!=0x2833 ||
|
||||
m->hardware_revision_min>1 || m->hardware_revision_max<1) return false;
|
||||
for(off=0;off<m->image_size;off+=n) {
|
||||
n=(m->image_size-off<32)?(unsigned int)(m->image_size-off):32;
|
||||
if(!s->io.read(s->io.user,bs_base(slot)+off,p,n)) return false;
|
||||
crc=f28335_boot_crc(crc,p,n);
|
||||
}
|
||||
if(crc!=m->image_crc32 || !bs_table(s,slot,m->image_size,0,0,&local_entry)) return false;
|
||||
if(entry) *entry=local_entry;
|
||||
return true;
|
||||
}
|
||||
/* COMMIT публикует проверенный образ как pending, не меняя active.
|
||||
* Повторная проверка EEPROM непосредственно перед публикацией не позволяет
|
||||
* назначить кандидатом образ, испорченный после предыдущего VERIFY. */
|
||||
bool f28335_boot_commit(f28335_boot_store_t *s,unsigned int slot,const pcan_boot_manifest_t *m)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE ||
|
||||
!f28335_boot_verify(s,slot,m,0)) return false;
|
||||
next.image[slot]=*m; next.pending=slot; next.attempted=0;
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Подтверждать можно только действительно выбранный для пробного запуска слот.
|
||||
* Требование attempted исключает подтверждение ещё ни разу не запускавшегося
|
||||
* кандидата. Повтор CONFIRM уже активного слота возвращает успех без записи. */
|
||||
bool f28335_boot_confirm(f28335_boot_store_t *s,unsigned int running_slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || running_slot>1) return false;
|
||||
if(s->pending==F28335_AB_NONE) return s->valid && s->active==running_slot;
|
||||
if(s->pending!=running_slot || !s->attempted) return false;
|
||||
next.active=running_slot; next.pending=F28335_AB_NONE; next.attempted=0;
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Выбор выполняется при старте загрузчика. attempted сохраняется ДО перехода
|
||||
* в приложение: после зависания, watchdog reset или потери питания следующий
|
||||
* старт уже знает о незавершённой пробе и возвращает прежний active.
|
||||
* Успех функции с slot=NONE означает recovery, а не разрешение перехода в RAM. */
|
||||
bool f28335_boot_select(f28335_boot_store_t *s,unsigned int *slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s; *slot=F28335_AB_NONE;
|
||||
if(s->io_fault) return false;
|
||||
if(s->pending!=F28335_AB_NONE) {
|
||||
if(!s->attempted && f28335_boot_verify(s,s->pending,&s->image[s->pending],0)) {
|
||||
next.attempted=1;
|
||||
if(!bs_publish(s,&next)) return false;
|
||||
*slot=s->pending; return true;
|
||||
}
|
||||
/* Кандидат уже запускался без подтверждения либо не прошёл проверку.
|
||||
* Снимаем pending отдельной публикацией; подтверждённый active остаётся прежним. */
|
||||
next.pending=F28335_AB_NONE; next.attempted=0;
|
||||
if(!bs_publish(s,&next)) return false;
|
||||
}
|
||||
if(s->active<2 && f28335_boot_verify(s,s->active,&s->image[s->active],0)) *slot=s->active;
|
||||
return true;
|
||||
}
|
||||
/* Сначала полная проверка CRC и всех адресов без записи, затем перенос слов.
|
||||
* Платформа вызывает entry только при true; частичная загрузка при ошибке I/O
|
||||
* не даёт разрешения на запуск приложения. */
|
||||
bool f28335_boot_load(f28335_boot_store_t *s,unsigned int slot,
|
||||
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
|
||||
{
|
||||
return slot<2 && writer && entry && f28335_boot_verify(s,slot,&s->image[slot],entry) &&
|
||||
bs_table(s,slot,s->image[slot].image_size,writer,user,entry);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef F28335_BOOT_STORE_H
|
||||
#define F28335_BOOT_STORE_H
|
||||
#include "../../include/pcan_boot.h"
|
||||
|
||||
/* All EEPROM offsets/lengths below are OCTETS, CPU destinations are WORDS. */
|
||||
/* Физическая EEPROM 256 КиБ: две страницы журнала по 256 октетов,
|
||||
* два слота по 130560 октетов. С 0x3FE00 начинается резерв, с 0x3FEFC —
|
||||
* прежняя область параметров. Константы ниже не являются CPU word-адресами. */
|
||||
#define F28335_AB_SLOT_BYTES 0x1FE00UL
|
||||
#define F28335_AB_SLOT_A 0x00200UL
|
||||
#define F28335_AB_SLOT_B 0x20000UL
|
||||
#define F28335_AB_PARAMS 0x3FEFCUL
|
||||
#define F28335_AB_NONE 0xFFU
|
||||
#define F28335_AB_RECORD 64U
|
||||
#define F28335_AB_MAX_SECTIONS 64U
|
||||
|
||||
/* Контракт I/O: address и n измеряются в октетах, независимо от sizeof(char).
|
||||
* true означает, что выполнена вся операция; короткий обмен должен вернуть false.
|
||||
* Callbacks вызываются синхронно из main, а user передаётся им без изменений. */
|
||||
typedef struct {
|
||||
bool (*read)(void *, uint32_t, pcan_boot_byte_t *, unsigned int);
|
||||
bool (*write)(void *, uint32_t, const pcan_boot_byte_t *, unsigned int);
|
||||
void *user;
|
||||
} f28335_boot_io_t;
|
||||
typedef struct {
|
||||
f28335_boot_io_t io;
|
||||
/* sequence — номер последней публикации; page — выбранная копия журнала.
|
||||
* active — подтверждённый слот; pending — кандидат; attempted — уже была проба.
|
||||
* valid показывает наличие распознанной записи, io_fault запрещает изменения
|
||||
* после ошибки чтения. Отсутствующий слот обозначается NONE, а не нулём. */
|
||||
uint32_t sequence;
|
||||
unsigned int page, active, pending, attempted;
|
||||
bool valid, io_fault;
|
||||
pcan_boot_manifest_t image[2];
|
||||
} f28335_boot_store_t;
|
||||
|
||||
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n);
|
||||
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io);
|
||||
/* Blank/unrecognised EEPROM opens successfully with active=NONE. I/O errors fail. */
|
||||
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot);
|
||||
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t offset,
|
||||
const pcan_boot_byte_t *data, unsigned int n);
|
||||
bool f28335_boot_verify(f28335_boot_store_t *s, unsigned int slot,
|
||||
const pcan_boot_manifest_t *m, uint32_t *entry);
|
||||
bool f28335_boot_commit(f28335_boot_store_t *s, unsigned int slot,
|
||||
const pcan_boot_manifest_t *m);
|
||||
bool f28335_boot_confirm(f28335_boot_store_t *s, unsigned int running_slot);
|
||||
/* Records a trial BEFORE returning it; a second unconfirmed boot rolls back. */
|
||||
bool f28335_boot_select(f28335_boot_store_t *s, unsigned int *slot);
|
||||
/* Запись одного проверенного слова приложения по CPU word-адресу.
|
||||
* Должна вернуть false при невозможности записи; после отказа запуск запрещён. */
|
||||
typedef bool (*f28335_boot_ram_writer)(void *, uint32_t, uint16_t);
|
||||
bool f28335_boot_load(f28335_boot_store_t *s, unsigned int slot,
|
||||
f28335_boot_ram_writer write_word, void *user, uint32_t *entry);
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "f28335_can_application_config.h" /* Получает от проекта разрешение CAN A/B и версию приложения; этот конфигурационный заголовок должен находиться в include paths CCS. */
|
||||
#ifndef F28335_CAN_BOOT_APPLICATION_ENABLED /* Отсутствующее аппаратное решение нельзя трактовать как разрешение записи служебных областей EEPROM. */
|
||||
#error "Define F28335_CAN_BOOT_APPLICATION_ENABLED in f28335_can_application_config.h" /* Останавливает сборку до появления приложения с неполной интеграцией загрузчика. */
|
||||
#endif /* Завершает проверку обязательного конфигурационного признака проекта. */
|
||||
#if F28335_CAN_BOOT_APPLICATION_ENABLED /* Самостоятельный Flash-образ и платы без M95M02 исключают весь CAN A/B-код на этапе компиляции. */
|
||||
#include "../../src/pcan_boot.c" /* Компилирует единственный автомат команд ProtoCAN Boot с его штатными сессиями, CRC32 и проверкой последовательности пакетов. */
|
||||
#include "f28335_boot_store.c" /* Подключает общий A/B-журнал, readback, проверку SCI8 и выбор подтверждённого слота той же версии templates. */
|
||||
#include "f28335_boot_hw.c" /* Предоставляет SPI EEPROM, watchdog и согласованный handoff между приложением и постоянным Flash-загрузчиком. */
|
||||
#include "f28335_can_boot.c" /* В режиме приложения обслуживает вход в boot и READY/CONFIRM, сохраняя служебные mailbox 3/4/6 отдельно от технологического CAN. */
|
||||
#endif /* Постоянный загрузчик собирается отдельным CCS-проектом templates с F28335_BOOT_LOADER=1, а не этим прикладным модулем. */
|
||||
@@ -0,0 +1,284 @@
|
||||
/* Separate Flash recovery loader and application service adapter. */
|
||||
#include "DSP2833x_Device.h"
|
||||
#include "f28335_can_boot.h"
|
||||
#if EEPROM_M95M02
|
||||
#include "f28335_boot_platform.h"
|
||||
#include <string.h>
|
||||
/* boot хранит текущую CAN-сессию в RAM, store — прочитанное состояние журнала.
|
||||
* Сброс сессии не удаляет active/pending из EEPROM. */
|
||||
static pcan_boot_t boot;
|
||||
static f28335_boot_store_t store;
|
||||
/* Кольцевая очередь: ISR производит кадры, main потребляет. Из 32 элементов
|
||||
* используется 31, чтобы head==tail однозначно означало пустую очередь.
|
||||
* Долгие операции SPI запрещены внутри обработчика CAN. */
|
||||
static volatile unsigned int head,tail,overflow;
|
||||
static struct { uint32_t id; unsigned int dlc; pcan_boot_byte_t data[8]; } queue[32];
|
||||
/* reset_requested только планирует reset; Service ждёт завершения TX.
|
||||
* ready сообщает об окончании старта приложения, confirmed — о сохранённом
|
||||
* подтверждении слота. healthy отражает результат открытия журнала при Init. */
|
||||
static unsigned int reset_requested,reset_session,ready,healthy,confirmed;
|
||||
static uint32_t sent_at,ready_at;
|
||||
static unsigned int main_passes;
|
||||
static unsigned int addressed; /* Разрешает обслуживание только после проверки номера PERIPH и установки согласованных фильтров CAN. */
|
||||
static int tx_busy,tx_canceling;
|
||||
/* Один исходящий mailbox исключает перезапись ещё передаваемого ответа.
|
||||
* MDL содержит первые четыре октета, MDH — следующие четыре, младшим вперёд.
|
||||
* Запрос CANTRS ещё не означает успешную доставку: CANTA проверяется в Service.
|
||||
* Аппаратный CAN ACK не является отдельным прикладным подтверждением от ПК. */
|
||||
static bool transmit(void *u, uint32_t id, const pcan_boot_byte_t *d, pcan_boot_byte_t n)
|
||||
{
|
||||
unsigned int i; unsigned long lo=0, hi=0; (void)u;
|
||||
if (tx_busy) return false;
|
||||
for(i=0;i<n;++i) {
|
||||
if(i<4) lo|=(unsigned long)(d[i]&255U)<<(8U*i);
|
||||
else hi|=(unsigned long)(d[i]&255U)<<(8U*(i-4U));
|
||||
}
|
||||
EALLOW;
|
||||
ECanaRegs.CANME.all &= ~16UL;
|
||||
ECanaMboxes.MBOX4.MSGID.all=0x80000000UL|id;
|
||||
ECanaMboxes.MBOX4.MSGCTRL.all=n;
|
||||
ECanaMboxes.MBOX4.MDL.all=lo; ECanaMboxes.MBOX4.MDH.all=hi;
|
||||
ECanaRegs.CANME.all |=16UL;
|
||||
ECanaRegs.CANTA.all=16UL; ECanaRegs.CANAA.all=16UL;
|
||||
ECanaRegs.CANTRS.all=16UL;
|
||||
EDIS;
|
||||
sent_at=F28335Boot_Now(); tx_busy=1; tx_canceling=0; return true;
|
||||
}
|
||||
|
||||
/* Изменение образа разрешено только сборке Flash-загрузчика. В приложении
|
||||
* эти callbacks возвращают отказ, даже если им удастся передать команду записи.
|
||||
* ERASE для EEPROM означает инвалидирование manifest выбранного слота. */
|
||||
static bool erase(void *u,pcan_boot_byte_t slot)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_prepare(&store,slot); }
|
||||
static bool write_block(void *u,pcan_boot_byte_t slot,uint32_t offset,const pcan_boot_byte_t *d,pcan_boot_byte_t n)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_write(&store,slot,offset,d,n); }
|
||||
/* Проверка включает CRC EEPROM и структуру SCI8. Сейчас отказ этого слоя
|
||||
* кодируется CRC_ERROR как общий отказ проверки образа; причины внутри
|
||||
* парсера SCI8 отдельными статусами протокола не различаются. */
|
||||
static pcan_boot_status_t verify(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||||
{
|
||||
(void)u;
|
||||
if(!F28335_BOOT_LOADER || !healthy) return PCAN_BOOT_STATUS_INVALID_STATE;
|
||||
return f28335_boot_verify(&store,slot,m,0)?PCAN_BOOT_STATUS_OK:PCAN_BOOT_STATUS_CRC_ERROR;
|
||||
}
|
||||
static bool commit(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_commit(&store,slot,m); }
|
||||
/* Подтверждение принадлежит приложению после READY. Загрузчик не может
|
||||
* подтвердить кандидата сам: иначе исчезнет смысл проверки пробного запуска.
|
||||
* Разрешение кормления watchdog из ISR открываем только после записи журнала. */
|
||||
static bool confirm(void *u)
|
||||
{
|
||||
unsigned int running=F28335Boot_RunningSlot(); (void)u;
|
||||
if(F28335_BOOT_LOADER || !healthy || !ready || running>1) return false;
|
||||
if(!f28335_boot_confirm(&store,running)) return false;
|
||||
boot.config.active_slot=running; confirmed=1; return true;
|
||||
}
|
||||
static bool can_reboot(void *u) { (void)u; return healthy; }
|
||||
static void reboot(void *u) { (void)u; reset_session=0; reset_requested=1; }
|
||||
/* Контракт enter_boot: 0 — загрузчик готов и ядро может отправить ACK;
|
||||
* 1 — переход отложен, ACK придёт после reset; 2 — вход запрещён.
|
||||
* Приложение требует handoff нашего загрузчика и подтверждённый текущий слот,
|
||||
* чтобы не начать обновление из старого SPI-ROM старта или незавершённой пробы. */
|
||||
static unsigned int enter(void *u,pcan_boot_byte_t session)
|
||||
{
|
||||
(void)u;
|
||||
if(!healthy) return 2;
|
||||
if(F28335_BOOT_LOADER) return 0;
|
||||
/* No destructive fallback on boards still starting through the SPI ROM. */
|
||||
if((F28335Boot_RunningSlot()>1 && F28335Boot_RunningSlot()!=F28335_BOOT_FLASH_SLOT) || !confirmed) return 2; /* Только handoff общего загрузчика разрешает переход из EEPROM либо защищённой внутренней Flash; старый SPI-ROM старт остаётся запрещён. */
|
||||
reset_session=session; reset_requested=1; return 1;
|
||||
}
|
||||
static void info32(pcan_boot_byte_t *p,uint32_t v)
|
||||
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(i*8))&255U); }
|
||||
/* Паспорт помещается в classic CAN по страницам из восьми октетов.
|
||||
* NAME возвращает фрагменты строки с завершающим NUL; неизвестная страница
|
||||
* отклоняется. MEMORY: 0 — ёмкость/страница/режим/running, 1 — базы A/B,
|
||||
* 2 — active/pending/attempted/healthy/тайм-аут, 3 — область параметров. */
|
||||
static bool info(void *u,pcan_boot_byte_t command,pcan_boot_byte_t page,pcan_boot_byte_t *out)
|
||||
{
|
||||
const char *name=boot.config.device_type==1U && boot.config.device_id==8U?"Zadatchik":boot.config.device_type==1U && boot.config.device_id==5U?"UMP1":boot.config.device_type==1U && boot.config.device_id==6U?"UMP2":F28335_CAN_BOOT_DEVICE_NAME; /* Паспорт различает три назначенных Excel устройства; резервные номера сохраняют аппаратное имя. */
|
||||
unsigned int i,index,name_size=(unsigned int)strlen(name)+1U; (void)u; /* Длина включает завершающий NUL, чтобы последняя страница имени однозначно завершала чтение. */
|
||||
if(command==PCAN_BOOT_CMD_CAPABILITIES && page==0) {
|
||||
/* bits: ping, discovery, identity, name, memory, A/B, confirm, rollback,
|
||||
* separate loader, session timeout, readback, SCI8 validation. */
|
||||
info32(out,(F28335_BOOT_LOADER || F28335Boot_RunningSlot()<2 || F28335Boot_RunningSlot()==F28335_BOOT_FLASH_SLOT)?0xFFFUL:0x1FUL); /* Flash-приложение с достоверным handoff поддерживает вход в постоянный CAN Boot так же, как подтверждённый EEPROM-образ. */
|
||||
info32(out+4,F28335_AB_SLOT_BYTES); return true;
|
||||
}
|
||||
if(command==PCAN_BOOT_CMD_DEVICE_NAME) {
|
||||
if((unsigned int)page*8U>=name_size) return false; /* Страница после конца выбранного имени не является допустимой частью паспорта. */
|
||||
for(i=0;i<8;++i) { index=(unsigned int)page*8U+i;
|
||||
out[i]=index<name_size?(pcan_boot_byte_t)name[index]:0; } /* Дополняет последнюю восьмибайтовую страницу нулями без выхода за строку. */
|
||||
return true;
|
||||
}
|
||||
if(command!=PCAN_BOOT_CMD_MEMORY_INFO) return false;
|
||||
if(page==0) { info32(out,0x40000UL); out[4]=0; out[5]=1;
|
||||
out[6]=F28335_BOOT_LOADER?1:0; out[7]=F28335Boot_RunningSlot(); return true; }
|
||||
if(page==1) { info32(out,F28335_AB_SLOT_A); info32(out+4,F28335_AB_SLOT_B); return true; }
|
||||
if(page==2) { out[0]=store.active; out[1]=store.pending; out[2]=store.attempted;
|
||||
out[3]=healthy?1:0; info32(out+4,boot.config.session_timeout_ms); return true; }
|
||||
if(page==3) { info32(out,F28335_AB_PARAMS); info32(out+4,258); return true; }
|
||||
return false;
|
||||
}
|
||||
/* Вызывается до разрешения прикладных IRQ. Нельзя полагаться на случайное
|
||||
* содержимое RAM или оставшееся состояние confirmed от предыдущей программы. */
|
||||
void F28335CanBoot_EarlyInit(void) { confirmed=0; ready=0; }
|
||||
/* Общий Init для приложения и загрузчика: открываем журнал, привязываем
|
||||
* callbacks, задаём идентификацию, ёмкость слота, окно 16 и тайм-аут 30 секунд.
|
||||
* В загрузчике FW берётся из active manifest; приложение сообщает версию сборки. */
|
||||
void F28335CanBoot_Init(unsigned int periph_id) /* Один номер платы преобразуется одинаково в приложении и постоянном загрузчике. */
|
||||
{
|
||||
pcan_boot_config_t cfg; pcan_boot_port_t port;
|
||||
f28335_boot_io_t io; unsigned int running=F28335Boot_RunningSlot();
|
||||
addressed=0U; EALLOW; ECanaRegs.CANME.all&=~88UL; EDIS; /* Закрывает только Boot mailbox 3/4/6 до проверки адреса, включая повторный Init с ошибочным номером. */
|
||||
if(periph_id>=F28335_CAN_BOOT_PERIPH_COUNT) return; /* Номер вне Excel-диапазона 0–31 не подменяется общим адресом и не открывает запись другой платы. */
|
||||
memset(&cfg,0,sizeof(cfg)); memset(&port,0,sizeof(port));
|
||||
io.read=F28335Boot_Read; io.write=F28335Boot_Write; io.user=0;
|
||||
healthy=f28335_boot_open(&store,&io);
|
||||
cfg.device_type=1U+periph_id/16U; cfg.device_id=periph_id%16U; /* Excel: PERIPH 0–15 → Type 1, 16–31 → Type 2; младшие четыре бита задают Device ID. */
|
||||
cfg.product_type=F28335_CAN_BOOT_PRODUCT; cfg.hardware_revision=1;
|
||||
#if F28335_BOOT_LOADER
|
||||
cfg.firmware_version=store.active<2?store.image[store.active].firmware_version:0;
|
||||
#else
|
||||
cfg.firmware_version=F28335_CAN_BOOT_FIRMWARE_VERSION;
|
||||
#endif
|
||||
head=tail=overflow=reset_requested=reset_session=ready=main_passes=0; tx_busy=tx_canceling=0;
|
||||
confirmed=running==F28335_BOOT_FLASH_SLOT || (healthy && running<2 && store.active==running && store.pending==F28335_AB_NONE); /* Flash уже проверена CRC постоянным boot; пробное подтверждение требуется только EEPROM-кандидату. */
|
||||
cfg.active_slot=store.active;
|
||||
cfg.slot_count=2; cfg.max_image_size=F28335_AB_SLOT_BYTES; cfg.ack_window=16;
|
||||
cfg.session_timeout_ms=30000UL;
|
||||
port.send=transmit; port.erase_slot=erase; port.write_slot=write_block;
|
||||
port.verify_status=verify; port.set_pending_slot=commit; port.reboot=reboot;
|
||||
port.confirm_running_slot=confirm; port.enter_boot=enter;
|
||||
port.can_reboot=can_reboot; port.service_info=info;
|
||||
if(!pcan_boot_init(&boot,&cfg,&port,0)) return;
|
||||
addressed=1U; /* Дальнейшие ISR, основной цикл и handoff используют только успешно созданную адресную конфигурацию. */
|
||||
EALLOW;
|
||||
/* Меняем только MBOX3/4/6: маска 88=8+16+64. MBOX0..2 — старый обмен,
|
||||
* MBOX5 — heartbeat. Приём broadcast выделен в отдельный точный mailbox,
|
||||
* чтобы слишком широкая маска Boot не перехватывала сообщения приложения.
|
||||
* 72=8+64 — оба RX, 16 — TX. Регистры флагов очищаются записью единицы. */
|
||||
ECanaRegs.CANME.all &= ~88UL;
|
||||
ECanaMboxes.MBOX3.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,cfg.device_type,cfg.device_id,0,0);
|
||||
ECanaLAMRegs.LAM3.all=0x100FFFFFUL;
|
||||
ECanaMboxes.MBOX3.MSGCTRL.all=8;
|
||||
/* MBOX5 belongs to the application's heartbeat. MBOX6 is discovery only. */
|
||||
ECanaMboxes.MBOX6.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,7,15,13,0);
|
||||
ECanaLAMRegs.LAM6.all=0x10000000UL; /* Ignore priority only. */
|
||||
ECanaMboxes.MBOX6.MSGCTRL.all=0;
|
||||
ECanaRegs.CANMD.all=(ECanaRegs.CANMD.all|72UL)&~16UL;
|
||||
ECanaRegs.CANRMP.all=72UL;
|
||||
ECanaRegs.CANMIM.all |=72UL;
|
||||
ECanaRegs.CANME.all |=88UL;
|
||||
EDIS;
|
||||
}
|
||||
/* ISR только проверяет заголовок и копирует кадр. Чужие адреса и RTR
|
||||
* отбрасываются до очереди. Глобальный адрес допускается только для discovery;
|
||||
* DLC и смысл команды окончательно проверит ядро в основном цикле. */
|
||||
static void receive_box(volatile struct MBOX *box,uint32_t mask)
|
||||
{
|
||||
unsigned int next=(head+1U)&31U, i;
|
||||
unsigned long lo, hi;
|
||||
uint32_t id; unsigned int type;
|
||||
if(!(ECanaRegs.CANRMP.all&mask)) return;
|
||||
if(box->MSGCTRL.bit.RTR) { ECanaRegs.CANRMP.all=mask; return; }
|
||||
id=box->MSGID.all&0x1FFFFFFFUL;
|
||||
type=pcan_boot_id_msg_type(id);
|
||||
if(!(box->MSGID.all&0x80000000UL) || pcan_boot_id_route(id)!=0 ||
|
||||
(type!=9 && type!=10 && type!=11 && type!=13) ||
|
||||
!((pcan_boot_id_device_type(id)==boot.config.device_type && /* Сравнивает тип именно этой платы, а не прежнюю общую константу 3. */
|
||||
pcan_boot_id_device_id(id)==boot.config.device_id) || /* Отбрасывает команды соседних PERIPH ещё до очереди и любых операций EEPROM. */
|
||||
(type==13 && pcan_boot_id_device_type(id)==7 && pcan_boot_id_device_id(id)==15 &&
|
||||
pcan_boot_id_msg_body(id)==0))) { ECanaRegs.CANRMP.all=mask; return; }
|
||||
if(next==tail) overflow=1;
|
||||
else {
|
||||
queue[head].id=box->MSGID.all&0x1FFFFFFFUL;
|
||||
queue[head].dlc=box->MSGCTRL.bit.DLC;
|
||||
lo=box->MDL.all; hi=box->MDH.all;
|
||||
for(i=0;i<4;++i) { queue[head].data[i]=(lo>>(8U*i))&255U;
|
||||
queue[head].data[i+4]=(hi>>(8U*i))&255U; }
|
||||
head=next;
|
||||
}
|
||||
ECanaRegs.CANRMP.all=mask;
|
||||
}
|
||||
|
||||
void F28335CanBoot_RxISR(void)
|
||||
{
|
||||
if(!addressed) return; /* Неверный или ещё не настроенный номер PERIPH не должен принимать даже оставшиеся флаги RX. */
|
||||
receive_box(&ECanaMboxes.MBOX3,8UL);
|
||||
receive_box(&ECanaMboxes.MBOX6,64UL);
|
||||
}
|
||||
/* Вызывать после инициализации параметров, датчиков и прикладного режима.
|
||||
* От этого момента отсчитывается интервал наблюдения перед авто-CONFIRM. */
|
||||
void F28335CanBoot_Ready(void)
|
||||
{ ready=1; ready_at=F28335Boot_Now(); }
|
||||
/* Используется таймерными ISR приложения. Во время пробы кормление watchdog
|
||||
* должно доказывать выполнение main: работа одного ISR не доказывает готовность.
|
||||
* Старый старт без handoff сохраняет прежнюю политику watchdog. */
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void)
|
||||
{ return F28335Boot_RunningSlot()>1 || confirmed; }
|
||||
/* Неблокирующий диспетчер относительно ожидания CAN: вызывать в каждом проходе main.
|
||||
* Сами callbacks проверки/записи EEPROM синхронные. Порядок здесь важен:
|
||||
* проба приложения -> завершение TX -> reset -> poll -> ошибки RX -> один кадр. */
|
||||
unsigned int F28335CanBoot_Active(void) { return addressed && (boot.state!=PCAN_BOOT_STATE_IDLE || reset_requested); } /* Открытый ENTER_BOOT удерживает recovery; диагностический PING не меняет приоритет Flash и EEPROM. */
|
||||
void F28335CanBoot_Service(void)
|
||||
{
|
||||
uint32_t now=F28335Boot_Now();
|
||||
if(!addressed) return; /* Не обслуживает старую RAM-сессию после отказа повторной инициализации адреса. */
|
||||
if(!F28335_BOOT_LOADER && ready && !confirmed && healthy && F28335Boot_RunningSlot()<2) {
|
||||
/* A trial must keep executing main, not merely a timer ISR. */
|
||||
/* После 100 проходов main и 5 секунд пробуем сохранить CONFIRM.
|
||||
* Если до 10 секунд сохранение не удалось, main перестаёт кормить watchdog,
|
||||
* что позволяет следующему старту откатиться к подтверждённому образу. */
|
||||
if((uint32_t)(now-ready_at)<10000UL) ServiceDog();
|
||||
if(main_passes<100) ++main_passes;
|
||||
if(main_passes==100 && (uint32_t)(now-ready_at)>=5000UL) (void)confirm(0);
|
||||
}
|
||||
if(tx_busy) {
|
||||
/* После CANTRR ждём завершения отмены или запоздавшего ACK, прежде чем
|
||||
* повторно использовать mailbox. reset_requested к этому моменту уже снят,
|
||||
* поэтому позднее подтверждение не должно перезапустить устройство. */
|
||||
if(tx_canceling) {
|
||||
if(!((ECanaRegs.CANAA.all|ECanaRegs.CANTA.all)&16UL)) return;
|
||||
ECanaRegs.CANAA.all=16UL; ECanaRegs.CANTA.all=16UL;
|
||||
tx_busy=tx_canceling=0;
|
||||
} else if((uint32_t)(now-sent_at)>250UL) {
|
||||
/* Cancel reset immediately, even if a late CAN ACK races CANTRR. */
|
||||
reset_requested=0; pcan_boot_abort(&boot); tail=head;
|
||||
tx_canceling=1; ECanaRegs.CANTRR.all=16UL; return;
|
||||
} else if(ECanaRegs.CANTA.all&16UL) {
|
||||
ECanaRegs.CANTA.all=16UL; tx_busy=0;
|
||||
} else return;
|
||||
}
|
||||
if(reset_requested) { F28335Boot_Reset(reset_session); return; }
|
||||
pcan_boot_poll(&boot,now);
|
||||
if(tx_busy) return;
|
||||
/* После потери кадра нельзя молча продолжать запись: сбрасываем RAM-сессию
|
||||
* и накопленную очередь. Подтверждённый образ и опубликованный pending сохраняются. */
|
||||
if(overflow || (ECanaRegs.CANRML.all&72UL)) {
|
||||
overflow=0; ECanaRegs.CANRML.all=72UL;
|
||||
pcan_boot_abort(&boot); tail=head; return;
|
||||
}
|
||||
if(tail!=head) {
|
||||
pcan_boot_process(&boot,queue[tail].id,queue[tail].data,queue[tail].dlc);
|
||||
tail=(tail+1U)&31U;
|
||||
}
|
||||
}
|
||||
/* Используем session из NOINIT только для нового ENTER_BOOT после reset.
|
||||
* Это отправка отложенного ACK готовности, а не восстановление принятых блоков
|
||||
* или CRC незавершённой передачи: данные сессии через reset не сохраняются. */
|
||||
void F28335CanBoot_Resume(unsigned int session)
|
||||
{
|
||||
if(addressed && F28335_BOOT_LOADER && session && session<256) /* Восстанавливает только допустимую сессию адресованного загрузчика после reset. */
|
||||
(void)pcan_boot_process(&boot,pcan_boot_make_id(1,0, /* ACK относится к тому же физическому PERIPH, который принимал ENTER_BOOT в приложении. */
|
||||
boot.config.device_type,boot.config.device_id,9,(session<<8)|2),0,0); /* MsgType 9, команда 2 и исходный SessionID сохраняют протокол handoff. */
|
||||
}
|
||||
#else
|
||||
void F28335CanBoot_EarlyInit(void) {}
|
||||
void F28335CanBoot_Init(unsigned int periph_id) { (void)periph_id; } /* Сборка без M95M02 не занимает CAN-адрес и не открывает сервис записи. */
|
||||
void F28335CanBoot_RxISR(void) {}
|
||||
void F28335CanBoot_Service(void) {}
|
||||
unsigned int F28335CanBoot_Active(void) { return 0U; } /* Профиль без M95M02 не создаёт CAN-сеанс и не удерживает старт общего загрузчика. */
|
||||
void F28335CanBoot_Ready(void) {}
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void) { return 1; }
|
||||
void F28335CanBoot_Resume(unsigned int session) { (void)session; }
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef F28335_CAN_BOOT_H
|
||||
#define F28335_CAN_BOOT_H
|
||||
#include "../../include/pcan_boot.h"
|
||||
/* Приложение задаёт версию из firmware_version.h до подключения этого заголовка.
|
||||
* Формат поля FW: major[31:24], minor[23:16], patch[15:0]. Ноль — запасное
|
||||
* значение при отсутствии версии сборки, а не прошитая здесь версия устройства. */
|
||||
#ifndef F28335_CAN_BOOT_FIRMWARE_VERSION
|
||||
#define F28335_CAN_BOOT_FIRMWARE_VERSION 0UL /* Board supplies the build identity. */
|
||||
#endif
|
||||
/* Адрес ProtoCAN состоит из Device Type (3 бита) и Device ID (4 бита).
|
||||
* ProductType — отдельный код совместимости прошивки; это не CAN ID и не RS-адрес. */
|
||||
#define F28335_CAN_BOOT_PERIPH_COUNT 32U /* Лист «Интерфейс протокола», строки 45–76: PERIPH 0–31 занимают два типа по 16 экземпляров. */
|
||||
#define F28335_CAN_BOOT_PRODUCT 0x2833U
|
||||
#define F28335_CAN_BOOT_DEVICE_NAME "F28335-M95M02"
|
||||
/* Порядок интеграции приложения:
|
||||
* StartupWatchdog/EarlyInit сразу после InitSysCtrl; Init после настройки
|
||||
* таймера и EEPROM; Ready после старта прикладных подсистем; Service в main.
|
||||
* RxISR вызывается из CAN ISR, WatchdogAllowed проверяется перед кормлением
|
||||
* watchdog таймерным ISR. Resume применяется только отдельным загрузчиком. */
|
||||
void F28335CanBoot_Init(unsigned int periph_id); /* Принимает физический номер PERIPH: 5 — УМП1, 6 — УМП2, 8 — задатчик; общий адрес для всех плат исключён. */
|
||||
void F28335CanBoot_EarlyInit(void);
|
||||
void F28335CanBoot_RxISR(void);
|
||||
void F28335CanBoot_Service(void);
|
||||
unsigned int F28335CanBoot_Active(void); /* Удерживает загрузчик в CAN recovery после ENTER_BOOT, не запуская приложение по окончании стартового окна. */
|
||||
void F28335CanBoot_Ready(void);
|
||||
void F28335CanBoot_Resume(unsigned int session);
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void);
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "f28335_flash_app.h" /* Использует ту же границу сектора A и формат описания, что сборщик и RS-программатор. */
|
||||
bool f28335_flash_app_validate(f28335_flash_read_word_t read_word, void *user, uint32_t *entry) /* Не обращается к EEPROM и подходит для постоянного загрузчика и проверки staging RAM. */
|
||||
{ /* Никакие адреса из непроверенного заголовка не используются для чтения до проверки границ. */
|
||||
uint16_t header[F28335_FLASH_APP_HEADER_WORDS], word, octet, bit, i; /* Малый локальный буфер хранит 16 слов описания FAB1. */
|
||||
uint32_t address, start, end, expected, crc = 0xFFFFFFFFUL, target, inverse; /* CRC32 IEEE начинается со всех единиц и инвертируется после последнего октета. */
|
||||
if (!read_word || !entry) return false; /* Отсутствующий источник или выходной указатель не должен вызывать обращение к памяти. */
|
||||
for (i = 0; i < F28335_FLASH_APP_HEADER_WORDS; ++i) header[i] = read_word(user, F28335_FLASH_APP_START + i); /* Читает только фиксированное описание в секторе H. */
|
||||
if (header[0] != 0x4146U || header[1] != 0x3142U || header[2] != 1U || header[3] != F28335_FLASH_APP_HEADER_WORDS || header[14] != 0x5AA5U || header[15] != 0xA55AU) return false; /* Физические октеты ASCII FAB1, версия 1, размер и два маркера отличают завершённый образ от стёртого либо старого Flash. */
|
||||
target = (uint32_t)header[4] | ((uint32_t)header[5] << 16); /* Точка входа хранится младшим 16-битным словом вперёд внутри описания. */
|
||||
start = (uint32_t)header[6] | ((uint32_t)header[7] << 16); /* Нижняя граница защищённого CRC диапазона должна начинаться сразу после описания. */
|
||||
end = (uint32_t)header[8] | ((uint32_t)header[9] << 16); /* Верхняя граница является исключённым адресом слова, а не длиной в байтах. */
|
||||
expected = (uint32_t)header[10] | ((uint32_t)header[11] << 16); /* Сохранённая CRC32 рассчитана по физическим октетам всех слов диапазона. */
|
||||
inverse = (uint32_t)header[12] | ((uint32_t)header[13] << 16); /* Инверсия адреса выявляет частичную публикацию управляющих полей. */
|
||||
if (start != F28335_FLASH_APP_DATA || end <= start || end > F28335_FLASH_APP_STOP || target < start || target >= end || inverse != ~target) return false; /* Запрещает RAM, сектор загрузчика, пустой код и случайные адреса из повреждённого описания. */
|
||||
for (address = start; address < end; ++address) { /* Проверяет также стёртые промежутки между LOAD-секциями, чтобы JTAG и RS давали одинаковое содержимое. */
|
||||
word = read_word(user, address); /* Получает одно 16-битное слово из Flash либо из модели staging SCI8. */
|
||||
for (i = 0; i < 2U; ++i) { /* Каждый адрес C28x содержит два физических октета: сначала младший, затем старший. */
|
||||
octet = (word >> (8U * i)) & 255U; /* Маска отделяет октет независимо от 16-битного char TI. */
|
||||
crc ^= octet; /* Добавляет очередной октет в отражённое состояние CRC32 IEEE. */
|
||||
for (bit = 0; bit < 8U; ++bit) crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1); /* 0xEDB88320 — отражённый полином IEEE 802.3, совпадающий с zlib.crc32. */
|
||||
} /* Завершает обработку обоих октетов текущего слова. */
|
||||
} /* Завершает чтение только внутри области приложения ниже сектора A. */
|
||||
if ((crc ^ 0xFFFFFFFFUL) != expected) return false; /* Повреждённое или частично записанное приложение остаётся недоступным для запуска. */
|
||||
*entry = target; /* Публикует точку старта только после подтверждения всех адресов и данных. */
|
||||
return true; /* Разрешает загрузчику запустить именно проверенную Flash-программу. */
|
||||
} /* Ошибка никогда не меняет EEPROM, Flash или сохранённый выходной адрес. */
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef F28335_FLASH_APP_H /* Защищает общий контракт Flash-приложения от повторного объявления. */
|
||||
#define F28335_FLASH_APP_H /* Один заголовок используется загрузчиком, RS-проверкой и host-тестами. */
|
||||
#include "../../include/pcan_boot.h" /* Получает переносимые 16- и 32-битные типы и bool для C28x и ПК. */
|
||||
#define F28335_FLASH_APP_START 0x300000UL /* Первые 16 слов сектора H содержат описание FAB1, а не аппаратный boot. */
|
||||
#define F28335_FLASH_APP_HEADER_WORDS 16U /* Размер описания равен 32 октетам при 16-битном адресуемом слове C28x. */
|
||||
#define F28335_FLASH_APP_DATA 0x300010UL /* Код и Flash LOAD-секции следуют после служебного описания. */
|
||||
#define F28335_FLASH_APP_STOP 0x338000UL /* Начало сектора A является исключённой границей приложения. */
|
||||
#define F28335_BOOT_FLASH_SLOT 0xFEU /* Отдельный handoff отличает внутреннюю Flash от EEPROM A/B и неизвестного SPI-ROM запуска. */
|
||||
typedef uint16_t (*f28335_flash_read_word_t)(void *user, uint32_t address); /* Callback читает одно физическое слово без предположения о памяти host-тестов. */
|
||||
bool f28335_flash_app_validate(f28335_flash_read_word_t read_word, void *user, uint32_t *entry); /* Проверяет описание, границы и CRC32 до передачи управления приложению. */
|
||||
#endif /* Завершает единый контракт защищённой области Flash-приложения. */
|
||||
@@ -0,0 +1,4 @@
|
||||
# Служебные и выходные файлы CCS остаются локальными; исходники проектов хранятся выше этих каталогов.
|
||||
ccs/*/Debug/
|
||||
# Release использует собственные объекты, метаданные и архивы, чтобы не смешивать конфигурации.
|
||||
ccs/*/Release/
|
||||
@@ -0,0 +1,207 @@
|
||||
# Отдельные проекты CCS: F28335 и F2812
|
||||
|
||||
Весь комплект находится внутри `templates/c/protocan-boot`: проекты CCS,
|
||||
исходники загрузчиков, аппаратные порты, linker command files, необходимая
|
||||
поддержка TI, CLI-сборка, тесты и документация. Зависимости от исходников
|
||||
`28335_periph` и `BALZAM_167` при сборке отсутствуют. Требуется установленный
|
||||
TI C2000 compiler с поддержкой **COFF ABI**; проверена версия **22.6.0.LTS**.
|
||||
|
||||
| Проект CCS | Контроллер | Обновляемая память | Схема |
|
||||
| --- | --- | --- | --- |
|
||||
| `ccs/f28335` — `F28335_CAN_AB` | TMS320F28335 | M95M02, 256 КиБ | A/B, trial, CONFIRM, rollback |
|
||||
| `ccs/f2812` — `F2812_CAN_NOR` | TMS320F2812 / ПМ67 | AM29LV800BT-70EI, 1 МиБ, x16 | Один выделенный NOR-слот, проверяемый manifest, CAN recovery |
|
||||
|
||||
F2812 не использует EEPROM. Его карта, GPIO, частота CAN, допустимые адреса
|
||||
приложения и состояние протокола отделены от F28335.
|
||||
|
||||
## Открытие в Code Composer Studio
|
||||
|
||||
1. **Project → Import CCS Projects**, каталог поиска — `c/protocan-boot/projects/ccs`.
|
||||
2. Выбрать `F28335_CAN_AB` и `F2812_CAN_NOR`. Оставить проекты в исходном
|
||||
расположении: **не включать Copy projects into workspace**, поскольку
|
||||
переносимые ссылки `PCAN_ROOT` ведут к общей библиотеке выше проектов.
|
||||
3. Выбрать **Debug** либо **Release**, затем **Build Project**. Оба проекта
|
||||
содержат собственные `.project`, `.cproject`, `.ccsproject`.
|
||||
4. OUT/MAP появятся в `ccs/<контроллер>/<Debug|Release>/`.
|
||||
|
||||
В `targetConfigs` сохранены существующие конфигурации Sauris SAU510:
|
||||
F28335 — Iso Plus v.2, F2812 — Iso Plus. Для другого адаптера выбрать свой
|
||||
Connection в Target Configuration. Компиляция и импорт не подключаются к JTAG
|
||||
и не программируют плату.
|
||||
|
||||
Для отдельных workspace с ограниченными правами CCS допускает переменную
|
||||
`TI_APPDATA_DIR` с доступным для записи каталогом. Это служебный cache IDE,
|
||||
он не относится к исходникам загрузчиков.
|
||||
|
||||
## Сборка без запуска IDE
|
||||
|
||||
Из корня **templates**:
|
||||
|
||||
```powershell
|
||||
python c/protocan-boot/projects/build.py --device f28335
|
||||
python c/protocan-boot/projects/build.py --device f2812
|
||||
python c/protocan-boot/projects/build.py --device f28335 --configuration Release
|
||||
python c/protocan-boot/projects/build.py --device f2812 --configuration Release
|
||||
```
|
||||
|
||||
Можно передать `--compiler <каталог-ti-cgt-c2000>` и `--output <каталог>`.
|
||||
Скрипт читает состав исходников и настройки из этих же CCS-проектов, полностью
|
||||
пересобирает их и проверяет реальные COFF LOAD-адреса официальным `ofd2000`.
|
||||
Проверка отклоняет загружаемый RAM-код без Flash LOAD, пересечения, отсутствующие
|
||||
boot branch/C entry, запись CSM/OTP и посторонних внутренних секторов.
|
||||
Рядом с OUT сохраняются MAP, `build.log` и manifest с SHA-256.
|
||||
|
||||
Результаты:
|
||||
|
||||
- `ccs/f28335/Debug/F28335_CAN_AB.out`;
|
||||
- `ccs/f2812/Debug/F2812_CAN_NOR.out`.
|
||||
|
||||
OUT — постоянный загрузчик для первоначальной установки через JTAG во
|
||||
**внутреннюю Flash** соответствующего TMS. Образ самого приложения передаётся
|
||||
по CAN в его внешнюю память. Для автоматического запуска должен быть выбран
|
||||
внутренний Flash boot; у ПМ67 необходимо также учитывать штатную логику CPLD
|
||||
выбора источника reset/boot из `big_dsp_module` платы.
|
||||
|
||||
## Prebuild/postbuild и копирование в SETGUI
|
||||
|
||||
В `.cproject` обоих проектов настроены **Debug и Release**. Prebuild вызывает
|
||||
`projects/build.py --step prepare`: сохраняет UTC, Git-ревизию templates,
|
||||
контроллер, конфигурацию и SHA-256 исходников в
|
||||
`${BuildDirectory}/generated/canboot_build.json`. Это внешний паспорт сборки
|
||||
загрузчика; номер версии приложения 167 не изменяется.
|
||||
|
||||
Postbuild вызывает тот же скрипт с `--step postbuild`. Он требует подготовку
|
||||
той же конфигурации и неизменные исходники, проверяет фактические COFF LOAD-адреса,
|
||||
C entry и аппаратный boot-переход через TI `ofd2000`, затем сохраняет OUT/MAP,
|
||||
`.out.manifest.json`, `.out.sha256` и `firmware_identity.json` в локальном архиве
|
||||
`${BuildDirectory}/firmware/HASH_дата_UTC+3`. Manifest содержит контроллер,
|
||||
Debug/Release, назначение `canboot`, адреса проверки и SHA-256 точного OUT.
|
||||
`firmware/latest_build.json` указывает текущие локальную и экспортированную папки.
|
||||
|
||||
Проверенный архив автоматически копируется в SETGUI по следующей схеме:
|
||||
|
||||
```text
|
||||
SETGUI/..periph_28335/firmware/periph28335/balzam167/canboot/
|
||||
28335/HASH_дата_UTC+3/F28335_CAN_AB.out
|
||||
2812/HASH_дата_UTC+3/F2812_CAN_NOR.out
|
||||
```
|
||||
|
||||
MAP, manifest, SHA-256 и метаданные лежат рядом с соответствующим OUT.
|
||||
`..periph_28335` — буквальное имя существующего каталога SETGUI с двумя точками
|
||||
в начале. Время имени папки берётся из prebuild; `+` после HASH означает изменения
|
||||
в templates. Debug и Release имеют отдельные BuildDirectory и свои метаданные.
|
||||
|
||||
В **Project Properties → Build → Steps** команды уже заполнены для обеих
|
||||
конфигураций. Пример для F28335 Debug, каждая команда вводится одной строкой:
|
||||
|
||||
```bat
|
||||
python "${PROJECT_LOC}/../../build.py" --device f28335 --configuration Debug --compiler "${CG_TOOL_ROOT}" --output "${BuildDirectory}" --step prepare
|
||||
python "${PROJECT_LOC}/../../build.py" --device f28335 --configuration Debug --compiler "${CG_TOOL_ROOT}" --output "${BuildDirectory}" --step postbuild --export "${PROJECT_LOC}/../../../../../../SETGUI/..periph_28335/firmware/periph28335/balzam167/canboot/28335"
|
||||
```
|
||||
|
||||
Для F2812 используются `--device f2812` и конечная папка `2812`; для Release —
|
||||
`--configuration Release`. При другом расположении SETGUI измените `--export`
|
||||
в postbuild обеих конфигураций. Все пути с пробелами должны оставаться в кавычках.
|
||||
CLI-сборка из раздела выше выполняет те же prepare/postbuild и копирует в эти же
|
||||
каталоги; аргумент `--export <каталог>` переопределяет место копирования.
|
||||
При ошибке компиляции, проверки OUT, отсутствии MAP или изменении исходников
|
||||
после prebuild новый комплект в SETGUI не копируется. После изменения исходников
|
||||
выполните **Rebuild Project**, чтобы обновить и OUT, и подготовку.
|
||||
|
||||
В диалоге SETGUI **Flash JTAG (OUT)** для установки постоянного загрузчика F28335
|
||||
выбирайте `canboot/28335/HASH_дата_UTC+3/F28335_CAN_AB.out`.
|
||||
`F2812_CAN_NOR.out` предназначен для JTAG соответствующего F2812.
|
||||
Копирование файлов не запускает программирование платы и сетевую публикацию.
|
||||
|
||||
## F28335 / M95M02
|
||||
|
||||
Проект использует существующий порт и формат журнала без изменения адресов:
|
||||
**PERIPH 0–31: Type = 1 + PERIPH / 16, Device ID = PERIPH mod 16**.
|
||||
УМП1 — **1/5**, УМП2 — **1/6**, задатчик — **1/8**.
|
||||
Product **0x2833**, HW **1**, CAN **250000 бит/с**. Приложение и загрузчик
|
||||
используют один аппаратный номер платы; прежний общий адрес **3/7** отменён.
|
||||
Код загрузчика — внутренний сектор A `0x338000..0x33FF7F`, boot — `0x33FFF6`.
|
||||
Слоты M95M02 имеют по **130560 октетов**, параметры — прежние 258 октетов
|
||||
по адресу `0x3FEFC`. Приложение загружается в RAM ниже CPU word-адреса `0xE000`.
|
||||
|
||||
После установки постоянного загрузчика новое приложение 167 передаётся
|
||||
существующим клиентом SETGUI **CAN / ProtoCAN Boot**. Оно обязано содержать
|
||||
hooks READY/CONFIRM и watchdog: пример интеграции реализован в ветке 167.
|
||||
Без CONFIRM следующий reset возвращает подтверждённый active.
|
||||
Полная последовательность — [документация порта F28335](../ports/tms320f28335/README.md).
|
||||
|
||||
## F2812 / AM29LV800BT
|
||||
|
||||
Профиль: **Device Type 3, Device ID 6, Product 0x2812, HW 1,
|
||||
CAN 250000 бит/с, SYSCLKOUT 150 МГц**. CAN находится на **GPIOF6 TX / GPIOF7 RX**.
|
||||
F2812 использует программную арифметику `rts2800_ml.lib`; F28335 — FPU32
|
||||
и `rts2800_fpu32.lib`.
|
||||
|
||||
Внутренний сектор A загрузчика: `0x3F6000..0x3F7F7F`, boot — `0x3F7FF6`.
|
||||
Внешняя NOR подключена по **CPU word-адресу `0x100000`**. Новый CAN-раздел
|
||||
задан явно в `ports/tms320f2812/boot2812.h`:
|
||||
|
||||
| Область | CPU word-адреса включительно | Размер |
|
||||
| --- | --- | ---: |
|
||||
| Существующая нижняя область: прежние образы/параметры/FPGA | `0x100000..0x15FFFF` | 768 КиБ |
|
||||
| Новый SCI8 CAN-образ: обычные сектора 12/13/14 | `0x160000..0x177FFF` | **192 КиБ** |
|
||||
| Неизменяемые этим загрузчиком top-boot сектора 15/16/17 | `0x178000..0x17DFFF` | 48 КиБ |
|
||||
| Новый manifest: последний top-boot сектор 18 | `0x17E000..0x17FFFF` | 16 КиБ |
|
||||
|
||||
Это **новое явное размещение CAN-образа**, а не автоматическое определение
|
||||
свободного места в уже заполненном чипе. Перед первоначальной установкой
|
||||
согласовать эти два выделенных участка с фактической картой NOR конкретной
|
||||
платы. В известном исходнике FPGA начинается с `0x130000`, а новый CAN-слот
|
||||
находится выше; драйвер не изменяет ничего ниже `0x160000` и в секторах 15–17.
|
||||
|
||||
После reset доступно **3 секунды** для ENTER_BOOT. Открытая сессия удерживает
|
||||
загрузчик; пустой/повреждённый manifest оставляет CAN recovery постоянно.
|
||||
Для обновления уже работающего приложения нужно войти в это окно после reset;
|
||||
приложение F2812 само по себе пока не получает обработчик ENTER_BOOT от добавления
|
||||
отдельного проекта загрузчика.
|
||||
|
||||
Перед записью требуется AMD manufacturer **`0x0001`**, device **`0x22DA`**.
|
||||
Сначала стирается сектор manifest, затем три сектора образа. Запись проверяет
|
||||
каждое слово. VERIFY повторно читает NOR, проверяет CRC32, SCI8, entry,
|
||||
перекрытия и запрещённые назначения. COMMIT повторяет проверку, сохраняет
|
||||
manifest с собственной CRC, два magic-слова публикуются последними.
|
||||
Стирание сообщает BUSY каждые 250 мс, продлевая живую сессию.
|
||||
|
||||
Это single-slot схема: при обрыве обновления последнего CAN-образа остаётся
|
||||
recovery, автоматический A/B rollback для F2812 не заявляется. Полное стирание
|
||||
NOR, запись внутренней Flash по CAN и программирование FPGA отсутствуют.
|
||||
|
||||
Образ приложения — **TI SCI8 `AA 08`**, максимум **196608 октетов**. Старый
|
||||
flat BIN основного контроллера автоматически не мигрируется. Допустимый payload:
|
||||
|
||||
- L0 `0x8000..0x8EFF`;
|
||||
- H0 `0x3F8000..0x3F9FFF`;
|
||||
- внешняя SRAM zone2 `0x80000..0xFFFFF`.
|
||||
|
||||
Стек M1, L1 `0x9000..0x9FFF`, RAM InitFlash `0x8F00..0x8FFF`, периферия,
|
||||
внутренняя Flash и NOR не допускаются как назначения SCI8. `.ebss` приложения
|
||||
может занять L1 после перехода в его C runtime; инициализированный payload
|
||||
работающую RAM загрузчика перезаписывать не должен. Настройка приложения
|
||||
F2812/его linker — отдельная от импорта загрузчика операция.
|
||||
|
||||
Геометрия x16, ID и команды AA/55/A0/80/30/F0 взяты из
|
||||
[оригинального datasheet AMD Am29LV800B](https://cms.nacsemi.com/content/AuthDatasheets/FASLS02690-1.pdf).
|
||||
Аппаратные GPIO/CPLD и XINTF-тайминги перенесены из действующих
|
||||
`big_dsp_module.c` / `flash_tools.c` ПМ67, без зависимости от этих файлов при сборке.
|
||||
|
||||
## Проверки
|
||||
|
||||
```powershell
|
||||
$env:CC = 'C:/msys64/ucrt64/bin/gcc.exe'
|
||||
python c/protocan-boot/tests/run_tests.py
|
||||
```
|
||||
|
||||
Проверяются протокол, CRC, сессии/BUSY, A/B F28335 с trial/rollback/CONFIRM,
|
||||
NOR F2812 с настоящими AMD command cycles, ID, readback, запрещёнными секторами,
|
||||
обрывами COMMIT, повреждением CRC/SCI8 и тайм-аутом DQ7.
|
||||
Вариант с CMake/CTest также поддерживается.
|
||||
|
||||
Подтверждены реальные import/build обоих проектов в CCS 12.3, CLI-сборки
|
||||
Debug/Release TI 22.6 COFF и проверки секций OUT. Физические JTAG/CAN/XINTF,
|
||||
режим boot и поведение конкретной платы при отключении питания требуют стенда;
|
||||
физическое программирование этим комплектом автоматически не выполнялось.
|
||||
@@ -0,0 +1,69 @@
|
||||
# STM32: подключение исходных проектов к templates
|
||||
|
||||
Канонические загрузочные реализации находятся в `c/protocan-boot/ports`.
|
||||
В проектах сохранены прежние пути файлов, содержащие только include общего
|
||||
исходника из их submodule. Это сохраняет внешний API и настройки Keil,
|
||||
не оставляя локальных копий state machine, Flash writer или boot-control.
|
||||
|
||||
| Проект | Submodule | Подключённый порт | Сборки |
|
||||
|---|---|---|---|
|
||||
| `KONOR_ds18b20` | `lib` | `stm32f103` CMSIS, BKP request, CAN/UART | Keil app и BOOT |
|
||||
| `home/climate` | `third_party/templates` | `stm32f407` CMSIS boot, HAL app control | Keil app и BOOT |
|
||||
| `CANdle_stm32` | `templates` | `stm32g474` HAL FDCAN/Flash, CAN/UART, startup/control | Keil app/BOOT, CMake app/BOOT |
|
||||
|
||||
## Что перенесено
|
||||
|
||||
- Общие `pcan_boot.c/.h` используются через submodule; устаревшая копия
|
||||
`Libraries/ProtoCanBoot` climate заменена совместимыми include-обёртками.
|
||||
- F103/F407 CMSIS transport, Flash, CRC readback, безопасный naked handoff,
|
||||
single-slot callbacks и таймеры используют общий `boot_native.inc`.
|
||||
- F103 UART firmware flow теперь использует имеющийся общий `rs485-boot`;
|
||||
приложение KONOR продолжает использовать свой прикладной parser `proto.c`.
|
||||
- G474 локальная CAN state machine полностью заменена текущим `pcan_boot`.
|
||||
Единый Flash writer используется CAN/UART; вход из приложения и startup
|
||||
также принадлежат `templates`.
|
||||
- Конфигурации ядра заполняются по именам полей. Сохранены исходные Device ID,
|
||||
скорости CAN/UART, границы Flash и режим одного логического слота A.
|
||||
- В F407 BOOT и G474 app/BOOT linker исключает последние восемь байт используемой
|
||||
SRAM, где хранится запрос обновления. Initial MSP G474 согласован с этим.
|
||||
- В GCC-таблице IRQ G474 размер выведен из самого старшего назначенного IRQ:
|
||||
прежний предел по USB_LP не вмещал USART1 и ломал CMake app-сборку.
|
||||
|
||||
Clock tree, USB–CAN приложение, трансивер, GPIO board-support, настройки
|
||||
программирования ST-Link и application linker остаются в исходных проектах.
|
||||
Нативные `boot_main.c` не нужно компилировать вместе с альтернативным HAL boot
|
||||
main в одном target. Изменения не прошивают подключённые устройства.
|
||||
|
||||
## Зависимости и публикация
|
||||
|
||||
CANdle получен из `https://git.rd12.ru/setcorp/CANdle_stm32.git`, исходный HEAD
|
||||
`61a93c5`. Записанный там gitlink `templates` указывает на отсутствующий на
|
||||
сервере объект `520788491c803e07ba1889e1fa60ed1550afb848`. Для локальной
|
||||
интеграции submodule переключён на доступный локальный HEAD `templates`,
|
||||
после чего текущие общие исходники синхронизированы в рабочее дерево.
|
||||
Рабочие копии submodule KONOR/climate также содержат соответствующие текущие файлы.
|
||||
|
||||
Подключение проверено локально. Для воспроизводимого нового клонирования
|
||||
потребуется зафиксировать и опубликовать изменения `templates`, затем обновить
|
||||
gitlinks в исходных проектах на этот доступный commit. Коммиты и push не являются
|
||||
частью этой локальной интеграции; чужие незавершённые изменения не включались в commit.
|
||||
|
||||
## Проверка
|
||||
|
||||
21 октября 2026 года проверены настоящие Keil/Arm Compiler 6.22 сборки
|
||||
BOOT и приложения каждого из трёх проектов. Все загрузчики помещаются в
|
||||
исходные лимиты 12/32/32 КиБ. G474 дополнительно проверен ARM GCC/CMake
|
||||
для `STM32G474VE_CAN_bootloader` и `STM32G474VE_DevBoard_fw`.
|
||||
|
||||
Включённые в G474 BOOT строгие диагностики Arm Compiler выдают предупреждения
|
||||
о layout структур, GNU-расширениях и обычном C-доступе к буферам в HAL/общих
|
||||
модулях; компиляция и линковка завершаются без ошибок. GNU/newlib конфигурация
|
||||
также сообщает о заглушках syscall при отключённом semihosting. Эти сообщения
|
||||
не подавлялись общими флагами проекта.
|
||||
|
||||
Host CMake/CTest: 10 тестов, включая три production HAL-порта. G474 проверяется
|
||||
в DBANK=0 и DBANK=1: весь слот, переход между банками, отказ swap/неверной
|
||||
плотности, все хвосты DATA, CRC readback, тайм-аут и TX ACK до reset.
|
||||
Проверки интеграции climate подтверждают реальную цепочку include/Keil и границы
|
||||
Flash/SRAM. Аппаратное обновление, питание и восстановление после обрыва на
|
||||
физических платах в эту проверку не входят.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?ccsproject version="1.0"?>
|
||||
<projectOptions>
|
||||
<!-- Описывает ccsVersion: 12.3.0. -->
|
||||
<ccsVersion value="12.3.0" />
|
||||
<!-- Описывает deviceVariant: TMS320C28XX.TMS320F2812. -->
|
||||
<deviceVariant value="TMS320C28XX.TMS320F2812" />
|
||||
<!-- Описывает deviceFamily: C2000. -->
|
||||
<deviceFamily value="C2000" />
|
||||
<!-- Описывает deviceEndianness: little. -->
|
||||
<deviceEndianness value="little" />
|
||||
<!-- Описывает codegenToolVersion: 22.6.0.LTS. -->
|
||||
<codegenToolVersion value="22.6.0.LTS" />
|
||||
<!-- Описывает isElfFormat: false. -->
|
||||
<isElfFormat value="false" />
|
||||
<!-- Описывает rts: rts2800_ml.lib. -->
|
||||
<rts value="rts2800_ml.lib" />
|
||||
<!-- Описывает isTargetManual: false. -->
|
||||
<isTargetManual value="false" />
|
||||
</projectOptions>
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user