目录
6.数据被成功发送后被调用。用于管理TCP回显服务器的发送逻辑
前言
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擦除预设只有扇区擦除或者块擦除,没注意到是否有页擦除,所以在分区时不能存在两个区处于同一个扇区的情况,否则在擦除其中一个分区时会对其他分区造成影响。