自从传闻ARM要出售给美国公司,本人内心的不安全感促使我寻找ARM架构单片机的替代产品。网上问了些网友,有推荐GD32VF103,于是花了些时间了解这个系列的MCU,发现其实也还行,这个MCU跟GD32F103很像,RAM, ROM,外设,封装都差不多。虽说开发环境有些不太熟,不过花些心思,也能使用,至少,比51单片机好用多了。
因为官方Firmware没有IAP方面的内容,所有这篇文章专门讲讲此单片机如何进行IAP,毕竟实际工作应用中会经常使用。
一.开发环境。
我使用的IDE是Nuclei Studio IDE,这个是芯莱科技基于 MCU Eclipse IDE 开发的一款针对芯来公司处理器核产品的集成开发环境工具。GD32VF103也是这家这家公司联合兆易创新推出的。
IDE下载地址为:https://www.nucleisys.com/download.php
我使用的开发板是一款只需要30几块钱的带彩屏的开发板,非常小,MCU型号是GD32VF103CBT6。开发板型号叫longan nano
,资料下载地址为:https://dl.sipeed.com/LONGAN/Nano/
二.原理
回顾并参考STM32F1系列的IAP过程:
1.接收APP镜像数据,并将其写入对应Flash地址。
2.设置SP(堆栈指针)
3.设置PC(也就是跳转至APP特定地址)。
4.APP本身需要设置中断入口的地址偏移。
由于STM32F1XX复位后,硬件自动从特定地址获取SP和PC,所有启动文件不会再次设置SP。所有,当我们用C语言写IAP程序,反而需要模拟此过程,需要设置SP。
对于GD32VF103,复位后硬件本身并不会进行类似操作。也就是说,设置SP本身是启动文件必须的功能,无需C程序再次设置。其实不止SP,还有GP等。可以查看启动文件(一般命名为startup_xxx.s)和链接文件(.ld)了解其过程。
三.代码实现
1.IAP
由于我买的开发板有个TF卡座,所有我通过TF进行IAP。
#ifndef __IAP_H
#define __IAP_H
#define FMC_PAGE_SIZE 0x400
#define APP_STARTADDR 0x08010000
#define APPPATH_STR "0:/SYS/APP.BIN"
uint8_t iap_all(void);
#endif
#include "nuclei_sdk_soc.h"
#include "iap.h"
#include "lcd.h"
#include "./fatfs/ff.h" /* Declarations of FatFs API */
#include <stdio.h>
#include <string.h>
//保存读到的文件数据
uint32_t filebuf[FMC_PAGE_SIZE/2];
/*!
\brief erase fmc pages in addr
\param addr : fmc addr
\retval none
*/
void fmc_erase_onepage(uint32_t addr )
{
uint32_t erase_counter;
// unlock the flash program/erase controller
fmc_unlock();
// clear all pending flags
fmc_flag_clear(FMC_FLAG_END);
fmc_flag_clear(FMC_FLAG_WPERR);
fmc_flag_clear(FMC_FLAG_PGERR);
// erase the flash page
fmc_page_erase( addr );
fmc_flag_clear(FMC_FLAG_END);
fmc_flag_clear(FMC_FLAG_WPERR);
fmc_flag_clear(FMC_FLAG_PGERR);
// lock the main FMC after the erase operation
fmc_lock();
}
/*!
\brief program for one page
\param addr
\param buf
\retval none
*/
void fmc_program_onepage( uint32_t addr , uint32_t *buf )
{
uint16_t i;
for( i=0; i<FMC_PAGE_SIZE; i+=4 )
{
if( *(uint32_t*)(addr+i) != 0xFFFFFFFF )
{
break;
}
}
if( i != FMC_PAGE_SIZE )
{
fmc_erase_onepage( addr ) ;
}
// unlock the flash program/erase controller
fmc_unlock();
//adjust addr
addr-=(addr%FMC_PAGE_SIZE);
// program flash
for( i=0; i<FMC_PAGE_SIZE; i+=4 )
{
fmc_word_program( addr+i, buf[i/4] );
fmc_flag_clear(FMC_FLAG_END);
fmc_flag_clear(FMC_FLAG_WPERR);
fmc_flag_clear(FMC_FLAG_PGERR);
}
// lock the main FMC after the program operation
fmc_lock();
}
/*!
\brief 对比内容是否相同
\param appxaddr:flash地址
\param data:内存地址
\param size:要比较的大小
\retval 1:完全相同;0:不一致
*/
uint8_t iap_cmp(uint32_t appxaddr,uint32_t *data,uint32_t size)
{
uint32_t i;
if(size==0)return 1;
for(i=0;i<size;i+=4)
{
if( *(uint32_t*)(appxaddr+i) != data[i/4] )
{
return 0;
}
}
return 1 ;
}
/*!
\brief 过程:读文件,判断版本,写Flash,检查是否一致,显示进度,判断完整性,显示结果。
\param none
\retval 0:成功 ;Other:没有成功
*/
uint8_t iap_all(void)
{
FIL appfile;
FILINFO fileinfo;
uint8_t res = 0 ;
char * ptr = (char *)filebuf;
printf( "open appfile:" );
if( f_open( &appfile,APPPATH_STR ,FA_READ ) == FR_OK )
{
uint16_t i=0;
uint32_t tlen=0;
UINT br=0;
f_stat( (char*) APPPATH_STR ,&fileinfo );
printf("ok,file size :%d\r\n",fileinfo.fsize);
LCD_ShowString(0,8,"App Upd:",RED);
LCD_ShowString(2,8+16,"Prog: %",RED);
LCD_ShowNum( 0,16*4, fileinfo.fsize ,8,BLACK) ;
printf("\r\nRead appfile data:\r\n");
res=0;
if( fileinfo.fsize >= FMC_PAGE_SIZE )
{
for( i=0; i<(fileinfo.fsize/FMC_PAGE_SIZE); i++ )
{
memset(ptr,0xff,FMC_PAGE_SIZE);
res = f_read( &appfile, ptr , FMC_PAGE_SIZE/2 ,&br);
if( res )
{
printf("appfile read error\r\n");
res = 0x02 ;
break;
}else
{
tlen+=br;
if( i==0)
{
//把版本号放在APP_STARTADDR+4,这里比较,如果相同则不更新APP
if( filebuf[1] == *(uint32_t*)(APP_STARTADDR+4) )
{
res |= 0x10;
break;
}
//LCD_ShowNum( 8*9,8 , filebuf[1] ,8,RED) ;
}
res = f_read( &appfile, ptr+512 , FMC_PAGE_SIZE/2 ,&br);
tlen+=br;
fmc_program_onepage( APP_STARTADDR+i*FMC_PAGE_SIZE, filebuf );
if ( iap_cmp(APP_STARTADDR+i*FMC_PAGE_SIZE , filebuf ,br ) )
{
printf( "iap prog:%d%%\r\n", (uint32_t)(i*100/(fileinfo.fsize/FMC_PAGE_SIZE) +0.5 ) );
LCD_ShowNum( 2 +8*5,8+16 , (uint32_t)(i*100/(fileinfo.fsize/FMC_PAGE_SIZE) )+0.5 ,3,RED) ;
}
else
{
res = 0x03;
printf("cmp err\r\n" );
break;
}
}
}
}
//最后不够FMC_PAGE_SIZE的部分
if( res==0 && fileinfo.fsize%FMC_PAGE_SIZE)
{
memset( ptr,0xff,FMC_PAGE_SIZE);
res = f_read( &appfile, ptr , FMC_PAGE_SIZE/2 ,&br);
if( res )
{
printf("appfile read error\r\n");
res = 0x02 ;
}else
{
if( i==0 )
{
//把版本号放在APP_STARTADDR+4,这里比较,如果相同则不更新APP
if( filebuf[1] == *(uint32_t*)(APP_STARTADDR+4) )
{
res |= 0x10;
}
}
if( !res )
{
tlen+=br;
res = f_read( &appfile, ptr+512 , FMC_PAGE_SIZE/2 ,&br);
tlen+=br;
LCD_ShowNum( 2 +8*5,8+16 , (uint32_t)(i*100/(fileinfo.fsize/FMC_PAGE_SIZE) ) ,3,RED) ;
fmc_program_onepage( APP_STARTADDR+i*FMC_PAGE_SIZE, filebuf );
if ( iap_cmp(APP_STARTADDR+i*FMC_PAGE_SIZE , filebuf ,br ) )
{
printf( "iap prog:%d%%\r\n", (uint32_t)(i*100/(fileinfo.fsize/FMC_PAGE_SIZE) +0.5 ) );
LCD_ShowNum( 2 +8*5,8+16 , (uint32_t)(i*100/(fileinfo.fsize/FMC_PAGE_SIZE) )+0.5 ,3,RED) ;
}
else
{
res = 0x03;
printf("cmp err\r\n" );
}
}
}
}
printf( "\r\nreaded data size:%d\r\n",tlen );
f_close(&appfile);
LCD_ShowNum( 8*10,8 , tlen ,8,BLACK) ;
if( res==0 && tlen == fileinfo.fsize )
{
//res = f_unlink(APPPATH_STR); //del file
LCD_ShowString(0,16*3,"iap ok!",BLUE);
printf("IAP ok,res:%d\r\n",res );
}else if( res&0x10 )
{
res=0;
printf("version same\r\n" );
LCD_ShowString(0,16*3,"version same!",BLUE);
}else
{
res |= 0x80;
printf("iap fail\r\n" );
LCD_ShowString(0,16*3,"iap fail!",BLUE);
}
}else
{
res = 0x01 ;
printf("No appfile\r\n" );
}
return res;
}
上面会进行文件版本判断。文件版本放在镜像文件的第二个“字”,可以直接在启动文件里设置。
2.APP
写一个待验证app程序,例如定时器控制流水灯什么的,最好带中断,以便验证。
注意一点,需要在链接脚本文件里面修改起始地址。
/********************* Flash Configuration ************************************
* <h> Flash Configuration
* <o0> Flash Base Address <0x0-0xFFFFFFFF:8>
* <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>
* </h>
*/
__ROM_BASE = 0x08000000;
__ROM_SIZE = 0x00020000;
修改为
/********************* Flash Configuration ************************************
* <h> Flash Configuration
* <o0> Flash Base Address <0x0-0xFFFFFFFF:8>
* <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>
* </h>
*/
__ROM_BASE = 0x08010000;
__ROM_SIZE = 0x00010000;
注意要保证__ROM_BASE不能小于IAP程序代码大小,并且剩余flash空间足够放得下APP程序代码。
跳转指令:
((void(*)())(0x08010000))();
保险起见,跳转前关全局中断。
__disable_irq();
下图为测试图:IAP成功后跳转至APP。
GD32VF103 IAP测试
|