Bootloader介绍

Bootloader是什么?

  Bootloader实际上就是在我们应用程序启动前运行的一段代码,主要的作用呢,一是对应用程序进行一些校验工作,防止FALSH坏块导致问题。二是对APP进行更新迭代(都发布的产品总不能一个一个壳子拆了让你烧录),此时配合以太网就能实现远程升级(OTA)
  单片机的bootloader相较于Linux的Uboot还是要简单很多,因为单片机的内存是SRAM可以直接寻址访问,比SDRAM要方便。而且也没有太多的外设需要初始化。

bootloader工作原理

  为了进一步解释BOOTLoader的原理,首先要了解以下内容

  • bin文件的具体内容
    了解Bin里面内容的布局,要从具体的链接脚本开始看
    0x0800000就是FLASH的起始地址啦,可以看到RESET部分被放在了生成的bin文件的起始位置,具体的其它文件的排放次序可以查看.map文件

    LR_IROM1 0x08000000 0x00100000  {    ; load region size_region
        ER_IROM1 0x08000000 0x00100000  {  ; load address = execution address
            *.o (RESET, +First)
            *(InRoot$$Sections)
            .ANY (+RO)
            .ANY (+XO)
        }
        /*把变量的地址分配到以0x20000000起始的SRAM范围内,这样程序后面就可以把数据从FLASH中直接搬到SRAM的对应地址就行*/
        RW_IRAM1 0x20000000 0x00020000  {  ; RW data
        .ANY (+RW +ZI)  /*实际上ARMCC在这里还有一个压缩算法,感兴趣可以去搜搜一搜*/
        }
    }
    

    RESET的具体内容就是这个中断向量表啦,可以看到第一个位置存放的是SP指针的值,用来初始化C语言运行的基本环境,第二个值就是Reset_Handler,表示对复位的处理

        __Vectors       DCD     __initial_sp               ; Top of Stack
                    DCD     Reset_Handler              ; Reset Handler
                    DCD     NMI_Handler                ; NMI Handler
                    DCD     HardFault_Handler          ; Hard Fault Handler
                    DCD     MemManage_Handler          ; MPU Fault Handler
                    DCD     BusFault_Handler           ; Bus Fault Handler
                    DCD     UsageFault_Handler         ; Usage Fault Handler
                    DCD     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     SVC_Handler                ; SVCall Handler
                    DCD     DebugMon_Handler           ; Debug Monitor Handler
                    DCD     0                          ; Reserved
                    DCD     PendSV_Handler             ; PendSV Handler
                    DCD     SysTick_Handler            ; SysTick Handler
    
  • STM32启动过程(cortexM内核(cortexA内核不适用))
    启动的时候根据BooT0和Boot1的配置, SP指针和PC指针会去不同的地方取值
    以FLASH启动为例, 硬件会自动把0x0800000的值赋值给SP指针 把0x08000004的值赋值给PC指针,也就是(Reset_Handler),然后执行Reset_Handler

    • Reset_Handler
      SystemInit:做一些核心外设的初始化,配置中断向量表(VETOR)的位置
      __main:主要是一些初始化工作,包括清空ZI段,把RW段搬运到内存中,然后跳转main函数
        ;Reset handler
        Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main
        LDR     R0, =0xE000ED88    ; 使能浮点运算 CP10,CP11
        LDR     R1,[R0]
        ORR     R1,R1,#(0xF << 20)
        STR     R1,[R0]
        LDR     R0, =SystemInit
        BLX     R0
        LDR     R0, =__main
        BX      R0
        ENDP
    
  • 编写Bootloader需要注意什么
    所以对于Bootloader来说,如果想要跳转到APP,就需要手动模拟这个SP指针赋值和PC指针赋值的工作。但在这个过程中,还要注意以下几点。

    • 模拟SP指针赋值和PC指针赋值
      当APP是在RTOS环境跳转时,RTOS的任务用的是PSP指针,而我们要模拟的是给MSP指针赋值,所以这里要强行切换到MSP指针(__set_CONTROL(0)指令)
    • 中断向量表的配置
      Bootloader有自己的VECTOR,APP也有自己的VECTOR,所以跳转APP后,要切换到APP自己的VECTOR当中,否则就无法正确处理中断
    • 跳转前应该给APP提供一个"清洁"的外设环境
      因为我们的Bootloader和APP很可能共享一个外设,但是具体的业务逻辑完全不一样。假设我们的Bootloader使用了UART并且跳转前刚好有个中断没处理,跳转到APP后触发了UART中断就完蛋了,所以Bootloader在跳转前最好复位UART外设并清除所有中断标志位,APP自己再重新初始化
    • 跳转过程要关中断
      因为涉及到了VECTOR的切换,关中断也是为了防止意外。进入APP后在开启就行

    大概就是长这样

    __set_PRIMASK(1);
    __set_CONTROL(0);			//使用RTOS会使用PSP,要恢复MSP
    jump2app = (iapfun) * (volatile uint32_t *)((uint32_t)BOOT_ADDRESS + 4);
    sys_nvic_set_vector_table(FLASH_BASE, BOOT_ADDRESS - FLASH_BASE);	
    sys_msr_msp(*(volatile uint32_t *)BOOT_ADDRESS);// 复位MSP指针
    ethernet_deinit();
    carama_deinit();
    usart_deinit();
    SysTick->CTRL = 0X00;		//禁止SysTick
    SysTick->LOAD = 0;
    SysTick->VAL = 0;
    HAL_DeInit();
    sram_deinit();		/*现在RTOS在SRAM下 最后复位*/
    jump2app();
    

