#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 /* Копирует проверенные диапазоны 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. */