W7500x Dual Boot / BootLoader

C6000,C2000,CortexM3/M4/M4,x86,FPGA,STM8 등만 사용하다가 W7500x를 접한지 2주 정도 되었네요.

상용제품을 만들다 보니 Remote F/W update가 기본적으로 되어야 했습니다. 주로 외국에 수출 또는 보안이 까다로운 공장에 설치되다 보니 버그가 발견되면 F/W Update는 필수 입니다. 단 조건은 제품을 접근하지 않고 통신 port만으로 Update해야 합니다. 지금까지는 제품과 Host간 연결인 UART를 이용하여 Update를 진행 하였습니다. 이번에는 Ethernet이 되므로 TCP또는 RS-485/422 3Mbps로 Update하고자 합니다.
(W7500P가 UART 3Mbps 지원하지 않는다고 되어있는데, 48Mhz 그대로 UART Base Clock에 넣어니 잘 동작하더군요.)

이번 Project는 메인 CPU가 Nios(Intel 구:Altera)II/f이고 이에 더불어 FPGA도 Update해야 합니다.
그리고 Host와 붙은 Part가 W7500P이고 사용자가 TCP,RS-485,RS-422,RS-232 무엇을 사용할지 몰라 연결만 되어 있다면 W7500P, FPGA,NIOS 3종의 F/W Update 가능해야 합니다.

W7500P를 Update할때는 (W7500이 Flash에서 실행되는 구조라) User Bootloader 없이는 Update가 불가능 합니다. 그래서 W7500P는 User bootloader가 필수 입니다. 그리고 FPGA나 NIOS는 bootloader에 진입하지 않고 Update합니다.

하여, W7500P가 CortexM0가 내장되어 있어, CortexM3처럼 Interrupt Vector를 간단하게 변경이 가능할 것이라고 생각했는데, SCB->VTOR를 사용할 수 없더군요.

즉, CortexM3/M4/M7 처럼 Interrupt Vector를 옮기는 기능인 "SCB->VTOR"을 사용할 수 없네요.

되게 하는 방법은 CortexM0를 속이는 방법밖에 없다는 결론에 이러렀습니다.
즉, Target App.에서도 Bootloader의 Interrupt를 사용하는 것처럼…

하는 수 없이

  1. Bootloader + F/W Update : User Bootloader Prj. (이하 Bootloader)
    와 Target Bootloader Prj. (이하 Target )를 만듭니다.

  2. Target Prj.도 프로그램 사이즈가 별로 크지 않아
    Bootloader를 시작 : 0x00000000 Size : 0x8000
    Target을 : 0x00008000 Size: 0x18000으로 합니다.

  3. Booting 순서
    일반 부팅–> Bootloader–> DATA1 영역 (0x3FF00)에 Magic Code가 0xFFFFFFFF
    즉 flash 가 지워져 있다면 Application(0x8000)으로 Jump합니다.
    물론 0x8000, 0x8004 영역의 내용도 확인하고 Jump합니다.

    만약 Magic Code가 0xAA55AA55이면 Bootloader에 머물러 있습니다.
    <== 통신(UART,Ethernet) F/W Update시 Magic Code를 변경하고 System Reset합니다.

// 이 함수는 Cortex-M3… 등에도 그대로 사용할 수 있습니다.

typedef void (*pFunction)(void);
// Target Application Address
#define ADDR_APPLICATION (0x00008000)

void GotoApplication(void)
{
pFunction Jump_To_Application;
uint32_t JumpAddress;

// Check Jump Op Code 
if (((*(volatile uint32_t*)ADDR_APPLICATION) & 0xFFFE0000 ) == 0x20000000)
{
    // Jump to user application 
    JumpAddress = *(volatile uint32_t*) (ADDR_APPLICATION + 4); // Main Function
    Jump_To_Application = (pFunction) JumpAddress;
    __set_MSP(*(volatile uint32_t*) ADDR_APPLICATION);       // user application's Stack Pointer 
    Jump_To_Application();
}

}

// Magic Code가 무엇인지 확인
if(*(volatile uint32_t *)(DAT1_START_ADDR+0x10) == 0xFFFFFFFF)
{
GotoApplication();
}

  1. 핵심은 양쪽에서 모두 Interrupt를 사용하는 것입니다.
    위 Code만 있어도 Program Count 변경은 문제 없습니다.
    그러나 위 code만으로 Application에서 Interrupt는 사용할 수 없습니다.
    Interrupt vector가 Cortex-M0에서는 고정되어 있기 때문에… Application으로 진입할때
    Interrupt vector 함수 Point를 변경하면 됩니다.
    변경하는 방식은
    예를 들어 interrupt Uart0가 발생하면 항상 (0x0000+(12<<2))의 주소를 참조하여 PC를 변경합니다. 아마도 위와 같이 두 Prj를 나누어 놓았다면 Bootloader는 0x0000~7FFF 내용으로 되어 있고
    Target은 0x8000~1FFFF로 되어 있을 것입니다.

만약 Keil을 사용한다면 (다른 Compiler도 상관없음) Memory View로 보시면 됩니다.
아니면 map file을 분석하여도 됩니다.

그런데 프로그램을 수정할 때마다 내용이 변경될 수 있으므로 자동으로 매핑되도록 Interrupt 함수의 주소를 매핑하면 됩니다.

extern void UART0_Handler(void);
extern void UART1_Handler(void);
extern void SysTick_Handler(void);

#define FLASH_GO_BOOT 0x00000000