项目的内存布局

  因为这个项目里涉及到了AB分区,所以整个STM32f407ZGT6的1MB FLASH被分成了以下区域,在这里一定要注意FLASH可不是按字节操作的而是扇区操作的,所以一定不要跨扇区!!!(APPA绝对不能和APPB的部分内容在一个扇区下)。
  这里踩过的坑就是FLASH的写入和擦除了,主要就是对APP_PRAMA_ADDRESS下的操作,因为涉及到APPA的参数信息和APPB的参数信息写入。如果写入APPB的参数新时候不暂时保存APPA的参数信息的话,那APPA的参数信息就全完蛋了(因为所有的参数信息都在这128KB的扇区下)

/*FLASH分区: BOOT + A区 + B区 + 参数区 + WIFI固件区*/
#define BOOT_ADDRESS					((uint32_t )FLASH_BASE)  
#define BOOT_MAX_SIZE_BYTES				((uint32_t)0x20000)
#define APP_SECTORA_ADDRESS				((uint32_t )0x08020000)  
#define APP_SECTORA_MAX_SIZE_BYTES		((uint32_t)0x40000)
#define APP_SECTORB_ADDRESS				((uint32_t )0x08060000)  
#define APP_SECTORB_MAX_SIZE_BYTES		((uint32_t)0x40000)
#define APP_PRAMA_ADDRESS				((uint32_t )0x080A0000)  
#define APP_PRAMA_BYTES					((uint32_t)0x20000)
/*数字签名*/
#define APP_DS_ADDRESS					APP_PRAMA_ADDRESS
#define APP_DS_BYTES					((uint32_t) 0x800)	
#define APP_DS_SECTORA_ADDRESS			APP_DS_ADDRESS
#define APP_DS_SECTORA_BYTES			((uint32_t) 0x400)
#define APP_DS_SECTORB_ADDRESS			(APP_DS_SECTORA_ADDRESS + APP_DS_SECTORA_BYTES)
#define APP_DS_SECTORB_BYTES			((uint32_t) 0x400)
/*APP信息*/
#define APP_INFO_ADDRESS				(APP_DS_ADDRESS + APP_DS_BYTES)
#define APP_INFO_BYTES					((uint32_t) 0x800)	
#define APP_INFO_TOTAL_BYTES			(APP_DS_BYTES + APP_INFO_BYTES)
#define WIFI_FM_ADDRESS					((uint32_t )0x080C0000)  
#define WIFI_FM_BYTES					((uint32_t)0x40000)
/*******************************************************************************
 * 物理地址 (Address)      分区名称 (Partition)         大小 (Size) / 说明
 *******************************************************************************
 * 0x08000000 +-------+-----------------------+
 * |       |  BOOT (Bootloader)    | 128 KB
 * |       |                       | [存放引导程序]
 * 0x08020000 +-------+-----------------------+
 * |       |  APP_SECTORA (A区)    | 256 KB
 * |       |                       | [主运行程序 A]
 * 0x08060000 +-------+-----------------------+
 * |       |  APP_SECTORB (B区)    | 256 KB
 * |       |                       | [备用/升级程序 B]
 * 0x080A0000 +-------+-----------------------+
 * |       |  参数区 (PRAMA)        | 128 KB (总空间)
 * |       |  - APP_DS (数字签名区)  |  -> 子分区见下方详图
 * |       |  - APP_INFO (APP信息区) |
 * |       |  - (剩余空闲空间)       |
 * 0x080C0000 +-------+-----------------------+
 * |       |  WIFI_FM (WIFI固件区)  | 256 KB
 * |       |                       | [存放外部模块固件]
 * 0x08100000 +-------+-----------------------+ <--- 假设 Flash 结束点
 *******************************************************************************

  另外,在这里我还修改了链接脚本,把SRAM也做了进一步的划分。

LR_IROM1 0x08060000 0x00040000  {    ; load region size_region
  ER_IROM1 0x08060000 0x00040000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  ; 新增:专门给崩溃信息开辟一个极小的、不初始化的执行域
  ; 它会自动排在 RAM 的最前面
  ER_CRASH 0x20000000 UNINIT 0x00000100 { 
    *(.bss.NoInit)
  }
  ; 修改:原来的 RW_IRAM1 起始地址往后挪 0x100 (256字节)
  ; 这样就绝对不会和上面的 ER_CRASH 冲突了
  RW_IRAM1 0x20000100 0x0001FF00  {
   .ANY (+RW +ZI)
  }
}

  可以看到在SRAM里面,划分出来一块从不初始化的内存区域,这块区域后续也是很有用的,因为SRAM可不像SDRAM,不需要一直刷新。那只要Boot和APP跳转过程中不掉电,这块内存不就可以用来做沟通的桥梁了吗(当然在这里我还用一块外带的EEPROM做了备份,防止真掉电了>.<(RTC寄存器是不是也可以,都可以试一试))。
  那为什么不用FLASH,因为FLASH是按扇区操作的(.<),真的很要命,而且FLASH的擦写寿命这一块

  • 其它的疑问
      实际上在看完对Boot的介绍后,聪明的你可能就会想,那为什么升级要这么麻烦呢?
      APPA接收升级信号–>回到Bootloader做升级–>然后跳转新APP,为什么不直接在APPA里就完成对文件的写入和跳转呢?
      我在这里的考虑主要是内存方面,因为FLASH真的太小了…,如果我让APPA和APPB都具备升级文件的功能,就意味着在内存中要有两份升级相关的代码(mbedtls和LWIP协议栈加起来可能有几十KB),而且裸机也没有动态链接库这么一说。
      而且如果在APP里面升级APP,万一失误了是不是就变砖了。利用Bootloader也可以更好的做一些校验工作,万一变砖了可以Bootloader里面轮训等待新的升级文件并报警

具体功能实现

Ymodem协议

Ymodem协议介绍可以参考这篇博客:https://blog.csdn.net/huangdenan/article/details/103611081
Ymodem协议非常契合当前的升级模式,因为它是接收方通知发送方自己准备好了(跳转到Bootloader里面了),而且Ymodem协议的通信方式也非常适合通过有限状态机去编程(剩下的相信AI就好了)

  • 存在的问题
    掉电:A/B分区时不怕掉电的,掉电了校验不通过就会跳转老的分区
    断点续传: 因为升级文件可能会非常大,要是每次掉电重新来过就太麻烦了。这里后续也许可以考虑参考http的断点续传功能实现断点续传也不错

RSA数字签名

数字签名介绍可以参考:https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D/212550
环境搭建可以参考这篇:https://www.cnblogs.com/duapple/p/17915569.html
  总体来说,数字签名是我们验证APP完整性的重要手段,防止APP被破坏(蓄意破坏或者是FLASH坏块了)。这部分的代码放在了 /主机代码/enctypt_app 下
  整体思路就是在生成数字签名后,把数字签名和APP的信息(版本/大小/名字)整合在一起,固定大小为2KB,然后把这2KB和APP的bin文件合并,当接收到合并的文件后进行数字签名校验查看完整性。

uint8_t verify_app(uint8_t* fw_addr, uint32_t fw_size, uint32_t ds_addr)
{
	if ( fw_addr == NULL || ds_addr == 0xffffffff)
		return 0;
	static char public_key_args_N[] = "BE29E714F51611C4F22A892658DC9A3CCF4DA5BF3F054F5EE9752901AC53BDB97BD6114C318F0C17C3EFFAE6CAB12114B98578295E18E405E1A4C151EF82794D7A9E5D171CE46BB5A5F2447175CC194D656E5A861288BD12E8235E20D932ADC7FCEF4F33342E76F7DBF5EBCA52857183485C01AD6583E773679020259A76AD7C5D13BF31D01987578DC368FDA41FDF80E88D37DA975DA7985548055A7232DD5356DE4AEF0C1BCA602943CD24654976AAC03A0577F3E04FF0BDB3680F35238267D0AA8E612155F97C0D18DED1B5B75690D5F64019BE54238BED42D31D9B742CF80EC20F54D39F582C5B26C451B3E36431B37ECE3DEA85B3BC207ADB98B226D3CD";
	static char public_key_args_E[] = "010001";
	printf("verify start:0x%x fw_size: %d ds_addr:0x%x\r\n", (uint32_t)fw_addr, fw_size, ds_addr);
    int ret = 1;
    uint8_t verify_ok = 0;
    size_t i;
    mbedtls_rsa_context rsa;
    unsigned char hash[32];
    unsigned char *buf = mymalloc(SRAMEX, APP_DS_BYTES);	/*存放数字签名的*/
    if (buf == NULL)
	{
		printf("alloc buf fail\r\n");
		return 0;
	}
	memcpy(buf, (void *)ds_addr, APP_DS_BYTES);				/*把数字签名拷贝过来*/
    mbedtls_md_context_t ctx;
    uint8_t *buffer_2k= mymalloc(SRAMEX, 2048);
	if (buffer_2k == NULL)
	{
		myfree(SRAMEX, buf);
		printf("alloc buffer_2k fail\r\n");
		return 0;
	}
    int mod;
    
    printf("verify app......\r\n");

    mbedtls_rsa_init( &rsa );

    if( ( ret = mbedtls_mpi_read_string( &rsa.MBEDTLS_PRIVATE(N), 16, public_key_args_N ) ) != 0 ||
        ( ret = mbedtls_mpi_read_string( &rsa.MBEDTLS_PRIVATE(E), 16, public_key_args_E ) ) != 0 )
    {
        printf( " failed\n  ! mbedtls_mpi_read_string returned %d\n\n", ret );
        goto exit;
    }
    rsa.MBEDTLS_PRIVATE(len) = ( mbedtls_mpi_bitlen( &rsa.MBEDTLS_PRIVATE(N) ) + 7 ) >> 3;

    /*
     * Compute the SHA-256 hash of the input file and
     * verify the signature
     */
    printf( "\n  . Verifying the RSA/SHA-256 signature\r\n" );

    mbedtls_md_init(&ctx);

    ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
    if (ret) 
    {
        printf("mbedtls_md_setup error: %d\r\n", ret);
        goto exit;
    }
    if ( (ret = mbedtls_md_starts(&ctx)) )
    {
        printf("mbedtsl_md_starts error: %d\r\n", ret);
        goto exit;
    }
    
    mod = fw_size % 2048;
    for (i = 0; i < fw_size / 2048; i++)
    {
        memcpy(buffer_2k, (void *)(fw_addr + 2048 * i), 2048);
        
//        printf("-------------------part [%d](256bytes)--------------------------\r\n", i+1);
//        for (int j = 0; j < 256; j++) 
//        {
//            fprintf( stdout, "%02X%s", buffer_2k[j],
//                 ( j + 1 ) % 16 == 0 ? "\r\n" : " " );
//        }
//        printf("\r\n---------------------------------------------------------\r\n\n\n");
        
        if ( (ret = mbedtls_md_update(&ctx, buffer_2k, 2048)) )
        {
            printf("mbedtls_md_update error: %d\r\n", ret);
        }
    }
    
    if (mod) 
    {  
		 memcpy(buffer_2k, (void *)(fw_addr + 2048 * i), mod);
//        printf("-------------------part [%d]--------------------------\r\n", i+1);
//        for (int j = 0; j < mod; j++) 
//        {
//            fprintf( stdout, "%02X%s", buffer_2k[j],
//                 ( j + 1 ) % 16 == 0 ? "\r\n" : " " );
//        }
//        printf("\r\n---------------------------------------------------------\r\n\n\n");
        
        if ( (ret = mbedtls_md_update(&ctx, buffer_2k, mod)) )
        {
            printf("mbedtls_md_update error: %d\r\n", ret);
        }
    }
    
    if ( (ret = mbedtls_md_finish(&ctx, hash)))
    {
        printf("mbedtls_md_finish error: %d\r\n", ret);
    }

    printf("-----------------digital sign------------------------\r\n");
    for( i = 0; i < rsa.MBEDTLS_PRIVATE(len); i++ )
        printf("%02X%s", buf[i],
                 ( i + 1 ) % 16 == 0 ? "\r\n" : " " );
    printf("------------------digital sign-------------------------\r\n\n");
