stm32f746 discovery基于tcp服务器的IAP

avatar
作者
猴君
阅读量:0

目录

前言

一、工程环境

二、添加boot代码

1.入口

2.跳转

3.获得升级数据

4.扇区擦除 

5.标志位读取 

6.数据写入 

7.获取地址所在扇区 

8.读取数据 

三、添加tcp代码

 1.tcp初始化

2.处理tcp连接信息

3.处理收到的数据 

4.处理连接错误

 5.轮询函数,处理TCP连接事件

6.数据被成功发送后被调用。用于管理TCP回显服务器的发送逻辑

 7.发送从客户端接收到的数据

8. 关闭TCP连接,并释放与该连接相关的资源。

 四、问题

1. flash写入

2.注意stm32是小端模式

3. app代码中断向量表的修改

4.从以太网接收数据缺失 

5.boot、app1、app2分区注意 



前言

        IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。在应用编程(IAP)是用户的应用代码对片内Flash存储器进行擦除/编程的方法。这种方式的典型应用就是用一小段代码来实现程序的下载,实际上单片机的ISP功能就是通过IAP技术来实现的,即片子在出厂前就已经有一段小的boot程序在里面,片子上电后,开始运行这段程序,当检测到上位机有下载要求时,便和上位机通信,然后下载数据到数据存储区,从而实现固件升级。 

        本代码仅仅对文件头四个字节是否为0x5A5A5E5E和尾四个字节是否为0xA5A5E5E5进行判断,除此之外没有其他正确性验证,需要按需添加。

        本代码基于官方lwip与flash示例代码修改而来。


一、工程环境

 

        选yes的话软件会自动帮你配置该开发板的所有外设以及系统,选no则不会,在意内存消耗,并且没有其他功能需求就选no。我有其他需求这里选择了yes。

        因为我选择了yes大部分东西已经自动配置好了,这里告诉选择no的人需要修改的地方,可以自己参考,具体参数这里就不给出了,重新生成一个yes的配置就可以参考。

        时钟树默认即可

        配置完成后,生成代码并且用keil打开。


二、添加boot代码

1.入口

//启动标志 unsigned int x = 0x5A5A5E5E; void Start_BootLoader(void) { /* 测试代码 功能:在app1没有能力修改标志位时,将标志位清除,避免标志位锁死 后续将该处删除并且在app1中修改标志位后软重启即可实现需要功能 */ 	if(0 == n) 	{ 		n++; 		WriteFlash(0X080BFFFC, &n, 1); 	} /* 功能选择代码 功能:通过读取标志位的值,进入不同功能 1.启动app1 2.将app2中的升级程序写入到app1 ...... */ 	switch (Read_Start_Mode()) 	{ 		case Startup_Normol://正常启动Startup_Normol 		{ 			//printf("\nNormal start......\n"); 			IAP_ExecuteApp(Application_1_Addr); 			break; 		} 		case Startup_Update://升级Startup_Update 		{ 			//printf("> Start update......\r\n"); 			MoveCode(Application_2_Addr, Application_1_Addr, Application_Size); 			WriteFlash(0X080BFFFC, &x, 1);//修改标志位,写入跳转标志 			//printf("> Update down......\r\n"); 			break; 		} 		default: 			break; 	} /* 以太网接收升级程序代码 功能:连接以太网 */ 	while(Read_Start_Mode() != Startup_Normol && Read_Start_Mode() !=Startup_Update) 	{	 		MX_LWIP_Process();		 	}     } 

2.跳转

typedef void (*pFunction)(void); void IAP_ExecuteApp (uint32_t App_Addr) { 	pFunction JumpToApp; 	if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )	//检查栈顶 	{  		//__set_FAULTMASK(1);				 		__set_PRIMASK(1);                           //设置所有时钟到默认状态         HAL_RCC_DeInit();                  //关闭滴答定时器,复位到默认值         SysTick->CTRL = 0;         SysTick->LOAD = 0;         SysTick->VAL = 0;                  //关闭所有中断,清除所有中断挂起标志         for (int i = 0; i < 8; i++)         {             NVIC->ICER[i]=0xFFFFFFFF;             NVIC->ICPR[i]=0xFFFFFFFF;         }                  //使能全局中断         __set_PRIMASK(0);                  __set_CONTROL(0);                          		JumpToApp = (pFunction) * ( __IO uint32_t *)(App_Addr + 4);				//第二个字开始	 		//MSP( * ( __IO uint32_t * ) App_Addr );								//初始化堆栈 		__set_MSP(*(__IO uint32_t*) App_Addr); 		JumpToApp(); 		while(1); 	} 	 }

