494 lines
18 KiB
C
494 lines
18 KiB
C
/******************************************************************************
|
||
* @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);
|
||
}
|
||
|