Files
templates/c/protocan-boot/ports/stm32g474/pcan_boot_stm32g474.c
T
Andrey c6390ae1c9 feat(protocan-boot): Добавь порты MCU и сервисы загрузки
Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент.

Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
2026-10-23 14:16:19 +03:00

105 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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-порта. */