3.获得升级数据

void MoveCode(unsigned int src_addr, unsigned int des_addr, unsigned int byte_size) {      //擦掉app1     //printf("> Start erase des flash......\r\n");     Erase_page(des_addr, 0x0807FFFF);     //printf("> Erase des flash down......\r\n");      //接收数组     unsigned int temp[256]; /* 读app2写app1代码 功能:从app2的第五个字节开始,写入到app1中 ...... */ 	for(int i = 0; i < byte_size/1024; i++) 	{ 		ReadFlash((src_addr + i*1024 +4), temp, 256);//写入app2数据的头四个字节为0x5A5A5E5E需要跳过 		WriteFlash((des_addr + i*1024), temp, 256); 	}  	//printf("> Copy down......\r\n"); 	Erase_page(Application_2_Addr, 0x080BFFFF);     //校验数据  }

4.扇区擦除 

/*static*/ int Erase_page(uint32_t Start_Addr, uint32_t End_Addr) { 	uint32_t FirstSector = 0, NbOfSectors = 0;  	HAL_FLASH_Unlock(); 	     FirstSector = GetSector(Start_Addr);//起始扇区     NbOfSectors = GetSector(End_Addr) - FirstSector + 1;//擦除数  	//擦FLASH 	FLASH_EraseInitTypeDef FlashSet; 	FlashSet.TypeErase = FLASH_TYPEERASE_SECTORS;//扇区擦除FLASH_TYPEERASE_MASSERASE块擦除     FlashSet.VoltageRange  = FLASH_VOLTAGE_RANGE_3; 	FlashSet.Sector = FirstSector; 	FlashSet.NbSectors = NbOfSectors;      	/*设置PageError,调用擦除函数*/ 	uint32_t PageError = 0; 	HAL_FLASHEx_Erase(&FlashSet, &PageError); 	 	HAL_FLASH_Lock();  	return 1; }

5.标志位读取 

unsigned int Read_Start_Mode(void) {     unsigned int mode = 0; 	ReadFlash(0X080BFFFC, &mode, 1); 	return mode; }

6.数据写入 

/*static*/ void WriteFlash(uint32_t addr, uint32_t * buff, uint16_t word_size) {      //解锁flash     HAL_FLASH_Unlock();      // 写入flash     for(int i = 0;i < word_size ; i++)     {         if(HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addr + 4 * i ,buff[i])) 		{ 			// HAL_Delay(1); 		}     }      //flash上锁     HAL_FLASH_Lock();  }

7.获取地址所在扇区 

static uint32_t GetSector(uint32_t Address) { 	uint32_t sector = 0;  	if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)) 	{ 		sector = FLASH_SECTOR_0; 	} 	else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)) 	{ 		sector = FLASH_SECTOR_1; 	} 	else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)) 	{ 		sector = FLASH_SECTOR_2; 	} 	else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)) 	{ 		sector = FLASH_SECTOR_3; 	} 	else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)) 	{ 		sector = FLASH_SECTOR_4; 	} 	else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)) 	{ 		sector = FLASH_SECTOR_5; 	} 	else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)) 	{ 		sector = FLASH_SECTOR_6; 	} 	else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_7) */ 	{ 		sector = FLASH_SECTOR_7; 	} 	return sector; }

8.读取数据 

static void ReadFlash(uint32_t addr, uint32_t * buff, uint16_t word_size) { 	for(int i =0; i < word_size; i++) 	{ 		buff[i] = *(volatile uint32_t*)(addr + 4 * i); 	} 	return; }

三、添加tcp代码

 1.tcp初始化

