Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент. Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
105 lines
20 KiB
C
105 lines
20 KiB
C
#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-порта. */
|