ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

STM32 IAP固件升级(二)

2020-02-03 19:57:39  阅读:342  来源: 互联网

标签:__ 函数 DCD STM32 地址 Handler EXPORT 固件 IAP


章节说明

STM32 IAP固件升级实验分为一下的章节(加粗的字体是本章节的内容):

一、Flash和RAM的区域划分、工程建立、程序分散加载、程序烧写
二、Stm32 bootloader、application、firmware 程序的分析和编写
三、使用DMA来读取串口的不定长数据
四、通信协议的设计
五、STM32 IAP程序的设计
六、上位机的程序的编写

一、STM32F1XX的启动过程

在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南);

  • 0x0000 0000地址取出MSP(主堆栈寄存器)的值
  • 0x0000 0004地址取出PC(程序计数器)的值
  • 然后取出第一条指令执行

Cortex-M3权威指南 复位序列

二、STM32启动文件的分析

1、启动文件源代码分析

分析startup_stm32f10x_hd.s启动文件时会涉及到到一些汇编指令,如果不认识的指令可以到mdk集成开发工具help -> μVision Help 里面搜索;如下图:

代码块如下:

;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
;* File Name          : startup_stm32f10x_hd.s
;* Author             : MCD Application Team
;* Version            : V3.5.0
;* Date               : 11-March-2011
;* Description        : STM32F10x High Density Devices vector table for MDK-ARM 
;*                      toolchain. 
;*                      This module performs:
;*                      (上电复位后会做下面的几件事情)
;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
;*                      - Set the initial PC == Reset_Handler(设置PC的值)
;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
;*                        SRAM mounted on STM3210E-EVAL board to be used as data 
;*                        memory (optional, to be enabled by user)
;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)
;*                        calls main()).
;*                      After Reset the CortexM3 processor is in Thread mode,
;*                      priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>   
;*******************************************************************************
; THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
; WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME.
; AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
; INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
; CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
; INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
;*******************************************************************************

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

; ------------------分配栈空间----------------
Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
                AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
__initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到

                                           
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
; ------------------分配堆空间----------------
;和分配栈空间一样不过大小只是512字节
Heap_Size       EQU     0x00000200
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base        ;__heap_base堆的起始地址
Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
__heap_limit       ;__heap_base堆的结束地址

                PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐
                THUMB;THUMB作用是后面的指令使用Thumb指令集

; ------------------设置中断向量表----------------
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
                ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
                EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用          
                EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
                EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用


                
;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化

;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
__Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
                                                   ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) 
                                                   ;* __initial_sp的值是链接后,由链接器生成

                DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
                                                   ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
                                                   ;* Reset_Handler是一个函数,在下面定义
                ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
                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

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC

                .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件

                DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
                DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
                DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End                                    ;__Vectors_End向量表的结束地址

__Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小

                AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读

;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
; Reset handler
Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序

                EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
                                                          ;*编译时使用另一个地方的函数,否则使用这个函数
                                                
                                                          ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
                IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
                IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
                LDR     R0, =SystemInit
                BLX     R0              
                LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
                BX      R0
                ENDP
                
; Dummy Exception Handlers (infinite loops which can be modified)
;下面定义的都是异常服务函中断服务函数
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
.....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP

Default_Handler PROC

                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
.....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .

                ENDP

                ALIGN    ;四字节对齐

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;下面函数是初始化堆栈的代码
                 IF      :DEF:__MICROLIB     
                 ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
                 ;这种方式初始化堆栈是由 __main 初始化的
                 EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
                 EXPORT  __heap_base    ;堆的起始地址
                 EXPORT  __heap_limit   ;堆的结束地址
                
                 ELSE
                 ;由用户初始化堆
                 ;否则编译下面的
                 IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap
                 
                 LDR     R0, =  Heap_Mem              ;堆的起始地址
                 LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
                 LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址
                 LDR     R3, = Stack_Mem              ;栈的结束地址
                 BX      LR

                 ALIGN

                 ENDIF

                 END

;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****

2、小结

STM32的启动步骤如下:

  1. 上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的
  2. 0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成
  3. 调用SystemInit函数初始化系统时钟
  4. 跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序

三、bootloader分析

1.bootloader分析

