stm32f746 discovery基于tcp服务器的IAP


前言

        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擦除预设只有扇区擦除或者块擦除,没注意到是否有页擦除,所以在分区时不能存在两个区处于同一个扇区的情况,否则在擦除其中一个分区时会对其他分区造成影响。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值