STM32IAP升級-----編寫IAP升級遇到的問題總結

一,串口通信問題

1,串口通信兩端的TTL電平要一致,看選用的芯片,要麼都是3.3V要麼都是5V ..當兩端電平不一致時通常是收不到數據的..當檢測程序等都沒有問題但是依然收不到數據時,考慮檢測一下兩端的電平是否一致..可用示波器查看收發數據時的電平


2,在寫IAP是使用的官方的庫函數,結果串口一直收不到正確的數據。不管我發什麼,收到的都是將0x58或者0xF6,而且我發5個字節只能收到2個..確定自己的程序邏輯沒問題,然後懷疑是使用的庫函數有問題。使用beyond比挨着對照了一下以前寫過的正確的程序..發現系統初始化時外部晶振沒有改..在SystemInit );時就是爲了/ *配置系統時鐘爲168M使用外部8M晶體+ PLL * /在函數內有一個宏定義#define PLL_M 25 ....庫函數默認的是25但是我們必須要使用8M的晶振。 。所以要把25改成8 .......改完測試通過.....


3,在問題2的時候自己犯了個錯誤。當時我把另一個程序的庫文件都拷過來了替換了原來的庫文件,結果直接進不了中斷了。有中斷程序就死了...如圖所示

有中斷時彙編顯示就跳到了黃色的那句話。然後程序就死了..其實進不了中斷首先想到的是中斷向量表的問題。當時一着急沒想到這個問題..複製過來的庫文件是我寫的IAP的APP程序,我把其中的中斷向量表改了,我不是在主函數中改的,我是直接改的庫函數中的偏移量,然後就把這個問題給忘了,導致找了半天終於意識到是中斷向量表出了問題?


4,串口一上電未初始化時就開始瘋狂的發亂碼,等初始化完成之後就不發了如圖。


解決方法:我是看的官方的例程,如果需要更新再初始化串口,如果不需要更新的話就不初始化串口所以把串口初始化部分寫在了後面的判斷中......這樣操作會出現上述問題。把串口的初始化部分寫到主函數的前端就不會出現這個問題了..至於具體爲什麼會這樣的原因還沒搞明白......

經過詢問大神們說不初始化的時候會有浮空電平,有時出現亂髮一些數據也正常....


5,我在寫iap升級的時候,使用的是一次性全部接收完70K的bin文件,再去升級。。後期程序越寫越多,最後編譯完的bin文件大於了70K導致升級不能成功。前面的$HEAD命令能接收,發完bin文件後$TAIL命令接收不到。。。。。這是由於bin文件的大小超過了70k大小的buff後面的內容無法接收了。。。。。。。。。。。。。正規的升級應該接收10k或者多少就編程一次。。。。。。。。



二,FLASH問題

1,flash的擦除

實際上是把flash的內容全部寫1(擦除完再讀的話讀出來的全是0XFF),flash的編程要按字(32位)或半字(16位)編程,當接收的數據是奇數位是要補上0,湊夠半字來編程。

2,寫進flash跟讀出來的不一樣

注意編程flash時變量的存儲接收等要用unsigned類型的。存儲接收的變量類型要一致。以免造成越界問題,導致看到的數不一致


3,flash做存儲用時

當時寫了個程序,用flash來存數據,然後再讀出來,遇到的問題是設備不斷電時可以完整的讀出來,設備斷電後讀不出了。要讀多少個數我是把個數也存儲在flash中的。最終發現問題是程序中一個標誌位的操作有問題,每次斷電後再上電會自動把這個個數寫爲0,導致每次都讀不出來數


三,數組越界問題

1,嵌入式程序通常要求少佔內存,通常變量能定義8位不定義16位。。一定要注意變量的最大值。(細心一點)。。在越界問題上,吃虧了好幾次了。。。。

eg:①,我定義了一個int16_t的變量來接收flash中的一個變量值0xABCD。。結果很顯然越界了。。換成uint16_t即可

②,定義了一個串口接收數據計數器uint16_t 的變量,要接收60k的APP程序,自認爲足夠用了,但是串口發送60K大小的文件發送的字節數大於了65535,,,又耽誤了好久時間。。。

謹記:要細心


四,關於APP與IAP互跳之間的中斷處理問題

