STM32F4 CANopen firmware download -- Bootloader

简介

本篇是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的内存分配表:

nameaddress
bootloader vector table0x08000000 ~ 0x08000200
image header0x08000200 ~ 0x080002FF
bootloader code0x08000400 ~ 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脚本的方式来处理。
主要步骤:

  1. 生成 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)

	
  1. 将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();
}
摘要 随着汽车ECU(电子控制单元)应用开发的快速发展,其功能越来越强大,实现也越来 越复杂。如果应用软件后续需要进行功能升级,传统方法需要将零件从整车上拆卸下来,这 将增加更新的工作量,并且容易对车辆本身造成损坏。而Bootloader(启动加载)可以通过 车载网络传输数据,实现应用软件的在线更新,免除拆卸的麻烦。车载OBD(在线诊断)五大 诊断标准之一的CAN(控制器局域网总线)总线,拥有高度的弹性调整能力,可以在既有的 网络中增加节点而不用在软硬件上做任何修正与调整。因此,基于CAN网络的Bootloader 的研究具有很大的实用价值。 论文首先介绍了车载网络的发展历史,展望了其发展未来;在研究CAN总线协议的基础 上,选择、设计了CAN通信模块和外围模块,实现了主控芯片和外部CAN的接口;详细研 究了IS015765协议,并针对S Tmi。建立了数学模型;基于对IS015765网络层的研究,得出 UML描绘的状态转移图并加以实现;根据Bootloader的特点,重新设计Bootloader系统和工 作流程,对系统各个模块进行详细的设计与实现,并提出多项改进;在实现Bootloader后, 搭建软硬件环境对它的可行性和稳定性进行测试;最后,在证明Bootloader可以在高负载情 况下正常工作的基础上,提出后续可能增加的功能等。 测试结果分析表明:该系统不仅可以准确地完成应用程序自更新,而且能在高负载下正 常工作;相比于传统的采用串口的Bootloader和其他基于CAN总线的Bootloader,该系统稳 定性和兼容性更高,能够更好地完成程序更新的任务。研究结果表明该系统完全可以取代各 大供应商的Bootloader系统,极大地降低了产品成本。课题的研究对Bootloader系统的设计 与开发具有重要的指导作用和参考价值,对其的实际应用也具有很强的实用意义和商业价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值