void RemapInterruptB2A(void)
{
int i,k;
unsigned pFunc;
unsigned *IT4;

    IT4 = (unsigned *)U0RxBuffer;
    
		NVIC->ISER[0] = 0;	
    NVIC->ICER[0] = 0xFFFFFFFF;
   *(volatile uint32_t*) (SYST_CSR) &= 0xFFFFFFFE;	


    for(i=0;i<0x40;i++)
    {
	IT4[i] = *(volatile uint32_t *)(FLASH_GO_BOOT+(i<<2));
   }		


k = 0;
		
if( *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_TICK_TIMER<<2)) == 0xFFFFFFFF) 
  {
    k = 8;
}	

if( *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_UART0<<2)) == 0xFFFFFFFF) 
  {
	 k = 8;
}	

  if( *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_UART1<<2)) == 0xFFFFFFFF) 
  {
        k = 8;
}	

    IT4[5] = (k==8) ?  DEFAULT_IP: *(volatile uint32_t *)(DAT1_START_ADDR+0x14); 			
 IT4[6] = (k==8) ? 0 : *(volatile uint32_t *)(DAT1_START_ADDR+0x18); 
		
if((IT4[VIP_TICK_TIMER] & 0x8000)==0) 	k += 1;
if((IT4[VIP_UART0] & 0x8000)==0) 		k += 2;
if((IT4[VIP_UART1] & 0x8000)==0) 		k += 4;
		
		
if(k>=7)
{
            // Magic Code Clear : Boot--> Jump Target
	IT4[4] = 0xFFFFFFFF;
	DO_IAP(IAP_ERAS_SECT,DAT1_START_ADDR,0,0);
	DO_IAP(IAP_PROG, DAT1_START_ADDR,U0RxBuffer,SECT_SIZE);					   
}


    for(i=0;i<0x40;i++)
    {
		IT4[i] = *(volatile uint32_t *)(FLASH_GO_BOOT+(i<<2));
     }		
		
         // Tick Interrupt를 확인
		i = 0;
		pFunc = (unsigned)(&SysTick_Handler);
		if( IT4[VIP_TICK_TIMER] !=  pFunc) 
		{
			IT4[VIP_TICK_TIMER] = pFunc;
			    i |= 1;
	}	
		
           // UART0 interrupt Address를 확인
		pFunc = (unsigned)(&UART0_Handler);
		if( IT4[VIP_UART0] !=  pFunc) 
		{
				IT4[VIP_UART0] = pFunc;
			    i |= 2;
		}	

		pFunc = (unsigned)(&UART1_Handler);
		if( IT4[VIP_UART1] !=  pFunc) 
		{
				IT4[VIP_UART1] = pFunc;
			    i |= 4;
		}	

                  // 주소가 변경되었다고 판단되면 Interrupt vector function address를 임시로 변경
		if(i != 0)
		{	
	
			DO_IAP(IAP_ERAS_SECT,0x00000,0,0);
			DO_IAP(IAP_PROG, 0x00000,U0RxBuffer,SECT_SIZE);		
                    }				

}

위와 같이 Target에서 항상 Vector Address가 변경되었는지 확인하여 0x3FF00과 0x0000 Flash Address의 내용을 Update합니다. Bootloader의 원래 Data는 변경되었다고 판단되면 0x3FF00에 저장합니다.

위 Code는 Target 초기화 부분에 있을 내용이고

Bootloader로 Jump하기 직전에는 Interrupt를 복원시켜 놓아야 합니다.
기본적으로 현재 사용하고 있는 주소에서 Flash를 변경하면 Hard Fault에 빠질수 있기 때문에 미리
변경하고 Bootloader로 진입니다.

Bootloader로 진입하기 직전에 Interrupt vector jump address를 복원해 놓습니다.
Bootloader로 진입하기 위해 Magic Code도 변경해 놓습니다.

void RemapInterruptD12B(void)
{
int i;
unsigned *IT4;

    IT4 = (unsigned *)U0RxBuffer;
    
		NVIC->ISER[0] = 0;	
    NVIC->ICER[0] = 0xFFFFFFFF;
   *(volatile uint32_t*) (SYST_CSR) &= 0xFFFFFFFE;


    for(i=0;i<0x40;i++)
    {
			  IT4[i] = *(volatile uint32_t *)(FLASH_GO_BOOT+(i<<2));
		}		
		
   IT4[VIP_TICK_TIMER] = *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_TICK_TIMER<<2));
   IT4[VIP_UART0] = *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_UART0<<2));
		 IT4[VIP_UART1] = *(volatile uint32_t *)(DAT1_START_ADDR+(VIP_UART1<<2));
		
  // Change Interrupt Vector
		DO_IAP(IAP_ERAS_SECT,FLASH_GO_BOOT,0,0);
		DO_IAP(IAP_PROG, FLASH_GO_BOOT,U0RxBuffer,SECT_SIZE);			

    for(i=0;i<0x40;i++)
    {
			  IT4[i] = *(volatile uint32_t *)(DAT1_START_ADDR+(i<<2));
		}				
		
   IT4[4] = 0xAA55AA00;
		 IT4[5] =  0xC0A84D0A;//(192<<24) | (168<<16) | (77<8) | 10;
		 //IT4[7] = Network & BR
   
		// BootCode!!
		DO_IAP(IAP_ERAS_SECT,DAT1_START_ADDR,0,0);
		DO_IAP(IAP_PROG, DAT1_START_ADDR,U0RxBuffer,SECT_SIZE);					   
	   
		NVIC_SystemReset();

}


기타 의문사항이 있다면 댓글을 남겨 놓으시면 답변 드리겠습니다.