void tcp_echoserver_init(void) {     //套接字     tcp_echoserver_pcb = tcp_new();      if(tcp_echoserver_pcb != NULL)//创建成功     {         err_t err;         err = tcp_bind(tcp_echoserver_pcb, IP_ADDR_ANY, Port_Number);//绑定ip以及端口         if (err == ERR_OK)         {             tcp_echoserver_pcb = tcp_listen(tcp_echoserver_pcb);//开始监听             // printf("开始监听 \r\n");             tcp_accept(tcp_echoserver_pcb, tcp_echoserver_accept);//等待连接             // printf("挂载客户端连接回调函数 \r\n");         }         else         {             memp_free(MEMP_TCP_PCB, tcp_echoserver_pcb);         }       }  }

        记得在主函数中调用。 

2.处理tcp连接信息

static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err) {     err_t ret_err;     struct tcp_echoserver_struct *es;      LWIP_UNUSED_ARG(arg);     LWIP_UNUSED_ARG(err);      tcp_setprio(newpcb, TCP_PRIO_MIN);//新建立的TCP连接设置最低优先级     // printf("收到客户端连接请求,设置刚连接的客户端为最低优先级 \r\n");      // printf ("客户端 IP address: %s\r\n", iptxt);      es = (struct tcp_echoserver_struct *)mem_malloc(sizeof(struct tcp_echoserver_struct));     if (es != NULL)     {         es->state = ES_ACCEPTED;         es->pcb = newpcb;//将TCP PCB与新数据结构关联         es->retries = 0;//初始化重试计数器         es->p = NULL;         // printf("为新连接的客户端挂载需要的回调函数及 调用参数 \r\n");                  tcp_arg(newpcb, es);//将新分配的 es 结构作为参数传递给 newpcb           tcp_recv(newpcb, tcp_echoserver_recv);//初始化LWIP tcp_recv NewPCB的回调函数          tcp_err(newpcb, tcp_echoserver_error);//初始化NewPCB的LWIP tcp_err的回调函数          tcp_poll(newpcb, tcp_echoserver_poll, 0);//初始化LWIP tcp_poll NewPCB的回调函数          tcp_sent(newpcb, tcp_echoserver_sent);//初始化LWIP tcp_sent 的回调函数          ret_err = ERR_OK;     }     else     {         tcp_echoserver_connection_close(newpcb, es);//关闭连接         // printf("tcp_echoserver_struct 内存申请失败 关闭连接 \r\n");          ret_err = ERR_MEM;     }   return ret_err; }

3.处理收到的数据 

static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {     struct tcp_echoserver_struct *es;     struct pbuf *ptr = NULL;     uint16_t t = 0, s = 0;     err_t ret_err;     //unsigned int State = 0;      LWIP_ASSERT("arg != NULL",arg != NULL);     // printf("收到客户端数据\r\n");      es = (struct tcp_echoserver_struct *)arg;      if(p == NULL)//从客户端接收到一个空帧,关闭连接      {         // printf("收到断开连接请求 \r\n");         es->state = ES_CLOSING;//断开远程客户端         if(es->p == NULL)         {             tcp_echoserver_connection_close(tpcb,es);         }         else//数据未发送完成         {             // printf("发送的数据还未发送完成 \r\n");             // printf("装载发送完成回调函数 \r\n");             tcp_echoserver_send(tpcb, es);         }         ret_err = ERR_OK;     }     else if(err != ERR_OK)//从客户端收到非空帧,但由于某种原因err != ERR_OK     {         if(p != NULL)//释放p         {             es->p = NULL;             pbuf_free(p);         }         ret_err = err;     }     else if(es->state == ES_ACCEPTED)//保存并发送数据     {          rxdatalen = p->len;//长度         if(rxdatalen > RX_DATA_SIZE)         {             // printf("receive data too long\n");             while(1);         }         /*         接收缓存代码         功能:将phy中的数据缓存写入数组         */         ptr = p;	         while(ptr != NULL)         {             memset(rxdata,0,RX_DATA_SIZE);             for(; s < ptr->len; t++, s++)             {                 rxdata[t] =  *((uint8_t*)ptr->payload + s);             }             ptr = ptr->next;             s   = 0;         }           /*         接收数据处理代码         功能:在识别到标准数据头后开始写入         NumBering偏移量         */         if(rxdata[0] == 0x5A && rxdata[1] == 0x5A && rxdata[2] == 0x5E && rxdata[3] == 0x5E)         {             int i = 0;             for(;i < rxdatalen; i++ )//将收到的数据包进行处理转化为纯数据             {                 if( i % 4 == 0)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i];                 }                 else if( i % 4 == 1)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 8;                 }                 else if( i % 4 == 2)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 16;                 }                 else if( i % 4 == 3)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 24;                 }                 //my_send_data(tcp_echoserver_pcb, &rxdata[i], 4);             }             tcp_write(tpcb, RelayData, 1, TCP_WRITE_FLAG_COPY);//回复客户端,观察写入数据             Erase_page(Application_2_Addr, 0x080BFFFF);//擦除app2             WriteFlash((Application_2_Addr), RelayData, rxdatalen/4);//写入app2             NumBering += rxdatalen;//累加字节数             memset(RelayData,0,TR_DATA_SIZE*4);//清零         }         else if(NumBering > 0)         {             int i = 0;             for(;i < rxdatalen; i++ )//将收到的数据包进行处理转化为纯数据             {                 if( i % 4 == 0)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i];                 }                 else if( i % 4 == 1)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 8;                 }                 else if( i % 4 == 2)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 16;                 }                 else if( i % 4 == 3)                 {                     RelayData[i/4] = RelayData[i/4] | rxdata[i] << 24;                 }                 //my_send_data(tcp_echoserver_pcb, &rxdata[i], 4);             }             tcp_write(tpcb, RelayData, 1, TCP_WRITE_FLAG_COPY);//回复客户端,观察写入数据             if(0xE5E5A5A5 == RelayData[(i-1)/4])//在是标准尾时,将标志位写入升级             {                 WriteFlash((Application_2_Addr+NumBering), RelayData, i/4-1);                 WriteFlash(0X080BFFFC, &Upgrade_Flags, 1);             }             else             {                 WriteFlash((Application_2_Addr+NumBering), RelayData, i/4);             }             NumBering += rxdatalen;//累加字节数             memset(RelayData,0,TR_DATA_SIZE*4);//清零         }          tcp_recved(tpcb, p->tot_len);         pbuf_free(p);         ret_err = ERR_OK;        }     else     {         /* 未知的 es->state,垃圾数据  */         tcp_recved(tpcb, p->tot_len);         //es->p = NULL;         pbuf_free(p);         ret_err = ERR_OK;     }     return ret_err; }

4.处理连接错误

static void tcp_echoserver_error(void *arg, err_t err) {     struct tcp_echoserver_struct *es;      LWIP_UNUSED_ARG(err);     //printf("错误 : %d \r\n",err);     es = (struct tcp_echoserver_struct *)arg;     if (es != NULL)     {         /*  free es structure */         mem_free(es);     } }

 5.轮询函数,处理TCP连接事件

static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb) {     err_t ret_err;     struct tcp_echoserver_struct *es;      es = (struct tcp_echoserver_struct *)arg;     //HAL_Delay(250);     if (es != NULL)     {         if (es->p != NULL)         {             //tcp_sent(tpcb, tcp_echoserver_sent);             tcp_echoserver_send(tpcb, es);         }         else         {             if(es->state == ES_CLOSING)             {                 tcp_echoserver_connection_close(tpcb, es);// 发送完毕,关闭连接              }         }         ret_err = ERR_OK;     }     else     {         tcp_abort(tpcb);         ret_err = ERR_ABRT;     }     return ret_err; }

6.数据被成功发送后被调用。用于管理TCP回显服务器的发送逻辑

static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) {   struct tcp_echoserver_struct *es;    LWIP_UNUSED_ARG(len);    es = (struct tcp_echoserver_struct *)arg;   es->retries = 0;    if(es->p != NULL)   {     /* still got pbufs to send */     tcp_echoserver_send(tpcb, es);   }   return ERR_OK; }

 7.发送从客户端接收到的数据

static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es) {     struct pbuf *ptr;     err_t wr_err = ERR_OK;     //printf("发送数据的总长度 : %ld \r\n",es->p->tot_len);     //printf("发送数据: \r\n");     while ((wr_err == ERR_OK) && (es->p != NULL) && (es->p->len <= tcp_sndbuf(tpcb)))     {         /* get pointer on pbuf from es structure */         ptr = es->p; //得到当前需要发送的数据缓存 pbuf                  /* enqueue data for transmission */         wr_err = tcp_write(tpcb, ptr->payload, ptr->len, TCP_WRITE_FLAG_COPY);  //发送          if (wr_err == ERR_OK)         {             u16_t plen;             u8_t freed;              plen = ptr->len; //得到当前节点的数据长度              /* continue with next pbuf in c hain (if any) */             es->p = ptr->next; //得到链表的下一个节点              if(es->p != NULL)  //如果节点不为空             {                 /* increment reference count for es->p */                 pbuf_ref(es->p); //成员 ref 的值加1  当收到数据时,pbuf成员ref的值为1 ,在pbuf_free()函数中是对成员ref减一 如果结果为0则释放此节点内存并进入下一个节点  否则只是把成员ref的值减一             }             /* chop first pbuf from chain */             do             {                 /* try hard to free pbuf */                 freed = pbuf_free(ptr);  //每执行一个 pbuf中的成员ref的值减一  当它这个节点没有释放时,返回一直为0             }             while(freed == 0);  //执行直到释放这个节点内存为止  pbuf_free()函数的返回值表示释放的内存的节点数              /* we can read more data now */             tcp_recved(tpcb, plen);   //增加可以接收数据的大小         }         else if(wr_err == ERR_MEM)         {             /* we are low on memory, try later / harder, defer to poll */             es->p = ptr; //重发         }         else         {             /* other problem ?? */;         }     } } 

8. 关闭TCP连接,并释放与该连接相关的资源。

static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es) {      /* remove all callbacks */   tcp_arg(tpcb, NULL);   tcp_sent(tpcb, NULL);   tcp_recv(tpcb, NULL);   tcp_err(tpcb, NULL);   tcp_poll(tpcb, NULL, 0);      /* delete es structure */   if (es != NULL)   {     mem_free(es);   }      /* close tcp connection */   tcp_close(tpcb);   //printf("已关闭连接 \r\n"); }

 四、问题

1. flash写入

        在进行flash写入之前一定要对flash进行擦除操作,因为flash的编程原理都是只能将1写为0,而不能将0写为1,所以在使用flash之前,必须将区域擦除,而擦除的过程就是把所有位都写为1的过程。

        但写入标准位是需要重复进行的,所以需要在写入标志位之前合适的位置对app2进行擦除,或者是设计一个合适的标志位的值来进行功能选择,例如:0111 1111作为升级标志位,0011 1111作为启动标志位来避免擦除操作。

2.注意stm32是小端模式

         在我给出的代码 3.处理收到的数据 中接收数组通过字节的形式接收,但在写入flash时选择使用一字的方式进行写入,如果将第一个字节写入到int数组的最高位,会导致最后写入flash时数据高低位反向。

3. app代码中断向量表的修改

        在使用boot后,需要对中断向量表进行偏移,其偏移值就是你boot的大小,具体原因可以自行搜索。

        修改中断向量表有两种方法,第一种可以通过在main函数的开始通过在SCB->VTOR赋值的方式对中断向量表修改,或者在system_stm32f7xx.c文件中修改VECT_TAB_OFFSET宏来的方式对中断向量表进行修改,但个人在实际使用时,使用第一种方式后有部分工程在写入后无法运行,而第二种方式可行。

4.从以太网接收数据缺失 

        该问题的原因有很多,这里只说一种情况。

        当上位机一次发包的字节大于536,只有前536字节被接收其他字节的数据丢失。这种情况的原因可能是TCP_MSS宏定义过小,在opt.h中搜索TCP_MSS,然后将其值修改成自己需要的值可以解决。

5.boot、app1、app2分区注意 

         本工程模板中的flash擦除预设只有扇区擦除或者块擦除,没注意到是否有页擦除,所以在分区时不能存在两个区处于同一个扇区的情况,否则在擦除其中一个分区时会对其他分区造成影响。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!