单片机 > 单片机程序设计 > 详情

STM32 IAP在线升级详解

发布时间:2020-08-31 发布时间:
|

一,在进入主题之前我们先了解一些必要的基础知识---- stm32系列芯片的种类和型号:


startup_stm32f10x_cl.s互联型的器件,STM32F105xx,STM32F107xx 
startup_stm32f10x_hd.s大容量的STM32F101xx,STM32F102xx,STM32F103xx 
startup_stm32f10x_hd_vl.s大容量的STM32F100xx 
startup_stm32f10x_ld.s小容量的STM32F101xx,STM32F102xx,STM32F103xx 
startup_stm32f10x_ld_vl.s小容量的STM32F100xx 
startup_stm32f10x_md.s中容量的STM32F101xx,STM32F102xx,STM32F103xx 
startup_stm32f10x_md_vl.s中容量的STM32F100xx(我项目中用的是此款芯片stm32f100CB)
startup_stm32f10x_xl.s FLASH在512K到1024K字节的STM32F101xx,STM32F102xx,STM32F103xx(例如:像stm32f103re这个型号的芯片flash是512k的,启动文件用startup_stm32f10x_xl.s或者startup_stm32f10x_hd.s都可以;)
cl:互联型产品,stm32f105 / 107系列
vl:超值型产品,stm32f100系列
xl:超高密度产品,stm32f101 / 103系列
ld:低密度产品,FLASH小于64K 
md:中等密度产品,FLASH = 64或128 
HD:高密度产品,FLASH大于128



二,在拿到ST公司官方的IAP程序后我们要思考几点:

        1.ST官方IAP是什么针对什么芯片型号的,我们要用的又是什么芯片型号;

2.我们要用官方IAP适合我们芯片的程序升级使用,要在原有的基础上做那些改变;

