Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 2

Burn и boot

В данной публикации я хочу рассказать о своей реализации записи "прошивки" в 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Т

Сдержанный, "советский" дизайн упаковки )))

Odoo • A picture with a caption
Экологичненько. Грета одобряет )))