| **银杏科技有限公司旗下技术文档发布平台** |||| |技术支持电话|**0379-69926675-801**||| |技术支持邮件|Gingko@vip.163.com||| ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ | V1.0 | 2020-07-11 | gingko | 初次建立 | ===== 实验三十五:HTTP_IAP_ARM实验——更新升级STM32 ===== ==== 一、 实验目的与意义 ==== - 了解STM32的IAP结构。 - 了解STM32的IAP特征。 - 掌握STM32的IAP的使用方法。 - 掌握HTTP的使用方法。 - 掌握KEIL MDK 集成开发环境使用方法。 ==== 二、 实验设备及平台 ==== - iCore4 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.15.5923532fsFrHiE&id=551864196684|点击购买]]。 - JLINK(或相同功能)仿真器[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]。 - Micro USB线缆。 - 网线。 - Keil MDK 开发平台。 - 装有WIN XP(及更高版本)系统的计算机。 ==== 三、 实验原理 ==== === 1、HTTP简介 === * HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。HTTP是一个基于 TCP/IP通信协议来传递数据(HTML文件,图片文件,查询结果等)。 * HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。 * HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。 * 主要特点: * (1)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。 * (2)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。 * (3)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 * (4)无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。 * (5)支持B/S及C/S模式。 === 2、IAP简介 === * IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 * 通常在用户需要实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信管道(如USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作: * (1)检查是否需要对第二部分代码进行更新 * (2)如果不需要更新则转到4) * (3)执行更新操作 * (4)跳转到第二部分代码执行 * 第一部分代码必须通过其它手段,如JTAG或ISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一道烧入,以后需要程序更新是再通过第一部分IAP代码更新。 * 对于STM32来说,因为它的中断向量表位于程序存储器的最低地址区,为了使第一部分代码能够正确地响应中断,通常会安排第一部分代码处于Flash的开始区域,而第二部分代码紧随其后。 * 在第二部分代码开始执行时,首先需要把CPU的中断向量表映像到自己的向量表,然后再执行其他的操作。 * 如果IAP程序被破坏,产品必须返厂才能重新烧写程序,这是很麻烦并且非常耗费时间和金钱的。针对这样的需求,STM32在对Flash区域实行读保护的同时,自动地对用户Flash区的开始4页设置为写保护,这样可以有效地保证IAP程序(第一部分代码)区域不会被意外地破坏。 === 3、原理图 === * STM32具有IAP(在应用编程)功能,实现用户程序在运行的过程中对User Flash部分区域进行烧写来实现固件程序的更新升级,本实验有两部分代码(Bootloader程序和APP程序),第一部分代码不执行正常的功能操作,来执行对第二部分代码的更新,第二部分代码才是真正的功能代码,通过HTTP功能选择打开第二部分代码产生的bin文件,iCore3的IP地址为192.168.0.10,电脑的IP地址为192.168.0.2。如果直接上电则执行第二部分代码,如果按下ARM-KEY上电,则可在浏览器中输入iCore3的IP地址进入网页界面选择文件,执行第二部分代码的更新升级。 {{ :icore4:icore4_arm_hal_35_1.png?direct |}} ==== 四、 实验程序 ==== === 1、主函数 === int main(void) { led.initialize(); //LED初始化 key.initialize(); if(ARM_KEY_STATE == KEY_UP){ //按键松开状态直接跳向应用程序 goto start; } system_clock.initialize(); //系统时钟初始化 delay.initialize(216); //延时初始化 adc.initialize(); //AD初始化 my_malloc.initialize(SRAMIN); //动态内存初始化 usart6.initialize(115200); //串口波特设置 OSInit(); //UCOS初始化 while(lwip.initialize()) //lwip初始化 { LED_RED_ON; usart6.printf("\r\nETH initialize error!\r\n\r\n");//ETH初始化失败 } web.initialize(); //WEB初始化 OSTaskCreate(start_task,(void*)0,(OS_STK*)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO); OSStart(); //开启UCOS start: //测试用户代码是否从USER_FLASH_FIRST_PAGE_ADDRESS被编程 if (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000) { /* 转跳到用户程序 */ JumpAddress = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4); Jump_To_Application = (pFunction) JumpAddress; /* 初始化用户应用程序的堆栈指针 */ __set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS); Jump_To_Application(); /* 什么都不做 */ while(1); }else{ //蓝绿LED灯循环点亮 while(1){ LED_GREEN_ON; LED_BLUE_OFF; delay.ms(500); LED_GREEN_OFF; LED_BLUE_ON; delay.ms(500); } } } === 2、开始任务 === void start_task(void *pdata) { OS_CPU_SR cpu_sr; pdata = pdata ; OSStatInit(); //初始化统计任务 OS_ENTER_CRITICAL(); //关中断 #if LWIP_DHCP lwip_comm_dhcp_creat(); //创建DHCP任务 #if LWIP_DNS my_dns.initialize(); //创建DNS任务 #endif #endif OSTaskCreate(display_task,(void*)0,(OS_STK*)&DISPLAY_TASK_STK[DISPLAY_STK_SIZE-1],DISPLAY_TASK_PRIO); //显示任务 OSTaskSuspend(OS_PRIO_SELF); //挂起start_task任务 OS_EXIT_CRITICAL(); //开中断 } === 3、动态内存初始化 === //内存管理初始化 void my_mem_init(u8 memx) { mymemset(mallco_dev.memmap[memx],0,memtblsize[memx]*4); //内存状态表数据清零 mallco_dev.memrdy[memx]=1; //内存管理初始化OK } === 4、LwIP初始化 === //LWIP初始化(LWIP启动的时候使用) u8 lwip_comm_init(void) { OS_CPU_SR cpu_sr; struct netif *Netif_Init_Flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功 struct ip_addr ipaddr; //ip地址 struct ip_addr netmask; //子网掩码 struct ip_addr gw; //默认网关 if(lan8720.memory_malloc())return 1; //内存申请失败 if(lwip_comm_mem_malloc())return 1; //内存申请失败 if(lan8720.initialize())return 2; //初始化LAN8720失败 tcpip_init(NULL,NULL); //初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务 lwip_comm_default_ip_set(&lwipdev); //设置默认IP等信息 #if LWIP_DHCP //使用动态IP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; #else //使用静态IP IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]); IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); usart6.printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]); usart6.printf("静态IP地址........................%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); usart6.printf("子网掩码..........................%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); usart6.printf("默认网关..........................%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); #endif Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,&tcpip_input);//向网卡列表中添加一个网口 #if LWIP_DNS dns_init(); #endif if(Netif_Init_Flag==NULL)return 3;//网卡添加失败 else//网口添加成功后,设置netif为默认值,并且打开netif网口 { netif_set_default(&lwip_netif); //设置netif为默认网口 netif_set_up(&lwip_netif); //打开netif网口 } return 0;//操作OK. } === 5、WEB初始化 === static INT8U http_server_netconn_init(void)//动态刷新页面显示任务 { INT8U res; OS_CPU_SR cpu_sr; OS_ENTER_CRITICAL(); //关中断 res = OSTaskCreate(http_server_netconn_thread,(void*)0,(OS_STK*)&WEB_TASK_STK[WEB_STK_SIZE-1],WEB_PRIO); //创建TCP服务器线程 OS_EXIT_CRITICAL(); //开中断 return res; } === 6、http服务器服务函数 === static void http_server_serve(int httpcon) { int i; unsigned char recv_buffer[MAX_URI_SIZE]; unsigned long int file_len = 0; unsigned short int send_len = 0; char* name; //获取方法请求文件名 char req_name[32]={0x00,}; //发布方法请求文件名 unsigned long int content_len=0; unsigned long int rx_len = 0,start_buf=0; long int cnt; char sub[10]; char *p; long int wr_len = 0; unsigned long int tmp_len=0; unsigned char data[MAX_URI_SIZE]; int receive_length = 0; char boundary[64],endbound[64]; static __IO uint32_t FlashWriteAddress; unsigned char* http_response; st_http_request *http_request; receive_length = read(httpcon, recv_buffer, MAX_URI_SIZE); //提取http数据请求 http_response = (unsigned char*)recv_buffer; memset(tx_buf,0x00,MAX_URI_SIZE); http_request = (st_http_request*)tx_buf; parse_http_request(http_request, recv_buffer); // 分析请求后,转换为http_request //方法分析 switch (http_request->METHOD) { case METHOD_ERR : memcpy(http_response, ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE)); write(httpcon, (unsigned char *)http_response, strlen((char const*)recv_buffer)); break; case METHOD_HEAD: case METHOD_GET: //从uri获取文件名 name = http_request->URI; if(strcmp(name,"/index.htm")==0 || strcmp(name,"/")==0 || (strncmp(name,"/index.html",11)==0)) { file_len = strlen(ALLOCATION_HTML); make_http_response_head((unsigned char*)http_response, PTYPE_HTML,file_len); write(httpcon, (unsigned char *)http_response, strlen((char const*)recv_buffer)); send_len=0; while(file_len) { if(file_len>1024) { write(httpcon, (const unsigned char*)ALLOCATION_HTML+send_len, 1024); send_len+=1024; file_len-=1024; } else { write(httpcon, (const unsigned char*)ALLOCATION_HTML+send_len, file_len); send_len+=file_len; file_len-=file_len; } } }else if(strcmp(name,"/demo_get.asp") == 0){ make_measure_response(tx_buf); sprintf((char *)recv_buffer,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length:%d\r\n\r\n%s",strlen(tx_buf),tx_buf); write(httpcon, (unsigned char *)recv_buffer, strlen((char *)recv_buffer)); } break; /*POST 方法*/ case METHOD_POST: mid(http_request->URI, "/", " ", req_name); if(strcmp(req_name,"upload.cgi") == 0){ LED_BLUE_ON; //进入升级文件指示灯 mid(http_request->URI,"boundary=", "\r\n", (char*)boundary); //获取内容长度 mid(http_request->URI,"Content-Length: ","\r\n",sub); content_len = ATOI32(sub,10); p = strstr((char*)http_request->URI,boundary); p += strlen(boundary); p = strstr(p,boundary); rx_len = p - http_request->URI; rx_len = receive_length - rx_len + 2; p = strstr((char*)http_request->URI,"octet-stream\r\n\r\n"); p += strlen("octet-stream\r\n\r\n"); wr_len = p - http_request->URI + 5; start_buf = wr_len ; wr_len = receive_length - start_buf; FLASH_If_Init();//解锁 //擦除FLASH if(FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS)){ LED_RED_ON; }; FlashWriteAddress = USER_FLASH_FIRST_PAGE_ADDRESS; rx_len = wr_len; //从USER_FLASH_FIRST_PAGE_ADDRESS地址写入数据 for(cnt = 0;cnt < wr_len ;cnt++){ *(data + cnt) = *(recv_buffer + start_buf + cnt); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, FlashWriteAddress + cnt, *(uint32_t*)(data + cnt)) != HAL_OK) { LED_RED_ON;//写入失败,红灯亮 } } while(rx_len <= content_len) { memset(recv_buffer,0x00,MAX_URI_SIZE); tmp_len = read(httpcon, recv_buffer, MAX_URI_SIZE); if(tmp_len > (strlen(boundary) + 8)) { for(i = 0; i < strlen(boundary);i ++){ endbound[i] = recv_buffer[tmp_len - strlen(boundary) + i - 4]; } //判断有没有获取到结束标志字符串boundary if(strncmp(endbound,boundary,strlen(boundary)) !=0){ if(tmp_len > MAX_URI_SIZE) tmp_len = MAX_URI_SIZE; start_buf = tmp_len; for(cnt = 0;cnt < tmp_len ;cnt++){ if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, FlashWriteAddress + rx_len + cnt, *(uint32_t*)(recv_buffer + cnt)) != HAL_OK) { LED_RED_ON; } } rx_len += tmp_len; tmp_len = 0; }else{ if(tmp_len > MAX_URI_SIZE) tmp_len = MAX_URI_SIZE; //将除去获取的结束标识字符串boundary的数据,写入FLASH start_buf = tmp_len - strlen(boundary) - 8; for(cnt = 0;cnt < start_buf ;cnt++){ if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, FlashWriteAddress + rx_len + cnt, *(uint32_t*)(recv_buffer + cnt)) != HAL_OK) { LED_RED_ON;//写入失败,红灯亮 } } rx_len += tmp_len; tmp_len = 0; /*写入flash数据结束,返回http OK */ make_upload_response(tx_buf); sprintf((char *)http_response,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length:%d\r\n\r\n%s",strlen(tx_buf),tx_buf); write(httpcon, (unsigned char *)http_response, strlen((char const*)recv_buffer)); FLASH->ACR|=1<<10; HAL_FLASH_Lock();//锁FLASH LED_BLUE_OFF; LED_GREEN_ON; while(1); } } } LED_RED_ON;//升级失败,红灯亮 } break; //从uri获取文件名 default : break; } close(httpcon); ==== 五、 实验步骤 ==== - 把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); - 将跳线冒插在USB UART; - 把iCore4(USB UART)通过Micro USB线与计算机相连,为iCore4供电; - 打开Keil MDK开发环境,并打开APP实验工程,编译连接后,将Objects文件夹下的app.hex文件拷贝至hex_to_bin文件夹下,将其转化成app.bin文件(方法:将app.hex拉至HEX2BIN应用程序); - 把iCore4的网口通过网线与计算机的网口相连; - 设置本机IP;(方法见附录) - 打开Keil MDK 开发环境,并打开IAP实验工程; - 按下ARM-KEY将烧写Bootloader程序到iCore4上; - 打开浏览器输入iCore4的IP地址。 ==== 六、 实验现象 ==== * 在浏览器(谷歌)中输入iCore4的IP地址,进入网页(如下图所示),选择升级文件(app.bin文件),点击升级,成功后绿色ARM-LED点亮,重新上电三色FPGA-LED循环闪烁。 {{ :icore4:icore4_arm_hal_35_2.png?direct&600 |}} {{ :icore4:icore4_arm_hal_35_3.png?direct&600 |}}   ==== 附录: ==== 1、打开控制面板→网络和Internet→网络和共享中心→更改适配器设置→以太网→属性 {{ :icore4:icore4_arm_hal_35_4.png?direct |}} 2、Internet协议版本4→选择使用下面的IP地址(如下图所示),然后更改IP地址和默认网关 {{ :icore4:icore4_arm_hal_35_5.png?direct |}}