HC32L130基於Xmodem協議實現IAP串口在線升級

在開始寫文章之前,要先吐槽一下國產單片機,不對是國產單片機廠家,他們的技術支持實在太爛了,爛的讓你懷疑等技術支持還不如自己啃手冊,尤其是那種中間有代理商的廠家,技術支持更是良莠不齊,時效性也不行,問個問題等幾天之後回覆,最可氣的是回覆告訴你這個功能芯片不支持,真是傷不起了。如果成本不是特別敏感,研發實力不是很強大,不要碰國產單片機,特別是有中間代理的。

一、BOOT程序移植

HC32官方提供了一個自定義協議的IAP升級demo,自定義協議,但是苦於沒有提供上位機程序源碼,不方便以後升級,而且官方的升級軟件看着也很不完善。如下圖所示。所以決定自己移植一下Xmodem協議。

 

之前在別的單片機上用過Xmodem協議做串口IAP升級協議,所以這裏,只需要移植一下就可以了。Xmodem框架如下圖所示。

 

因爲框架已經寫好,所以只需要修改對應的接口就可以了。在進行IAP的過程主要涉及到如下四個外設

  1. 系統時鐘設置(選擇設置)
  2. 串口發送接收
  3. IO操作
  4. flash擦除寫入

  5. 定時器中斷

1、hc32L130上電默認時鐘爲內部4M時鐘,如果不想使用高的波特率這步可以省略,因爲串口波特率我要使用115200,如果不倍頻的話PCLK是4M,那麼波特率115200誤差會達到8.51%,而要想可靠使用串口,波特率誤差應該小於2%。所以我將系統時鐘倍頻到48M,這時115200誤差率0.16%,如下圖所示。

將4MRCH倍頻到48M代碼如下。

void App_RCH4MHzToPll48MHz (void)
{

		
		M0P_FLASH->BYPASS = 0x5A5A;
		M0P_FLASH->BYPASS = 0xA5A5;
		M0P_FLASH->CR_f.WAIT = FlashWaitCycle1;

		
		
    M0P_SYSCTRL->PLL_CR_f.FRSEL  = SysctrlPllInFreq4_6MHz;
    M0P_SYSCTRL->PLL_CR_f.FOSC   = SysctrlPllOutFreq36_48MHz;
    M0P_SYSCTRL->PLL_CR_f.DIVN   = SysctrlPllMul12;
    M0P_SYSCTRL->PLL_CR_f.REFSEL = SysctrlPllRch;
		
    M0P_SYSCTRL->PLL_CR_f.STARTUP = SysctrlPllStableCycle16384;


    M0P_SYSCTRL->SYSCTRL2 = 0x5A5A;
    M0P_SYSCTRL->SYSCTRL2 = 0xA5A5;
		M0P_SYSCTRL->SYSCTRL0_f.PLL_EN = TRUE;
		while(TRUE && (1 != M0P_SYSCTRL->PLL_CR_f.STABLE))
		{
				;
		}
		
    M0P_SYSCTRL->SYSCTRL2 = 0x5A5A;
    M0P_SYSCTRL->SYSCTRL2 = 0xA5A5;
		
    M0P_SYSCTRL->SYSCTRL0_f.CLKSW = SysctrlClkPLL;

    //更新Core時鐘(HCLK)
    SystemCoreClockUpdate();
		
		



}

2、串口初始化代碼

void UART0_Config(uint32_t baudRate)
{
	
    stc_gpio_cfg_t stcGpioCfg;

    stc_uart_cfg_t  stcCfg;
    stc_uart_baud_t stcBaud;

    DDL_ZERO_STRUCT(stcCfg);                               //初始化變量
    DDL_ZERO_STRUCT(stcBaud);                              //初始化變量
    
	  DDL_ZERO_STRUCT(stcGpioCfg);

	
    
    
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //GPIO外設模塊時鐘使能
    
    stcGpioCfg.enDir = GpioDirOut;
    Gpio_Init(GpioPortA,GpioPin9,&stcGpioCfg);
    Gpio_SetAfMode(GpioPortA,GpioPin9,GpioAf1); //配置PD00 爲UART1 TX
	
    stcGpioCfg.enDir = GpioDirIn;
    Gpio_Init(GpioPortA,GpioPin10,&stcGpioCfg);
    Gpio_SetAfMode(GpioPortA,GpioPin10,GpioAf1);//配置PA0D 爲UART1 RX
	
	
	
	
    Sysctrl_SetPeripheralGate(SysctrlPeripheralUart0,TRUE);//使能UART1外設時鐘門控開關

    stcCfg.enRunMode = UartMskMode1;                       //模式1
    stcCfg.enStopBit = UartMsk1bit;                        //1位停止位
    stcCfg.enMmdorCk = UartMskEven;                        //偶校驗
    stcCfg.stcBaud.u32Baud = baudRate;                         //波特率baudRate
    stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;             //通道採樣分頻配置
    stcCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq();        //獲得外設時鐘(PCLK)頻率值
    Uart_Init(M0P_UART0, &stcCfg);                         //串口初始化
		
//    Uart_EnableIrq(M0P_UART1,UartRxIrq);
//		EnableNvic(UART1_IRQn, IrqLevel3, TRUE);       					///<系統中斷使能

    Uart_ClrStatus(M0P_UART0,UartRC);                      //清接收請求
	

	
}