跳轉時中斷問題還是一個比較棘手的問題。。經常跳轉之後無法進入中斷,然後百度了一下,自己理解大概是,跳轉時只是強制改變了PC指正的位置,但是裏面的中斷寄存器什麼的都沒有變,這樣中斷存在,但是中斷函數什麼的都沒有了,造成程序死掉。。我在寫的過程中也遇到了問題,第一次從iap跳到app正常,但是從app跳回iap的時候由於殘留的中斷太多,在iap中程序死了。我的處理方式是把app中的跳轉命令換成了系統復位NVIC_SystemReset();(不同的固件庫可能函數名不同)其他的處理理的方式據我所知還有有①跳轉之前復位或者關閉所有打開的中斷②跳轉後在初始化時加入RCC_DeInit();,,NVIC_DeInit ();等讓中斷恢復默認值。。具體可參考下面這篇文章鏈接地址 總之要注意中斷寄存器的復位。也可以直接軟件復位管他什麼中斷寄存器殘留的.從iap跳到app前可以用USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);來關閉串口接收中斷。從app跳轉回iap可以用軟件復位。



五,總結一下在IAP升級中APP程序的中斷向量表的偏移

1.      關於APP程序的中斷向量表地址偏移(三種方法,stm32F2與F4系列通用。三種方法本質一樣只是看到網上的各種例程的表現形式不一樣)

① 直接操作寄存器

在APP程序的main函數的開頭設置中斷向量表偏移

SCB->VTOR = FLASH_BASE | 0x10000;

其中0x10000是偏移量。。也就是前面的IAP程序所佔用的空間大小,要是你的main函數中有SystemInit();的話要在SystemInit();之後添加。

因爲SystemInit();中有中斷向量表的偏移操作

 

在void SystemInit (void)系統初始化函數中有初始化中斷向量表的語句

#ifdef VECT_TAB_SRAM

 SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 使用內部SRAM啓動設置這一句. */

#else

 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 使用內部FLASH啓動設置這句*/

#endif

 可以直接修改VECT_TAB_OFFSE的值,這個值代表偏移量。不建議這麼改,不建議修改庫文件,應爲後面其他程序用的話經常忘了這裏動過中斷向量表,導致中斷不能正常運行(我就因爲這個浪費了快一天時間,串口就是進不了中斷)

其中

#define FLASH_BASE            ((uint32_t)0x08000000) /*!<FLASH base address in the alias region */

#define SRAM_BASE             ((uint32_t)0x20000000) /*!< SRAM baseaddress in the alias region */

 

對應keil設置中的(這是一般程序默認的,IAP升級中APP程序的這個地方還得根據中斷偏移量改)

 


 

② 使用庫函數設置偏移量

在庫文件中有專門的一個函數

在APP程序初始化時調用函數NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);

 

其中/* Vector Table Base----------------------------------*/

#define NVIC_VectTab_RAM             ((u32)0x20000000)

#define NVIC_VectTab_FLASH           ((u32)0x08000000)

 

 

/***********************************************************************

Function Name  : NVIC_SetVectorTable

* Description    : Sets the vector table location andOffset.

* Input          : - NVIC_VectTab: specifies if thevector table is in RAM or

*                    FLASH memory.

**********************************************************************/

void NVIC_SetVectorTable (u32NVIC_VectTab, u32 Offset)

{

 /* Check the parameters */

 assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));

 assert_param(IS_NVIC_OFFSET(Offset)); 

  

 SCB->VTOR = NVIC_VectTab | (Offset & (u32)0x1FFFFF80);

}

③修改庫文件(不建議使用)

直接修改固件庫裏面的數值。在void SystemInit(void)下的

  /* Configure the Vector Table location add offsetaddress ------------------*/

#ifdefVECT_TAB_SRAM

  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;/* Internal SRAM */

#else

  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;/* Internal FLASH */

#endif

 

直接修改

#define VECT_TAB_OFFSET  0x00 /*!< Vector Table base offset field.

                                   This valuemust be a multiple of 0x200. */

#define VECT_TAB_OFFSET  0x10000 /*!< Vector Table base offsetfield.

                                   This valuemust be a multiple of 0x200. */

 

2.關於IAP程序與APP程序keil中的設置

Stm32的flash都是從0x8000000開始的,結束地址看片子的flash大小

Stm32的sram都是從0x2000000開始的,結束地址看片子的sram大小

 

IAP程序基本默認就行,跟普通程序一樣


在APP程序中需要設置一下偏移量


默認的條件下,圖中IROM1的起始地址(Start)一般爲0X08000000,大小(Size)爲0X100000,即從0X08000000開始的1M空間爲我們的程序存儲(因爲我們的STM32F4的FLASH大小是1M)。而圖中,我們設置起始地址(Start)爲0X08010000,即偏移量爲0X10000(64K字節),因而,留給APP用的FLASH空間(Size)只有0X100000-0X10000=0XF0000(960K字節)大小了。設置好Start和Szie,就完成APP程序的起始地址設置。

       這裏的64K字節,需要大家根據Bootloader程序大小進行選擇,比如我們本章的Bootloader程序爲22K左右,理論上我們只需要確保APP起始地址在Bootloader之後,並且偏移量爲0X200的倍數即可(相關知識,請參考:鏈接地址)。這裏我們選擇64K(0X10000)字節,留了一些餘量,方便Bootloader以後的升級修改。

