1. 程式人生 > >STM32網路遠端升級韌體的IAP程式實現與解析 ---附親測穩定能用的程式

STM32網路遠端升級韌體的IAP程式實現與解析 ---附親測穩定能用的程式

STM32網路遠端升級韌體的IAP程式實現與解析 ---附親測穩定能用的程式
http://www.openedv.com/thread-104667-1-1.html
(出處: OpenEdv-開源電子網)
 


          本文主要對STM32網路升級韌體的IAP程式進行解析,也就是在STM32聯網的情況下在瀏覽器上輸入指定的IP地址(目前設定為192.168.1.101),然後在瀏覽器上輸入使用者名稱和密碼,登陸後可以選擇需要升級的bin檔案進行韌體升級。以下是目前該程式應用的硬體與軟體環境:

         1.硬體:STM32F407(理論上STM32系列都可以),網絡卡晶片LAN8720,其他部分參考正點原子的STM32F407探索者開發板

         2.軟體:Keil5 ,LWIP1.4.1 主要是基於正點原子STM32F407探索者的第六十章網路通訊實驗程式與第五十五章串列埠IAP實驗程式(這部分只用到了跳轉和燒寫FLASH程式)。

           說明:該程式與我之前編寫的《STM32F407通過SD卡進行程序升級(把bin檔案燒寫到FLASH的方式)》程式整合起來就可以實現SD卡+網路升級,即可以通過SD卡進行程序升級,如果升級失敗自動跳轉 去進行網路升級,也可以直接進行網路升級。目前該程式可以應用在專案上,網路升級和SD卡升級均穩定無差錯。該程式的網路升級大概需要15秒鐘(從點選<上傳>到程序升級成功)。

         STM32F407通過SD卡進行程序升級(把bin檔案燒寫到FLASH的方式)》本論壇的連結:http://www.openedv.com/forum.php?mod=viewthread&tid=90835                以下先對網路升級部分的操作步驟進行解說,後面再貼上各個C檔案的程式。

一、網路升級的主要實現過程

       按住K1鍵開機,自動進入網路升級模式(進入網路升級模式的方法不一定是要按下K1,可以自己編寫條件,比如SD卡升級失敗後就進入網路升級模式),然後在瀏覽器上輸入192.168.1.101登陸頁面

 

 

        輸入使用者名稱和密碼(使用者名稱和密碼都是admin)之後,點選<登陸>進入檔案升級介面,選擇升級的bin檔案然後點選<上傳>進行升級

 

             本程式已經通過測試,網路升級不會有錯亂,程式中會對升級的bin檔案的檔名與檔案的合法性進行校驗,其中網路IP、使用者名稱、密碼、檔名均可在程式裡自行更改。

二、網頁介面的HTML程式碼編寫

          網頁介面不是用他人那種把網頁介面轉成陣列然後存放在程式裡呼叫的方式,用的是直接編寫HTML程式碼的方式,簡單直觀而且可以任意修改,登陸介面和檔案升級介面都可以自己修改,可以看函式http_write_loginweb()和http_write_updatebinweb(),效果如上圖的登陸介面和檔案升級介面。

//使用者和密碼登陸WEB介面的HTML內容程式碼填充

voidhttp_write_loginweb(char *pbuff, int *ppos,int error)

{

      *ppos += sprintf(pbuff + *ppos,"<HTML><META HTTP-EQUIV = \"Pragma\"CONTENT=\"no-cache\">");

      http_write_script(pbuff,ppos,error);

      *ppos += sprintf(pbuff + *ppos,"<BODY onload=doLoad();>\r\n");

      *ppos += sprintf(pbuff + *ppos,"<FORM METHOD=POST ACTION=\"/checklogin.cgi\">");

 

      //新增WEB介面的內容

      *ppos += sprintf(pbuff + *ppos,"<strong>使用者名稱</strong><input type=\"text\"size=\"20\" name=\"username\">");

      *ppos += sprintf(pbuff + *ppos,"<strong>密碼</strong><input type=\"password\"size=\"20\" name=\"password\">");

      *ppos += sprintf(pbuff + *ppos,"<BR><input type=\"submit\" name=\"login\"value=\"登陸\">");

 

      *ppos += sprintf(pbuff + *ppos,"</FORM></BODY></HTML>");

}

