1. 程式人生 > >STM32F407+Lwip TFTP客戶端IAP升級教程

STM32F407+Lwip TFTP客戶端IAP升級教程

前言:網路上有關TFTP遠端升級MCU的資料基本上都是MCU作為TFTP伺服器,PC作為客戶端。從PC傳送升級的bin檔案給MCU進行升級。

本文將使用MCU作為TFTP的客戶端,PC作為TFTP服務端,MCU聯網進入升級模式後,主動向伺服器請求下載檔案,進而實現遠端升級IAP功能。

有關MCU作為TFTP服務端的升級過程(HAL庫),可以參考下面的連結

在lwip官方的原始碼中,目前最新穩定版本為lwip-2.1.0,但該版本中並未包含tftp作為客戶端的例子。在lwip的開發master分支中,官方提交了有關tftp作為客戶端的程式碼,我們可以對此加以利用。下載包含有tftp客戶端原始碼的lwip原始碼

使用上文提到的TFTP作為服務端的升級程式碼(包含按鍵升級的功能,該功能需要自行新增),開啟HAL_LWIP_TFTP_TEST.ioc檔案,進行如下設定

編譯,解決重定義的錯誤,移除導致重定義的多餘檔案。

將上文提到的lwip程式碼中的src檔案複製到工程的 Midderware->ThirdParty->LWIP 中,提示重名選擇覆蓋即可。

複製最新版lwip資料夾中的\contrib\examples\tftp 中的 tftp_example.c和tftp_example.h到工程的Bsp資料夾下

在工程中新增tftp.c,tftp_example.c

註釋掉main.c中關於tftp伺服器初始化的程式碼,將

  if(tftp_init(&tftpContext)==ERR_OK)
  {
    printf("TFTP初始化成功 \r\n");
  }

修改為以下程式碼並註釋

  //初始化TFTP伺服器
  if(tftp_init_server(&tftpContext)==ERR_OK)
  {
    printf("TFTP初始化成功 \r\n");
  }

在main.c中上面的程式碼下面增加

  //初始化TFTP客戶端
  tftp_example_init_client();

修改mytftpserverif.h為以下形式

修改mytftpserverif.c為

#include "mytftpserverif.h"
#include <string.h>
#include <stdio.h>
#include "main.h"
#include "flash_if.h"
#include "stdlib.h"
#include "string.h"

/******************從tftp_example.c中複製的********************/

/* Define a base directory for TFTP access
 * ATTENTION: This code does NOT check for sandboxing,
 * i.e. '..' in paths is not checked! */
#ifndef LWIP_TFTP_EXAMPLE_BASE_DIR
#define LWIP_TFTP_EXAMPLE_BASE_DIR ""
#endif

/* Define this to a file to get via tftp client */
#ifndef LWIP_TFTP_EXAMPLE_CLIENT_FILENAME
#define LWIP_TFTP_EXAMPLE_CLIENT_FILENAME "LED.bin"
#endif

/* Define this to a server IP string */
#ifndef LWIP_TFTP_EXAMPLE_CLIENT_REMOTEIP
#define LWIP_TFTP_EXAMPLE_CLIENT_REMOTEIP "192.168.137.1"
#endif

/******************從tftp_example.c中複製的********************/
__packed typedef struct 
{
  uint8_t   InitFlat;
  uint8_t   W_R_Mode;   //讀寫模式   1寫  0讀
  uint32_t  Flash_Addr ;
}Iap_FlashCont;


static  Iap_FlashCont   Iapflashcont ;

static void * OpenFile(const char* fname, const char* mode, u8_t write);
static void Close_File(void* handle);

/******************從tftp_example.c中複製的********************/
static void Tftp_Error(void* handle, int err, const char* msg, int size);
/******************從tftp_example.c中複製的********************/

static int Read_File(void* handle, void* buf, int bytes);
static int Write_File(void* handle, struct pbuf* p);

