ESA2GJK1DH1K升級篇: STM32遠程乒乓升級,升級流程源碼詳細說明


 

 

 

 

前言

  1.BootLoader程序,升級簡要流程圖

  

 

 

  2.其實主要的就是把程序文件寫入環形隊列,然后環形隊列取出來數據寫入Flash

 

  3.用戶程序,簡要流程圖

 

    

 

 

 

 下面的讀一下,有個印象就可以:

  說白了就是BootLoader里面通過http遠程下載完程序以后, 設置更新狀態是 0x01  然后重啟

  重啟以后還是會先執行BootLoader,然后BootLoader判斷更新狀態是 0x01 那么就設置更新狀態是 0xFF

  然后就會執行 用戶程序,用戶程序判斷更新狀態是 0xFF 就切換下版本號.設置升級狀態為0,升級完成

 

  假設咱更新的用戶程序有問題,那么就會執行用戶程序失敗,然后導致重啟執行了BootLoader

  BootLoader一判斷還是0xFF,就說明沒有正確執行用戶程序,就設置更新狀態是 0xEE,同時設置下切換執行文件

  然后執行另一份沒有問題的用戶程序.

 

 

關於乒乓升級

1.簡要

 

 

  每次更新的時候 用戶程序1和用戶程序2來回的切換寫入運行

  BootLoader程序主要做的工作就是如果上次運行的用戶程序1

  則獲取第二份用戶程序,然后把程序文件寫入用戶程序2地址

  如果上次運行的用戶程序2

  則獲取第一份用戶程序,然后把程序文件寫入用戶程序1地址

 

2.關於源碼中的兩份用戶程序

 

 

首先,兩份用戶程序除了設置的中斷偏移不一樣以外其他完全一樣!

 

 

 

 

 

 

 

 

不同的偏移值使其能運行在不同的Flash地址上

STM32F10xTemplate 設置的偏移值只能運行在 ↓

 

 

 

STM32F10xTemplate - 副本  設置的偏移值只能運行在 ↓

 

 

 

 

 

 

3.升級切換文件流程

首先,咱們需要每次要升級的時候需要把運行在不同地址,

功能完全一樣的兩份用戶程序的bin文件放到雲端,

(編譯 STM32F10xTemplate 產生的bin文件

和編譯 STM32F10xTemplate - 副本 產生的bin文件)

以供單片機下載.

假設當前單片機是在用戶程序1 地址加載運行的程序

那么單片機下次升級就需要下載 能在用戶程序2 地址運行的程序文件

然后寫到 用戶程序2 地址,然后單片機就加載用戶程序2 地址上的程序運行

下次再升級就是下載 能在用戶程序1 地址運行的程序文件

 

這里說一下為什么需要同時把兩份文件放上去.

大家可能會想,假設我單片機一開始是在用戶程序1 地址加載運行的程序

那么我單片機升級肯定是下載 能在用戶程序2 地址運行的程序文件

我直接把  能在用戶程序2 地址運行的一個程序文件放到雲端不就可以了

我單片機如果接着再升級,肯定是下載 能在用戶程序1 地址運行的程序文件

我直接把  能在用戶程序1 地址運行的一個程序文件放到雲端不就可以了

干嘛非要把 能在兩個地址運行的執行功能一樣的程序放上去呢??

 

我只問一句:

大家在看到軟件更新的時候,大家是否真的去執行更新???

是不是有時候都過去好幾個版本了才選擇升級??

 

大白話就是:

假設有兩個用戶買了咱的產品

假設一開始都是在用戶程序1 地址加載運行的程序

然后第一次升級的時候,其中一個用戶升級了

另一個用戶沒有執行升級.

假設咱又換了下版本,需要再升級!

現在的情況就變為:

第一個用戶再次升級的時候,需要下載能在用戶程序1 地址運行的程序文件

 

第二個用戶由於第一次沒有升級,

再次更新的時候需要下載能在用戶程序2 地址運行的程序文件

 

所以......

大家一定是要把在兩個地址運行的執行功能一樣的程序放上去!

BootLoader程序說明

  首先需要明確:

  總共把Flash分為了下面四部分

  

  每次更新的時候 用戶程序1和用戶程序2來回的切換寫入運行

  BootLoader程序主要做的工作就是如果上次運行的用戶程序1

  則獲取第二份用戶程序,然后把程序文件寫入用戶程序2地址

  如果上次運行的用戶程序2

  則獲取第一份用戶程序,然后把程序文件寫入用戶程序1地址

 