//    
//    printf("--------------------hash-------------------------\r\n");
//    for( i = 0; i < 256; i++ )
//        printf("%02X%s", hash[i],
//                 ( i + 1 ) % 16 == 0 ? "\r\n" : " " );
//    printf("--------------------hash---------------------\r\n\n");
    if( ( ret = mbedtls_rsa_pkcs1_verify( &rsa, MBEDTLS_MD_SHA256,
                                          32, hash, buf ) ) != 0 )
    {
        printf( " failed\n  ! mbedtls_rsa_pkcs1_verify returned -0x%0x\n\n", (unsigned int) -ret );
        goto exit;
    }

    printf( "\n  . OK (the signature is valid)\n\n" );

    verify_ok = 1;

exit:
    myfree(SRAMEX, buf);
	myfree(SRAMEX, buffer_2k);
    mbedtls_rsa_free( &rsa );
    mbedtls_md_free(&ctx);

    return verify_ok;
}
  • 存在的问题
    • 内存:
      mbedtls默认使用malloc和free,所以要给heap_size留出一定空间,我记得用了快8KB
    ;未用到编译器自带的内存管理(malloc,free等),设置Heap_Szie为0
    Heap_Size       EQU     0x00000000
    
                    AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    __heap_base
    
    当然,mebedtls也运行你配置为自己的malloc和free,具体来说要使能几个宏,并调用具体的API
        /*定义自己的内存分配器*/
        #define MBEDTLS_PLATFORM_MEMORY
        #define MBEDTLS_PLATFORM_C
    	mbedtls_platform_set_calloc_free(mycallocSRAMEX, my_freeSRAMEX);
    
    • 公钥与数字证书
      这个还是安全性的考虑,感兴趣可以搜一下

