структуризирован бутлоадер. работает

надо по протоколу подумать еще, доработать его и описать
This commit is contained in:
2025-09-15 14:41:46 +03:00
parent fbd36705f1
commit 0e834dfe3d
20 changed files with 1018 additions and 778 deletions

View File

@@ -0,0 +1,136 @@
#include "boot_can.h"
#include "boot_gpio.h"
CAN_HandleTypeDef hcan_boot;
/**
* @brief Инициализация CAN для бутлоадера
*/
void MX_BOOT_CAN_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
CAN_FilterTypeDef sFilterConfig;
/* Включаем тактирование */
__RCC_CAN_BOOT_CLK_ENABLE();
__RCC_CAN_PORT_CLK_ENABLE();
/* Настройка пинов RX/TX */
GPIO_InitStruct.Pin = CAN_PIN_RX;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(CAN_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = CAN_PIN_TX;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CAN_PORT, &GPIO_InitStruct);
/* Настройка CAN */
hcan_boot.Instance = CAN_BOOT;
hcan_boot.Init.Prescaler = CAN_SPEED_PRESCALER;
hcan_boot.Init.Mode = CAN_MODE;
hcan_boot.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan_boot.Init.TimeSeg1 = CAN_SPEED_BS1;
hcan_boot.Init.TimeSeg2 = CAN_SPEED_BS2;
hcan_boot.Init.TimeTriggeredMode = DISABLE;
hcan_boot.Init.AutoBusOff = DISABLE;
hcan_boot.Init.AutoWakeUp = DISABLE;
hcan_boot.Init.AutoRetransmission = ENABLE;
hcan_boot.Init.ReceiveFifoLocked = DISABLE;
hcan_boot.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan_boot) != HAL_OK)
{
Error_Handler();
}
/* Настройка фильтра: пропускать все сообщения */
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
if (HAL_CAN_ConfigFilter(&hcan_boot, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* Запускаем CAN */
if (HAL_CAN_Start(&hcan_boot) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief Приём CAN: страница + CRC
*/
void Bootloader_CAN_Receive_Page(Bootloader_t *bl)
{
uint16_t bytes_received = 0;
CAN_RxHeaderTypeDef canHeader;
uint8_t canData[8];
uint32_t start_tick = HAL_GetTick();
// Приём страницы прошивки
while(bytes_received < PAGE_SIZE)
{
if(HAL_CAN_GetRxFifoFillLevel(bl->hcan, CAN_RX_FIFO0) > 0)
{
if(HAL_CAN_GetRxMessage(bl->hcan, CAN_RX_FIFO0, &canHeader, canData) == HAL_OK)
{
uint8_t len = canHeader.DLC;
if(bytes_received + len > PAGE_SIZE)
len = PAGE_SIZE - bytes_received;
memcpy(&bl->fw_buffer[bytes_received], canData, len);
bytes_received += len;
start_tick = HAL_GetTick(); // сброс таймаута
LED_BOOT_TOOGLE();
}
}
// проверка таймаута
if(HAL_GetTick() - start_tick >= FW_RECEIVE_TIMEOUT_MS)
{
bl->error.bit.timeout_receive = 1;
bl->state = BL_STATE_ERROR;
return;
}
}
// Приём CRC (4 байта)
start_tick = HAL_GetTick(); // сброс таймаута
while(1)
{
if(HAL_CAN_GetRxFifoFillLevel(bl->hcan, CAN_RX_FIFO0) > 0)
{
if(HAL_CAN_GetRxMessage(bl->hcan, CAN_RX_FIFO0, &canHeader, canData) == HAL_OK)
{
// CRC в первых 4 байтах пакета
bl->fw_crc = (canData[0] << 24) |
(canData[1] << 16) |
(canData[2] << 8) |
canData[3];
break;
}
}
// Таймаут
if(HAL_GetTick() - start_tick >= FW_RECEIVE_TIMEOUT_MS)
{
bl->error.bit.timeout_receive = 1;
bl->state = BL_STATE_ERROR;
return;
}
}
bl->fw_len = PAGE_SIZE;
bl->state = BL_STATE_IDLE;
}

View File

@@ -0,0 +1,92 @@
#include "boot_flash.h"
uint32_t word_data;
HAL_StatusTypeDef FLASH_Erase_App(void) //
{
HAL_StatusTypeDef res;
uint32_t PageError = 0x00;
res = HAL_FLASH_Unlock();
if (res != HAL_OK) return res;
FLASH_EraseInitTypeDef EraseInitStruct;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;// erase pages
EraseInitStruct.Banks = 1;
EraseInitStruct.PageAddress = MAIN_APP_START_ADR; //address
EraseInitStruct.NbPages = MAIN_APP_NUM_OF_PAGE;// num of erased pages
res = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
if (res != HAL_OK) return res;
res = HAL_FLASH_Lock();
return res;
}
HAL_StatusTypeDef FLASH_Write_Page(uint32_t *Address, uint8_t *Data, int Data_size)
{
//GPIOB->ODR^=(0x2000);
// GPIOB->ODR|=0x4000;
HAL_StatusTypeDef res;
int data_cnt = 0;
uint32_t adr;
res = HAL_FLASH_Unlock();
if (res != HAL_OK) return res;
for (adr = *Address; adr < *Address + Data_size; adr = adr+4)
{
word_data = (
Data[data_cnt]|
Data[data_cnt+1]<<8|
Data[data_cnt+2]<<16|
Data[data_cnt+3]<<24);
res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, adr, word_data);
if (res != HAL_OK) return res;
data_cnt +=4;
}
*Address += Data_size;
res = HAL_FLASH_Lock();
return res;
}
HAL_StatusTypeDef FLASH_Write_Word(uint32_t Address, uint64_t Data) //Куда записывать
{
HAL_StatusTypeDef res;
res = HAL_FLASH_Unlock();
if (res != HAL_OK) return res;
res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, (uint32_t)(Data));
if (res != HAL_OK) return res;
res = HAL_FLASH_Lock();
return res;
}

View File

@@ -0,0 +1,24 @@
#include "boot_gpio.h"
/**
* @brief Инициализация GPIO для бутлоадера
*/
void MX_BOOT_GPIO_Init(void)
{
__RCC_LED_BOOT_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = LED_BOOT_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Push-Pull выход
GPIO_InitStruct.Pull = GPIO_NOPULL; // Без подтяжки
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // Низкая скорость
HAL_GPIO_Init(LED_BOOT_GPIO_Port, &GPIO_InitStruct);
for(int cnt = 0; cnt < 5; cnt++)
{
LED_BOOT_ON();
for(int delay = 0; delay < 10000; delay++);
LED_BOOT_OFF();
for(int delay = 0; delay < 10000; delay++);
}
}

View File

@@ -0,0 +1,150 @@
/**
* @file boot_jump.c
* @brief Функции для перехода между бутлоадером и основным приложением,
* управление ключом BOOT и проверка прошивки.
*
* Основные возможности:
* - Настройка вектора прерываний для запуска приложения
* - Управление ключом BOOT в Flash
* - Проверка корректности прошивки перед прыжком
* - Функции прыжка: Bootloader <-> Application
*/
#include "boot_jump.h"
/**
* @brief Инициализация приложения.
* Устанавливает вектор прерываний на начало основного приложения.
*/
void App_Init(void)
{
__disable_irq();
SCB->VTOR = MAIN_APP_START_ADR;
__enable_irq();
}
/**
* @brief Переход в бутлоадер.
* Сбрасывает ключ BOOT и выполняет системный сброс.
*/
void JumpToBootloader(void)
{
// jump to boot
ResetKey(); // сброс ключа (не erase, просто битый ключ)
NVIC_SystemReset(); // сброс и переход в бутлоадер (т.к. нет ключа)
}
/**
* @brief Переход к основному приложению.
* Настраивает стек и переход к ResetHandler приложения.
*/
void JumpToApplocation(void)
{
//Деинициализация HAL
HAL_DeInit();
//Перенос вектора прерываний на начало зашитой программы
__disable_irq();
__set_MSP(*((volatile uint32_t*)MAIN_APP_START_ADR));
__enable_irq();
//Переход к выполнению зашитой программы
__ASM volatile(
"ldr r0, [%0, #4]\n" // r0 = *(MAIN_APP_START_ADR + 4)
"bx r0\n" // переход по адресу в r0
:
: "r"(MAIN_APP_START_ADR)
: "r0"
);
//Note: asm потому что при O0 компилятор делал локальные переменные,
// из-за чего при смене стека он не мог получить адрес для прыжка
}
/**
* @brief Сброс ключа BOOT в Flash.
* Делает ключ «битым», чтобы MCU остался в бутлоадере при следующем перезапуске.
*/
void ResetKey(void)
{
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOOTLOADER_KEY_ADR, 0);
HAL_FLASH_Lock();
}
/**
* @brief Установка ключа BOOT в Flash.
* Указывает, что прошивка записана корректно.
*/
void SetKey(void)
{
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOOTLOADER_KEY_ADR, BL_KEY_APP_WRITTEN);
HAL_FLASH_Lock();
}
/**
* @brief Чтение ключа BOOT из Flash.
* @retval Значение ключа
*/
uint32_t ReadKey(void)
{
return (*(__IO uint32_t*)BOOTLOADER_KEY_ADR);
}
/**
* @brief Стирание ключа BOOT в Flash (одна страница).
*/
void EraseKey(void)
{
FLASH_EraseInitTypeDef EraseInitStruct;
HAL_FLASH_Unlock();
uint32_t PageError = 0x00;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;// erase pages
EraseInitStruct.PageAddress = BOOTLOADER_KEY_ADR; //address
EraseInitStruct.NbPages = 0x01;// num of erased pages
HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
HAL_FLASH_Lock();
}
/**
* @brief Проверка валидности прошивки перед переходом к приложению.
* Проверяет MSP и ResetHandler.
* @retval HAL_OK - прошивка валидна
* HAL_ERROR - прошивка повреждена или некорректна
*/
HAL_StatusTypeDef Verify_Firmware(void)
{
uint32_t msp = *((volatile uint32_t*)(MAIN_APP_START_ADR));
uint32_t reset = *((volatile uint32_t*)(MAIN_APP_START_ADR + 4));
/* 1) Проверка MSP: должен быть указателем в SRAM */
if ((msp < SRAM_START_ADR) || (msp > SRAM_END_ADR))
{
/* Некорректный стек — прошивка невалидна */
return HAL_ERROR;
}
/* 2) Проверка reset handler:
- бит0 должен быть 1 (Thumb)
- адрес без бита0 должен лежать в пределах flash (MAIN_APP_START_ADR .. FLASH_END_ADR)
*/
if ((reset & 0x1) == 0)
{
/* Не Thumb-при-старте — подозрительно */
return HAL_ERROR;
}
uint32_t reset_addr = (reset & (~1U)); /* выравненный адрес */
if ((reset_addr < FLASH_START_ADR) || (reset_addr > FLASH_END_ADR))
{
/* Reset handler вне flash */
return HAL_ERROR;
}
return HAL_OK;
}