main函數初始化里面

 

  提醒:為了便於敘述,升級程序都寫在了main里面,后面章節為了便於移植使用,全部封裝成了單個函數!

  一,初始化變量

    

  二,獲取存儲的雲端版本,獲取當前設備版本

    獲取更新狀態:如果是0x01 設置更新狀態為:0xFF

    如果更新狀態是0xFF  設置更新狀態為:0xEE  同時切換執行程序

    

  三,獲取當前應該運行哪一份用戶程序,然后設置運行和更新的Flash起始地址

    

 

 

配網

  對於一個新的Wi-Fi模塊而言,沒有連接路由器的狀態下,無法實現遠程http訪問

  所以在BootLoader里面有個配網程序

  一,BootLoader里面配網完成以后會初始化版本號,然后設置升級標志

    

配網重啟以后,便能連接上Web服務器

  

  

 

 

  

 

 

  連接TCP服務器(Web服務器)是用的自動重連的透傳模式

 

 

然后定時http詢問程序版本

  定時詢問雲端的版本

  

  

 

  就是訪問的雲端的下面文件

  

詢問到版本信息以后處理

  一,處理獲取的版本號

  因為設置的是串口TCP透傳,所以獲取的數據直接在串口里面獲取

  

 

 

 

  二,獲取升級文件的校驗和

  校驗和累加方式是每一個字節進行累加,取低八位

 

  

 

 

 

  三,如果獲取的版本號和校驗和都是可以的

    1.擦除對應的Flash地址

    2.把雲端版本存儲起來

    3.發送獲取相應的程序文件http指令

    4.然后設置 IAPStructValue.PutDataFlage = 1;  (允許把數據寫入環形隊列)

    提示:發送完獲取文件指令以后,Web服務器便會下發程序文件了

    

 

 

 

到串口中斷里面看下接收程序文件

  

 

 

  if(IAPStructValue.PutDataFlage && (IAPStructValue.PutDataFlage^IAPStructValue.Overflow))

  IAPStructValue.PutDataFlage: (允許把程序寫入環形隊列)

  IAPStructValue.Overflow:如果溢出過,就不再往里面寫了

 

  PutData(&rb_tIAP,NULL,&Res,1) : 把接收的數據寫入環形隊列

 

  提示:主循環只要判斷環形隊列里面有數據,就把數據寫入Flash

  提示:主循環只要判斷環形隊列里面有數據,就把數據寫入Flash

  提示:主循環只要判斷環形隊列里面有數據,就把數據寫入Flash

  具體里面的判斷后面會說明!

  

 

 

 

 

  提醒:由於http會返回數據頭,需要去掉數據頭

 

  

