В данной публикации я хочу рассказать о своей реализации записи "прошивки" в ROM ( boot-flash ) и передачи ей управления.
Исходники ( sorry, но совершенно полными они не будут, но, надеюсь, достаточными для Ваших доработок ) приведу с упором на использование отечественного микроконтроллера К1921ВК01Т .
Не забываем, что код, вызывающий перезапись загрузочного флэша, должен вызывать из RAM ( у этого МК она располагается с 0x20000000 ). Для этого я сложил все функции работы с boot-flash в один файл boot_flash.c и задал для него следующие настройки :
main() из main.c
int main()
{
PeriphInit(); // Инициализируем периферию
DelayMicroS(500); // Немножко ждём
__disable_irq(); // отключаем прерывания
firmware_burn(); // прошиваем, если надо
__enable_irq(); // включаем прерывания
DelayMicroS(500);
boot_exit(); // выходим из boot-a, передаём управление на main из прошивки servcore
while(1) ; // соблюдаем канон бесконечного цикла )))
return 0;
}
firmware_burn() из boot_flash.c
int8_t is_burn; // флаг прошивки
uint8_t buf256[256]; // буфер для чтения/записи
uint32_t ii, jj, x_addr,
resourceOffset, resourceSize, // расположение и размер прошивки во внешней flash
pg_From, pg_To; // начальный и конечный адреса BOOTFLASH (ROM), куда прошивку надо записать
union {
uint8_t data8[USERFLASH_NVR_PAGE_SIZE_BYTES];
uint32_t data32[USERFLASH_NVR_PAGE_SIZE_BYTES / 4];
} page_arr;
uint32_t data;
uint32_t cfgword;
void firmware_burn(void)
{
// читаем флаг прошивки из внешней flash
// OuterFlash_ReadBytes( ...
// is_burn = ...
if( ! is_burn )
return;
// OuterFlash_ReadBytes( ...
// resourceOffset = ...
// resourceSize = ...
// pg_From = ... // с какого адреса
// pg_To = ... // по какой адрес записывать в BOOTFLASH
for(ii = 0; ii <= resourceSize; ii+=256 )
{
x_addr = resourceOffset+ii;
DelayMicroS(500);
OuterFlash_ReadBytes(buf256, x_addr, 256);
DelayMicroS(500);
for(jj = 0; jj < 16; jj++)
{
x_addr = pg_From + ii + jj*16;
if( x_addr % BOOTFLASH_PAGE_SIZE_BYTES == 0 )
{
flash_erase_page(x_addr, BOOTFLASH_MEM, FLASH_MAIN); // стираем страницу в BOOTFLASH перед записью в неё
DelayMicroS(500);
}
DelayMicroS(500);
flash_write(x_addr, BOOTFLASH_MEM, FLASH_MAIN, (uint32_t*)(buf256+jj*16)); // записываем в BOOTFLASH по 16 байт (особенность архитектуры К1921ВК01Т) за один раз
DelayMicroS(500);
}
}
// для К1921ВК01Т после записи прошивки необходимы следующие манипуляции :
// чтобы он загрузился после рестарта,
// устанавливает бит BOOTFROM_IFB в config word
//читаем всю страницу
for (uint32_t i = 0; i < USERFLASH_NVR_PAGE_SIZE_BYTES; i++)
{
flash_read(i, USERFLASH_MEM, FLASH_NVR, &data);
DelayMicroS(1000);
page_arr.data8[i] = (uint8_t)data;
}
cfgword = page_arr.data32[CFGWORD_OFFSET / 4];
//модифицируем конфигурацию
page_arr.data32[CFGWORD_OFFSET / 4] = cfgword | BOOTFROM_IFB;
//стираем и пишем обратно
flash_erase_page(CFGWORD_OFFSET, USERFLASH_MEM, FLASH_NVR);
DelayMicroS(1000);
for (uint32_t i = 0; i < USERFLASH_NVR_PAGE_SIZE_BYTES; i++)
{
if (page_arr.data8[i] != 0xFF)
{
data = page_arr.data8[i];
flash_write(i, USERFLASH_MEM, FLASH_NVR, &data);
DelayMicroS(1000);
}
}
// всё сделали,
// теперь сохраним флаг, что уже прошили
// OuterFlash_WriteBytes( ...
}
boot_exit() из boot_core.c
void boot_exit()
{
typedef void (*p_func)(void);
p_func user_app; // эту функцию будем вызывать, чтобы перейти в servcore
// отключаем прерывания
__disable_irq();
NVIC->ICER[0] = 0xFFFFFFFF;
NVIC->ICER[1] = 0xFFFFFFFF;
NVIC->ICER[2] = 0xFFFFFFFF;
NVIC->ICER[3] = 0xFFFFFFFF;
NVIC->ICER[4] = 0xFFFFFFFF;
__DSB(); // Data Synchronization Barrier
__ISB(); // Instruction Synchronization Barrier
//сбрасываем порты
NT_COMMON_REG->GPIODEN0 = 0x00020062;
NT_COMMON_REG->GPIODEN1 = 0x08000000;
NT_COMMON_REG->GPIODEN2 = 0x00000400;
NT_COMMON_REG->GPIODEN2 = 0x00000000;
NT_COMMON_REG->GPIOPCTLA = 0x0;
NT_GPIOA->ALTFUNCCLR = 0xFFFFFFFF;
NT_GPIOA->OUTENCLR = 0xFFFFFFFF;
NT_GPIOA->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLB = 0x0;
NT_GPIOB->ALTFUNCCLR = 0xFFFFFFF8;
NT_GPIOB->OUTENCLR = 0xFFFFFFFF;
NT_GPIOB->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLC = 0x0;
NT_GPIOC->ALTFUNCCLR = 0xFFFFFFFF;
NT_GPIOC->OUTENCLR = 0xFFFFFFFF;
NT_GPIOC->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLD = 0x0;
NT_GPIOD->ALTFUNCCLR = 0xFFFFF7FF;
NT_GPIOD->OUTENCLR = 0xFFFFFFFF;
NT_GPIOD->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLE = 0x0;
NT_GPIOE->ALTFUNCCLR = 0xFFFFFBFC;
NT_GPIOE->OUTENCLR = 0xFFFFFFFF;
NT_GPIOE->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLF = 0x0;
NT_GPIOF->ALTFUNCCLR = 0xFFFFFFFF;
NT_GPIOF->OUTENCLR = 0xFFFFFFFF;
NT_GPIOF->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLG = 0x0;
NT_GPIOG->ALTFUNCCLR = 0xFFFFFFFF;
NT_GPIOG->OUTENCLR = 0xFFFFFFFF;
NT_GPIOG->DATAOUT = 0x0;
NT_COMMON_REG->GPIOPCTLH = 0x0;
NT_GPIOH->ALTFUNCCLR = 0xFFFFFFFF;
NT_GPIOH->OUTENCLR = 0xFFFFFFFF;
NT_GPIOH->DATAOUT = 0x0;
//сбрасываем периферию
NT_COMMON_REG->PER_RST0 = 0;
NT_COMMON_REG->UART_CLK = 0;
//переходим на тактирование от RC
NT_COMMON_REG->SYS_CLK = 0;
uint32_t timeout_counter = SYSCLK_SWITCH_TIMEOUT;
while (timeout_counter) {
timeout_counter--;
}
NT_COMMON_REG->PLL_NF = 0;
NT_COMMON_REG->PLL_NR = 0;
NT_COMMON_REG->PLL_OD = 0;
NT_COMMON_REG->PLL_CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
SysTick->CTRL = 0;
uint32_t *ui4004 = (uint32_t*)(0x00004000 + 4); // адрес перехода в servcore
user_app = (void (*)(void))(*ui4004); // это будет main() из servcore
SCB->VTOR = 0x00004000;
__enable_irq();
__set_MSP(*(volatile uint32_t*)(0x00004000));
Delay910(1000);
user_app();
while (1) {
};
}
main() инициализирует необходимую периферию, если необходимо - прожигает boot_flash, и передаёт управление прошивке servcore.
Внимание. Код для прожига должен располагаться в RAM, смотрите предыдущую статью Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 1
Как работать с boot-flash на k1921 подсмотрено здесь - https://bitbucket.org/niietcm4/k1921vkx_flasher/src/master/
К1921ВК01Т
Сдержанный, "советский" дизайн упаковки )))
Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 2
Burn и boot
Микроконтроллеры