3、flash初始化代碼

  Flash_Init(12, TRUE);    // PCLK 爲48M

 4、定時器3這裏給xmodem提供時間基準定時時間爲4ms。

//Timer3 配置
void App_Timer3Cfg(uint16_t u16Period)
{
    uint16_t                    u16ArrValue;
    uint16_t                    u16CntValue;
    stc_tim3_mode0_cfg_t     stcTim3BaseCfg;
    
    //結構體初始化清零
    DDL_ZERO_STRUCT(stcTim3BaseCfg);
    
    Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外設時鐘使能
    
    stcTim3BaseCfg.enWorkMode = Tim3WorkMode0;              //定時器模式
    stcTim3BaseCfg.enCT       = Tim3Timer;                  //定時器功能,計數時鐘爲內部PCLK
    stcTim3BaseCfg.enPRS      = Tim3PCLKDiv64;              //PCLK/64 0.75M
    stcTim3BaseCfg.enCntMode  = Tim316bitArrMode;           //自動重載16位計數器/定時器
    stcTim3BaseCfg.bEnTog     = FALSE;
    stcTim3BaseCfg.bEnGate    = FALSE;
    stcTim3BaseCfg.enGateP    = Tim3GatePositive;
    
    Tim3_Mode0_Init(&stcTim3BaseCfg);                       //TIM3 的模式0功能初始化
        
    u16ArrValue = 0x10000 - 750*u16Period ;
    
    Tim3_M0_ARRSet(u16ArrValue);                            //設置重載值(ARR = 0x10000 - 週期)
    
    u16CntValue = 0x10000 - 750*u16Period;
    
    Tim3_M0_Cnt16Set(u16CntValue);                          //設置計數初值
    
    Tim3_ClearIntFlag(Tim3UevIrq);                          //清中斷標誌
    Tim3_Mode0_EnableIrq();                                 //使能TIM3中斷(模式0時只有一箇中斷)
    EnableNvic(TIM3_IRQn, IrqLevel3, TRUE);                 //TIM3 開中斷 
}

 定時器3中斷函數

void TIM3_IRQHandler(void)
{
    static uint8_t i,count;
    
    //Timer3 模式0 計時溢出中斷
    if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))
    {
//			count++;
//			if(count>10)
			{
				count = 0;
				xmodem_timeout++;
			
			
        if(0 == i)                        //測試
        {
						LED0(LED_OFF);
            i++;
        }
        else
        {
						LED0(LED_ON);
            i = 0;
        }
				
				
			}

        
        Tim3_ClearIntFlag(Tim3UevIrq);  //Timer3模式0 中斷標誌清除
    }
}

 

下面開始移植Xmodem協議程序,首先將框架程序添加進工程

 修改xmodem_HW.C硬件接口。將串口發送接收程序移植進去。

//串口收發,實用查詢方式。
void xm_port_write(uint8 *ch)
{
	 /* 一直等待, 直到緩衝區爲空 */

	
	 M0P_UART0->ICR_f.TCCF = 0;
	 M0P_UART0->SBUF = (uint8_t)*ch;
	 while(M0P_UART0->ISR_f.TC == 0);
	 M0P_UART0->ICR_f.TCCF = 0; 
	
}

//串口接收函數,需要移植
sint8 xm_port_read(uint8 *ch)
{
    if(Uart_GetStatus(M0P_UART0, UartRC))
    {
        Uart_ClrStatus(M0P_UART0,UartRC);

				*ch = M0P_UART0->SBUF;

        return 1;
     }
    /* 返回接收到的8位數據 */
	
	return 0;
}

 