(我的资源里有官方IAP源码:http://download.csdn .NET / detail / yx_l128125 / 6445811)

       

初略看了一下IAP源码后,现在我们可以回答一下上面的2个问题了:

1.官网刚下载的IAP针对的是stm32f103c8芯片的,所以他的启动代码文件选择的是  startup_stm32f10x_md.s,而我的芯片是stm32f100cb,所以我的启动代码文件选择的是   startup_stm32f10x_md_lv.s 




          2。第二个问题就是今天我们要做详细分析才能回答的问题了

          (1)知道了IAP官方源码的芯片和我们要用芯片的差异,首先我们要在源码的基础上做芯片级的改动。

A.首先改变编译器KEIL的芯片型号上我们要改成我们的芯片类型--- STM32F100CB;

 B.在keil 的选项中,选择C / C ++ / PREPROMCESSOR符号的定义栏里定义,把有关STM32F10X_MD的宏定义改成:STM32F10X_MD_VL

也可以在STM32F10X.H里用宏定义

  1.   / *根据您使用的目标STM32设备取消下面的行注释  

  2.    应用   

  3.   * /  

  4.   

  5. (STM32F10X_LD)&&!定义(STM32F10X_LD)&&!定义(STM32F10X_MD)&&!定义(STM32F10X_MD)&&!定义(STM32F10X_HD)&&!定义(STM32F10X_HDL)&&!   

  6.   / * #define STM32F10X_LD * / / *!

  7.   / * #define STM32F10X_LD_VL * / / *!

  8.   / * #define STM32F10X_MD * / / *!

  9.    #define STM32F10X_MD_VL / *!

  10.   / * #define STM32F10X_HD * / / *!

  11.   / * #define STM32F10X_HD_VL * / / *!

  12.   / * #define STM32F10X_XL * / / *!

  13.   / * #define STM32F10X_CL * / / *!

  14. #万一  

上面代码说的是如果没有定义STM32F10X_MD_VL,则宏定义STM32F10X_MD_VL

C.外部时钟问价在stm32f10x.h依据实际修改,原文是说如果没有宏定义外部时钟HES_VALUE的值,但是宏定义了stm32f10x_cl则外部时钟设置为25MHZ,否则外部时钟都设置为8MHZ; 我用的外部晶振是8MHZ的所以不必修改这部分代码;


  1. #if!定义HSE_VALUE  

  2.  #ifdef STM32F10X_CL     

  3.   #define HSE_VALUE((uint32_t)25000000)//外部振荡器的值为Hz

     #else  pre> #define HSE_VALUE((uint32_t)8000000)//值外部振荡器以Hz #endif / * STM32F10X_CL * /#endif / * HSE_VALUE * /  



D.做系统主频时钟的更改

system_stm32f10x.c的系统主频率,依实际情况修改;我用的芯片主频时钟是24MHZ;

  1.       

  2. #if defined(STM32F10X_LD_VL)|| (定义为STM32F10X_MD_VL)|| (定义为STM32F10X_HD_VL)  

  3. / * #define SYSCLK_FREQ_HSE HSE_VALUE * /  

  4.  #define SYSCLK_FREQ_24MHz 24000000  

  5. #其他  

  6. / * #define SYSCLK_FREQ_HSE HSE_VALUE * /  

  7.  #define SYSCLK_FREQ_24MHz 24000000   

  8. / * #define SYSCLK_FREQ_36MHz 36000000 * /  

  9. / * #define SYSCLK_FREQ_48MHz 48000000 * /  

  10. / * #define SYSCLK_FREQ_56MHz 56000000 * /  

  11. / *#define SYSCLK_FREQ_72MHz 72000000 * /   

  12. #万一  

E.下面是关键部分操作了,在说这部分操作前我们先来说一下内存映射:          下图在stm32f100芯片手册的29页,我们只截取关键部分

从上图我们看出几个关键部分:

1.内部flash是从0x0800 0000开始到0x0801 FFFF结束,0x0801FFFF-0x0800 0000 = 0x20000 = 128k 128也就是flash的大小;

2.SRAM的开始地址是0x2000 0000;

我们要把我们的在线升级程序IAP放到FLASH里以0x0800 0000开始的位置,应用程序放APP APP到达0x08003000开始的位置,中断向量表也放在0x0800 3000开始的位置;如图


所以我们需要先查看一下misc.h文件中的中断向量表的初始位置宏定义为NVIC_VectTab_Flash 0x0800 0000

那么要就要设置编译器keil中的目标的选项选项中的IROM1地址为0x0800 0000大小为0x20000即128K;

                                                                                                   IRAM1地址为0x2000 0000大小为0x2000;

(提示:这一项IROM1地址即为当前程序下载到flash的地址的起始位置)

下面我们来分析一下修改后的IAP代码:

 

  1. / ******************* ******************************  

  2.   * @函数名称主  

  3.   * @函数说明主函数  

  4.   * @输入参数无  

  5.   * @输出参数无  

  6.   * @返回参数无  

  7. ************************************************** ************ /  

  8. int main(void)  

  9. {  

  10.     // Flash解锁  

  11.     FLASH_Unlock();  

  12.   

  13.     //配置PA15管脚  

  14.     KEY_Configuration();  

  15.     //配置串口1  

  16.     IAP_Init();  

  17.     // PA15是否为低电平  

  18.     if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)== 0x00)  

  19.     {  

  20.           

  21.         //执行IAP驱动程序更新的Flash程序  

  22.   

  23.         SerialPutString(“\ r \ n =========================================== ===========================“);  

  24.         SerialPutString(“\ r \ n =(C)COPYRIGHT 2011 Lierda =”);  

  25.         SerialPutString(“\ r \ n = =”);  

  26.         SerialPutString(“\ r \ n =应用程序编程应用程序(版本1.0.0)=”);  

  27.         SerialPutString(“\ r \ n = =”);  

  28.         SerialPutString(“\ r \ n =由wuguoyan =”);  

  29.         SerialPutString(“\ r \ n =========================================== ===========================“);  

  30.         SerialPutString( “\ r \ n \ r \ n”);  

  31.         主菜单 ();  

  32.     }  

  33.     //否则执行用户程序  

  34.     其他  

  35.     {  

  36.         //判断用处是否已经下载了用户程序,因为正常情况下此地址是栈地址  

  37.         //若没有这一句话,即使没有下载程序也会进入而导致跑飞。  

  38.         if(((*(__ IO uint32_t *)ApplicationAddress)&0x2FFE0000)== 0x20000000)  

  39.         {  

  40.             SerialPutString(“执行用户程序\ r \ n \ n”);  

  41.             //跳转至用户代码  

  42.             JumpAddress = *(__ IO uint32_t *)(ApplicationAddress + 4);  

  43.             Jump_To_Application =(pFunction)JumpAddress;  

  44.   

  45.             //初始化用户程序的堆栈指针  

  46.             __set_MSP(*(__ IO uint32_t *)ApplicationAddress);  

  47.             Jump_To_Application();  

  48.         }  

  49.         其他  

  50.         {  

  51.             SerialPutString(“no user Program \ r \ n \ n”);  

  52.         }  

  53.     }  


