elbear_fw_bootloader/src/bootloader.c

493 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "mik32_hal_pcc.h"
#include "mik32_hal_spifi_w25.h"
#include "power_manager.h"
#include "uart.h"
#include "pad_config.h"
#include "mik32_memory_map.h"
#include "riscv_csr_encoding.h"
#include "csr.h"
#include "string.h"
#define JALR_TO_SPIFI() \
asm volatile( "la ra, 0x80000000\n\t" \
"jalr ra" \
);
#define CHIP_MODE 1 /* Режим работы МФП (SPIFI Memory Mode): 0 = QSPI или 1 = QPI */
#define ACK 0x0F /* Подтверждение */
#define NACK 0xF0 /* Нет подтверждения */
#define MAX_PACKAGE_SIZE 256 /* максимальный размер пакета */
#define TIMEOUT_VALUE 1000000 /* Время ожидания загрузчика до прыжка по умолчанию в RAM 1000000 */
/* Виды команд */
#define FULL_ERASE_CMD_LEN 4
typedef enum
{
PACKAGE_SIZE = 0x30, /* Команда размера пакета */
SEND_PACKAGE = 0x60, /* Команда отправить пакет */
FULL_ERASE = 0xBADC0FEE /* Команда стирания spifi */
} BotloaderComand;
typedef enum
{
SPIFI_ADDRESS = 0x80000000
} AddressMemory;
/* Виды ошибок */
typedef enum
{
ERROR_NONE = 0,
ERROR_TIMEOUT = 1, // Время ожидания истекло
ERROR_CRC
} Bootloader_error;
typedef struct
{
uint8_t* address; // Адрес для записи присылаемых байт
uint16_t size_package; // Размер пакета
uint8_t error;
uint8_t command; // Текущая принятая загрузчиком команда
} Bootloader_attributes;
Bootloader_attributes hBootloader = {(uint8_t*) SPIFI_ADDRESS, 0, ERROR_NONE, 0};
uint32_t timeout = 0;
uint32_t validCommandsTimeout = 0;
void go_to_spifi();
/* Инициализация UART */
void Bootloader_UART_Init()
{
PM->CLK_APB_P_SET = PM_CLOCK_APB_P_UART_0_M; // Включение тактирования UART0
// Настройка выводов PORT0.5 и PORT0.6 на последовательный интерфейс
PAD_CONFIG->PORT_0_CFG |= (0b01 << (5 << 1)) | (0b01 << (6 << 1));
// Включение притяжки к питанию на линии rx
PAD_CONFIG->PORT_0_PUPD |= (0b01 << (5 << 1));
/*
* Настройки USART:
* Асинхронный режим. Включен RX и TX;
* Кадр: 8 бит данных, бит четности выключен, 1 стоп бит;
* Байт LSB - первый бит нулевой.
*/
UART_0->CONTROL1 = 0;
UART_0->CONTROL2 = 0;
UART_0->CONTROL3 = 0;
UART_0->DIVIDER = 138; /* Baudrate = 230400 */
UART_0->FLAGS = 0xFFFFFFFF;
UART_0->CONTROL1 = UART_CONTROL1_RE_M | UART_CONTROL1_TE_M | UART_CONTROL1_UE_M;
/* Ожидание флагов готовности RX и TX */
while (!(UART_0->FLAGS & (UART_FLAGS_REACK_M | UART_FLAGS_TEACK_M)))
;
}
void Bootloader_UART_Deinit()
{
UART_0->CONTROL1 = 0;
UART_0->CONTROL2 = 0;
UART_0->CONTROL3 = 0;
UART_0->DIVIDER = 0x0000; // сброс бодрейта
UART_0->FLAGS = 0xFFFFFFFF; // сброс всех флагов
UART_0->TXDATA = 0x00;
// Настройка выводов PORT0.5 и PORT0.6 на порт общего назначения
PAD_CONFIG->PORT_0_CFG &= ~((0b11 << (5 << 1)) | (0b11 << (6 << 1)));
// Отключение притяжки на линии rx
PAD_CONFIG->PORT_0_PUPD &= ~(0b01 << (5 << 1));
PM->CLK_APB_P_CLEAR = PM_CLOCK_APB_P_UART_0_M; // Выключение тактирования UART0
}
/* Отправить байт */
void Bootloader_UART_WriteByte(uint16_t Write_Byte)
{
UART_0->TXDATA = Write_Byte;
/* Ожидаем успешную передачу */
while (!(UART_0->FLAGS & UART_FLAGS_TC_M))
;
}
/* Ожидание и считывание байта */
uint16_t Bootloader_UART_ReadByte()
{
timeout = 0;
while ((!(UART_0->FLAGS & UART_FLAGS_RXNE_M)) && (timeout != TIMEOUT_VALUE))
{
timeout++;
validCommandsTimeout++; // Увеличить таймаут валидных команд
}
if (timeout == TIMEOUT_VALUE)
{
hBootloader.error = ERROR_TIMEOUT;
}
return (uint16_t)UART_0->RXDATA;
}
/* Обработчик ошибок */
void Bootloader_ErrorHandler()
{
switch (hBootloader.error)
{
case ERROR_TIMEOUT:
if (UART_0->FLAGS & UART_FLAGS_ORE_M)
UART_0->FLAGS |= UART_FLAGS_ORE_M;
go_to_spifi(); // переход в основную программу, если в течение TIMEOUT_VALUE нет принятых данных
break;
case ERROR_CRC:
// отправить nack и перейти в основную программу, если приняли некорректные данные
Bootloader_UART_WriteByte(NACK);
if (UART_0->FLAGS & UART_FLAGS_ORE_M)
UART_0->FLAGS |= UART_FLAGS_ORE_M;
go_to_spifi();
break;
}
hBootloader.error = ERROR_NONE;
}
SPIFI_HandleTypeDef spifi = {.Instance = SPIFI_CONFIG};
uint8_t erase_chip(SPIFI_HandleTypeDef *spifi)
{
const uint32_t cmd_chip_erase =
SPIFI_DIRECTION_INPUT |
SPIFI_CONFIG_CMD_INTLEN(0) |
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_SERIAL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
SPIFI_CONFIG_CMD_OPCODE(0xC7); //CHIP_ERASE = 0x60 или 0xC7
HAL_SPIFI_W25_WriteEnable(spifi);
uint8_t stat = HAL_SPIFI_SendCommand_LL(spifi, cmd_chip_erase, 0, 0, 0, 0, 0, HAL_SPIFI_TIMEOUT);
stat = HAL_SPIFI_W25_WaitBusy(spifi, 10000000); // SPIFI_W25_PROGRAM_BUSY = 100000
return stat;
}
/* Загрузить данные пакета в RAM */
#define SIZE_4K 4096
// разметка строки в хекс-файле
#define BYTE_COUNT_POS 0 // индекс счетчика байт данных
#define ADDRESS_POS 1 // индекс адреса
#define ADDRESS_QTY 2 // количество байт адреса
#define RECORD_TYPE_POS 3 // индекс типа записи
#define DATA_POS 4 // индекс начала данных в команде
// типы записей в хекс-файле
#define REC_TYPE_DATA 0x00
#define REC_TYPE_EOF 0x01
#define REC_TYPE_EXT_LIN_ADDR 0x04
uint32_t abs_addr = 0; // адрес из хекса
uint32_t rel_addr = 0; // адрес от начала области spifi, по нему определяем, надо ли стирать сектор и какой именно
#define TAIL_SIZE 15 // если попадутся строки хекса, в которых не 16 байт, то мы рискуем записать данные уарта мимо буфера package_data. а если больше 16 байт, то это проблема завтрашнего дня
uint8_t page_data[MAX_PACKAGE_SIZE + TAIL_SIZE] = {0}; // сюда собираем распарсенные данные из хекса
uint16_t page_fill_size = 0; // счетчик, сколько заполнно в page_data. когда page_data заполнена до конца - будем записывать в spifi
void mem_write()
{
// если адрес дошел до начала нового сектора, стираем новый сектор
rel_addr = (uint32_t)(hBootloader.address) - 0x80000000;
if ((rel_addr % SIZE_4K) == 0)
HAL_SPIFI_W25_SectorErase4K(&spifi, rel_addr);
// записываем страницу в 256 байт в spifi
HAL_SPIFI_W25_PageProgram(&spifi, (uint32_t)hBootloader.address, MAX_PACKAGE_SIZE, page_data);
// увеличиваем адреса, по которым писать и стирать
hBootloader.address += MAX_PACKAGE_SIZE;
// обнуляем часть буфера, которая была записана в память и уменьшаем счетчик заполнения буфера на столько, сколько было записано
if (page_fill_size <= MAX_PACKAGE_SIZE)
page_fill_size = 0;
else
page_fill_size -= MAX_PACKAGE_SIZE;
memset(&page_data[0], 0, MAX_PACKAGE_SIZE);
// хвост копируем в начало буфера, чтобы записать его в следующий раз
memcpy(&page_data[0], &page_data[MAX_PACKAGE_SIZE], TAIL_SIZE);
// а сам хвост обнуляем
memset(&page_data[MAX_PACKAGE_SIZE], 0, TAIL_SIZE);
}
void Bootloader_parseHexAndLoadInMemory(uint8_t rx_data[])
{
// из принятых данных вытаскиваем тип записи
uint8_t rec_type = rx_data[RECORD_TYPE_POS];
switch (rec_type)
{
case REC_TYPE_EXT_LIN_ADDR:
// если так получилось, что нам слали данные, буфер на 256 не заполнился, а тут прилетела команда смены адреса, то пишем сколько есть
if (page_fill_size != 0)
mem_write();
// собираем адрес, с которого начинаем писать из данных команды смены адреса. нам присылают только 2 старших байта адреса
abs_addr = (rx_data[DATA_POS] << 24) + (rx_data[DATA_POS+1] << 16);
hBootloader.address = (uint8_t*)abs_addr;
break;
case REC_TYPE_DATA:
// перекладываем из приемного буфера данные команды в буфер для записи
memcpy(&page_data[page_fill_size], &rx_data[DATA_POS], rx_data[BYTE_COUNT_POS]);
// указываем, на сколько заполнился буфер
page_fill_size += rx_data[BYTE_COUNT_POS];
// если пора записывать целую страницу - пишемм
if (page_fill_size >= 256)
mem_write();
break;
case REC_TYPE_EOF: // конец прошивки
Bootloader_UART_WriteByte(ACK);
// если есть недозаполненная страница, записываем ее как есть
if (page_fill_size != 0)
mem_write();
// и идем в записанную программу
go_to_spifi();
break;
default:
break;
}
}
uint8_t uart_data[MAX_PACKAGE_SIZE] = {0}; // Массив данных из полученного пакета
uint32_t crc_acc = 0;
uint8_t crc = 0;
void Bootloader_UART_ReadPackage()
{
crc_acc = 0;
for (uint32_t counter = 0; counter < hBootloader.size_package; counter++)
{
timeout = 0;
while ((!(UART_0->FLAGS & UART_FLAGS_RXNE_M)) && (timeout != TIMEOUT_VALUE)) // Ожидание байта пакета
timeout++;
if (timeout == TIMEOUT_VALUE)
{
hBootloader.error = ERROR_TIMEOUT;
break;
}
uart_data[counter] = UART_0->RXDATA;
// контрольная сумма идет последним байтом в пакете. Суммируем всё, кроме нее
if (counter < (hBootloader.size_package-1))
crc_acc += uart_data[counter];
else
{
// посчитать контрольную сумму и сравнить
crc = (256 - crc_acc % 256) % 256;
if (crc != uart_data[counter])
hBootloader.error = ERROR_CRC;
}
}
if (hBootloader.error != ERROR_NONE)
Bootloader_parseHexAndLoadInMemory(uart_data);
}
uint8_t eraseChipBufferIndex = 0; // Индекс для накопления команды erase chip
void Bootloader_Commands()
{
while (1)
{
hBootloader.command = Bootloader_UART_ReadByte(); // Ожидание и считывание команды
// если долго не приходили валидные команды, переход в основную программу
if (validCommandsTimeout >= TIMEOUT_VALUE)
go_to_spifi();
if (hBootloader.error)
Bootloader_ErrorHandler(); // Обработчик ошибок
else
{
switch (hBootloader.command)
{
case PACKAGE_SIZE:
validCommandsTimeout = 0; // Сброс таймаута валидных команд
eraseChipBufferIndex = 0;
Bootloader_UART_WriteByte(ACK); // Подтвердить команду
hBootloader.size_package = Bootloader_UART_ReadByte() + 1; // Прочитать размер пакета
Bootloader_UART_WriteByte(ACK); // Подтвердить
break;
case SEND_PACKAGE:
validCommandsTimeout = 0; // Сброс таймаута валидных команд
eraseChipBufferIndex = 0;
Bootloader_UART_WriteByte(ACK); // Подтвердить команду
Bootloader_UART_ReadPackage(); // Получить пакет и скопировать его в RAM
if (hBootloader.error)
Bootloader_ErrorHandler(); // Обработчик ошибок
else
Bootloader_UART_WriteByte(ACK); // Подтвердить считывание и копирования пакета в RAM
break;
default:
// Вычислить ожидаемый байт команды FULL_ERASE на основе текущего индекса
uint8_t expectedByte = (FULL_ERASE >> ((FULL_ERASE_CMD_LEN - eraseChipBufferIndex - 1) * 8)) & 0xFF;
// Если полученный байт совпадает с ожидаемым байтом команды FULL_ERASE
if (hBootloader.command == expectedByte)
{
// Переход к следующему байту
eraseChipBufferIndex++;
// Если команда FULL_ERASE полностью получена
if (eraseChipBufferIndex == FULL_ERASE_CMD_LEN)
{
Bootloader_UART_WriteByte(ACK); // Подтвердить получение команды
// Очистка чипа
if (erase_chip(&spifi) != HAL_OK)
Bootloader_UART_WriteByte(NACK);
else
Bootloader_UART_WriteByte(ACK); // Подтвердить успешную очистку
eraseChipBufferIndex = 0; // Сброс индекса для следующего приема
}
validCommandsTimeout = 0; // Сброс таймаута валидных команд
}
else // Если байт не совпал ни с какими валидными командами
eraseChipBufferIndex = 0; // Сброс индекса команды FULL_ERASE
break;
}
}
}
}
void SystemClock_Config();
int main()
{
SystemClock_Config();
HAL_SPIFI_MspInit(&spifi);
HAL_SPIFI_Reset(&spifi);
/* Переключение флеш-памяти в нормальный режим с командами, передав ей "0" в промежуточном байте */
const uint32_t cmd_chip_read_xip_init =
SPIFI_DIRECTION_INPUT |
#if CHIP_MODE == 1
SPIFI_CONFIG_CMD_INTLEN(1) |
#else
SPIFI_CONFIG_CMD_INTLEN(3) |
#endif
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_3ADDR) |
SPIFI_CONFIG_CMD_OPCODE(0xEB);
uint8_t tmp_byte_xip_init[1] = {0};
HAL_SPIFI_SendCommand_LL(&spifi, cmd_chip_read_xip_init, 0, 1, tmp_byte_xip_init, 0, 0, HAL_SPIFI_TIMEOUT);
#if CHIP_MODE == 1
/* Переключение флеш-памяти из режима QPI в обычный режим SPI */
const uint32_t cmd_qpi_disable =
SPIFI_DIRECTION_INPUT |
SPIFI_CONFIG_CMD_INTLEN(0) |
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
SPIFI_CONFIG_CMD_OPCODE(0xFF);
HAL_SPIFI_SendCommand_LL(&spifi, cmd_qpi_disable, 0, 0, 0, 0, 0, HAL_SPIFI_TIMEOUT);
#endif
Bootloader_UART_Init(); // Инициализация UART. Настройка выводов и тактирования
Bootloader_Commands(); // Обработка и ожидание команд
while (1)
{
/* code */
}
}
void SystemClock_Config(void)
{
PCC_InitTypeDef PCC_OscInit = {0};
PCC_OscInit.OscillatorEnable = PCC_OSCILLATORTYPE_ALL;
PCC_OscInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
PCC_OscInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
PCC_OscInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
PCC_OscInit.AHBDivider = 0;
PCC_OscInit.APBMDivider = 0;
PCC_OscInit.APBPDivider = 0;
PCC_OscInit.HSI32MCalibrationValue = 128;
PCC_OscInit.LSI32KCalibrationValue = 128;
PCC_OscInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
PCC_OscInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;
HAL_PCC_Config(&PCC_OscInit);
}
void SPIFI_disableDataCache(SPIFI_MemoryModeConfig_HandleTypeDef *spifi)
{
spifi->Instance->CTRL |= SPIFI_CONFIG_CTRL_D_CACHE_DIS_M;
}
void SPIFI_Init()
{
HAL_SPIFI_MspInit(&spifi);
HAL_SPIFI_Reset(&spifi);
/* В Winbond для выставления QE используется команда 0x01 в 1-м бите 2го статус регистра. */
uint8_t sreg1 = HAL_SPIFI_W25_ReadSREG(&spifi, W25_SREG1);
if (sreg1 > 0x03) sreg1 = 0; // снятие защиты от записи (protection bits)
uint8_t sreg2 = HAL_SPIFI_W25_ReadSREG(&spifi, W25_SREG2);
if (!(sreg2 & 0x02)) sreg2 |= 0x02; // установка бита QE (quad enable)
HAL_SPIFI_W25_WriteSREG(&spifi, sreg1, sreg2);
#if CHIP_MODE == 1
/* Переключение флеш-памяти в режим QPI, когда весь обмен четырёхпроводной */
const uint32_t cmd_qpi_enable =
SPIFI_DIRECTION_INPUT |
SPIFI_CONFIG_CMD_INTLEN(0) |
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_SERIAL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
SPIFI_CONFIG_CMD_OPCODE(0x38);
HAL_SPIFI_SendCommand_LL(&spifi, cmd_qpi_enable, 0, 0, 0, 0, 0, HAL_SPIFI_TIMEOUT);
/* Переключение флеш-памяти в режим без последующих команд чтения, передав ей "0x20" в промежуточном байте */
const uint32_t cmd_chip_read_qpi_xip_init =
SPIFI_DIRECTION_INPUT |
SPIFI_CONFIG_CMD_INTLEN(1) |
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE_3ADDR) |
SPIFI_CONFIG_CMD_OPCODE(0xEB);
uint8_t tmp_byte_xip_init[1] = {0};
HAL_SPIFI_SendCommand_LL(&spifi, cmd_chip_read_qpi_xip_init, 0, 1, tmp_byte_xip_init, 0, 0x20, HAL_SPIFI_TIMEOUT);
#else
/* Переключение флеш-памяти в режим без последующих команд чтения, передав ей "0x20" в первом промежуточном байте */
const uint32_t cmd_chip_read_xip_init =
SPIFI_DIRECTION_INPUT |
SPIFI_CONFIG_CMD_INTLEN(3) |
SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_OPCODE_SERIAL) |
SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE_3ADDR) |
SPIFI_CONFIG_CMD_OPCODE(0xEB);
uint8_t tmp_byte_xip_init[1] = {0};
HAL_SPIFI_SendCommand_LL(&spifi, cmd_chip_read_xip_init, 0, 1, tmp_byte_xip_init, 0, 0x20, HAL_SPIFI_TIMEOUT);
#endif
/* Режим SPIFI без передачи команд, но с "0x20" в первых промежуточных байтах. */
SPIFI_MemoryCommandTypeDef cmd_mem = {
.OpCode = 0xEB,
.FieldForm = SPIFI_CONFIG_CMD_FIELDFORM_ALL_PARALLEL,
.FrameForm = SPIFI_CONFIG_CMD_FRAMEFORM_NOOPCODE_3ADDR,
.InterimData = 0x20,
#if CHIP_MODE == 1
.InterimLength = 1 /* Количество промежуточных данных в команде 0xEB режима QPI равно 1 байт. */
#else
.InterimLength = 3 /* Количество промежуточных данных в команде 0xEB режима QSPI равно 3 байта. */
#endif
};
SPIFI_MemoryModeConfig_HandleTypeDef spifi_mem = {
.Instance = spifi.Instance,
.CacheEnable = SPIFI_CACHE_ENABLE,
.CacheLimit = 0x00010000,
.Command = cmd_mem
};
SPIFI_CONFIG->CTRL &= 0xFFF0FFFF;
HAL_SPIFI_MemoryMode_Init(&spifi_mem);
// SPIFI_disableDataCache(&spifi_mem);
}
void go_to_spifi()
{
Bootloader_UART_Deinit();
SPIFI_Init();
write_csr(mtvec, 0x80000000);
JALR_TO_SPIFI();
}