const struct tftp_context  tftpContext={       //TFTP SERVER/CLIENT 對接介面
 OpenFile,
 Close_File,
 Read_File,    
 Write_File,
/******************新增加的********************/
 Tftp_Error, // For TFTP client only
/******************新增加的********************/
};

/******************從tftp_example.c中複製的,部分有修改********************/
static char full_filename[256];

static void *
tftp_open_file(const char* fname, u8_t is_write)
{
  snprintf(full_filename, sizeof(full_filename), "%s%s", LWIP_TFTP_EXAMPLE_BASE_DIR, fname);
  full_filename[sizeof(full_filename)-1] = 0;

  printf("開啟檔案  %s \r\n",fname);    
  Iapflashcont.W_R_Mode = is_write ;
  Iapflashcont.W_R_Mode ? printf("寫檔案\r\n") : printf("讀檔案\r\n");

  if (Iapflashcont.W_R_Mode == 1) {
	  
	FLASH_If_Init();   //解鎖
    Iapflashcont.Flash_Addr = USER_FLASH_FIRST_PAGE_ADDRESS ;  //FLASH起始地址
    printf("開始擦除Flash  時間較長 \r\n");  
	  
    if( FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS) == 0 )   //擦除使用者區FLASH資料
    {
      Iapflashcont.InitFlat =1 ;   //標記初始化完成
      printf("Write File Init Succes \r\n");
    }
//    return (void*)fopen(full_filename, "wb");
	return (Iapflashcont.InitFlat) ? (&Iapflashcont) : NULL ;//如果初始化成功  返回有效控制代碼
  } else {
	  
    return (void*)fopen(full_filename, "rb");
  }
}
/******************從tftp_example.c中複製的,部分有修改********************/

  /**
   *  開啟檔案  返回檔案控制代碼
   * @param const char* fname   檔名
   * @param const char* mode
   * @param u8_t write   模式  1寫  0讀
   * @returns 檔案控制代碼
  */
static void * OpenFile(const char* fname, const char* mode, u8_t write)
{   
  printf("開啟檔案  %s \r\n",fname);    
  printf("開啟模式  %s \r\n",mode);
  Iapflashcont.W_R_Mode = write ;
  Iapflashcont.W_R_Mode ? printf("寫檔案\r\n") : printf("讀檔案\r\n");
 
  if(Iapflashcont.W_R_Mode == 1)
  {
    FLASH_If_Init();   //解鎖
    Iapflashcont.Flash_Addr = USER_FLASH_FIRST_PAGE_ADDRESS ;  //FLASH起始地址
    printf("開始擦除Flash  時間較長 \r\n");  
    if( FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS) == 0 )   //擦除使用者區FLASH資料
    {
      Iapflashcont.InitFlat =1 ;   //標記初始化完成
      printf("Write File Init Succes \r\n");
    }
  } //如果為讀檔案模式
  else if(memcmp(fname,"flash.bin",strlen("flash.bin"))==0)  //可以讀內部FLASH
  {
    Iapflashcont.InitFlat =1 ;   //標記初始化完成
    printf("Read File Init Succes \r\n");
    Iapflashcont.Flash_Addr = FLASH_BASE ;  //FLASH起始地址
  }
  return (Iapflashcont.InitFlat) ? (&Iapflashcont) : NULL ;  //如果初始化成功  返回有效控制代碼
}

  /**
   *  關閉檔案控制代碼
   * @param None
   * @param None
   * @param None
   * @returns None
   */