这里重点说一下几句经典且非常重要的代码:

第一句:if((*(__ IO uint32_t *)ApplicationAddress)&0x2FFE0000)== 0x20000000)//判断栈定地址值是否在0x2000 0000 - 0x 2000 2000之间

怎么理解呢?(1),在程序里#define ApplicationAddress 0x8003000,*(__ IO uint32_t *)ApplicationAddress)即取0x8003000开始到0x8003003的4个字节的值,因为我们的应用程序APP中设置把中断向量表放置在0x08003000开始的位置; 而中断向量表里第一个放的就是栈顶地址的值

也就是说,这句话即通过判断栈顶地址值是否正确(是否在0x2000 0000 - 0x 2000 2000之间)来判断是否应用程序已经下载了,因为应用程序的启动文件刚开始就去初始化化栈空间,如果栈顶值对了,说应用程已经下载了启动文件的初始化也执行了;



第二句:JumpAddress = *(__ IO uint32_t *)(ApplicationAddress + 4);  [common.c文件第18行定义了:pFunction Jump_To_Application;]
                      

ApplicationAddress + 4即为0x0800 3004,里面放的是中断向量表的第二项“复位地址”JumpAddress = *(__ IO uint32_t *)(ApplicationAddress + 4); 之后此时JumpAddress

第三句:    Jump_To_Application =(pFunction)JumpAddress; 
 startup_stm32f10x_md_lv。  文件中别名   typedef void(* pFunction)(void); 这个看上去有点奇怪;正常第一个整型变量typedef int a; 就是给整型定义一个别名a

 void(* pFunction)(void); 是声明一个函数指针,加上一个typedef之后   pFunction只不过是类型 void(*)(void)的一个别名;例如:


  1. p功能a1,a2,a3;  

  2.   

  3. void   fun(void )  

  4. {  

  5.     ......  

  6. }  

  7.   

  8. a1 =乐趣  


所以,Jump_To_Application =(pFunction)JumpAddress;此时Jump_To_Application指向了复位函数所在的地址;

第四,五句:__set_MSP(*(__ IO uint32_t *)ApplicationAddress); \\设置主函数栈指针
               Jump_To_Application(); \\执行复位函数

我们看一下启动文件startup_stm32f10x_md_vl.s中的启动代码,更容易理解


移植后的IAP代码在我的资源(如果是stm32f100cb的芯片可以直接用):http://download.csdn .net / detail / yx_l128125 / 6475219

三,我们来简单看下启动文件中的启动代码,分析一下这更有利于我们对IAP的理解:(下面这篇文章写的非常好,有木有!)

下文来自于:HTTP://blog.sina.com.cn/s/blog_69bcf45201019djx.html

解析STM32  的启动过程 

解析STM32 的启动过程

