通过LST和HEX文件分析CortexM3的复位启动过程

[复制链接]
查看3 | 回复0 | 2021-12-8 16:47:31 | 显示全部楼层 |阅读模式
      从汇编编程的角度上看,STM32从复位到主程序的过程要比MCS稍显复杂,一个比较主要的原因是CortexM3用很多伪指令来描述启动过程,会让我这样的菜鸟感到云遮雾罩。
      说到底,CortexM3也好,MCS51也罢,启动过程都是大同小异的,无非是PC指针初始化,执行完用户的复位程序之后取出主程序地址,跳到主程序上。
      对于MCS51来说,这个过程非常好理解:
                    ORG        0000H
                    LJMP        Reset
                    ORG        0003H
                    ;配置各个中断服务程序的入口
                      ………………
                    ORG        0100H
Reset:
                     ………………
                   ;初始化及主程序段
     51单片机复位,PC=0000H指向首地址,取指令和操作数,转0100H执行初始化及主程序,先定义堆栈指针SP,然后再干别的。
      这个过程说明MCS51程序存储器是这样分区的:
复位入口        0000H-0002H。放置越过中断向量程序段,直达主程序段的跳转指令。
中断向量        0003H-00BBH。放置中断服务程序向量。有些增强型51单片机的中断向量已经用到了00BBH。
复位程序        从最后一个中断向量操作数之后的地址开始。通常是先配置堆栈指针SP。
主程序段        主程序代码区。
      但对于CortexM3来说,它的复位过程有点儿特殊:如果BOOT0配置成从FLASH区启动,那首先是PC=0x08000000,先把这个地址开始的4个字节数值赋值给主栈指针MSP,然后再把0x08000004开始的4个字节赋值给PC,转到复位程序。
      实际上,复位时PC=0x00000000,但BOOT0=0时,0x00000000会被映射到0x08000000上,所以说PC是从0x08000000开始也是可以的。
      CortexM3程序存储器分区是如下:
MSP赋值        0x08000000—0x08000003。当PC指向这个数据块时,4字节数自动赋值给MSP。
复位入口        0x08000004—0x08000007。4个字节,放置复位程序的入口地址。
中断向量        在0x08000007之后,每个向量的入口地址可以定义。每个中断向量地址可以浮动。
复位程序        从最后一个中断向量操作数之后的地址开始。
主程序段        主程序代码区。
      归结起来,两者跳转的方式有所不同,MCS51是通过入口放置指令,而CortexM3则是通过入口处放置地址来实现跳转。
      为了深入探究一下CortexM3的复位机制,我编一个简单的程序,力图通过HEX和LST文件理清脉络。
环境如下:
IDE:Keil 4.12
硬件:STM32F103VCT6,BOOT0=0
程序:汇编语言

LST程序如下:
(1)        初始化程序Initialization.LST
00000000 20005000    MSP_Top  EQU  0x20005000
00000000                                          THUMB
00000000                                          AREA     Reset, Data, ReadOnly
00000000                                          EXPORT   __Vectors
00000000                                          EXPORT   __Vectors_End
00000000                                          EXPORT   __Vectors_Size
00000000                      __Vectors
00000000 20005000                         DCD     MSP_Top
00000004 00000000                         DCD     Reset_Handler
00000008 00 00 00
               00 00 00
               00 00                                  SPACE            0x8
00000010                     __Vectors_End
00000010 00000010                         __Vectors_Size      EQU   __Vectors_End - __Vectors
00000010                                         AREA     |.text|, Code, Readonly
00000000                     Reset_Handler        PROC
00000000                                         EXPORT   Reset_Handler           [WEAK]
00000000                                         IMPORT   Main
00000000 4800                                LDR      R0,=Main
00000002 4700                                BX       R0
00000004                                         ENDP
00000004                                         ALIGN
00000004                                         END
      主栈配置到SRAM区0x20005000,暂时未定义主栈大小。暂时未定义其它中断向量。
(2)        主程序Main.LST
                                     INCLUDE  Stm32F103REG.h
00000000                     Main     PROC
00000000                                        EXPORT    Main
00000000 4801                               LDR       R0,=RCC_CR
00000002 6801                               LDR       R1,[R0]
00000004 E7FE                               B         Main
00000006                                        ENDP
00000006                                        END
      Stm32F103REG.h是自行编制的寄存器定义程序。
上述程序编译之后打开工程HEX文件和两个程序段的LST文件并整理一下,删除一些不重要的数据,结果如下:
(3)工程HEX文件
:020000040800F2
:100000000050002011000008000000000000000067
:10001000004800471900000801480168FCE700009B
:04002000001002408A
:0400000508000019D6
:00000001FF
看起来有些乱七八糟,整理一下。

数据长度    地址偏移   数据类型                    数据块                                                                 校验和
1byte          2bytes      1byte                          n bytes                                                                1byte
02              00 00         04                08 00                                                                                 F2
10                  00 00            00                00 50 00 20 11 00 00 08 00 00 00 00 00 00 00 00             67
10                  00 10            00                00 48 00 47 19 00 00 08 01 48 01 68 FC E7 00 00             9B
04                  00 20            00                00 10 02 40                                                                          8A
04                  00 00            05                08 00 00 19                                                                          D6
00                  00 00            01                                                                                                          FF

     数据长度好理解,是说本行中数据块有几个字节。
     地址偏移是指当前行第一个数据相对于基址的位置。
     查了一下资料,数据类型可分为5种类型:
00=这一行的数据块里全是数据;
01=HEX文件结束符,所在行数据块为空;
02=数据块里的数据是扩展段地址;
03=数据块里的数据是段地址;
04=数据块里的数据是线性地址高位;
05=数据块里的数据是线性地址。
      看起来,数据类型可分为三大类:数据类(00)、地址类(02~05)和编译标识类(01)

      第一行数据类型为04,说明后面的0800和地址偏移0000构成了一个基址0x08000000,这是STM32F103 FLASH区的首址,后序行的数据存储地址都可以根据这个基址加偏移量计算出来。
      第二行数据类型为00,偏移量为0000,也就是说,数据块的第一个00被存放在地址0x08000000中,这就是单片机复位时从FLASH区启动的首址。
      按照前面的说明,首址之后的4个字节应当是准备赋给MSP的值,也就是程序指定的0x20005000,可现在却是00 50 00 20,看了半天恍然大悟,突然想起CortexM3数据格式是所谓的小端模式Little-endian,所以这4个字节的32位数据得按字节倒过来读成20 00 50 00。但当数据类型为地址类时,数据块数据就可以正着读了,哈哈。
      00 50 00 20之后又是一个地址,倒过来是08 00 00 11,这就是复位程序入口地址。其后的数据是8个字节的00,这就是SPACE 0x8的功劳了。 按照流程,复位地址赋值给PC之后会从地址0x08000011开始运行,0x08000011单元的数据是48,对照一下Initialization.LST,对应的是它的第一条可执行语句'LDR  R0,=Main',只不过这条指令的代码48 00也存储成了小端模式。
      以此类推,逐一阅读后面的数据,过程就出来了,Main的段地址是第三行的那个大红圈里的数据0x08000019,从它以后就是Main的指令代码了。但这里有一个问题,Main.LST表明‘B  Main’指令的代码是E7 FE,可HEX里却是E7 FC,这又是怎么回事儿呢?不知道哪位前辈能告诉我。
     还有一个问题:从0x08000020开始的数据明显是RCC_CR的地址,它怎么会出现在这儿呢?

      用汇编写STM32程序是苦中有乐,有人说我这是自找麻烦,但我不这么认为。与使用固件库编程相比,无论是面向寄存器编程还是直接用汇编,编程难度都要大很多,但是,程序透明度也比前者大得多,绝对不会出现神龙见首不见尾的感觉,尤其是对于我这种不太会C语言的硬件工程师就更是如此。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则