银杏科技有限公司旗下技术文档发布平台 |
技术支持电话 | 0379-69926675-801 |
技术支持邮件 | Gingko@vip.163.com |
版本 | 日期 | 作者 | 修改内容 |
V1.0 | 2020-07-11 | gingko | 初次建立 |
实验三十一:HTTP_IAP_FPGA实验——更新升级FPGA
一、 实验目的与意义
了解FPGA的IAP结构。
了解FPGA的IAP特征。
掌握FPGA的IAP的使用方法。
掌握HTTP的使用方法。
掌握KEIL MDK 集成开发环境使用方法。
二、 实验设备及平台
-
-
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、FPGA简介
FPGA(Field Programmable Gate Array)是在PAL、GAL等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。
FPGA 器件属于专用集成电路中的一种半定制电路,是可编程的逻辑列阵,能够有效的解决原有的器件门电路数较少的问题。FPGA 的基本结构包括可编程输入输出单元,可配置逻辑块,数字时钟管理模块,嵌入式块RAM,布线资源,内嵌专用硬核,底层内嵌功能单元。由于FPGA具有布线资源丰富,可重复编程和集成度高,投资较低的特点,在数字电路设计领域得到了广泛的应用。FPGA的设计流程包括算法设计、代码仿真以及设计、板机调试,设计者以及实际需求建立算法架构,利用EDA建立设计方案或HD编写设计代码,通过代码仿真保证设计方案符合实际要求,最后进行板级调试,利用配置电路将相关文件下载至FPGA芯片中,验证实际运行效果。
FPGA采用了逻辑单元阵列LCA(Logic Cell Array)这样一个概念,内部包括可配置逻辑模块CLB(Configurable Logic Block)、输入输出模块IOB(Input Output Block)和内部连线(Interconnect)三个部分。 现场可编程门阵列(FPGA)是可编程器件,与传统逻辑电路和门阵列(如PAL,GAL及CPLD器件)相比,FPGA具有不同的结构。FPGA利用小型查找表(16×1RAM)来实现组合逻辑,每个查找表连接到一个D触发器的输入端,触发器再来驱动其他逻辑电路或驱动I/O,由此构成了既可实现组合逻辑功能又可实现时序逻辑功能的基本逻辑单元模块,这些模块间利用金属连线互相连接或连接到I/O模块。FPGA的逻辑是通过向内部静态存储单元加载编程数据来实现的,存储在存储器单元中的值决定了逻辑单元的逻辑功能以及各模块之间或模块与I/O间的联接方式,并最终决定了FPGA所能实现的功能,FPGA允许无限次的编程。
FPGA 具有远程升级功能(PS 模式),STM32 程序在运行的过程中可以实现对 FPGA 进行程序烧写,以此能够实现对产品中的固件进行更新升级,本实验通过 HTTP 功能选择打开升级文件,进而更新升级FPGA。iCore4的IP地址为192.168.0.10,电脑的IP的地址为192.168.0.2,在浏览器中输入 iCore4的 IP 地址即可网页界面选择文件。
四、 实验程序
1、主函数
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOI_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6|GPIO_PIN_8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_3, GPIO_PIN_SET);
/*配置GPIO引脚 : PI8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
/*配置GPIO引脚 : PH6 PH8 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/*配置GPIO引脚 : PH7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/*配置GPIO引脚 : PI3 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
led.initialize(); //LED初始化
key.initialize();
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
}
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、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;
}
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、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;
char data;
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;
NCONFIG_OFF;
DCLK_OFF;
for(i = 0; i < 5000; i++);
if(NSTATUS == 1)
{
LED_BLUE_ON;
while(1);
}
for(i = 0;i < 40;i++);
NCONFIG_ON;
for(i = 0; i < 40; i++);
for(cnt = 0;cnt < wr_len;cnt++){
data = *(recv_buffer + start_buf + cnt);
for(i = 0; i < 8; i++)
{
if(data&0x01)DATA0_ON;
else DATA0_OFF;
DCLK_ON;
data >>= 1;
DCLK_OFF;
}
}
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++){
data = *(recv_buffer + cnt);
for(i = 0; i < 8; i++)
{
if(data&0x01)DATA0_ON;
else DATA0_OFF;
DCLK_ON;
data >>= 1;
DCLK_OFF;
}
}
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++){
data = *(recv_buffer + cnt);
for(i = 0; i < 8; i++)
{
if(data&0x01)DATA0_ON;
DCLK_ON;
data >>= 1;
DCLK_OFF;
}
}
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));
if(CONFIG_DONE == 1){
LED_BLUE_OFF;
LED_GREEN_ON;
}else{
LED_BLUE_ON;
}
for(i = 0; i < 40; i++)
{
DCLK_ON;
for(i = 0; i < 800; i++);//延时100us
DCLK_OFF;
for(i = 0; i < 800; i++);//延时 100us
}
while(1);
}
}
}
LED_RED_ON;//升级失败,红灯亮
}
break;
//从uri获取文件名
default :
break;
}
close(httpcon);
}
五、 实验步骤
把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连);
将跳线冒插在USB UART;
把iCore4(USB UART)通过Micro USB线与计算机相连,为iCore4供电;
把iCore4的网口通过网线与计算机的网口相连;
设置本机IP;(方法见附录)
打开Keil MDK 开发环境,并打开本实验工程;
烧写程序到iCore4上;
也可以进入Debug模式,单步运行或设置断点验证程序逻辑。
打开浏览器输入iCore4的IP地址。
六、 实验现象
附录:
1、打开控制面板→网络和Internet→网络和共享中心→更改适配器设置→以太网→属性
2、Internet协议版本4→选择使用下面的IP地址(如下图所示),然后更改IP地址和默认网关