xmodem.c中KBflashcopy函數添加flash擦除寫入函數。

void KBflashcopy(uint32_t flash_addr,uint8_t* src_addr,sint32 count)
{
	uint32_t start_addr = flash_addr;
	uint16_t i  =  0;
	uint32_t temp = 0;
	
	DI(); 

	Flash_Erase(start_addr);
	Flash_Erase(start_addr+512);
	for(i=0;i<count;i+=4)
	{
			temp = (src_addr[i+3] << 24) |
		 (src_addr[i+2] << 16) |
		 (src_addr[i+1] << 8)  |
		 (src_addr[i+0]) ;
//	  Flash_Program1LongWord(start_addr,temp);
			Flash_Write(start_addr,temp);

	  start_addr += 4;

	}
	EI();

	
}

 回到main.c中在定時器3中斷中添加xmodem超時計數變量xmodem_timeout,需要外部引入下。

void TIM3_IRQHandler(void)
{
    static uint8_t i,count;
    
    //Timer3 模式0 計時溢出中斷
    if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))
    {
//			count++;
//			if(count>10)
			{
				count = 0;
				xmodem_timeout++;
			
			
        if(0 == i)
        {
						LED0(LED_OFF);
            i++;
        }
        else
        {
						LED0(LED_ON);
            i = 0;
        }
				
				
			}

        
        Tim3_ClearIntFlag(Tim3UevIrq);  //Timer3模式0 中斷標誌清除
    }
}

主程序中掛載xm_receive()函數。FLASH_APP_ADDR爲APP程序寫入起始地址。

寫入完成後執行APP程序,實現方法爲

//跳轉到應用程序段
//appxaddr:用戶代碼起始地址
void iap_load_app(uint32_t appxaddr)
{
	if(((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	//檢查棧頂地址是否合法
	{ 
		jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);		//用戶代碼區的第二個字爲程序開始地址(復位地址)		
		//MSR_MSP(*(__IO uint32_t*)appxaddr);					//初始化APP堆棧指針(用戶代碼區的第一個字用於存放棧頂地址)
        __set_MSP(*(__IO uint32_t *)appxaddr);
		jump2app();									//跳轉到APP.
	}
}		 

完整的主程序

int32_t main(void)
{
	App_RCH24MHzToPll48MHz();
	
  Flash_Init(12, TRUE);

	
	LED_GPIO_Config();
	
	UART0_Config(115200);
	
  App_Timer3Cfg(4); //4ms
  Tim3_M0_Run();   //TIM3 運行。

	
	
   i = xm_receive(FLASH_APP_ADDR);            //xmodem接收的APP地址從APP_ADDR開始

	
			M0P_RESET->PERI_RESET = 0;      //所有外設復位
			M0P_RESET->PERI_RESET = 0xFFFFFFFF;
    while(1)
		{
			
			iap_load_app(FLASH_APP_ADDR);    ///*IAP 跳轉*///    	


//		
		
		};
}

 程序移植完成編譯通過後,下載進目標板打開上位機軟件即可開始IAP更新程序了。

 二、APP程序keil環境設置

    APP 程序主要修改中斷向量的偏移,打開APP程序中的啓動文件startup_hc32l130j8ta.s文件,需要修改兩處。

 第一處在堆棧定義下面添加中斷向量偏移,new_vect_table  EQU     0x00001800         ;中斷向量偏移長度

 

第二處在RAMCODE處添加               LDR     R2, =new_vect_table

 

到這裏啓動文件就修改好了,接下來打開keil 魔術棒,修改編譯生成的程序的偏移地址。因爲APP佔用了8K的flash,所以偏移量設置成8K,即0x1800。

在生成bin文件,網上教程很多,不詳細寫了。

三、一切準備就緒後打開上位機軟件,上位機軟件是基於一個QT開源軟件修改的,感興趣的可以上GitHub上搜索。

選擇正確的串口,連接串口,加載生產的main.bin程序,點擊升級,即可完成APP更新。

總結

總體上是單片機上電先運行Boot,然後運行app,這裏有個問題,如果boot裏面將PCLK倍頻到了48M,在APP中再進行倍頻單片機就會死機,所以建議APP中上電讀取下當前系統頻率,如果已經倍頻了就不要在倍頻了,否則分分鐘死機。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章