注意:設置的起始地址要與程序中設置的中斷向量表的偏移量對應起來(如果給IAP程序64k的空間則APPkeil中起始地址爲0x8010000相應的程序中中斷向量偏移0x10000)我用的1M  flash大小的片子。。具體的大小設置,看自己的片子。。

跳轉問題:

一,網上下載的例程,跳轉部分的代碼有差異,尤其是用的彙編那句

eg:

Jump_To_Application  = (pFunction)(*(vu32*) (IAPSTART + 4));
__MSR_MSP(*(vu32*) IAPSTART);
Jump_To_Application();

跟蹤__MSR_MSP(一般這個函數都在庫文件裏有,跟蹤不到就用搜索找)找到彙編函數爲

__MSR_MSP 
 
    MSR MSP, r0 ; set Main Stack value
    BX r14


//跳轉到應用程序段
//appxaddr:用戶代碼起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//檢查棧頂地址是否合法.

jump2app=(iapfun)*(vu32*)(appxaddr+4);//用戶代碼區第二個字爲程序開始地址(復位地址)
MSR_MSP(*(vu32*)appxaddr);//初始化APP堆棧指針(用戶代碼區的第一個字用於存放棧頂地址)
jump2app();   //跳轉到APP.
}
}

跟蹤MSR_MSP找到函數爲

//設置棧頂地址
//addr:棧頂地址
__asm void MSR_MSP(u32 addr) 
{
    MSR MSP, r0 //set Main Stack value
    BX r14
}


 //判斷用戶是否已經下載程序,因爲正常情況下此地址是棧地址。
        //若沒有這一句的話,即使沒有下載程序也會進入而導致跑飛。
        if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
        {
            SerialPutString("Execute user Program\r\n\n");
            //跳轉至用戶代碼
            JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
            Jump_To_Application = (pFunction) JumpAddress;


            //初始化用戶程序的堆棧指針
            __set_MSP(*(__IO uint32_t*) ApplicationAddress);
            Jump_To_Application();
        }

跟蹤__set_MSP找到函數爲

__ASM void __set_MSP(uint32_t mainStackPointer)
{
  msr msp, r0
  bx lr
}

總結以上發現都是操作ARM的R0跟R14(LR)寄存器。

還有一種不太一樣的,就是stm32F4的庫函數中的跳轉,如下所示

//測試用戶app地址是不是在APPLICATION_ADDRESS位置。檢測棧頂的地址,來檢驗app是否下載成功
    if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
    { 
    //APPLICATION_ADDRESS + 4對應的是app中斷向量表的第二項,復位地址
    JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
//把地址強轉爲函數指針
    Jump_To_Application = (pFunction) JumpAddress;
    //設置主函數棧指針
    __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
//調用函數,實際失去app復位地址去執行復位操作
    Jump_To_Application();
    }

跟蹤__set_MSP找到函數爲

static __INLINE void __set_MSP(uint32_t topOfMainStack)
{
  register uint32_t __regMainStackPointer     __ASM("msp");
  __regMainStackPointer = topOfMainStack;
}

對於M4的這個庫函數我也不太懂,感覺最終的操作應該跟其他的一樣吧

二,關於跳轉部分的代碼的理解(轉)

這裏重點說一下幾句經典且非常重要的代碼:

第一句: if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)   //判斷棧定地址值是否在0x2000 0000 - 0x 2000 2000之間

怎麼理解呢? (1),在程序裏#define ApplicationAddress    0x8003000 ,*(__IO uint32_t*)ApplicationAddress)  即取0x8003000開始到0x8003003 的4個字節的值, 因爲我們的應用程序APP中設置把 中斷向量表 放置在0x08003000 開始的位置;而中斷向量表裏第一個放的就是棧頂地址的值

也就是說,這句話即通過判斷棧頂地址值是否正確(是否在0x2000 0000 - 0x 2000 2000之間) 來判斷是否應用程序已經下載了,因爲應用程序的啓動文件剛開始就去初始化化棧空間,如果棧頂值對了,說應用程已經下載了啓動文件的初始化也執行了;


第二句:    JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);   [  common.c文件第18行定義了:  pFunction   Jump_To_Application;]
                      

ApplicationAddress + 4  即爲0x0800 3004 ,裏面放的是中斷向量表的第二項“復位地址”  JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); 之後此時JumpAddress

