/****************************************************************************** * @file bootloader.c * @brief Бутлоадер STM32 — реализован как конечный автомат (state machine). * * @details * Логика работы: * - Структура Bootloader_t содержит текущее состояние и ошибки. * - После сброса проверяются предыдущие ошибки. * - Проверяется BOOT KEY, чтобы определить: запускать основное приложение или оставаться в бутлоадере. * - Основной цикл — state machine: * INIT — проверка ключа, инициализация периферии * IDLE — ожидание команд по UART/CAN * RECEIVE_UART/RECEIVE_CAN — приём страницы прошивки * WRITE — запись страницы во Flash * ERASE — стирание приложения * JUMP_TO_APP — проверка прошивки и переход к приложению * JUMP_TO_BOOT— возврат в бутлоадер * RESET — программный сброс * ERROR — обработка ошибок и уведомление внешнего интерфейса * - Команды прошивки (BootloaderCommand_t) обрабатываются через Receive_FW_Command(). * - Проверка целостности данных осуществляется через CRC32. * * Подключение бутлоадера в основном приложении: * 0) Подключить boot_jump.h и boot_jump.c для взаимодействия с бутлоадером: * @code #include "boot_jump.h" @endcode * 1) В начале main() вызвать App_Init(), чтобы установить VTOR на * начало приложения: * @code App_Init(); @endcode * 2) Для перехода в бутлоадер (например, при ошибке или обновлении): * @code JumpToBootloader(); @endcode ******************************************************************************/ #include "bootloader.h" #include "boot_gpio.h" #include "boot_flash.h" #include "boot_uart.h" #include "boot_can.h" #include "boot_jump.h" // ----------------------------------------------------------------------------- // Глобальные переменные для HAL-периферии // ----------------------------------------------------------------------------- HAL_StatusTypeDef res_hal; CAN_TxHeaderTypeDef TxHeaderBoot; CAN_RxHeaderTypeDef RxHeaderBoot; uint32_t TxMailBoxBoot = 0; uint8_t TXDataBoot[8] = {0}; uint32_t led_err_lasttick = 0; // ----------------------------------------------------------------------------- // Локальные (static) функции // ----------------------------------------------------------------------------- static uint8_t Receive_FW_Command(Bootloader_t *bl); static uint32_t CRC32_Compute(const uint8_t* data, uint32_t length); /** * @brief Проверка после сброса MCU. * Определяет причину предыдущего сброса, проверяет ошибки и при необходимости * выставляет соответствующие биты в структуре ошибок бутлоадера. */ void Bootloader_StartCheck(Bootloader_t *bl) { uint32_t ErrCodeBoot = 0; uint32_t ErrCntBoot = 0; // Проверка watchdog reset (IWDGRSTF или WWDGRSTF в RCC->CSR) if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) || __HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) { //SaveErrorCode(0x0D0D); __HAL_RCC_CLEAR_RESET_FLAGS(); // Очистить флаги сброса, чтобы не повторялось } // Чтение сохранённого кода ошибки и количества сбоев ErrCodeBoot = GetErrorCode(); ErrCntBoot = GetErrorCnt(); // Если ошибок было больше 5, фиксируем тип ошибки if(ErrCntBoot > 5) { ClearErrorCode(); if(ErrCodeBoot == 0xDEAD) // HardFault { ResetKey(); bl->error.bit.hardfault_cycle = 1; bl->state = BL_STATE_ERROR; } else if(ErrCodeBoot == 0xBEEF) // MemManage { ResetKey(); bl->error.bit.memmanage_cycle = 1; bl->state = BL_STATE_ERROR; } /*else if(ErrCodeBoot == 0x0D0D) пока хз надо ли { ResetKey(); bl->error.bit.watchdog_reset = 1; // Добавь бит в структуру BootloaderError_ bl->state = BL_STATE_ERROR; }*/ } } /** * @brief Инициализация периферии бутлоадера (UART, CAN, системный такт). * Привязывает дескрипторы HAL к структуре бутлоадера и задаёт * начальный адрес приложения. */ void Bootloader_Init(Bootloader_t *bl) { HAL_Init(); Boot_SystemClock_Config(); MX_BOOT_UART_Init(); MX_BOOT_CAN_Init(); // Привязка дескрипторов к структуре бутлоадера bl->huart = &huart_boot; bl->hcan = &hcan_boot; bl->TxHeader.DLC = 8; bl->TxHeader.StdId = 123; bl->addr = MAIN_APP_START_ADR; // адрес начала приложения } /** * @brief Основной цикл работы бутлоадера (машина состояний). * Обрабатывает состояния INIT, IDLE, RECEIVE, WRITE, ERASE, JUMP и ERROR. */ void Bootloader_Task(Bootloader_t *bl) { int receive_uart_flag; switch (bl->state) { case BL_STATE_INIT: /* * Состояние инициализации. * - включаем индикацию (LED), * - проверяем ошибки, * - читаем "ключ" (метку, что приложение уже записано). * Если ключ установлен -> сразу переход в приложение. * Иначе -> переходим в режим ожидания команд от хоста (IDLE), * инициализируем интерфейсы (CAN/UART, CRC и т.д.). */ bl->prev_state = bl->state; MX_BOOT_GPIO_Init(); Bootloader_StartCheck(bl); if ((ReadKey() == BL_KEY_APP_WRITTEN)) { bl->state = BL_STATE_JUMP_TO_APP; } else { bl->state = BL_STATE_IDLE; Bootloader_Init(bl); } break; case BL_STATE_IDLE: /* * Состояние ожидания команд. * - если ошибка уже зафиксирована -> переход в ERROR, * - если это первый вход в IDLE -> отправляем "готов" (0x00) по CAN/UART, * - далее слушаем команды от хоста (erase, write, jump и т.п.). * Неизвестная команда -> ошибка. */ if(bl->error.all) { bl->prev_state = bl->state; bl->state = BL_STATE_ERROR; break; } if((bl->state != bl->prev_state) && (bl->prev_state != BL_STATE_ERROR)) { TXDataBoot[0] = 0x00; res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot); res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100); } bl->prev_state = bl->state; if (Receive_FW_Command(bl) == 0xFF) { bl->error.bit.unknown_cmd = 1; bl->state = BL_STATE_ERROR; } break; case BL_STATE_RESET: /* * Состояние сброса. * Вызывает системный reset через NVIC -> контроллер запускается заново. */ NVIC_SystemReset(); break; case BL_STATE_ERASE: /* * Состояние стирания Flash. * - сбрасываем "ключ" приложения, * - стираем область памяти под приложение, * - при успехе возвращаемся в IDLE, * - при ошибке отмечаем ошибку стирания и уходим в ERROR. * По завершению гасим LED. */ bl->prev_state = bl->state; EraseKey(); if (FLASH_Erase_App() == HAL_OK) { HAL_Delay(50); bl->state = BL_STATE_IDLE; } else { bl->error.bit.erase_err = 1; bl->state = BL_STATE_ERROR; } LED_BOOT_OFF(); break; case BL_STATE_RECEIVE_UART: case BL_STATE_RECEIVE_CAN: /* * Состояние приёма страницы прошивки от хоста. * - различаем, пришло ли по UART или CAN, * - отправляем ACK (0x00), * - читаем блок данных (страницу) в буфер, * - после приёма проверяем CRC полученного блока, * - если CRC не совпадает -> очищаем буфер, фиксируем ошибку и уходим в ERROR. * По завершению приёма гасим LED. */ receive_uart_flag = (bl->state == BL_STATE_RECEIVE_UART) ? 1 : 0; TXDataBoot[0] = 0x00; bl->prev_state = bl->state; if(receive_uart_flag) { res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100); Bootloader_UART_Receive_Page(bl); } else { res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot); Bootloader_CAN_Receive_Page(bl); } uint32_t crc_calculated = CRC32_Compute((uint8_t *)bl->fw_buffer, bl->fw_len); if(crc_calculated != bl->fw_crc) { for(int i = 0; i < bl->fw_len; i++) { bl->fw_buffer[i] = 0; } bl->error.bit.crc_err = 1; bl->state = BL_STATE_ERROR; } LED_BOOT_OFF(); break; case BL_STATE_WRITE: /* * Состояние записи страницы прошивки во Flash. * - пытаемся записать буфер в указанную область памяти, * - если успешно -> возвращаемся в IDLE (ждём следующего блока), * - если ошибка -> фиксируем ошибку записи и уходим в ERROR. * После завершения гасим LED. */ bl->prev_state = bl->state; if (FLASH_Write_Page(&bl->addr, bl->fw_buffer, bl->fw_len) == HAL_OK) { bl->state = BL_STATE_IDLE; } else { bl->error.bit.write_err = 1; bl->state = BL_STATE_ERROR; } LED_BOOT_OFF(); break; case BL_STATE_JUMP_TO_APP: /* * Состояние перехода в приложение. * - выполняем проверку корректности прошивки (Verify_Firmware), * - если проверка пройдена -> устанавливаем "ключ" приложения, * чтобы пометить прошивку как валидную, * - если проверка не пройдена -> ошибка verify и переход в ERROR. * В случае успеха вызываем JumpToApplication(), передавая управление основному коду. */ bl->prev_state = bl->state; if (Verify_Firmware() == HAL_OK) { EraseKey(); SetKey(); } else { bl->error.bit.verify_err = 1; bl->state = BL_STATE_ERROR; break; } JumpToApplocation(); break; case BL_STATE_JUMP_TO_BOOT: /* * Состояние возврата в bootloader. */ bl->prev_state = bl->state; JumpToBootloader(); break; case BL_STATE_ERROR: /* * Состояние ошибки. * - при первом входе в ERROR отправляем код ошибки (0xFF + код ошибки), * - продолжаем слушать команды, чтобы можно было сбросить или стереть Flash, * - мигаем LED раз в 500 мс для визуальной индикации ошибки. */ if(bl->state != bl->prev_state) { TXDataBoot[0] = 0xFF; TXDataBoot[1] = (bl->error.all >> 8) & (0xFF); TXDataBoot[2] = bl->error.all & (0xFF); res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot); res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100); } bl->prev_state = bl->state; if (Receive_FW_Command(bl) == 0xFF) { bl->error.bit.unknown_cmd = 1; bl->state = BL_STATE_ERROR; } if(HAL_GetTick() - led_err_lasttick > 500) { led_err_lasttick = HAL_GetTick(); LED_BOOT_TOOGLE(); } break; default: /* * Попадание в неизвестное состояние. * Считается ошибкой: ставим unknown_cmd и переходим в ERROR. */ bl->error.bit.unknown_cmd = 1; bl->state = BL_STATE_ERROR; break; } } /** * @brief Устанавливает новое состояние бутлоадера в зависимости от команды. * @param bl: указатель на структуру бутлоадера * @param cmd: команда бутлоадера (BootloaderCommand_t) * @param uart_flag: 1 — команда пришла по UART, 0 — по CAN * @retval 0x00 — команда успешно обработана, 0xFF — неизвестная команда */ static uint8_t SetBootState(Bootloader_t *bl, BootloaderCommand_t cmd, uint8_t uart_flag) { switch(cmd) { case CMD_ERASE: // команда: стереть Flash bl->state = BL_STATE_ERASE; return 0x00; case CMD_START_RECEIVE: // команда: принять блок if(uart_flag) bl->state = BL_STATE_RECEIVE_UART; else bl->state = BL_STATE_RECEIVE_CAN; return 0x00; case CMD_WRITE: // команда: записать блок bl->state = BL_STATE_WRITE; return 0x00; case CMD_GOTOAPP: // команда: прыжок в приложение bl->state = BL_STATE_JUMP_TO_APP; return 0x00; case CMD_RESET: // команда: прыжок в приложение bl->state = BL_STATE_RESET; return 0x00; case CMD_GOTOBOOT: // команда: прыжок в бутлоадер bl->state = BL_STATE_JUMP_TO_BOOT; return 0x00; default: return 0xFF; // неизвестная команда } } /** * @brief Обработка команд прошивки по UART или CAN * @param bl: указатель на структуру бутлоадера * @retval 0x00 - команда принята и обработана, 0xFF - ошибка */ static uint8_t Receive_FW_Command(Bootloader_t *bl) { BootloaderCommand_t cmd = 0; HAL_StatusTypeDef res = HAL_ERROR; uint8_t ret_val = 0x00; // --------------------------- // Чтение команды по UART // --------------------------- res = HAL_UART_Receive(bl->huart, &cmd, 1, 10); // таймаут 10 ms if (res == HAL_OK) { ret_val = SetBootState(bl, cmd, 1); } // --------------------------- // Чтение команды по CAN // --------------------------- uint8_t canData[8]; if (HAL_CAN_GetRxFifoFillLevel(bl->hcan, CAN_RX_FIFO0) > 0) { if (HAL_CAN_GetRxMessage(bl->hcan, CAN_RX_FIFO0, &RxHeaderBoot, canData) == HAL_OK) { cmd = canData[0]; // предполагаем, что команда в первом байте ret_val = SetBootState(bl, cmd, 0); } } #ifdef TEST_CAN TxHeaderBoot.StdId = 0x200; // ID OF MESSAGE TxHeaderBoot.ExtId = 0; // STANDART FRAME (NOT EXTENTED) TxHeaderBoot.RTR = CAN_RTR_DATA; // TRANSMIT DATA OR TxHeaderBoot.IDE = CAN_ID_STD; // STANDART FRAME TxHeaderBoot.DLC = 8; // DATA SIZE TxHeaderBoot.TransmitGlobalTime = DISABLE; //THIS MODE IS NOT USED, SO DISABLE uint8_t asd[8] = "ABCDEFGL"; res_hal = HAL_CAN_AddTxMessage(&hcan_boot, &TxHeaderBoot, asd, &TxMailBoxBoot); // add to mail for transmit HAL_Delay(1000); #endif if((bl->state != BL_STATE_IDLE) && (bl->state != BL_STATE_ERROR)) { LED_BOOT_ON(); } return ret_val; } /** * @brief Вычисление CRC32 блока данных. * @param data: указатель на массив данных * @param length: длина массива в байтах * @retval CRC32 вычисленное значение */ static uint32_t CRC32_Compute(const uint8_t* data, uint32_t length) { const uint32_t polynomial = 0x04C11DB7; uint32_t crc = 0xFFFFFFFF; for(uint32_t i = 0; i < length; i++) { crc ^= ((uint32_t)data[i] << 24); for(uint8_t j = 0; j < 8; j++) { if(crc & 0x80000000) crc = (crc << 1) ^ polynomial; else crc <<= 1; } } return crc ^ 0xFFFFFFFF; } /** * @brief Конфигурация системного тактирования (должна быть переопределена пользователем). */ __WEAK void Boot_SystemClock_Config(void) { } /** * @brief Обработчик ошибок (может быть переопределён пользователем). */ __WEAK void Error_Handler(void) { while(1); }