//選擇升級程式WEB介面的HTML內容程式碼填充

void http_write_updatebinweb(char *pbuff, int *ppos,int error)

{

  *ppos += sprintf(pbuff +*ppos, "<HTML><META HTTP-EQUIV = \"Pragma\"CONTENT=\"no-cache\">");

  http_write_script(pbuff,ppos,error);

  *ppos += sprintf(pbuff +*ppos, "<BODY onload=doLoad();>\r\n");

  *ppos += sprintf(pbuff +*ppos, "<FORM METHOD=POST enctype=\"multipart/form-data\"ACTION=\"/update.cgi\">");

 

  //新增WEB介面的內容

  *ppos += sprintf(pbuff +*ppos, "<TD><input name=\"upbin\"id=\"upbin\" type=\"file\"></TD>");

  *ppos += sprintf(pbuff +*ppos, "<TD> <INPUT name=\"subbin\"class=\"button\" id=\"subbin\" type=\"submit\"value=\"上傳\"></TD>");

  *ppos += sprintf(pbuff +*ppos, "</FORM></BODY></HTML>");

}

網頁介面還可以做得更美觀,但這個就要學一些HTML程式設計了,由於對HTML不是特別熟練,所以對於HTML部分就不做過多解析了。關於網路解析部分,可以直接看httpd.c和httpd_cgi_ssi.c。

三、登陸介面的POST指令解析

         網路升級主要用到POST指令,這個需要先了解一下POST指令相關的內容,還有就是CGI的解析,也用到了POST指令下發資料,但和GET的CGI解析方法一樣,關於SSI、CGI及GET和POST指令可以看我寫的《STM32 網路通訊Web Server中 SSI與CGI的應用解析》,本論壇連結:http://www.openedv.com/forum.php?mod=viewthread&tid=104581&extra=。用到CGI的部分主要是在輸入使用者名稱和密碼之後,點選登陸時,會下發POST指令,這時需要對POST指令進行解析,該POST指令資料通過軟體Wireshark來檢視,首先開啟Wireshark,選擇本地連線

 




           然後在位址列輸入 ip.addr == 192.168.1.101,然後按右邊的箭頭開始接收資料[size=14.6667px]

 

 

          關於解析的過程就是把POST指令找出來,然後執行相應的程式,比如對比使用者名稱和密碼等。接下來先看輸入使用者名稱和密碼,按下<登陸>之後,瀏覽器下發的POST指令資料:

 

 

           POST指令很明顯就是由字串”POST /XXXXX.cgi”開頭,而這個CGI現在是checklogin.cgi,這個可以看函式http_write_loginweb()裡面的HTML程式碼,

"<FORMMETHOD=POST ACTION="/checklogin.cgi">",這個HTML的意思是該form表單是通過post指令的方式下發資料,並由checklogin.cgi進行響應(本人HTML也是很菜,目前就這麼理解吧)。

           開啟POST指令需要定義#defineLWIP_HTTPD_SUPPORT_POST   1

           然後編寫接收和解析POST指令的三個函式:

err_thttpd_post_begin(void *connection, const char *uri, const char *http_request,

                       u16_t http_request_len, intcontent_len, char *response_uri,

                       u16_t response_uri_len,u8_t *post_auto_wnd)

{

#ifLWIP_HTTPD_CGI

  int i = 0;

#endif

structhttp_state *hs = (struct http_state *)connection;

if(!uri || (uri[0] == '\0')) {

    return ERR_ARG;

}

hs->cgi_handler_index = -1;   // 此變數為本人自己在struct http_state 新增 用於儲存CGI handler 索引為-1表示無CGI handler索引

hs->response_file = NULL; // 此變數為本人自己在struct http_state 新增 用於儲存 CGI handler 處理完後返回的響應uri.

#ifLWIP_HTTPD_CGI

  if (g_iNumCGIs && g_pCGIs) {

    for (i = 0; i < g_iNumCGIs; i++) {

      if (strcmp(uri, g_pCGIs.pcCGIName) ==0) {

hs->cgi_handler_index = i; // 找到響應的 CGI handler 將其儲存在cgi_handler_index 以便在httpd_post_receive_data中使用

         break;

       }

    }

  }

  if(i == g_iNumCGIs) {

    return ERR_ARG; // 未找到CGI handler

  }

#endif

  return ERR_OK;

}

#defineLWIP_HTTPD_POST_MAX_PAYLOAD_LEN    512//MEM2_MAX_SIZE//512

static char http_post_payload[LWIP_HTTPD_POST_MAX_PAYLOAD_LEN];//主要是存放參數值,如username=admin&password=admin&login=%B5%C7%C2%BD

static u32_thttp_post_payload_len = 0;

err_thttpd_post_receive_data(void *connection, struct pbuf *p)

{

    struct http_state *hs = (struct http_state*)connection;

    struct pbuf *q = p;

    int count;

    u32_t http_post_payload_full_flag = 0;

    while(q != NULL)  // 快取接收的資料至http_post_payload

    {   

      if(http_post_payload_len + q->len<= LWIP_HTTPD_POST_MAX_PAYLOAD_LEN) {

          MEMCPY(http_post_payload+http_post_payload_len,q->payload, q->len);

          http_post_payload_len += q->len;

      }

      else { // 快取溢位 置溢位標誌位

        http_post_payload_full_flag = 1;

        break;

      }

      q = q->next;

    }

    pbuf_free(p); // 釋放pbuf

    if(http_post_payload_full_flag) // 快取溢位 則丟棄資料

    {

        http_post_payload_full_flag = 0;

        http_post_payload_len = 0;

        hs->cgi_handler_index = -1;

        hs->response_file = NULL;

    }

    else if(hs->post_content_len_left == 0){  // POST資料已經接收完畢則處理

        if(hs->cgi_handler_index != -1) {

            count = extract_uri_parameters(hs,http_post_payload);  // 解析

            hs->response_file =g_pCGIs[hs->cgi_handler_index].pfnCGIHandler(hs->cgi_handler_index,count, hs->params,

                                            hs->param_vals); // 呼叫解析函式

            http_post_payload_len = 0;

        }      

        else {

            hs->response_file = NULL;

            http_post_payload_len = 0;

        }

    }

    return ERR_OK;

}

voidhttpd_post_finished(void *connection, char *response_uri, u16_tresponse_uri_len)

{

      struct http_state *hs = (struct http_state*)connection;

    if(hs->response_file != NULL) {

        strncpy(response_uri,hs->response_file,response_uri_len); // 拷貝uri 用於給瀏覽器響應相應的請求

    }

}

             這三個函式有多種寫法,基本思路就是把抽離出來後的資料,如username=admin&password=admin&login=%B5%C7%C2%BD,再把相應的資料存起來,比如把username存入hs->params[0]中,把admin存入hs->param_vals[0],具體看函式extract_uri_parameters()。

四、檔案升級介面的POST指令解析

          對下發升級檔案資料的POST指令進行解析這部分用不到上面的三個函式,所以是在函式http_recv()裡面直接對POST指令進行接收和解析。先看檔案升級介面的HTML程式碼,該程式碼是函式http_write_updatebinweb()實現。

          在這個HTML中,FORM表單的程式碼和其他的不一樣,這個要注意:<FORMMETHOD=POST enctype="multipart/form-data"ACTION="/update.cgi">其實就是添加了enctype="multipart/form-data"編碼方式,這個主要是用於下發檔案資料。

          在選擇好檔案之後,點選<上傳>,瀏覽器就開始下發POST指令以及升級檔案的資料。

          需要注意的是,#define PBUF_POOL_BUFSIZE       1600這個大小不能太小,因為一幀資料最大是1514,小於1514會導致接收到的資料出現錯誤。

            POST指令資料的解析可以分為三個步驟:

            第一步是接收到下發資料的POST指令,該POST指令的資料開頭為"POST/update.cgi",後面跟上一堆資料,這個可以通過軟體Wireshark來檢視[size=14.6667px]

 

 

          找到這個POST指令頭之後,接著找"boundary="這個字串,這個字串後面跟著39個字元”---------------------------------7e17132213f8”(長度和字元有可能是變化的),後面的39個字元是用來判斷下發的檔案資料的截止位置(我們叫它B),我們把這些字串先叫boundarystring。接下來還要找"Content-Length: "這個字串,這個字串後面的資料是指接下來還要接收的資料量,也就是總的資料量:361774。當然這個總資料量不等於升級檔案的資料量,所以才需要boundarystring來判斷。

            接下來是找"octet-stream"字串,這個字串後面跟著的就是升級檔案的真正資料了,所以這個是用來判斷接收檔案資料的起始位置,我們叫它A。
[size=14.6667px]

 

 

            那麼第一步可以說完成了,我們需要找到真正的升級檔案的資料可以理解為找到A和B之間的資料,並且把這些資料收集起來。


 

 

         所以第二步就是把檔案資料收集起來放到陣列mem2base[],這個陣列是存於外部SRAM中。

         第三步就是把陣列mem2base[]裡面的資料進行校驗,然後把資料燒寫入FLASH中。

         以上三步的實現過程可以看httpd.c裡面的函式http_recv()。

 

           按照以上的思路,只要把SD卡升級的部分加上,那就可以實現SD卡+網路升級,具體的工程檔案就不貼上來了,就貼幾個主要的C檔案。感興趣的朋友還可以對W25Q128這個FLASH晶片進行系統檔案升級,原理是一樣,16M的檔案升級也是穩定無誤。目前經過測試這個網路升級程式能夠穩定執行,15秒左右就能升級完畢,而且對W25Q128升級16M系統檔案也完全沒有問題(程式上稍加修改就可以),2分鐘左右就能升級完成。

本主題由 正點原子 於 2018-5-14 14:23 加入精華

 

STM32 網路遠端升級韌體的IAP程式實現與解析.zip

 

30.57 KB, 下載次數: 1405

 

主要的C檔案

收藏收藏27 支援支援4 反對反對

  回覆

舉報

   

lg75

  離線 

 

15

主題

32

帖子

0

精華

中級會員

Rank: 3Rank: 3

積分

282

金錢

282

註冊時間

2015-11-8

線上時間

37 小時

推薦

 發表於 2018-2-27 23:59:02 | 只看該作者

精神可嘉,分析透徹!

但是給你個建議,這種WEB方式更新韌體只適合裝置數量少的場合,幾百臺裝置要升級能把你累死在現場。
其次雖然埠對映可以實現穿透內網,但是在專案上基本上不可行,因為客戶不會讓你去動他們的路由器,你讓客戶給你對映一堆埠一般網管也不會配合你,客戶越大越難實施。
最後WEB方式佔用空間較大,浪費空間。且裝置要一直後臺執行WEB服務,影響效率。
最最後,你要考慮BIN檔案的安全性,不做加密釋出出去,別人抄板下載你的BIN就能直接銷售了。
 
  回覆 支援 2 反對 0

舉報

   

lzq12

  離線 

 

16

主題

216

帖子

1

精華

高階會員

Rank: 4

積分

970

金錢

970

註冊時間

2016-11-24

線上時間

156 小時

3#

  樓主| 發表於 2017-6-28 08:56:52 | 只看該作者

yklstudent 發表於 2017-6-27 18:26
這個是區域網吧

目前只在區域網測試,外網沒有機會試,哪位有機會可以試試外網反饋一下