最近须要实现经过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则跳出,表示没有接收到文件,发送出错文件,或者直接跳回当前网页均可以(具体能够看官方例程).