的当前嵌入式应用程序开发过程里,并且Ç 语言成为了绝大部分场合的最佳选择如此一来。主函数似乎成为了理所当然的起点-因为Ç 程序往往从主。函数开始执行但一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行主?函数的呢很显然微控制器无法从硬件上定位主函数的入口地址,因为使用ç 语言作为开发语言后,变量/ 函数的地址便由编译器在编译时自行分配,这样一来主函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“ Bootloader ” 。

无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行主函数”最近常见的51 ,AVR 或MSP430 等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从主函数开始进行应用程序的设计即可。

话题转到STM32 微控制器, 无论是keil 
uvision4 还是IAR EWARM 开发环境,ST 公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C 应用程序的开发。大型减小开发人员从其它微控制器平台跳转至STM32 平台,也降低了适应STM32 微控制器的难度(对于上一代ARM 的当家花旦ARM9 ,启动文件往往是第一道难叮而又无法逾越的坎)。

相对于ARM 上一代的主流ARM7 / ARM9 内核架构,新一代Cortex 内核架构的启动方式有了比较大的变化。ARM7 / ARM9 内核的控制器在复位后,CPU 会从存储空间的绝对地址0x000000 取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000 (PC = 0x000000 )同时中断向量表的位置并不是固定的。而Cortex-M3 内核则正好相反,有3 种情况:
1 ,通过引导设置可以将中断向量表定位于SRAM 区,即起始地址为0x2000000 ,同时复位后PC 指针位于0x2000000 处; 2 ,通过引导引脚设置可以将中断向量表定位于闪 
 区,即起始地址为0x8000000 ,同时复位后PC 指针位于0x8000000 处; 
3 ,通过引导设置可以将中断向量表定位于内置Bootloader 区,本文不对这种情况做论述; 而Cortex-M3 内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3的内核复位后,会自动从起始地址的下一个32 位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比 ARM7 / ARM9 内核,Cortex-M3的内核则是固定了中断向量表的位置而起始地址是可变化的。有了上述准备只是后,下面以STM32 的2.02 固件库提供的启动文件“ stm32f10x_vector.s ” 为模板,对STM32的启动过程做一个简要而全面的解析。程序清单一:;文件“ 