View File

@@ -0,0 +1,189 @@
#include "bootloader.h"
Bootloader_t boot = {0};
int main()
{
__disable_irq();
SCB->VTOR = FLASH_BASE;
__enable_irq();
boot.state = BL_STATE_INIT;
while (1)
{
Bootloader_Task(&boot);
}
}
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
void Boot_SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* Включаем внутренний генератор HSI */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF; // без PLL
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/* Настройка шин AHB/APB */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; // HSI = 8 MHz
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 8 MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // PCLK1 = 8 MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = 8 MHz
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/******************************************************************************/
/* Cortex-M3 Processor Interruption and Exception Handlers */
/******************************************************************************/
/**
* @brief This function handles Non maskable interrupt.
*/
void NMI_Handler(void)
{
/* USER CODE BEGIN NonMaskableInt_IRQn 0 */
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
while (1)
{
}
/* USER CODE END NonMaskableInt_IRQn 1 */
}
/**
* @brief This function handles Hard fault interrupt.
*/
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* Включаем тактирование PWR и BKP (APB1) и разрешаем доступ к BKP domain */
/* Записываем напрямую в регистры RCC/APB1ENR и PWR->CR */
SaveErrorCode(0xDEAD);
NVIC_SystemReset();
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
/**
* @brief This function handles Memory management fault.
*/
void MemManage_Handler(void)
{
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
SaveErrorCode(0xBEEF);
NVIC_SystemReset();
/* USER CODE END MemoryManagement_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
/* USER CODE END W1_MemoryManagement_IRQn 0 */
}
}
/**
* @brief This function handles Prefetch fault, memory access fault.
*/
void BusFault_Handler(void)
{
/* USER CODE BEGIN BusFault_IRQn 0 */
/* USER CODE END BusFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
/* USER CODE END W1_BusFault_IRQn 0 */
}
}
/**
* @brief This function handles Undefined instruction or illegal state.
*/
void UsageFault_Handler(void)
{
/* USER CODE BEGIN UsageFault_IRQn 0 */
/* USER CODE END UsageFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
/* USER CODE END W1_UsageFault_IRQn 0 */
}
}
/**
* @brief This function handles System service call via SWI instruction.
*/
void SVC_Handler(void)
{
/* USER CODE BEGIN SVCall_IRQn 0 */
/* USER CODE END SVCall_IRQn 0 */
/* USER CODE BEGIN SVCall_IRQn 1 */
/* USER CODE END SVCall_IRQn 1 */
}
/**
* @brief This function handles Debug monitor.
*/
void DebugMon_Handler(void)
{
/* USER CODE BEGIN DebugMonitor_IRQn 0 */
/* USER CODE END DebugMonitor_IRQn 0 */
/* USER CODE BEGIN DebugMonitor_IRQn 1 */
/* USER CODE END DebugMonitor_IRQn 1 */
}
/**
* @brief This function handles Pendable request for system service.
*/
void PendSV_Handler(void)
{
/* USER CODE BEGIN PendSV_IRQn 0 */
/* USER CODE END PendSV_IRQn 0 */
/* USER CODE BEGIN PendSV_IRQn 1 */
/* USER CODE END PendSV_IRQn 1 */
}
void Error_Handler(void)
{
NVIC_SystemReset();
}