//解析http數據-------------------------------Start
        //HTTP/1.1 200 OK
        if(!HttpHeadOK && IAPStructValue.PutDataFlage)
        {
            if(Res=='H' && HttpHeadCnt==0)HttpHeadCnt++;
            else if(Res=='T' && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res=='T' && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res=='P' && HttpHeadCnt==3)HttpHeadCnt++;
            else if(Res=='/' && HttpHeadCnt==4)HttpHeadCnt++;
            else if(Res=='1' && HttpHeadCnt==5)HttpHeadCnt++;
            else if(Res=='.' && HttpHeadCnt==6)HttpHeadCnt++;
            else if(Res=='1' && HttpHeadCnt==7)HttpHeadCnt++;
            else if(Res==' ' && HttpHeadCnt==8)HttpHeadCnt++;
            else if(Res=='2' && HttpHeadCnt==9)HttpHeadCnt++;
            else if(Res=='0' && HttpHeadCnt==10)HttpHeadCnt++;
            else if(Res=='0' && HttpHeadCnt==11)HttpHeadCnt++;
            else if(Res==' ' && HttpHeadCnt==12)HttpHeadCnt++;
            else if(Res=='O' && HttpHeadCnt==13)HttpHeadCnt++;
            else if(Res=='K' && HttpHeadCnt==14){HttpHeadOK = 1;HttpHeadCnt=0;HttpDataLength=0;}  
            else
            {
                HttpHeadCnt=0;
            }
        }
        
        
        #ifdef UserContentLength 
        //Content-Length: XXXXXXXX
        if(HttpHeadOK && !HttpDataLengthOK)//獲取http發過來的數據個數
        {
            if(Res=='-' && HttpHeadCnt==0)     HttpHeadCnt++;
            else if(Res=='L' && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res=='e' && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res=='n' && HttpHeadCnt==3)HttpHeadCnt++;
            else if(Res=='g' && HttpHeadCnt==4)HttpHeadCnt++;
            else if(Res=='t' && HttpHeadCnt==5)HttpHeadCnt++;
            else if(Res=='h' && HttpHeadCnt==6)HttpHeadCnt++;
            else if(Res==':' && HttpHeadCnt==7)HttpHeadCnt++;
            else if(Res==' ' && HttpHeadCnt==8)HttpHeadCnt++;
            else if(HttpHeadCnt>=9 && HttpHeadCnt<=16 )//最大99999999個字節. 16:99999999  17:999999999 18:9999999999
            {
                if(Res!=0x0D)
                {
                    HttpDataLength = HttpDataLength*10 + Res - '0';
                    HttpHeadCnt++;
                }
                else
                {
                    HttpDataLengthOK = 1;
                    HttpHeadCnt = 0;
                }
            }
            else
            {
                HttpHeadCnt = 0;
            }
        }
        
        if(HttpHeadOK && HttpDataLengthOK && HttpDataLength && !HttpHeadEndOK)
        #else
        if(HttpHeadOK && !HttpHeadEndOK)
        #endif
        {//0D 0A 0D 0A
            if(Res==0x0D && HttpHeadCnt==0)HttpHeadCnt++;
            else if(Res==0x0A && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res==0x0D && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res==0x0A && HttpHeadCnt==3){HttpHeadEndOK = 1;}
            else HttpHeadCnt = 0;
        }
        
        if(HttpHeadEndOK == 1)//http數據的head已經過去,后面的是真實數據
        {
            HttpHeadEndOK=0;
            HttpHeadCnt = 0;
            HttpDataLengthOK=0;
            
            HttpDataStartFlage=1;
            
            Usart1IdleTime = 3000;//GPRS判斷空閑時間需要3S左右,因為GPRS有延遲
        }
        //解析http數據-------------------------------end

 

 

 

 

 

把程序文件寫入Flash

  只要環形隊列里面有數據,就提取數據寫入Flash

  

 

 

  1: 不用多說

 

  2:http服務器的頭信息會返回程序文件的大小,程序中獲取了這個數據

  對比下寫入的和http實際返回的數據個數是不是一致

  

  獲取程序文件大小的程序如下:

 

  

 

 

  不過鑒於有的http服務器不會返回數據大小,所以

 

  如果用戶 #define UserContentLength 才會進行判斷

 

 

 

  3:計算校驗和

 

  4:把數據寫入Flash

 

 

 

 

 

判斷接收完數據

  

 

 

 

 

  如果判斷 IAPStructValue.ReadDataEndFlage == 1

  才認為是接收完了數據

 

  由於我是串口接收數據,所以只要是判斷串口出現了空閑就說明接收完了數據

  

 

 

 

 

