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,成功執行。
主要參考資料:
- 【STM32乙太網線上培訓】手把手搭建TCP伺服器及TFTP伺服器
- /contrib/examples/tftp/tftp_example.c
- /contrib/examples/tftp/tftp_example.h
- /src/include/lwip/apps/tftp_client.h
- /src/apps/tftp/tftp.c