第三句:    Jump_To_Application = (pFunction) JumpAddress;
 startup_stm32f10x_md_lv. 文件中別名  typedef  void (*pFunction)(void);     這個看上去有點奇怪;正常第一個整型變量   typedef  int  a;  就是給整型定義一個別名 a

 void (*pFunction)(void);   是聲明一個函數指針,加上一個typedef 之後  pFunction只不過是類型 void (*)(void) 的一個別名;例如:

  1. pFunction   a1,a2,a3;  
  2.   
  3. void  fun(void)  
  4. {  
  5.     ......  
  6. }  
  7.   
  8. a1 = fun;  

所以,Jump_To_Application = (pFunction) JumpAddress;  此時Jump_To_Application指向了復位函數所在的地址;

第四 、五句: __set_MSP(*(__IO uint32_t*) ApplicationAddress);      \\設置主函數棧指針
               Jump_To_Application();                         \\執行復位函數

Jump_To_Application()是把用戶代碼的復位地址付給PC指針,我看到Jump_To_Application()這句代碼debug的時候對應的彙編代碼是

LDR r0,[pc,#12] ;相對PC的數據加載,去函數指針的地址
LDR r0,[r0,#00] ;R0做索引,無偏移,數據裝載到R0,這個內容就是函數指針指向的內容,也就是函數的地址了,用戶程序的起始地址;
BLX r0              ;這個不解釋,說了是跳轉

我們看一下啓動文件startup_stm32f10x_md_vl.s 中的啓動代碼,更容易理解

 


三,關於跳轉時能否不用按鍵,用軟件標誌位以及APP與IAP之間的互跳

完全可以不用按鍵,可以模擬一個按鍵信號,或者用軟件的一個標誌位來判斷是否更新。。我設計的在flash中中存儲一個值,當APP運行中需要更新時串口發來更新命令,然後在flash中存一個值之後跳到IAP部分,來讀取flash中存儲的那個值,如果是需要更新則更新,如果不是需要跟新標誌位就直接跳轉到APP部分。。。這樣也不用重複上電,斷電。


四,關於APP與IAP互跳之間的中斷處理問題

跳轉時中斷問題還是一個比較棘手的問題。。經常跳轉之後無法進入中斷,然後百度了一下,自己理解大概是,跳轉時只是強制改變了PC指正的位置,但是裏面的中斷寄存器什麼的都沒有變,這樣中斷存在,但是中斷函數什麼的都沒有了,造成程序死掉。。我在寫的過程中也遇到了問題,第一次從iap跳到app正常,但是從app跳回iap的時候由於殘留的中斷太多,在iap中程序死了。我的處理方式是把app中的跳轉命令換成了系統復位NVIC_SystemReset();(不同的固件庫可能函數名不同)其他的處理理的方式據我所知還有有①跳轉之前復位或者關閉所有打開的中斷②跳轉後在初始化時加入RCC_DeInit();,,NVIC_DeInit ();等讓中斷恢復默認值。。具體可參考下面這篇文章鏈接地址

關於stm32的軟件復位:

STM32軟件復位(基於庫文件V3.5) ,對於STM32來說軟件復位有兩種方式:    

1)採用官方自帶的軟件庫  
    在官方軟件庫的 core_cm3.h 文件裏 直接提供了 系統復位的函數    

static __INLINE void NVIC_SystemReset(void) 


SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)    |   

(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk)   |  

SCB_AIRCR_SYSRESETREQ_Msk);                   /* Keep priority group unchanged */ 
  __DSB();                                                                                       /* Ensure completion of memory access */               
  while(1);                                                                                        /* wait until reset */

 } 
  但是不是直接調用這個函數就OK了?    在Cortex-M3權威指南中有這麼一句話:     
這裏有一個要注意的問題:從SYSRESETREQ 被置爲有效,到復位發生器執行復位命令,  往往會有一個延時。在此延時期間,處理器仍然可以響應中斷請求。但我們的本意往往是要  讓此次執行到此爲止,不要再做任何其它事情了。所以,最好在發出復位請求前,先把  FAULTMASK 置位。  

  所以最好在將FAULTMASK 置位才萬無一失。    同樣官方 core_cm3.h 文件裏也直接提供了該函數  
  static __INLINE void __set_FAULTMASK(uint32_t faultMask) 

register uint32_t __regFaultMask       __ASM("faultmask");   

__regFaultMask = (faultMask & 1);

 } 
  把上面這兩個函數寫在一起就可以實現軟件復位了~~ 

void SoftReset(void) 


__set_FAULTMASK(1);      // 關閉所有中端 

NVIC_SystemReset();// 復位 


 /*------


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