View File

@@ -0,0 +1,91 @@
#include "boot_uart.h"
#include "boot_gpio.h"
UART_HandleTypeDef huart_boot;
/**
* @brief Инициализация UART для бутлоадера
*/
void MX_BOOT_UART_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Включаем тактирование */
__RCC_UART_BOOT_CLK_ENABLE();
__RCC_UART_PORT_CLK_ENABLE();
/* Настройка GPIO TX/RX */
GPIO_InitStruct.Pin = UART_PIN_TX | UART_PIN_RX;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(UART_PORT, &GPIO_InitStruct);
/* Настройка UART */
huart_boot.Instance = UART_BOOT;
huart_boot.Init.BaudRate = UART_SPEED;
huart_boot.Init.WordLength = UART_WORDLENGTH_8B;
huart_boot.Init.StopBits = UART_STOPBITS_1;
huart_boot.Init.Parity = UART_PARITY_NONE;
huart_boot.Init.Mode = UART_MODE_TX_RX;
huart_boot.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart_boot.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart_boot) != HAL_OK)
{
Error_Handler(); // твоя функция обработки ошибок
}
}
/**
* @brief Приём UART: страница + CRC
*/
void Bootloader_UART_Receive_Page(Bootloader_t *bl)
{
uint16_t bytes_received = 0;
uint8_t crc_buf[4];
HAL_StatusTypeDef res;
uint32_t start_tick = HAL_GetTick(); // старт таймера
// Приём данных страницы
while(bytes_received < PAGE_SIZE)
{
uint8_t byte = 0;
res = HAL_UART_Receive(bl->huart, &byte, 1, FW_RECEIVE_TIMEOUT_MS); // блокирующий приём 100ms
if(res == HAL_OK)
{
bl->fw_buffer[bytes_received++] = byte;
start_tick = HAL_GetTick(); // сброс таймера при успешном приёме
LED_BOOT_TOOGLE();
}
else
{
bl->error.bit.timeout_receive = 1;
bl->state = BL_STATE_ERROR; // превышен таймаут
return;
}
}
// Приём CRC (4 байта)
for(uint8_t i = 0; i < 4; i++)
{
res = HAL_UART_Receive(bl->huart, &crc_buf[i], 1, FW_RECEIVE_TIMEOUT_MS);
if(res == HAL_OK)
{
start_tick = HAL_GetTick(); // сброс таймера
}
else
{
bl->error.bit.timeout_receive = 1;
bl->state = BL_STATE_ERROR;
return;
}
}
// Сохраняем CRC в структуру
bl->fw_crc = (crc_buf[0] << 24) | (crc_buf[1] << 16) | (crc_buf[2] << 8) | crc_buf[3];
bl->fw_len = bytes_received;
bl->state = BL_STATE_IDLE;
}

View File

@@ -0,0 +1,493 @@
/******************************************************************************
* @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);
}