接收完了更新程序以后檢驗下整個過程

            if(IAPStructValue.ReadDataEndFlage)//接收完更新程序
            {
                IAPStructValue.PutDataFlage = 0;//停止向環形隊列寫入數據
                IAPStructValue.UpdateFlage = 0; //更新標志清零
                IAPStructValue.ReadDataEndFlage = 0;//清零接收完更新程序標志
                
                #ifdef UserContentLength   //自己的Web服務器返回 Length: XXXXXXXX (本次的數據長度)
                //寫入的數據個數和http實際返回的數據個數不相等
                if( (IAPStructValue.UpdateAddressCnt - IAPStructValue.UpdateAddress) != HttpDataLength) 
                {
                    IAPSetUpdateStatus(UpdateStatus_MissingData);//數據錯誤(數據丟失)
                }
                else if(IAPStructValue.FlashWriteErrFlage == 1)//Flash寫錯誤
                #else
                if(IAPStructValue.FlashWriteErrFlage == 1)//Flash寫錯誤
                #endif    
                {
                    IAPSetUpdateStatus(UpdateStatus_FlashWriteErr);//Flash寫錯誤
                }
                else if(!IAPStructValue.Overflow)//沒有溢出過
                {
                    if(IAPCheckRamFlashAddress(IAPStructValue.UpdateAddress))//檢測某些位置的Flash的高位地址是不是0x08        //RAM的高位地址是不是0x20    
                    {
                        if(IAPStructValue.SumBin != -1)//獲取了雲端的校驗和
                        {
              if(IAPStructValue.SumBin == IAPStructValue.Sum)//校驗和正確
                            {
                                IAPSetUpdateChangeProgram();//切換運行程序地址
                                IAPSetUpdateStatus(UpdateStatus_WriteAppOk);//寫入0x01標志
                            }
                            else
                            {
                                IAPSetUpdateStatus(UpdateStatus_SumCheckErr);//數據和校驗錯誤
                            }    
                        }
                        else
                        {                
                            IAPSetUpdateChangeProgram();//切換運行程序地址
                            IAPSetUpdateStatus(UpdateStatus_WriteAppOk);//寫入0x01標志
                        }
                    }
                    else
                    {
                        IAPSetUpdateStatus(UpdateStatus_DataAddressError);//數據錯誤
                    }
                }
                else//數據溢出
                {
                    IAPSetUpdateStatus(UpdateStatus_DataOverflow);//數據溢出
                }
                delay_ms(2000);
                Reset_MCU();//重啟
            }

 

 

 

  1. 如果用戶使用了對比http返回的數據個數,則對比數據個數

    

 

 

 

 

  2.判斷寫Flash有沒有錯誤

 

    

 

 

  3.判斷寫環形隊列的時候有沒有溢出過

 

    

 

 

  4.檢測數據一開頭的那幾個地址是不是0x08 和 0x20

 

    

 

 

  5.如果有校驗和,則對比下程序的校驗和

    否則就不對比,然后寫入更新標志是0x01,切換程序運行的地址 ,  最后重啟

 

    

 

 

 

 

 

重啟以后,加載用戶程序

  

 

 

  

 

情況1:正常運行了用戶程序

  打開用戶程序

  正常運行用戶程序便會執行

  

 

 

  先看一下處理更新

 

  IAPSetUpdateStatus(UpdateStatus_None);//清除升級狀態(設置升級狀態為0)

 

  然后判斷如果是0x01,說明是運行的新程序

 

  然后切換下程序版本

 

 

 

  再看一下 GetUpdateInfo();

 

  

 

 

  為了便於一眼看出更新的狀態

 

  便把更新狀態,設置了對應的字符串

 

  

 

   現在就完成了升級了

 

情況2:運行用戶程序失敗

  

 

 

 

 

  提示:大家的這個處理更新程序,最好是讓程序運行一段時間,

 

  感覺程序運行沒有問題以后再調用

 

 

 

  如果用戶程序運行失敗,則執行不到處理更新程序,然后看門狗超時重啟

 

  接着運行 BootLoader程序

 

  

 

 

 

 

 

   BootLoader 判斷還是0xFF,說明沒有運行起來新更新的用戶程序

  設置更新狀態是 0xEE

  切換運行程序的地址(運行另一套用戶程序)

 

 

  

 

 

 

關於BootLoader程序里面的兩個定時器

一,下載超時定時器

  1.1下載程序的時候該定時器開始累加

    

  

 

 

 

    

 

 

  1.2.調用

    

 

 

 

  1.3.該定時器在寫入Flash的時候清零

    

 

 

二,整體運行超時定時器

  1.該定時器只要執行BootLoader程序,就開始運行,除非是Wi-Fi配網的時候才會清零,是強制的.

    

 

 

 

其它: Flash調整(保持所有程序的stmflash文件一樣)

一,Flash調整(可根據自己使用的f103系列的芯片進行設置),兼容f103全系列

  1.1 可在stmflash.h 調整Flash存儲位置

    

 

 

  1.2 一般,用戶只需要修改

    

 

 

 

 

  1.3 可在串口打印查看兩份程序文件的配置信息 

    user1ROMStart: 0x8004000   用戶程序1 Flash存儲的開始地址
    user1ROMSize : 0x5c00        用戶程序1 程序大小

    user2ROMStart: 0x8009c00   用戶程序2 Flash存儲的開始地址
    user2ROMSize : 0x5c00         用戶程序2 程序大小

 

    

 

 

 

    當前Flash存儲分配如下圖

    

 

 

 

 

 

  1.4 用戶程序1配置

 

    

 

 

  1.5 用戶程序2配置

 

    

 

 

結語

  有什么不明白的可在下面留言,我將根據情況對文章再做更改.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM