TCP/IP協議學習(一) LWIP實現網絡遠程IAP下載更新

  最近須要實現經過TCP/IP遠程IAP在線更新功能,忙了2周終於在原有嵌入式服務器的基礎上實現了該功能,這裏就記錄下實現的過程。編程

  IAP又稱在應用編程,其實說簡單點就是實現不須要jlink,僅經過芯片自帶接口如CAN,USB,Ethernet便可實現下載功能.以我用過的stm32f207芯片爲例,就有三種啓動方式,SRAM啓動,User boot(即flash地址啓動,用戶應用執行),System boot(即系統地址啓動,用於串口下載),看到這是否明白點什麼,System boot模式下載實現的過程就是IAP應用編程,不過這段程序通常都是芯片公司燒好在內部固定地址的(通常不容許修改)。而本例中的網絡遠程下載更新也是實現這個過程,不過執行將徹底在user boot(即flash)中。 上面講的都是IAP的概念,下面進入正題: 數組

  第一步:瞭解ARM的啓動過程(這個網上有很清晰的說明,這裏就粗略講下)瀏覽器

  上電,復位,STM32芯片根據boot引腳將中斷向量表地址(起始地址)置於0x80000000,同時將PC指針置於該地址0x80000000處(這裏的0x80000000是由flash地址0x08000000映射的),即起始是跳轉到flash首地址,以後完成的就是創建堆棧,最後跳轉到_main函數(啓動文件中,固然stm32f2xx的頭文件是先跳到SystemInit),此時程序正式執行。其實瞭解了上面的知識,IAP的實現就比較好理解了(由於我實際操做與STM32提供的方案(見STM32F2x7_ETH_IAP)有區別,這裏先以stm32提供的方案作介紹),首先確定要有可以實現下載的程序(也就是所謂的引導guidance程序)和用戶實際執行的程序,通過初步設計,在flash中結構以下圖:服務器

  其中guidance區域用於實現升級引導,user application就是用戶實際運行的代碼。規劃好代碼在整個flash中的地址,下面就是具體的實現了。由上可知guidance程序就是整個IAP實現的核心程序,它須要實現兩個功能:網絡

  (1)通常狀況下跳轉user application, 執行用戶代碼app

  (2)特殊狀況下進入IAP模式,能夠更新用戶代碼(通常是按鍵,固然也可接收外部指令進入IAP模式)函數

 1. 跳轉代碼post

  官方例程有標準代碼能夠直接使用,它實現的就是將PC指針跳轉到0x08008000,此時user application處代碼開始執行。ui

  typedef  void (*pFunction)(void);   pFunction Jump_To_Application;   uint32_t JumpAddress; JumpAddress = *(__IO uint32_t*) (USER_APPLICATION_FIRST_ADDRESS + 4); Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) USER_APPLICATION_FIRST_ADDRESS); Jump_To_Application();

  注意:user application程序的main函數中須要將中斷向量表從新定位到user application首地址。url

  即須要添加NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x08008000);(通常NVIC初始化時會重定義到0x0,注意註釋掉)不然應用代碼的中斷會不響應,這與中斷響應的機制有關,默認地址爲0x0,此時中斷產生時查找向量表得到的地址是guidance程序中斷對應地址,執行就會出錯。

2.遠程網絡IAP實現

  遠程網絡IAP的實現是基於lwip的,由於lwip中網絡接口層,IP,TCP層的配置與正常的網絡通訊並無區別,且知識複雜,這裏不在贅述,主要講述http層代碼的處理.http層代碼處理包含兩部分,

 1.http首部的處理

 2.正文代碼的處理(即接收數據的處理)

以官方例程爲例,上面兩部分的處理都是在http_recv函數中實現的,想了解http發送的實際過程,那麼經過抓包分析就是最簡潔的方式,這裏使用的就是WireShark軟件。

1.上圖是瀏覽器執行升級時發送的http首部,既然知道首部格式,那麼在服務器中就能夠對接收數據進行處理,上面框中數據就是須要處理的字段。首先將TCP層接收的數據存儲
data中,Post/upload.cgi爲http包含了http的方式,url,則經過比較strncmp(data,「Post /upload.cgi」,16) == 0便可判斷是否進入升級(cgi)模式。Content-Length爲正文的長度,由於傳輸的時候都是以字符傳輸的,所以將接收到的字符還要轉化成數字,例程中是經過Parse_Content_Length實現的。 固然也能夠利用strstr函數得到正文長度,代碼以下: /*讀取Content-Length的首地址*/
if((ContentLengthStart= strstr(data, "Content-Length: ")) != NULL) { ptr = &ContentLengthStart[16]; }
得到ptr即爲Content-Length: 後數字字符首地址,後續便可將字符數組轉換成數字,見示例程序,不在贅述。

2.首部處理完畢後,下面即是http正文的處理。處理http正文,固然也須要對瀏覽器發送的包進行分析,如下圖爲例 http發送起始包(728字節)

 http發送起始包實際內容

  與上面的首部作比較便可知第一個接收到的數據包728字節僅包含http首部,其中結尾的0d 0a 0d 0a即\r\n\r\n(休止符),即肯定爲upload.cgi時,修改標誌位(UploadSymbol),解鎖flash,擦除指定地址的flash(本例中擦除sector2,3,4),得到正文長度後便可跳出,等待下個數據包的接收,下面真正開始正文的處理。如何處理正文,仍是以發送的實際數據包爲準判斷,其中首頁和尾頁要特殊處理,固然這也是根據實際傳輸的數據包得出的,以下。

正文首頁:

正文尾頁:

  看到上面兩段數據報文,就能夠清晰的知曉,真正的程序起始是從87.I那一行開始的,程序的結尾到community那一行結束,固然紅色部分整個都包含在正文數據中,這就須要咱們寫入flash時要去掉數據中附加的信息,由於這裏比較重要,我就貼上個人處理代碼:

char   *httpHeadEndStart;     //首部末地址
char   *postContent;          //正文首地址
char   *pstr;            //接收到數據包首地址 int    TotalReceived = 0;      //接收到的數據包
int    len;                    //實際接收到數據包的長度
TotalReceived+= p->tot_len;   //後面參數爲每一次接收到的數據包總長度
len= p->tot_len;

if(UploadSymbol== 1)        //正文接收的第一個數據包 { if((pstr = strstr(pstr,"\r\n\r\n"))!= NULL)//去除http附加信息 { pstr = &pstr[4];         //87.行首地址 len = len - (pstr - psl);        //減去附加信息長度,即實際數據長度 } } if(TotalReceived== ContentLength)        //末尾去除http附加信息,正文接收的最後一個包 { if((psl = strstr(psl, "\r\n--"))!=NULL) { len = psl - pstr; } If(len) {    IAP_HTTP_writedata (pstr,len);       //將數據寫入flash中 TotalReceived = 0; UploadDateSymbol = 0; FLASH_Lock(); } } else { If(len)   IAP_HTTP_writedata (pstr, len);       //將數據寫入flash中 } UploadSymbol++;
  如上,就將整個數據完整的寫入flash中,固然實際工做遠不僅如此,如flash的解鎖,寫入和加鎖,其中若是按照4字節即word寫入,還要考慮接收到的數據包不是4的倍數的狀況,這時須要從後續數據包裏拿出數據湊足4字節,這部分的代碼都是由IAP_HTTP_writedata函數實現,方法很巧妙,想了解的人也能夠本身去解讀這部分代碼。
上面主要都是正常操做數據怎麼處理,但有一句話說,好的代碼是錯誤操做時也要給予合適的應對,那麼客戶若是沒上傳文件就點升級按鈕了怎麼辦,固然了,仍是老辦法,先抓包分析,在給出解決辦法,抓包圖以下:

  看到和上面有什麼區別沒,filename=」」中間爲空,那麼就好辦了,這裏我就給出例程中的處理辦法:
for (i=0;i<len;i++) { if (strncmp ((char*)(data+i),"filename=", 9)==0) { FilenameOffset = i+10; //得到filename=「後字符偏移量
       break; } } i =0; if (FilenameOffset) { while((*(data+FilenameOffset + i)!=0x22)&&(i<13)) //去查ASCII表很容易就知曉0x22表明字符" "
 { filename[i] = *(data+FilenameOffset + i); i++; } filename[i] = ‘\0’; }

若i == 0則跳出,表示沒有接收到文件,發送出錯文件,或者直接跳回當前網頁均可以(具體能夠看官方例程).

相關文章
相關標籤/搜索