AES加密

AES加密介绍的可以参考:https://blog.csdn.net/qq_28205153/article/details/55798628
  这部分的代码主要是aes.c(忘记具体代码从哪copy的了),也只是为了看起来唬人,把Ymodem的协议包在封装完之后,再通过aes加密发送出去,来保证安全性。
AES是对称加密算法,这意味这其密钥必须妥善保管,否则一旦泄露就完蛋了。RSA是非对称加密算法,虽然更加保险,但是本身加密数据和解密需要大量时间和性能。所以就有了RSA数字签名+传输过程AES加密的方案

LWIP协议栈

  这部分的代码就是正点原子提供的例程,之前写过一篇关于LWIP协议栈移植的博客:
https://blog.csdn.net/qq_45731845/article/details/145420389?spm=1011.2415.3001.5331

A/B分区

  A/B分区的意思就是当我们升级的时候,如果当前活跃的是A分区,那么就把新的APP信息写入到B分区,然后跳转到B分区去跑新的程序,这样的好处就是增加了一道保险,哪怕B分区的程序出bug了,也可以回退到A分区运行。
A/B分区的代码在实现的时候,核心问题有以下几点:

  • Bootloader如何知道当前活跃的分区是哪个分区?
  • Bootloader怎么知道当前活跃的分区是否有BUG?
  • Bootloader怎么知道当前有无升级标志位?
      这几个问题要解决,核心就是通过Bootloader和APP共享的外设去记录一些信息,比如给FLASH的某个扇区预留出来专门做这个工作。我这边也是为了学习嘛,开发板上有个基于I2C的EEPROM外设,不用白不用(>.<)。
    所以上面几个问题就好回答了
  • Bootloader如何知道当前活跃的分区是哪个分区?—由app启动后写入地址A,bootloader只读
  • Bootloader怎么知道当前活跃的分区是否有BUG?—由app发生异常后写入地址B,bootloader只读
  • Bootloader怎么知道当前有无升级标志位?—由app接受到升级信号后写入地址C,bootloader只读
  • APP启动后,写入当前活跃分区,并清除是否有BUG的标志位以及升级标志位