由于芯片复位启动后执行的是bootloader程序,而bootloader的主要作用之一就是可以启动application。又因为application的空间和bootloader的空间是独立的。所以要顺利启动applicationbootloader还要做以下处理:

  1. 设置applicationMSP的值; 从0x0800 C000地址取出栈顶地址的值赋给MSP。(0x0800 C000application的起始地址 )
  2. 0x0800 C004 地址取出application第一个执行的函数地址赋值给函数指针fun_point
  3. 使用fun_point函数针跳到application执行
  4. 跳到application后需要设置中断向量表的偏移;也就是设置VTOR寄存器的值(VTOR寄存器可以到《Cortex-M3权威指南》查看)。为什么要设置向量表偏移?原因也很简单。因为发生中断后,cpu要到application的空间找到中断服务程序的;如果不设置偏移,默认是从bootloader空间找到中断服务程序的。
    简单总结一下:其实从bootloader跳到application执行和上电复位启动bootloader差不多。两者的差别就是前者需要开发者自己设置,后者是由硬件自动完成的。

2.bootloader 程序

typedef void(*App_Fun_t)(void);
typedef void(*Firm_Reload_Fun_t)(void);

/* 定义一个函数指针,主要用于跳到app */
App_Fun_t app_main ;

/* 定义一个函数指针,主要用于调用firmware区的重定位 */
Firm_Reload_Fun_t firm_fun;
int main(void)
{
    /* 初始化串口和滴答定时器 */
    Sys_Init();
    
        
    firm_fun = (Firm_Reload_Fun_t)*(vu32*)(0x0807c000);
    /* frimware的RW和ZI段重定位 */
    firm_fun();
    /* 串口输出一些信息 */
    Sys_Printf("hello bootloader\r\n");
       
    Sys_Delay_ms(10);
        /* app的起始地址是0x0800C000 */
        /* 简单的判断一下app区域的向量表的数据正不正确 */
    if(((*(vu32*)(0x0800C000+4))&0xFF000000)==0x08000000){

            /* 设置应用程序的堆栈 */
            /* 
             * MSR_MSP 是一个函数,原型如下:
             * __asm void MSR_MSP(u32 addr)  //__asm的作用是将告诉编译器,函数体内部的代码是汇编指令 
             * {   
             *     // 由 ATPCS 规则,汇编跟C语言传参使用的是R0-R3寄存器(参数小于等于4个的情况下),所以r0的值就是addr    
             *     MSR MSP, r0 //MSR指令是将r0寄存器的值赋给MSP(主堆栈寄存器)    
             *     BX r14 //r14是lr寄存器(连接寄存器),保存返回地址的。这条指令的作用是函数执行完返回
             * }
             */
        MSR_MSP(*(vu32*)(0x0800C000));


            /* 从0x0800C004地址取出app第一个执行的函数入口 */
            app_main = (App_Fun_t)*(vu32*)(0x0800C004);

            /* 设置中断向量表偏移 */
            /* 其实中断向量的偏移值在这里设置也可以,不过需要注意的是在app的SystemInit函数又重新设置为0了,
             * 所以可以注释掉app的 SystemInit 的 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;语句,
             * 或者将VECT_TAB_OFFSET宏定义为: #define VECT_TAB_OFFSET  0xC000 
             */
            SCB->VTOR = (FLASH_BASE | 0xC000);


            /* 跳转到应用程序执行 */
            /* 这里跳过去就不会回来了 */
        app_main();

    }else{
        Sys_Printf("boot addr error\r\n");
    }
    while(1);

三、application

bootloader跳到application后,application的程序也会做下面的几件事情:

  1. 执行SystemInit函数(这个是application空间的函数了)
  2. 执行__main(这个也是application空间的函数),然后跳到main函数执行业务程序

application 需要注意的就是,如果使用官方的启动文件,需要注释 SystemInit 函数的 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;语句。或者修改VECT_TAB_OFFSET的值。否者在bootloader设置的向量表偏移又会被设置为0了。然后app程序根据业务编写程序就好。

四、firmware

1、firmware程序的分和实现

通过上面分析启动文件后,编写firmware也是比较简单了。firmwareapplicationbootloader都不在一个同一个工程,所以不能通过函数名调用firmware的区域的函数。要调用firmware区的函数时只能通过函数指针调用。所以在编写完firmware区域的函数后,还需要将函数的入口地址暴露出来。怎么暴露出来才能让其他的地方能正确的调用到呢? 其实我们也可以仿照中断向量表的方式,将函数firmware的所有函数定义成一张表,这张表放在firmware区域的前面地址。函数表的定义如下代码块:

                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size


;__Vectors 是firmware的起始地址
;使用DCD指令将函数的入口地址存放在表中。
;下面是我自己实现的函数,感兴趣的可以自己定义或者修改

__Vectors       DCD     RW_And_ZI_Init              ;初始化RW段和ZI段
                DCD     Num_Inc                     ;数字自增
                DCD     Num_Dec                     ;数字自减
                DCD     Get_Num                     ;获取数字的值
                DCD     Get_Num1
                DCD     Get_Num_Addr
                DCD     Get_Flag
                DCD     Swap_Num                    ;交换数据
                DCD     My_Men_Copy                 ;内存copy                 
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

;使用AREA指令定义一个代码段。然后定义所有的函数在下面。

                AREA    |.text|, CODE, READONLY
                EXPORT  RW_And_ZI_Init            [WEAK]
                EXPORT  Num_Inc                   [WEAK]
                EXPORT  Num_Dec                   [WEAK]
                EXPORT  Get_Num                   [WEAK]
                EXPORT  Get_Num1                  [WEAK]
                EXPORT  Get_Num_Addr              [WEAK]
                EXPORT  Get_Flag                  [WEAK]
                EXPORT  Swap_Num                  [WEAK]
                EXPORT  My_Men_Copy               [WEAK]
                   
RW_And_ZI_Init      
Num_Inc
Num_Dec
Get_Num
Get_Num1
Get_Num_Addr
Get_Flag
Swap_Num
My_Men_Copy

                B   .
                END

2、firmware注意事项

a.注意事项一

由于firmware没有初始化堆所以firmware区域的函数不能使用malloccalloc这一系列的函数。同时firmware区域的函数是由application或者bootloader调用执行的,firmware区域不需要初始化和设置栈。

b.注意事项二

因为firmware区域不会调用C库__main,如果有使用全局变量,则需要自己实现代码重定位函数。并且需要在application或者bootloader调用执行一次。重定位的程序如下代码块:

unsigned int flag;
void RW_And_ZI_Init (void)
{
    /* flag是一个全局变量,但是在执行if判断的时候并没有进行重定位,所以这个值是一个随机值(不确定的) */
    /* flag的作用是防止 RW_And_ZI_Init函数 被调用 */ 
    
    if(flag!=0xf55faa55){   /* 一般第一次执行的时候不会等于0xf55faa55。如果你执行的时候等于0xf55faa55,那么恭喜你,这运气你可以去买彩票了 */
        /**********这些变量都是是由链接器链接的时候生成确定的***********/
        extern unsigned char Image$$ER_IROM1$$Limit;      //&Image$$ER_IROM1$$Limit;只读段的末尾地址,也是可读可写数据段的起始地址
        extern unsigned char Image$$RW_IRAM1$$Base;       //&Image$$RW_IRAM1$$Base是可读可写数据段的重定位的起始地址
        extern unsigned char Image$$RW_IRAM1$$RW$$Limit;  //&Image$$RW_IRAM1$$Base是RW数据段的重定位的结束地址,也是ZI数据段的重定位的起始地址
        extern unsigned char Image$$RW_IRAM1$$ZI$$Limit;  //&Image$$RW_IRAM1$$ZI$$Limit 是ZI数据段的重定位的结束地址
        /**********这些变量都是是由链接器链接的时候生成确定的***********/
        
        unsigned char * psrc, *pdst, *plimt;
        
        psrc  = (unsigned char *)&Image$$ER_IROM1$$Limit;
        
        pdst  = (unsigned char *)&Image$$RW_IRAM1$$Base;
        plimt = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit;
        /* 数据copy,也就是将存在flash空间的数据copy到RAM空间 */
        while(pdst < plimt){
            *pdst++ = *psrc++;
        }
        psrc  = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit;
        plimt = (unsigned char *)&Image$$RW_IRAM1$$ZI$$Limit;
        /* 初始化ZI段 */
        while(psrc < plimt){
            *psrc++ = 0;
        }
        /* 执行到这里flag的值和一些未初始化全局变量的值都是0 */
        
        flag=0xf55faa55; //将flag的值设置0xf55faa55防止再次被重定位
    }
}  

c.注意事项三

在调用firware区域的函数时,需要定义的函数指针的格式(即参数和返回值)一定要一致,否则可能发生一些不可预判的错误

五、

最后将工程的链接附上--->点我project!!!

标签:__,函数,DCD,STM32,地址,Handler,EXPORT,固件,IAP
来源: https://www.cnblogs.com/gulan-zmc/p/12248509.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有