stm32f10x_vector.s “ ,其中注释为行号
DATA_IN_ExtSRAM EQU 0  ; 1 
Stack_Size EQU 0x00000400  ; 2 
AREA STACK,NOINIT,READWRITE,ALIGN = 3  ; 3 
Stack_Mem SPACE Stack_Size  ; 4 
__initial_sp  ; 5 
Heap_Size EQU 0x00000400  ; 6 
AREA HEAP,NOINIT, READWRITE,ALIGN = 3  ; 7 
__heap_base  ; 8 
Heap_Mem SPACE Heap_Size  ; 9 
__heap_limit  ; 10 
THUMB  ; 11 
PRESERVE8  ; 12
IMPORT NMIException  ; 13 
IMPORT HardFaultException  ; 14 
IMPORT MemManageException  ; 15 
IMPORT BusFaultException  ; 16 
IMPORT UsageFaultException  ; 17 
IMPORT SVCHandler  ; 18 
IMPORT DebugMonitor  ; 19 
IMPORT PendSVC  ; 20 
IMPORT SysTickHandler  ; 21 
IMPORT WWDG_IRQHandler  ; 22 
IMPORT PVD_IRQHandler  ; 23 
IMPORT TAMPER_IRQHandler  ; 24 
IMPORT RTC_IRQHandler  ; 25
IMPORT FLASH_IRQHandler  ; 26 
IMPORT RCC_IRQHandler  ; 27 
IMPORT EXTI0_IRQHandler  ; 28 
IMPORT EXTI1_IRQHandler  ; 29 
IMPORT EXTI2_IRQHandler  ; 30 
IMPORT EXTI3_IRQHandler  ; 31 
IMPORT EXTI4_IRQHandler  ; 32 
IMPORT DMA1_Channel1_IRQHandler  ; 33 
IMPORT DMA1_Channel2_IRQHandler  ; 34 
IMPORT DMA1_Channel3_IRQHandler  ; 35 
IMPORT DMA1_Channel4_IRQHandler  ; 36 
IMPORT DMA1_Channel5_IRQHandler  ; 37
IMPORT DMA1_Channel6_IRQHandler  ; 38 
IMPORT DMA1_Channel7_IRQHandler  ; 39 
IMPORT ADC1_2_IRQHandler  ; 40 
IMPORT USB_HP_CAN_TX_IRQHandler  ; 41 
IMPORT USB_LP_CAN_RX0_IRQHandler  ; 42 
IMPORT CAN_RX1_IRQHandler  ; 43 
IMPORT CAN_SCE_IRQHandler  ; 44 
IMPORT EXTI9_5_IRQHandler  ; 45 
IMPORT TIM1_BRK_IRQHandler  ; 46 
IMPORT TIM1_UP_IRQHandler  ; 47 
IMPORT TIM1_TRG_COM_IRQHandler  ; 48 
IMPORT TIM1_CC_IRQHandler  ; 49
IMPORT TIM2_IRQHandler  ; 50 
IMPORT TIM3_IRQHandler  ; 51 
IMPORT TIM4_IRQHandler  ; 52 
IMPORT I2C1_EV_IRQHandler  ; 53 
IMPORT I2C1_ER_IRQHandler  ; 54 
IMPORT I2C2_EV_IRQHandler  ; 55 
IMPORT I2C2_ER_IRQHandler  ; 56 
IMPORT SPI1_IRQHandler  ; 57 
IMPORT SPI2_IRQHandler  ; 58 
IMPORT USART1_IRQHandler  ; 59 
IMPORT USART2_IRQHandler  ; 60 
IMPORT USART3_IRQHandler  ; 61 
IMPORT EXTI15_10_IRQHandler ; 62 
IMPORT RTCAlarm_IRQHandler  ; 63 
IMPORT USBWakeUp_IRQHandler  ; 64 
IMPORT TIM8_BRK_IRQHandler  ; 65 
IMPORT TIM8_UP_IRQHandler  ; 66 
IMPORT TIM8_TRG_COM_IRQHandler  ; 67 
IMPORT TIM8_CC_IRQHandler  ; 68 
IMPORT ADC3_IRQHandler  ; 69 
IMPORT FSMC_IRQHandler  ; 70 
IMPORT SDIO_IRQHandler  ; 71 
IMPORT TIM5_IRQHandler  ; 72 
IMPORT SPI3_IRQHandler  ; 73 
IMPORT UART4_IRQHandler  ; 74
IMPORT UART5_IRQHandler  ; 75 
IMPORT TIM6_IRQHandler  ; 76 
IMPORT TIM7_IRQHandler  ; 77 
IMPORT DMA2_Channel1_IRQHandler  ; 78 
IMPORT DMA2_Channel2_IRQHandler  ; 79 
IMPORT DMA2_Channel3_IRQHandler  ; 80 
IMPORT DMA2_Channel3_IRQHandler ; 80 IMPORT DMA2_Channel4_5_IRQHandler  ; 81 
AREA RESET,DATA,READONLY  ; 82 
EXPORT __Vectors  ; 83 
__Vectors  ; 84 
DCD __initial_sp  ; 85 
DCD Reset_Handler  ; 86 
DCD NMIException  ;87 
DCD HardFaultException  ; 88 
DCD MemManageException  ; 89 
DCD BusFaultException  ; 90 
DCD UsageFaultException  ; 91 
DCD 0  ; 92 
DCD 0  ; 93 
DCD 0  ; 94 
DCD 0  ; 95 
DCD SVCHandler  ; 96 
DCD DebugMonitor  ; 97 
DCD 0  ; 98 
DCD PendSVC  ; 99 
DCD SysTickHandler  ; 100 
DCD WWDG_IRQHandler  ; 101 
DCD PVD_IRQHandler ; 102 
DCD TAMPER_IRQHandler  ; 103 
DCD RTC_IRQHandler  ; 104 
DCD FLASH_IRQHandler  ; 105 
DCD RCC_IRQHandler  ; 106 
DCD EXTI0_IRQHandler  ; 107 
DCD EXTI1_IRQHandler  ; 108 
DCD EXTI2_IRQHandler  ; 109 
DCD EXTI3_IRQHandler  ; 110 
DCD EXTI4_IRQHandler  ; 111 
DCD DMA1_Channel1_IRQHandler  ; 112 
DCD DMA1_Channel2_IRQHandler  ; 113 
DCD DMA1_Channel3_IRQHandler  ; 114
DCD DMA1_Channel4_IRQHandler  ; 115 
DCD DMA1_Channel5_IRQHandler  ; 116 
DCD DMA1_Channel6_IRQHandler  ; 117 
DCD DMA1_Channel7_IRQHandler  ; 118 
DCD ADC1_2_IRQHandler  ; 119 
DCD USB_HP_CAN_TX_IRQHandler  ; 120 
DCD USB_LP_CAN_RX0_IRQHandler  ; 121 
DCD CAN_RX1_IRQHandler  ; 122 
DCD CAN_SCE_IRQHandler  ; 123 
DCD EXTI9_5_IRQHandler  ; 124 
DCD TIM1_BRK_IRQHandler  ; 125 
DCD TIM1_UP_IRQHandler  ; 126
DCD TIM1_TRG_COM_IRQHandler  ; 127 
DCD TIM1_CC_IRQHandler  ; 128 
DCD TIM2_IRQHandler  ; 129 
DCD TIM3_IRQHandler  ; 130 
DCD TIM4_IRQHandler  ; 131 
DCD I2C1_EV_IRQHandler  ; 132 
DCD I2C1_ER_IRQHandler  ; 133 
DCD I2C2_EV_IRQHandler  ; 134 
DCD I2C2_ER_IRQHandler  ; 135 
DCD SPI1_IRQHandler  ; 136 
DCD SPI2_IRQHandler  ; 137 
DCD USART1_IRQHandler  ; 138 
DCD USART2_IRQHandler  ; 139
DCD USART3_IRQHandler  ; 140 
DCD EXTI15_10_IRQHandler  ; 141 
DCD RTCAlarm_IRQHandler  ; 142 
DCD USBWakeUp_IRQHandler  ; 143 
DCD TIM8_BRK_IRQHandler  ; 144 
DCD TIM8_UP_IRQHandler  ; 145 
DCD TIM8_TRG_COM_IRQHandler  ; 146 
DCD TIM8_CC_IRQHandler  ; 147 
DCD ADC3_IRQHandler  ; 148 
DCD FSMC_IRQHandler  ; 149 
DCD SDIO_IRQHandler  ; 150 
DCD TIM5_IRQHandler  ; 151 
DCD SPI3_IRQHandler  ;152 
DCD UART4_IRQHandler  ; 153 
DCD UART5_IRQHandler  ; 154 
DCD TIM6_IRQHandler  ; 155 
DCD TIM7_IRQHandler  ; 156 
DCD DMA2_Channel1_IRQHandler  ; 157 
DCD DMA2_Channel2_IRQHandler  ; 158 
DCD DMA2_Channel3_IRQHandler  ; 159 
DCD DMA2_Channel4_5_IRQHandler  ; 160 
AREA | .text |,CODE,READONLY  ; 161 
Reset_Handler PROC  ; 162 
EXPORT Reset_Handler  ; 163 
IF DATA_IN_ExtSRAM == 1  ; 164
LDR R0,= 0x00000114  ; 165 
LDR R1,= 0x40021014  ; 166 
STR R0,[R1]  ; 167 
LDR R0,= 0x000001E0  ; 168 
LDR R1,= 0x40021018  ; 169 
STR R0,[R1]  ; 170 
LDR R0,= 0x44BB44BB  ; 171 
LDR R1,= 0x40011400  ; 172 
STR R0,[R1]  ; 173 
LDR R0,= 0xBBBBBBBB  ; 174 
LDR R1,= 0x40011404  ; 175 
STR R0,[R1]  ; 176 
LDR R0,= 0xB44444BB  ; 177 
LDR R1,= 0x40011800  ;178 
STR R0,[R1]  ; 179 
LDR R0,= 0xBBBBBBBB  ; 180 
LDR R1,= 0x40011804  ; 181 
STR R0,[R1]  ; 182 
LDR R0,= 0x44BBBBBB  ; 183 
LDR R1,= 0x40011C00  ; 184 
STR R0,[R1 ]  ; 185 
LDR R0,= 0xBBBB4444  ; 186 
LDR R1,= 0x40011C04  ; 187 
STR R0,[R1]  ; 188 
LDR R0,= 0x44BBBBBB  ; 189 
LDR R1,= 0x40012000  ; 190 
STR R0,[R1]  ; 191 
LDR R0, = 0x44444B44  ;192 
LDR R1,= 0x40012004  ; 193 
STR R0,[R1]  ; 194 
LDR R0,= 0x00001011  ; 195 
LDR R1,= 0xA0000010  ; 196 
STR R0,[R1]  ; 197 
LDR R0,= 0x00000200  ; 198 
LDR R1,= 0xA0000014  ; 199 
STR R0,[R1]  ; 200 
ENDIF  ; 201 
IMPORT __main  ; 202 
LDR R0,= __ main  ; 203 
BX R0  ; 204 
ENDP  ; 205 
ALIGN  ; 206 
IF:DEF:__ MICROLIB ; 207 
EXPORT __initial_sp  ; 208 
EXPORT __heap_base  ; 209 
EXPORT __heap_limit  ; 210 
ELSE  ; 211 
IMPORT __use_two_region_memory  ; 212 
EXPORT __user_initial_stackheap  ; 213 
__user_initial_stackheap  ; 214 
LDR R0,= Heap_Mem  ; 215 
LDR R1,=(Stack_Mem + Stack_Size)  ; 216 
LDR R2 = (Heap_Mem + Heap_Size)  ; 217 
LDR R3,= Stack_Mem  ; 218 
BX LR  ; 219 
ALIGN  ;220 
ENDIF  ; 221 
END  ; 222 
ENDIF  ; 223 
END  ; 224 
如程序清单一,STM32 的启动代码一共224 行,使用了汇编语言编写,这其中的主要原因下文将会给出交代现在从第一行开始分析: 第1 行:定义是否使用外部SRAM ,为1 则使用,为0 则表示不使用此语行若用。ç 语言表达则等价于: #定义DATA_IN_ExtSRAM 0  第2 行:定义栈空间大小为0x00000400时个字节,即1KB的此语行亦等价于: #定义STACK_SIZE 0x00000400时 第3 行:伪指令AREA
 

 

 ,表示 第4 行:开辟一段大小为Stack_Size 的内存空间作为栈 第5 行:标号__initial_sp ,表示栈空间顶地址 第6 行:定义堆空间大小为0x00000400 个字节,也为1Kbyte 。 第7 行:伪指令AREA ,表示 第8 行:标号__heap_base ,表示堆空间起始地址。 第9 行:开辟一段大小为Heap_Size 。内存的空间作为堆 第10 行:标号__heap_limit ,表示堆空间结束地址 第11 行:告诉编译器使用
 
 
 
 
 
 
 
 THUMB 指令集。 第12 行:告诉编译器以8 字节对齐。 第13 - 81 行:IMPORT 指令,指示后续符号是在外部文件定义的(类似ç 语言中的全局变量声明),而下文可以会使用到这些符号 第82 行:定义只读数据段,实际上是在CODE 区(假设STM32 从FLASH 启动,则此中断向量表起始地址即为0x8000000 ) 第83 行:将标号__Vectors 声明为全局标号,这样外部文件就可以使用这个标号。 第84 行:标号__Vectors 。,中断表示向量表入口地址 第85 - 160
 
 
 
 
 
 行:建立中断向量表 第161 行: 第162 行:复位中断服务程序,PROC ... ENDP 结构表示程序的开始和结束 第163 行:声明复位中断向量Reset_Handler 为全局属性,这样外部文件就可以调用此复位中断服务。 第164 行:IF ... ENDIF 为预编译结构,判断是否使用外部SRAM ,在第1 行中已定义为“ 不使用” 。 第165 - 201 行:此部分代码的作用是设置FSMC 总线以支持SRAM ,因不使用外部SRAM 因此此部分代码不会被编译 第
 

 
 
 
 202 行:声明__main 标号 第203 - 204 行:跳转__main 地址执行 第207 行:IF ... ELSE ... ENDIF 结构,判断是否使用DEF:__ MICROLIB (此处为不使用) 第208 - 210 行:若使用DEF:__ microlib中,则将__initial_sp ,__heap_base ,__heap_limit 亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。 第212 行:定义全局标号__use_two_region_memory 。 第213
 
 
 
 
 行:声明全局标号__user_initial_stackheap ,这样外程序也可调用此标号 第214 行:标号__user_initial_stackheap ,表示用户堆栈初始化程序入口 第215 - 218 行:分别保存栈顶指针和栈大小,堆始地址和堆大小至R0 ,R1 ,R2 ,R3 寄存器。 第224 行:完毕程序以上便是STM32 的启动代码的完整解析,接下来对几个小地方做解释: 1 ,AREA 指令:伪指令,用于定义代码段或数据段,后跟属性标号。其中比较重要的一个标号为“ READONLY ” 或者“ READWRITE ” ,其中“ READONLY ”
 

 

 表示该段为只读属性,联系到STM32 的内部存储介质,可知具有只读属性的段保存于FLASH 区,即0x8000000 地址后。而“READONLY ” 表示该段为“ 可读写” 属性,可知“ 可读写” 段保存于SRAM 区,即0x2000000 地址后。由此可以从第3 ,7 行代码知道,堆栈段位于SRAM 空间。从第82 行可知,中断向量表放置与FLASH 区,而这也是整片启动代码中最先被放入FLASH 区的数据。因此可以得到一条重要的信息:0x8000000 地址存放的是栈顶地址__initial_sp ,0x8000004 地址存放的复位中断向量Reset_Handler (STM32使用32 位总线,因此存储空间为4 字节对齐)
2 ,DCD 指令:作用是开辟一段空间,其意义等价于C 语言中的地址符“ &” 。因此从第84 行开始建立的中断向量表则类似于使用C 语言定义了一个指针数组,其每一个成员都是一个函数指针,分别指向各个中断服务函数 。3 ,标号:前文多处使用了“ 标号”一词。标号主要用于表示一片内存空间的某个位置,等价于ç 语言中的“ 地址” 概念。地址仅仅表示存储空间的一个位置,从ç 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别 。4 ,第202 行中的__main 标号并不表示C 程序中的主函数入口地址,因此第 
 
 204 行也并不是跳转至主函数开始执行C 程序__main 标号表示C / C ++ 标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(对于程序清单一来说则是跳转__user_initial_stackheap 标号进行初始化堆栈的),并初始化映像文件,最后跳转ç 程序中的主函数这就解释了为何所有的。ç 程序必须有一个主函数作为程序的起点- 因为这是由C / C ++ 标准实时库所规定的- 并且不能更改,因为C / C ++ 。标准实时库并不对外界开发源代码因此,实际上在用户可见的前提下,程序在第204 行后就跳转至.C 文件中的主函数,开始执行ç 程序了。至此可以总结一下STM32
的启动文件和启动过程。首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转¬¬ C / C ++ 标准实时库的__main 函数,完成用户堆栈等的初始化后,跳转的.c 文件中的主函数开始执行ç 程序。假设STM32 被设置为从内部FLASH 启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000 ,则栈顶地址存放于0x8000000 处,而复位中断服务入口地址存放于0x8000004 处。当STM32 遇到复位信号后,则从0x80000004 处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main 函数,最后进入冕函数,来到ç 的世界。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM JTAG 调试原理