void app_flag_init(void)
{
	at24cxx_write_one_byte(APP_ACTIVE_ADDRESS, sectorA); 	/*在哪里就写入哪个标志位*/
	delay_ms(40);
	at24cxx_write_one_byte(APP_RUN_ERR_ADDRESS, appNoErr); 
	delay_ms(40);
	at24cxx_write_one_byte(APP_UPDATE_FLAG_ADDRESS, updNoNeed); 
	delay_ms(40);
}

  不过因为写入不是原子性的,所以还是在某些场景下有bug,比如程序卡死在写入at24cxx_write_one_byte(APP_ACTIVE_ADDRESS, sectorA)之后,就算重启之后发现当前活跃分析还是SectorA就完蛋咯。
  如何判断是不是出现ERR了,我在这里的做法是吧hardfault_handler重写了,在hardfault_handler里面记录,不过问题是万一hardfault就是写入at24cxx_write_one_byte引起的,就会陷入死循环了。
  AI觉得可以看门狗,APP定时喂狗,在Bootloader里判断这次进入Bootloader是不是看门狗超时导致的,可以试试

void rt_hw_hard_fault_exception(struct exception_info *exception_info)
{
	/*要是FLASH有额外的 还可以保存一下现场*/
//  struct exception_stack_frame *exception_stack = &exception_info->stack_frame.exception_stack_frame;
    struct stack_frame *context = &exception_info->stack_frame;
	appErrFlag err_flag = appNoErr;
	printf(" lr: 0x%08x\r\n", context->exception_stack_frame.lr);
	printf("--------------------hard_fault_happen APPA------------------\r\n");

    printf("psr: 0x%08x\r\n", context->exception_stack_frame.psr);

    printf("r00: 0x%08x\r\n", context->exception_stack_frame.r0);
    printf("r01: 0x%08x\r\n", context->exception_stack_frame.r1);
    printf("r02: 0x%08x\r\n", context->exception_stack_frame.r2);
    printf("r03: 0x%08x\r\n", context->exception_stack_frame.r3);
    printf("r04: 0x%08x\r\n", context->r4);
    printf("r05: 0x%08x\r\n", context->r5);
    printf("r06: 0x%08x\r\n", context->r6);
    printf("r07: 0x%08x\r\n", context->r7);
    printf("r08: 0x%08x\r\n", context->r8);
    printf("r09: 0x%08x\r\n", context->r9);
    printf("r10: 0x%08x\r\n", context->r10);
    printf("r11: 0x%08x\r\n", context->r11);
    printf("r12: 0x%08x\r\n", context->exception_stack_frame.r12);
    printf(" lr: 0x%08x\r\n", context->exception_stack_frame.lr);
    printf(" pc: 0x%08x\r\n", context->exception_stack_frame.pc);


    printf("hard fault happend\r\n");

    if ( (exception_info->exc_return & 0x10) == 0)
    {
        printf("FPU active!\r\n");
    }
	
	volatile uint32_t cfsr = SCB->CFSR;
    volatile uint32_t hfsr = SCB->HFSR;
    volatile uint32_t mmfar = SCB->MMFAR;
    volatile uint32_t bfar = SCB->BFAR;

    if (cfsr & 0xFF) 
	{
		err_flag |= appMemErr_Bit;
        printf("MemManage Fault:\n");
        if (cfsr & (1 << 7)) printf("  -> MMARVALID: MMFAR=%08x\n", mmfar);
        if (cfsr & (1 << 0)) printf("  -> IACCVIOL\n");
        if (cfsr & (1 << 1)) printf("  -> DACCVIOL\n");
    }
    if (cfsr & 0x00FF00) 
	{
        printf("BusFault:\n");
		err_flag |= appBusErr_Bit;
        if (cfsr & (1 << 15)) printf("  -> BFARVALID: BFAR=%08x\n", bfar);
        if (cfsr & (1 << 8)) printf("  -> IBUSERR\n");
        if (cfsr & (1 << 9)) printf("  -> PRECISERR\n");
    }
    if (cfsr & 0xFF0000) 
	{
        printf("UsageFault:\n");
		err_flag |= appUsgFaultErr_Bit;
        if (cfsr & (1 << 16)) printf("  -> UNDEFINSTR\n");
        if (cfsr & (1 << 17)) printf("  -> INVSTATE\n");
        if (cfsr & (1 << 18)) printf("  -> INVPC\n");
        if (cfsr & (1 << 19)) printf("  -> NOCP\n");
        if (cfsr & (1 << 25)) printf("  -> UNALIGNED\n");
        if (cfsr & (1 << 26)) printf("  -> DIVBYZERO\n");
    }
	at24cxx_write_one_byte(APP_RUN_ERR_ADDRESS, err_flag); 
    while (1);
}

补充:问过的问题

这些问题的答案大伙可以自行AI解决,印象不是很深刻了,而且完全可以基于这些问题吧代码好好改改,到时候方便和面试官吹逼。

  • STM32的启动过程?
  • bootloader的代码逻辑(A/B分区是怎么做的, 升级的代码流程是什么样的)
  • 怎么保证升级的可靠性与安全性?
  • LWIP协议栈的移植?
  • Uboot的启动过程(是的Linux的Uboot,面试官觉得我应该懂(但是没答上来))?
  • 升级过程中断电怎么办,当前的代码有没有考虑到这些问题?
    额…基本没有
  • bootlaoder的业务逻辑这么简单,为啥还要上RTOS呢?
    之前的bootlaoder确实上RTOS了,后来发现真没必要啊就这点代码上啥RTOS,就改过一版
  • 涉及到I2C通信的一些问题
    • I2C的通信原理
    • I2C总线上通常会有哪些问题,协议上要做哪些容错处理?
    • I2C出现设备冲突或者总线挂死要怎么回复?
  • 如何减少STM32中bin文件代码的体积(从编译器/代码实现/代码设计)?

更多推荐