stlink 升级固件以后失败_STM32固件升级的一点经验
上面理論+實踐當初花了3天時間弄完的,但是,當你真正做項目的時候,你會發現,只有上面的這些知識還不夠,還有更多的細節要去處理:
經過一個項目的固件升級功能洗禮,以上問題都得到了較好的解決,為了避免以后忘記,在此記錄一下。芯片為:STM32F103ZET6
第一個問題,APP程序怎么跳轉到BootLoader程序?看似很簡單,因為這是基本的功能,但是實際情況并不簡單。
由前面的小節了解到,從BootLoader跳轉到APP可以通過指針進行跳轉,但是當你從APP通過指針跳轉到BootLoader時,發現會出現問題(具體原因不明,有機會的話去研究一下)。那么又該怎么辦?
可以通過復位的方式,讓程序重新從開始地址運行,有以下幾種方式復位:
第一、第二種方式都是通過設置相關寄存器使單片機發生復位的,兩者的區別就是,內核復位只復位芯片的內核,但對單片機的片上外設并不進行復位,比如USART、SPI、USB等外設是不會進行復位的。
系統復位的話,就會對整個芯片進行復位,不管是外設還是內核,都會回到最初始的狀態,就如按下復位按鍵一樣。
最后一種上電復位,其實和系統復位、按鍵復位的效果差不多,都是會進行全部復位的,不過這個需要外部硬件控制單片機的電源的開啟與關閉,增加了額外的硬件。
一開始魚鷹準備采用系統復位的,直接設置寄存器觸發導致復位,因為這樣更徹底,測試發現項目中的單片機根本無法復位,而我自己的開發板是能進行復位的,后來經過硬件工程師的查找,發現是看門狗電路導致無法復位,這樣一來,系統復位這條路堵死了(因為項目的硬件已經確定,無法再更改了)。
那么是否有其它方法,前面提到的指針跳轉的方式發現會出現問題,因為項目比較急,就沒怎么花心思解決。后來在調試過程中,突然發現KEIL中的復位按鈕是能進行復位的,那么問題就簡單了,既然調試器能進行復位,那我應該也能進行復位才對,之前說了系統復位不好使,那么按下復位按鈕時應該是采用的內核復位(實際上CMSIS-DAP調試器是有單獨的一條復位線的,但是當時沒考慮它可能采用了這種方式,只考慮可能采用了內核復位,陰差陽錯)。
那么就試試內核復位吧,一試發現果然有效,但是因為內核復位不徹底,導致出現了問題。
這就到了第二個問題,兩個程序之間是否會有影響?
第一,首先從BootLoader對APP的影響考慮,我們知道,BootLoader程序也是需要一些資源的,比如串口之類的用于固件的傳輸,如果說BootLoader的寄存器和APP的寄存器配置要求不一樣,那么就可能出現問題(魚鷹的項目中還用了一個定時器喂狗,發現一進入APP程序就掛了,后來才找到這個原因)。
比如BootLoader采用串口查詢的方式接收數據,而APP為了提高效率,使用DMA+空閑中斷的方式處理,那么兩者的寄存器配置肯定不同,那么該怎么消除BootLoader程序對APP的影響呢?
有人說,讓BootLoader程序用完串口之后自動復位串口外設即可,確實,這是一種方法,但是你是否考慮過兩個程序是獨立的,萬一后面的人在BootLoader程序中忘記了復位串口呢?所以說,靠別人不如靠自己,與其擔心害怕別人不靠譜,不如APP自己去復位串口,即APP在配置串口之前,可以先復位串口,再進行配置(從這里可以知道,為什么有些代碼會使用XXX_DeInit()之類的函數在配置前復位片上外設,一開始以為是多余的,畢竟一般程序開始運行的時候一般都是上電之后才運行的,這個時候已經復位外設了,為什么還要多此一舉,直到現在才明白這才是安全的做法)。
第二,從APP對BootLoader的影響考慮,APP程序使用的資源一般比BootLoader的資源多,如果兩者之間使用了相同的資源,比如串口,那么肯定得考慮兩者的差異性,所以根據上面的考慮,也可以讓BootLoader程序在使用串口之前先進行復位,然后再進行配置,這是比較安全的做法。但是僅僅如此就足夠了嗎?
在項目里的APP程序中,有一個加熱過程,如果說APP跳轉到BootLoader之前沒有考慮這一點就盲目的運行到BootLoader,那么很可能出現APP正在加熱,但是因為跳轉到了BootLoader中運行,導致無法對溫度進行控制,那么結果將是災難性的,輕點的只是設備燒毀,重的可能就引發火災了。
所以說,兩者之間的影響一定要慎重考慮。
事實上,如果采用系統復位或者上電復位的方式,第二點關于APP對BootLoader的影響是可以不考慮的,因為系統復位或者上電復位自動將外設進行初始化了,但是你不能肯定你現在采用這些方式,以后就不會采用內核復位的方式,所以為了安全,還是要考慮進去。
現在說說第三個問題,APP和BootLoader之間如何傳遞參數?
首先思考為什么要傳遞參數?
在前面的小節中,選擇讓BootLoader程序在開始復位時等待一段時間再進入APP運行,在等待的過程中,就可以判斷是否需要固件升級,比如等待時,由上位機發送一條特殊的命令確定是否升級,或者通過引腳電平等方式,反正就是要讓BootLoader程序知道,下面我要開始升級了,別急著進入APP運行。
但這里有一個問題就是,這里需要一個冷啟動的過程,即先上電后再接收命令,而且時間短暫,有一個好處就是,即使單片機中暫時沒有APP程序,也能夠實現固件升級過程,這樣保證了由BootLoader接收升級命令而不是由APP接收,所以當初在無法解決升級到一半時如何恢復時有考慮使用這種強制升級的方式。
那么有沒有更好一點方式,不需要冷啟動過程,而是由APP決定是否升級?有的。
既然是APP決定是否升級,那么肯定需要在進入BootLoader之前給它傳遞一個參數,告訴它,這次復位需要升級,不能直接跳到APP中運行,那么BootLoader就會乖乖地等著升級了。
那么怎么傳遞呢?有人說往FLASH中寫入參數,這樣復位的時候就可以判斷是否需要升級了,這確實是一個方法,但是我們知道,如果我們要往FLASH寫入參數,那么必須先進行擦除工作才行,而擦除的往往是一個扇區,為了寫入幾個字節的參數,擦除幾K的數據,魚鷹感覺實在是太浪費了;還有這個參數保存地址也是需要好好考慮的,放在APP區還是BootLoader區?那么有沒有更好的方式?
有的。還不只一種。
一開始魚鷹想到的是利用后備域保存參數,因為如果有電池存在的話,它的數據是不會丟失的,但不巧的是,這個項目沒有這個功能。
還有可以使用外部的FLASH空間,有些FLASH芯片是可以進行字節編程的,不需要整片擦除,挺合適的。但是缺點就是,你的項目要有這種芯片,而你的BootLoader需要寫相應的代碼驅動這個芯片,顯然很麻煩。
最后魚鷹采用的是RAM傳參。魚鷹在之前的小節說過,APP和BootLoader共用RAM,如果說能用RAM傳遞參數的話,只是操作一個變量,相當方便。
但是怎么保證兩者之間順利傳遞參數呢?
我們知道,C語言申請的變量空間是由編譯器自動分配的,也就是說,同樣申明一個同名變量,APP和BootLoader申請的變量地址不一定是一樣的,而且還有一點就是,即使你申請的變量通過某些方法讓它地址固定,也會有問題,因為申請的變量會在進入main函數之前會被初始化掉,當然你可以說通過某種方式讓它不被初始化,但是魚鷹想到了更好的方法。
通過指針直接操作RAM空間最后幾個字節用于參數傳遞(之前有看到說STM32單片機中有個寄存器可以直接掉電不丟失,但具體不知道是哪一個)。
因為采用指針操作,所以編譯器并不會對你指向的地址進行初始化,這樣可以很方便的繞過編譯器的處理。其次,通過操作最后幾個字節,保證了這個空間不會被程序的其他變量占用(其實占用了也關系不大,只要你傳遞的參數足夠特別,比如0x05055555,就問題不大)。
這樣,BootLoader在復位后只要檢查這個地址的值,就可以輕松知道是否該升級了。
但是還有一個隱患就是,在上電那一刻,如果這個地址的值剛好是你設置的特殊值(因為上電后,RAM的值是隨機的),那么必然會出現問題,但這種可能性微乎其微,因為要讓四個字節在復位哪一刻剛好都變成你設置的特殊值,簡直比中彩票還要困難。
不過即使你真的中彩票了,重新上電復位一下就好了,如果說第一次中彩票還能接受,第二次還如此,那就需要燒燒香、拜拜佛了。
需要注意的是,一旦使用完這個參數,必須清零,防止下次內核復位又進入升級了(比如在線調試時可能會使用KEIL中的復位按鈕)。
第四個問題,固件更新一到一半,因為某種原因失敗了(通信錯誤,掉電),該如何處理?
我們知道,升級過程中很大可能是會失敗的,但是單片機升級不像電腦升級,這次升級不成功,恢復成原來的系統就是了。單片機空間有限,沒辦法同時保存兩份APP程序的,那么又該如何處理呢?
現在換個角度思考,你如何確定你升級失敗了?如果能做到這一點,那么你的BootLoader程序就可以在升級失敗后繼續運行BootLoader的程序,而不進入APP運行那只升級到一半的程序(運行這種半殘程序,鬼知道會發生什么怪異事件呢)。
如果從這個角度來看,其實就簡單了,只要上位機把bin文件的大小發下來,然后由BootLoader程序判斷是否升級完成就可以了,而Ymodem剛好可以有這個功能。
但是這樣一來,就出現了一個問題,需要一個掉電不丟失的參數來保存是否升級成功,否則下次上電又會繼續運行APP。但是魚鷹對整個扇區擦除的方式很反感,就是不愿使用這種方式,怎么辦?
苦思冥想之下,終于找到了一個巧妙的方式去處理。
我們知道,運行APP之前,一般會對APP的前面8個字節的棧頂指針、復位地址的合法性進行判斷,判斷是否是有效的APP程序,畢竟隨便拿一個程序去升級,還不亂套了。
不管怎樣,APP程序都是要往FLASH更新程序的,那么我們是否能利用這個過程呢?
APP更新之前,必定把該擦除的扇區進行擦除了,如果說我們一開始,就對寫入的工作進行特殊處理,那么是否可以達到我們想要的效果呢?
比如說,前面8個字節,本來是在一開始的時候就會被寫入的,如果我們在一開始寫入數據的時候,跳過這8個字節的寫入,然后把剩余的代碼全部寫入,當判斷已全部接收到(大于等于bin文件大小,因為Ymodem協議稍微有點特殊,最后一幀數據可能填充0)固件之后,最后再對前面8個字節寫入,這樣一來,就保證了程序的完整性,如果說你中途數據中斷了(掉電或上位機中斷),那么前面那8個字節肯定不會寫入,也就無法正常進入APP中運行了。
第五個問題,如何確保更新的APP是你需要的APP,而不是別的一個APP?
前面的問題保證了更新的程序是完整的,但是完整的程序不一定就是你需要的程序,那么怎么確定是你需要的APP呢
通過bin文件名來確定嗎?這是一個方法,但是bin文件名可以被用戶輕易更改,那就只能從bin文件內容本身入手了。
常規方法是,通過某些工具,在bin文件中加幾個字節標著文件的特殊性,但是眾所周知的是,魚鷹比較懶,看似簡單的只是加入幾個字節的事情,如果產品成型的話比較好說,更新次數比較少,但是一旦產品處于測試階段,更新頻繁,累人不說,還可能出錯。
那么有什么辦法呢?就從代碼本身入手好了。
方案確定下來了,但是怎么處理呢,標志位放在哪里,怎么放?又是一番苦思冥想。
一開始想到的是想將標志位放在bin文件最后,但是bin文件的大小是不固定的,雖然說上位機可以把bin文件大小傳下來,但是怎么放是個問題,開始打算通過修改鏈接文件實現,但是發現自己對鏈接過程不熟,對匯編語言(匯編可以指定地址)也不熟,怎么辦?
最終魚鷹選擇把程序標志放在向量表的后面,即通過指定變量地址的方式保證地址唯一性(需要注意一點的就是,通過指定地址的方式,不一定就確保最終的地址就是你設定的地址,如果和其它地址沖突了的話,可能不一致,需要看map文件確定)。而且在設置APP程序的時候,一般都會重新定義向量表的位置,那么可以在定義標志地址時利用這個地址進行偏移。
當BootLoader在寫前面8個字節前,只要再判斷這個地址的標志位是否正確即可。
到此,固件升級方面的知識應該比較完善了,但還有一個問題,如何對bin文件加密與解密呢?
這個問題只有下次項目需要的時候再研究了。
------------------------------------------------------------------------------2019-06-30 Osprey
喜歡就來關注魚鷹吧!
如果對你有幫助,點個贊再走唄!
總結
以上是生活随笔為你收集整理的stlink 升级固件以后失败_STM32固件升级的一点经验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 迪士尼第三季度营收223.3亿美元 Di
- 下一篇: comsol分析时总位移代表什么_超弹性