简介
本篇是STM32F4 CANopen firmware download 系列的第二篇, 主要解释如何构建bootloader。
Bootloader 需要实现的功能:
- 共享数据:在SRAM划分一片区域, 用于存放 bootloader 和 application的共享数据, 如 boot_app_flag.
- 将bootloader imgae header 插入boot.hex, 生成 boot_imageHeader.hex。
- 实现CANOPEN SDO 读写功能: 接收app.hex,并将app.hex文件的数据写入对应的地址
- 跳转到application
image header 的主要作用是记录原hex文件的CRC,以及hex文件长度和版本信息。
在download, 写入flash的过程中,计算写入数据的CRC,下载结束后,通过比较image header里记录的CRC和计算的CRC是否一致来判断下载是否成功。
在每次跳转到application时,通过比较image header里记录的CRC和计算得到的Flash里application 区域的CRC是否一致来判断application是否正确。
本工程基于STM32F429, 由STM32CubeIDE 生成。
共享数据
SRAM 划分一片区域
STM32F429 有192KBytes SRAM. 从SRAM划分1024 Bytes 用于存放共享数据,需要修改链接文件 xxx.ld, 如下 SharedData_RAM 即为划分出的区域:
/* Memories definition */
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000 + 0x400, LENGTH = 192K - 0x400
SharedData_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x400
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
}
/* Sections */
SECTIONS
{
/* Define output sections */
.SharedData (NOLOAD) :
{
*(ShardDataArea)
. = ALIGN(4);
} > SharedData_RAM
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
···
···
/* other section defination */
···
}
将共享数据存储在 SharedData_RAM
// Pointer to shared memory region
static SharedData_t* s_pRegionBase __attribute__((section("SharedData"))) = 0;
生成boot_imageHeader.hex
在bootloader 的hex文件中插入image header,用于说明bootloader 的相关信息,数据结构如下:
typedef struct
{
uint32_t image_LengthInBytes;
uint32_t image_CRC;
char image_Name[32];
char image_Version[16];
char image_Date[16];
uint8_t _reserved[252-( 2*sizeof( uint32_t )+32+16+16 )];
uint32_t header_CRC;
} Image_Header_t;
整个结构体的大小为255字节。
实现这一步,首先需要在boot.hex中预留一片区域, 然后使用srec_cat 工具计算处header 文件,并将header文件写入boot.hex。
在boot.hex中预留空间
如下是boot.hex的内存分配表:
name | address |
---|---|
bootloader vector table | 0x08000000 ~ 0x08000200 |
image header | 0x08000200 ~ 0x080002FF |
bootloader code | 0x08000400 ~ bootloader end |
由上表可以看出, image header 紧跟在中断向量表之后,因此需要在startup文件预留出255字节的区域。
/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
*******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
...
...
...
.word LTDC_ER_IRQHandler /* LTDC_ER_IRQHandler */
.word DMA2D_IRQHandler /* DMA2D */
/*******************************************************************************
* Image Header for identification
********************************************************************************/
.section .imageHeaderArea,"a",%progbits
.type l_ImageHeader, %object
.size l_ImageHeader, .-l_ImageHeader
.align 9 @ align 512 bytes for the header, so header start from offset 0x200
l_ImageHeader:
.word 0x00000000 @ Image Size = not set
.word 0x00000000 @ Image Checksum = not set
.space 32 @ Image Name (String inserted by external tool)
.space 16 @ Image Version (String inserted by external tool)
.space 16 @ Image Date (String inserted by external tool)
.space 252-16-16-32-2*4
.word 0x00000000 @ Header checksum (inserted by external tool)
此外,还需要再linker文件里添加 section imageHeaderArea:
/* Memories definition */
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000 + 0x400, LENGTH = 192K - 0x400
SharedData_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x400
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
}
/* Sections */
SECTIONS
{
/* Define output sections */
.SharedData (NOLOAD) :
{
*(ShardDataArea)
. = ALIGN(4);
} > SharedData_RAM
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The ImageHeader into "FLASH" Rom type memory */
.imageHeaderArea :
{
. = ALIGN(4);
KEEP(*(.imageHeaderArea)) /* ImageHeader */
. = ALIGN(4);
} >FLASH
/* other sectors */
...
...
...
}
插入image header
处理hex文件需要使用 srec_cat 工具。这里通过python脚本的方式来处理。
主要步骤:
- 生成 header.hex
try:
srec_tool = os.path.join(os.environ['SRECORD_HOME'], "srec_cat")
except KeyError:
print('!!! SRECORD_HOME not set, exiting now')
sys.exit(1)
# Construct header (sparse)
srec_args = (
"%s -binary -offset 0x00000010 "
"%s -binary -offset 0x00000030 "
"%s -binary -offset 0x00000040 "
"-o %s -intel") % (
PRGNAME_FILE,
VERSION_FILE,
BUILDDATE_FILE,
HEADER_FILE
)
# print("SRec Args ", srec_args)
subprocess.check_output(srec_tool + " " + srec_args)
- 将header.hex 插入 boot.hex中,起始地址0x08000000 + 512。
# Combine header and application to final downloadable image
srec_args = (
"%s -intel "
"-exclude 0x%x 0x%x "
"%s -intel -offset 0x%x "
"-o %s\\%s -intel -output_block_size 128") % (
IMAGE_FILE_FULLPATH, # path of boot.hex
IMAGEHEADER_START, # 0x08000000 + 512
IMAGEHEADER_END, # 0x08000000 + 256
HEADER_FILE,
IMAGEHEADER_START,
IMAGE_FOLDER,
FULLIMAGE_FILE
)
# print(srec_args)
subprocess.check_output(srec_tool + " " + srec_args)
CANOPEN 读写 SDO
本工程使用emotas的CANOPEN 协议栈, 只需要实现对应的callback.
接收hex文件
SdoServerDomainWriteCB即为 OD 0x1f50 对应的 callback,
Hex_ByteReceive 接收app.hex。
void BootloaderCanopen_Main(void)
{
RET_T ret = RET_INVALID_PARAMETER;
/* Init CAN controller & CANopen stack */
codrvHardwareInit();
ret = codrvOSConfig();
if(ret != RET_OK){
Error_Handler();
}
ret = codrvCanInit(CAN_BIT_RATE);
if(ret != RET_OK){
Error_Handler();
}
ret = coCanOpenStackInit(LoadParaCB);
if(ret != RET_OK){
Error_Handler();
}
/* register callback */
···
ret = coEventRegister_SDO_SERVER_DOMAIN_WRITE(SdoServerDomainWriteCB);
if(ret != RET_OK){
Error_Handler();
}
···
ret = codrvCanEnable();
if(ret != RET_OK){
Error_Handler();
}
for(;;)
{
coCommTask();
codrvWaitForEvent(portMAX_DELAY);
}
}
RET_T SdoServerDomainWriteCB(UNSIGNED16 index, UNSIGNED8 subindex, UNSIGNED32 domainBufSize, UNSIGNED32 transferSize)
{
for(uint8_t index = 0; index < domainBufSize; index++)
{
bool Suceeded = Hex_ByteReceive( s_domain_buf[index] );
if( !Suceeded )
{
debug_printf("\n*** HEX file invalid\n" );
}
}
return RET_OK;
}
处理hex文件
hex文件格式
参考https://blog.csdn.net/lone5moon/article/details/117792834
解析&写入hex文件
bool Hex_ByteReceive(uint8_t byte)
{
if(byte == ":")
{
/* parse received record, former line*/
if( !Hex_ParseRecord())
{
return false;
}
/* start new line record*/
RecordLen = 0;
}
else // data in line
{
if(RecordLen < HEX_MAX_RECORD_LENGTH)
{
RecordBuffer[sw_RecordLen++] = byte;
}
else
{
return false;
}
}
return true;
}
bool Hex_ParseRecord(void)
{
Hex_RecordType type = 0;
uint8_t cs = 0;
uint8_t calc_cs = 0;
uint16_t w_i = 0;
bool o_ret = false;
/* record buffer empty*/
if(RecordLen == 0)
{
return true;
}
/* get current line record type */
type = HexToByte(sab_RecordBuffer+HEX_k_TYPE_OFFSET);
/* add record to checksum */
calc_cs += b_type;
/* get current line data len */
DataLen = HexToByte(RecordBuffer);
/* add data len to checksum */
calc_cs += sb_DataLen;
/* check if data len overflow */
if(DataLen>HEX_k_MAX_DATA_LENGTH)
{
return false;
}
/* get address and add address to checksum */
Address = HexToByte(RecordBuffer+HEX_ADDR_HIGH_OFFSET);
calc_cs += HexToByte(RecordBuffer+HEX_ADDR_HIGH_OFFSET);;
Address = Address<<8;
Address += HexToByte(RecordBuffer+HEX_ADDR_LOW_OFFSET);
calc_cs += HexToByte(RecordBuffer+HEX_ADDR_LOW_OFFSET);
/* copy data and calculated checksum */
for(w_i=0; w_i<DataLen; w_i++)
{
RecordBuffer[w_i] = HexToByte( &RecordBuffer[HEX_k_DATA_OFFSET+(w_i<<1)] );
calc_cs += HexToByte( &RecordBuffer[HEX_k_DATA_OFFSET+(w_i<<1)] );
}
calc_cs = ~ calc_cs;
calc_cs += 1;
/* compare calculated checksum with record checksum */
cs = HexToByte( &RecordBuffer[HEX_DATA_OFFSET+(w_i<<1)] );
if( cs != calc_cs)
{
return false;
}
/* parse record data */
switch( type )
{
case _Hex_Record_Data:
/* Data record */
/* store app to internel flash */
//TODO: write to internel flash
return Hex_WriteRecordToInternelFlash( Offset, Address, RecordBuffer, DataLen);
return false;
case _Hex_Record_EOF:
o_ret = Hex_Eof();
/* hex download complete, reset hex type */
sHex_UpdateType = _Hex_UpdateInvalid_;
return o_ret;
case _Hex_Record_ExtSegAdd:
// extended segment address
Offset = HexToByte( RecordBuffer + HEX_DATA_OFFSET );
Offset = Offset<<8;
Offset += HexToByte( RecordBuffer + HEX_DATA_OFFSET + 2 );
Offset = Offset<<4;
break;
case _Hex_Record_ExtLinearAdd:
// extended linear address
// not sopported because of lack of flash memory
Offset = HexToByte( RecordBuffer + HEX_DATA_OFFSET );
Offset = Offset<<8;
Offset += HexToByte( RecordBuffer + HEX_DATA_OFFSET + 2 );
Offset = Offset<<16;
break;
default:
// wrong record is silently ignored
printf("Hex_ParseRecord: not handled record type %d \r\n", type);
break;
}
return true;
}
static bool Hex_WriteRecordToInternelFlash( uint32_t dw_Offset, uint16_t w_Address, uint8_t * pb_RecordBuffer, uint8_t b_DataLen)
{
/* calculate CRC */
/* check address valid */
/* write data to flash */
}
跳转到application
下载成功后,在接收到 Master 写 OD 0x1F51, 值为 0x01时,跳转到application.
typedef void ( *JumpToApplication)( void );
void Boot_StartApplication( void )
{
JumpToApplication jumpToApplication;
uint32_t dw_reset;
printf( "Starting application at %08x\n", APPLICATION_ENTRY );
HAL_Delay(1000);
HAL_DeInit();
HAL_RCC_DeInit();
/* Disable All interrupts */
__disable_irq();
/* stop systick*/
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* Suspend Tick increment */
HAL_SuspendTick();
/* clear interrupt flags */
for (uint8_t i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
__set_PSP(*(__IO uint32_t*) APPLICATION_ENTRY);
__set_CONTROL(0);
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);
// Start application
dw_reset = *(uint32_t *)(APPLICATION_ENTRY+4);
jumpToApplication = (JumpToApplication )( dw_reset );
pf_reset();
}