static void Close_File(void* handle)
{
  Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;
     
  FLASH_If_UnInit();  //FLASH上鎖
  Filehandle->InitFlat = 0 ;
  printf("關閉檔案\r\n");  
  if(Filehandle->W_R_Mode)  //如果之前是寫檔案
  {
    typedef  void (*pFunction)(void);
    pFunction Jump_To_Application;
    uint32_t JumpAddress;
    /* Check if valid stack address (RAM address) then jump to user application */
   if (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
   {
	 printf("正常執行中! \r\n");
     /* Jump to user application */
     JumpAddress = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
     Jump_To_Application =   (pFunction)JumpAddress;
     /* Initialize user application's Stack Pointer */
     __set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
     Jump_To_Application();
     /* do nothing */
     while(1);
   }
  }
}

  /**
   *  讀取檔案資料
   * @param handle  檔案控制代碼 
   * @param *buf    儲存資料的快取 
   * @param bytes   讀取的資料長度
   * @returns 返回讀取資料長度   小於0則錯誤
   */
static int Read_File(void* handle, void* buf, int bytes)
{  
  Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;
  printf("讀取檔案資料 讀取長度: %ld \r\n",bytes);    
  if(!Filehandle->InitFlat)  //未初始化
  {
    return ERR_MEM ;
  } 
  uint16_t Count ;
  for(  Count = 0 ; (Count < bytes)&&(Filehandle->Flash_Addr<=FLASH_END)  ;  Count++ ,Filehandle->Flash_Addr++ )
  {
   ((uint8_t *)buf)[Count] = *((__IO uint8_t *) Filehandle->Flash_Addr);  
  }
  return Count;
}


  /**
   *  寫檔案資料
   * @param handle           檔案控制代碼
   * @param struct pbuf* p   資料快取結構體  裡面的資料快取全為實際需要寫入的資料
   * @returns 小於0為錯誤  
   */
static int Write_File(void* handle, struct pbuf* p)
{
  uint16_t Count ;
  Iap_FlashCont * Filehandle = (Iap_FlashCont *) handle ;     
  printf("寫檔案資料  資料長度 %ld \r\n",p->len); 
  if(!Filehandle->InitFlat)
  {
    printf("寫檔案  沒有初始化 \r\n");
    return ERR_MEM ;
  }
  Count = p->len/4 +((p->len%4)>0);  //得到要寫入的資料
  printf("開始寫FLASH  地址 :0X%08X \r\n",Filehandle->Flash_Addr);
  if( FLASH_If_Write((__IO uint32_t*)&Filehandle->Flash_Addr,(uint32_t *)p->payload,Count) == 0 )
  {
    printf("寫FLASH成功  下一次地址 :0X%08X \r\n",Filehandle->Flash_Addr);
    return ERR_OK;
  }
  else
  {
    printf("寫FLASH 失敗 出錯地址 : 0X%08X \r\n",Filehandle->Flash_Addr);
  }
  return ERR_MEM;
}

/******************從tftp_example.c中複製的********************/
/* For TFTP client only */
static void
Tftp_Error(void* handle, int err, const char* msg, int size)
{
  char message[100];

  LWIP_UNUSED_ARG(handle);

  memset(message, 0, sizeof(message));
  MEMCPY(message, msg, LWIP_MIN(sizeof(message)-1, (size_t)size));

  printf("TFTP error: %d (%s)", err, message);
}
/******************從tftp_example.c中複製的********************/

/******************從tftp_example.c中複製的********************/
void
tftp_example_init_client(void)
{
  void *f;
  err_t err;
  ip_addr_t srv;
  int ret = ipaddr_aton(LWIP_TFTP_EXAMPLE_CLIENT_REMOTEIP, &srv);
  LWIP_ASSERT("ipaddr_aton failed", ret == 1);

  err = tftp_init_client(&tftpContext);
  LWIP_ASSERT("tftp_init_client failed", err == ERR_OK);

  f = tftp_open_file(LWIP_TFTP_EXAMPLE_CLIENT_FILENAME, 1);
  LWIP_ASSERT("failed to create file", f != NULL);

  err = tftp_get(f, &srv, TFTP_PORT, LWIP_TFTP_EXAMPLE_CLIENT_FILENAME, TFTP_MODE_OCTET);
  LWIP_ASSERT("tftp_get failed", err == ERR_OK);
}
/******************從tftp_example.c中複製的********************/


編譯,提示重定義問題,我們將tftp_example.c排除在編譯檔案之外即可

下載到開發板,設定開發板的IP地址為192.168.137.120,PC中tftp伺服器IP為192.168.137.100,成功執行。

主要參考資料: