极海APM32E103VET6测评之IAP升级
[复制链接]
本帖最后由 Zachary_yo 于 2022-9-21 22:58 编辑
本期分享APM32E103VET5的串口 IAP 在线升级程序(Bootloader),使用了Ymodem协议,本协议源自ST官方程序。因Flash页或扇区大小的不同(如F4系列是扇区,F1系列是页擦除),对其进行了更改。下面是APM32E103VET6的Flash存储结构,如下图所示,每页2K,共256页,合计512K。我们对其中的前8页,也就是前16K(0x08000000-0x08003FFF)用于存放Bootloader程序,后面的Flash空间用于存放App程序。在这里我仅分享Bootloader+App的在线升级方式,初次之外,为了防止升级过程中的突发失败,还可以采用Bootloader+App+App备份的方式来升级。
接着,我们来说一说本升级程序的原理。首先,需要对App程序区(0x08004000-0807FFFF)进行擦除(Flash特性是擦除后,才能写入新字节);擦除完成后,通过串口每接收1K App数据后,写入对应的Flash地址中,写入成功后,Flash地址自增,如此往复,直到上位机完全发送完数据(协议控制)。
下面是程序内容,从ST官方程序中复制Ymodem所需文件并添加到工程中,并对其中的commom.c、flash_if.c、menu.c做相应的修改,程序片段如下。
以下是 common.c文件内容,只修改uint32_t SerialKeyPressed( uint8_t *key )函数中内容,该函数的作用是串口查询接收数据。
/*
*********************************************************************************************************
* 函 数 名: SerialKeyPressed
* 功能说明: 获取整数输入
* 形 参: key 按键
* 返 回 值: 1 返回数据 2 没有返回
*********************************************************************************************************
*/
uint32_t SerialKeyPressed( uint8_t *key )
{
if ( USART_ReadStatusFlag( IAP_COM, USART_FLAG_RXBNE ) != RESET )
{
//*key = (uint8_t)USART_DATA(IAP_COM);
*key = USART_RxData( IAP_COM );
// USART_ClearStatusFlag( IAP_COM, USART_FLAG_RXBNE );
return 1;
}
else
{
return 0;
}
}
以下是 flash_if.c文件内容,该文件中,进本上所有函数都要做修改,修改内容需要根据不同单片机Flash存储结构进行更改,以下是根据APM32E103VET6进行更改的。
/**************************************************************
* [url=home.php?mod=space&uid=32621]@name[/url] FLASH_If_Init
* [url=home.php?mod=space&uid=159083]@brief[/url] * @param None
* @retval
* [url=home.php?mod=space&uid=1315547]@author[/url] Zachary
* [url=home.php?mod=space&uid=34591]@data[/url] 2022-09-01
**************************************************************/
void FLASH_If_Init( void )
{
FMC_Unlock();
FMC_ClearStatusFlag( FMC_FLAG_OC );
FMC_ClearStatusFlag( FMC_FLAG_PE );
FMC_ClearStatusFlag( FMC_FLAG_WPE );
}
/**************************************************************
* @Name FLASH_If_Erase
* @brief
* @param StartSector: [输入/出]
* @retval
* @author Zachary
* @Data 2022-09-01
**************************************************************/
uint32_t FLASH_If_Erase( uint32_t StartSector )
{
uint32_t UserStartSector = 0, i = 0, EndSector = 0,pageaddr = 0x08000000;
EndSector = 255;
/* 用户flash区所在的扇区 */
UserStartSector = GetPage( StartSector );
pageaddr = pageaddr + ( UserStartSector * 0x800 );
for( i = UserStartSector; i <= EndSector; i++ )
{
if ( FMC_ErasePage( pageaddr ) != FMC_STATUS_COMPLETE )
{
return ( 1 );
}
FMC_ClearStatusFlag( FMC_FLAG_OC );
FMC_ClearStatusFlag( FMC_FLAG_PE );
FMC_ClearStatusFlag( FMC_FLAG_WPE );
pageaddr += 0x800;
}
return ( 0 );
}
/**************************************************************
* @Name FLASH_If_Write
* @brief
* @param FlashAddress: [输入/出]
** Data: [输入/出]
** DataLength: [输入/出]
* @retval
* @author Zachary
* @Data 2022-05-22
**************************************************************/
uint32_t FLASH_If_Write( __IO uint32_t* FlashAddress, uint32_t* Data, uint32_t DataLength )
{
uint32_t i = 0;
for ( i = 0; ( i < DataLength ) && ( *FlashAddress <= ( USER_FLASH_END_ADDRESS - 4 ) ); i++ )
{
/* Device voltage range supposed to be [2.7V to 3.6V], the operation will
be done by word */
if ( FMC_ProgramWord( *FlashAddress, *( uint32_t* )( Data + i ) ) == FMC_STATUS_COMPLETE )
{
/* 检查写入的数据 */
if ( *( uint32_t* )*FlashAddress != *( uint32_t* )( Data + i ) )
{
/* 读出的和写入的不相同 */
return( 2 );
}
/* 地址递增 */
*FlashAddress += 4;
}
else
{
return ( 1 );
}
}
return ( 0 );
}
/**************************************************************
* @Name FLASH_If_GetWriteProtectionStatus
* @brief
* @param None
* @retval
* @author Zachary
* @Data 2022-09-01
**************************************************************/
uint16_t FLASH_If_GetWriteProtectionStatus( void )
{
// uint32_t UserStartSector = 0;
// UserStartSector = GetPage( APPLICATION_ADDRESS );
// if ( FMC_GetReadProtectionStatus() != RESET )
// {
return 1;
// }
// else
// {
// return 0;
// }
}
/**************************************************************
* @Name FLASH_If_DisableWriteProtection
* @brief
* @param None
* @retval
* @author Zachary
* @Data 2022-09-01
**************************************************************/
uint32_t FLASH_If_DisableWriteProtection( void )
{
FMC_Unlock();
FMC_DisableReadOutProtection();
return ( 1 );
}
/**************************************************************
* @Name GetSector
* @brief
* @param Address: [输入/出]
* @retval
* @author Zachary
* @Data 2022-09-01
**************************************************************/
static uint32_t GetPage( uint32_t Address )
{
uint32_t sector = 0;
sector = ( Address - 0x08000000 ) / 0x800;
return sector;
}
以下是 menu.c文件内容,该文件中,只更改Main_Menu()函数,更改程序从Bootloader跳转到App的方式。
void Main_Menu( void )
{
uint8_t key = 0;
/* Test if any sector of Flash memory where user application will be loaded is write protected */
if ( FLASH_If_GetWriteProtectionStatus() == 0 )
{
FlashProtection = 1;
}
else
{
FlashProtection = 0;
}
while ( 1 )
{
SerialPutString( "\r\n================== Bootloader Menu =======================\r\n\n" );
SerialPutString( " Download Image To the GD32F470 Internal Flash -------- 1\r\n\n" );
SerialPutString( " Upload Image From the GD32F470 Internal Flash -------- 2\r\n\n" );
SerialPutString( " Execute The New Program ------------------------------ 3\r\n\n" );
if( FlashProtection != 0 )
{
SerialPutString( " Disable the write protection(Not Used) --------------- 4\r\n\n" );
}
SerialPutString( "==========================================================\r\n\n" );
/* Receive key */
key = GetKey();
if ( key == 0x31 )
{
/* Download user application in the Flash */
SerialDownload();
}
else if ( key == 0x32 )
{
//SerialUpload();
}
else if ( key == 0x33 )
{
if ( 0x20000000 == ( ( *( __IO uint32_t* )APPLICATION_ADDRESS ) & 0x2FFE0000 ) )
{
SysTick->VAL = 0;
SysTick->LOAD = 0;
SysTick->CTRL = 0;
USART_Disable( IAP_COM );
JumpAddress = *( __IO uint32_t* ) ( APPLICATION_ADDRESS + 4 );
/* Jump to user application */
Jump_To_Application = ( pFunction ) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP( *( __IO uint32_t* ) APPLICATION_ADDRESS );
Jump_To_Application();
}
}
else if ( ( key == 0x34 ) && ( FlashProtection == 1 ) )
{
/* Disable the write protection */
switch ( FLASH_If_DisableWriteProtection() )
{
case 1:
{
SerialPutString( "Write Protection disabled...\r\n" );
FlashProtection = 0;
break;
}
case 2:
{
SerialPutString( "Error: Flash write unprotection failed...\r\n" );
break;
}
default:
{
}
}
}
else
{
if ( FlashProtection == 0 )
{
SerialPutString( "Invalid Number ! ==> The number should be either 1, 2 or 3\r" );
}
else
{
SerialPutString( "Invalid Number ! ==> The number should be either 1, 2, 3 or 4\r" );
}
}
}
}
下面是main.c中的重要内容,单片机上电首先是从Bootloader开始的,再主程序中,判断是需要进行升级还是跳转到App中运行程序。main程序中,使用UpgradeFlag来判断是否需要进行在线升级。如果不需要升级,则检查App区中是否有程序,如果没有,则进行在线升级。升级标志UpgradeFlag我们定义如下uint16_t UpgradeFlag __attribute__ ((at(0x2001FC00)))。并且将0x2001FC00后的RAM空间设置为不初始化,作用是使单片机软件复位,数据不丢失,方便从App设置标志位跳转到Bootloader进行升级。
int main( void )
{
__disable_irq();
if( UpgradeFlag == 0x1234 )
{
UpgradeFlag = 0x0000;
FLASH_If_Init();
SysInit();
goto __IAP_START;
}
if ( 0x20000000 == ( ( *( __IO uint32_t * )APPLICATION_ADDRESS ) & 0x2FFE0000 ) )
{
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
USART_Disable( USART1 );
JumpAddress = *( __IO uint32_t* ) ( APPLICATION_ADDRESS + 4 );
Jump_To_Application = ( pFunction ) JumpAddress;
__set_MSP( *( __IO uint32_t* ) APPLICATION_ADDRESS );
//__set_CONTROL(0);
Jump_To_Application();
}
else
{
FLASH_If_Init(); /* 初始化flash操作 */
SysInit();
goto __IAP_START;
}
__IAP_START:
Main_Menu();
while( 1 )
{
// GPIOB->ODATA_B.ODATA9 ^= 1;
// delay_ms( 1000 );
}
}
以下是Bootloader程序和App程序中UpgradeFlag定义区域的设置,uint16_t UpgradeFlag __attribute__ ((at(0x2001FC00)))。
测试App程序代码,当接收到0x31('1')时,进行设置标志位,并软件复位,复位后Bootloader检测到UpgradeFlag标志后,进入升级程序。
/**************************************************************
* @Name main
* @brief
* @param None
* @retval
* @author Zachary
* @Data 2022-09-13
**************************************************************/
int main( void )
{
SCB->VTOR |= 0x4000;
NVIC_ConfigPriorityGroup( NVIC_PRIORITY_GROUP_4 );
SysInit();
while( 1 )
{
if( comGetChar( COM1, &rxdata ) )
{
if( rxdata == 0x31 )
{
UpgradeFlag = 0x1234;
__disable_irq();
NVIC_SystemReset();
}
}
GPIOB->ODATA_B.ODATA9 ^= 1;
printf( "hello world\r\n" );
delay_ms( 1000 );
}
}
Bin文件生成方式如下,输入C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin --output =BIN\@L.bin !L
测试视频
WeChat_20220921224952
最后附上App和Bootloader的程序代码,使用APM32E103VET6 MINIBOARD开发板测试通过,如有问题,请留言。
本程序仅供测试。
|