liuxiaofei126
认证:VIP会员
所在专题目录 查看专题
多功能控制小车
STM32定时器的来龙去脉
底层程序如何在STM32上编译运行
从IIC实测波形入手搞懂IIC通信
实例讲解(一)移位寄存器
单片机之USB 硬件和数据的四种传输方式
作者动态 更多
stm32知识点总结
2021-11-24 23:12
工程师单片机LED项目
2021-11-11 07:46
电容型负载对跟随器的影响
2021-10-28 23:33
图腾柱和互补推挽两个“小冤家”
2021-10-22 23:15
学习单片机的捷径
2021-10-17 23:07

底层程序如何在STM32上编译运行

C程序是如何运行的 

STM32F0xx 系列是ARM Cortex-M0架构,地址空间32位,也就是4G Bytes的访问范围。数据和代码使用同一编址,下图是地址空间的布局:

实际上单片机用到的资源很少,地址空间大部分都没有内容。我使用的STM32F072C8T6带有64kB的Flash ROM, 16kB的SRAM,起始分别是 0x08000000 和 0x20000000. (由于有硬件映射功能,在0x00000000也就是最低地址,还可以访问ROM或者RAM的内容). 单片机片上外设的寄存器,则分布在更高的地址空间。读写这些寄存器,在CPU看来和读写内存(RAM)操作是一样的。

所以,C语言访问设备寄存器,和访问内存中的一个变量一样。只要知道寄存器的地址,通过一个指针访问就可以实现读写。上一贴子我的程序中引用了 RCC, GPIOA, TIM6 这三个(结构)指针,它们的值(也就是地址)以及类型(代表访问的内容)定义在 stm32f0xx.h 这个头文件中。因为设备寄存器太多了哇,如果每一个都定义一个指针就太烦琐了,所以把按功能划分定义成组,每组用一个C语言的结构类型表示,写起来也更清晰。而寄存器里面的位描述也可以定义成一些宏,在读程序的时候就知道是什么意思了。如果有兴趣,可以把 stm32f0xx.h 文件和STM32F0的手册对照着阅读。

好,假设已经熟悉寄存器操作了,知道怎么配置寄存器实现想要的功能,那么就可以写C程序让STM32工作了。现在需要一个工具来将C程序翻译成机器代码——编译器,或者是叫做工具链(Tool chain)。Keil MDK-ARM 或者 IAR-EWARM 开发环境都带有各自的编译器,不过我更偏向于用开源的GCC-ARM. 在launchpad.net上可以下载到编译好的arm-gcc工具链zip包,将它解压缩,加到PATH里面就可以直接用了,很方便(很精简吧)。

OK,现在来编译上面那个mini.c文件,命令行: arm-none-eabi-gcc -c -Os -mcpu=cortex-m0 -mthumb mini.c gcc的参数 -c 是表示仅编译,-Os 是优化代码大小,-mcpu=cortex-m0 -mthumb 是指定指令集的,因为ARM有不同的版本。对了,include的头文件还没弄到呢。要编译通过需要把 stm32f0xx.h 这个文件找来。我的建议是下载ST提供的 "STM32F0x2 USB FS Device Library" 程序库,把里面需要的头文件等等扒出来。在 stm32f0xx.h 中还包含了另外几个头文件,一并弄出来放到工程目录下。

如果编译成功,将得到 mini.o 目标文件。可以用 arm-none-eabi-objdump -S mini.o 反汇编看看翻译成什么代码了。

E:\arm\test072\mini>arm-none-eabi-objdump -S mini.o

 

mini.o:     file format elf32-littlearm

 Disassembly of section .text.startup:

 00000000 <main>:

   0:   4b16            ldr     r3, [pc, #88]   ; (5c <main+0x5c>)

   2:   2280            movs    r2, #128        ; 0x80

   4:   6959            ldr     r1, [r3, #20]

   6:   0292            lsls    r2, r2, #10

   8:   430a            orrs    r2, r1

   a:   b510            push    {r4, lr}

   c:   2180            movs    r1, #128        ; 0x80

   e:   615a            str     r2, [r3, #20]

  10:   2290            movs    r2, #144        ; 0x90

  12:   0249            lsls    r1, r1, #9

  14:   05d2            lsls    r2, r2, #23

  16:   6011            str     r1, [r2, #0]

  18:   69da            ldr     r2, [r3, #28]

1a:   2110            movs    r1, #16

  1c:   430a            orrs    r2, r1

  1e:   61da            str     r2, [r3, #28]

  20:   4b0f            ldr     r3, [pc, #60]   ; (60 <main+0x60>)

  22:   4a10            ldr     r2, [pc, #64]   ; (64 <main+0x64>)

  24:   851a            strh    r2, [r3, #40]   ; 0x28

  26:   2290            movs    r2, #144        ; 0x90

  28:   32ff            adds    r2, #255        ; 0xff

  2a:   62da            str     r2, [r3, #44]   ; 0x2c

  2c:   2205            movs    r2, #5

  2e:   801a            strh    r2, [r3, #0]

  30:   4a0d            ldr     r2, [pc, #52]   ; (68 <main+0x68>)

  32:   7812            ldrb    r2, [r2, #0]

  34:   8a1c            ldrh    r4, [r3, #16]

  36:   2101            movs    r1, #1

  38:   4809            ldr     r0, [pc, #36]   ; (60 <main+0x60>)

  3a:   420c            tst     r4, r1

  3c:   d0fa            beq.n   34 <main+0x34>

  3e:   8a04            ldrh    r4, [r0, #16]

  40:   438c            bics    r4, r1

  42:   8204            strh    r4, [r0, #16]

  44:   2090            movs    r0, #144        ; 0x90

  46:   2480            movs    r4, #128        ; 0x80

  48:   05c0            lsls    r0, r0, #23

  4a:   408c            lsls    r4, r1

  4c:   2a00            cmp     r2, #0

  4e:   d102            bne.n   56 <main+0x56>

  50:   6184            str     r4, [r0, #24]

  52:   1c0a            adds    r2, r1, #0

  54:   e7ee            b.n     34 <main+0x34>

  56:   8504            strh    r4, [r0, #40]   ; 0x28

  58:   2200            movs    r2, #0

  5a:   e7eb            b.n     34 <main+0x34>

  5c:   40021000        .word   0x40021000

  60:   40001000        .word   0x40001000

  64:   0000270f        .word   0x0000270f

  68:   00000000        .word   0x00000000

如上,其实里面就一个main函数。但是 main 的入口地址还没有确定,而且它还使用了一个static型的内存变量,地址也还没有确定。可以用 arm-none-eabi-nm mini.o 来查看模块里面的全局符号表:

E:\arm\test072\mini>arm-none-eabi-nm mini.o

00000000 b a.4686

00000000 T main

那么,怎么让程序放到ROM中合适的地址,并运行呢?如果熟悉C语言编程就知道还有一步——链接,才能确定符号的地址。但是,到目前为止我们还没有告诉GCC地址的布局,也就是RAM从哪里开始,代码放在哪里。因为ARM的器件很多,这并不是统一的,所以需要提供一些信息给链接程序。具体地,需要一个Linker Script, 可以从软件包中找到 STM32F072C8_FLASH.ld (或者用近似的来修改得到)

/*

*****************************************************************************

**  File        : stm32_flash.ld

*****************************************************************************

*/

 

/* Entry Point */

ENTRY(Reset_Handler)

 

/* Highest address of the user mode stack */

_estack = 0x20003FFF;    /* end of RAM */

 

/* Generate a link error if heap and stack don't fit into RAM */

_Min_Heap_Size = 0x200;      /* required amount of heap  */

_Min_Stack_Size = 0x400; /* required amount of stack */

 

/* Specify the memory areas */

MEMORY

{

FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 64K

RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 16K

}

 

/* Define output sections */

SECTIONS

{

  /* The startup code goes first into FLASH */

  .isr_vector :

  {

    . = ALIGN(4);

    KEEP(*(.isr_vector)) /* Startup code */

    . = ALIGN(4);

  } >FLASH

 

  /* The program code and other data goes into FLASH */

  .text :

  {

    . = ALIGN(4);

}

这里面 -L 参数是添加标准库文件的搜索路径,虽然暂时并没有用到C标准库里面的东西,但是Linker Script里面引用了标准库文件。-o 指定输出的目标文件。这么就快要得到最终的机器码了,不过好象还缺少了什么…… arm-none-eabi-ld: warning: cannot find entry symbol Reset_Handler; defaulting to  08000000

linker给了一个警告:找不到入口地址 Reset_Handler 的值,设成了默认 0x08000000. 下面再用objdump -S反汇编看一下

E:\arm\test072\mini>arm-none-eabi-objdump -S mini.elf

 

mini.elf:     file format elf32-littlearm

 

 

Disassembly of section .text:

 

08000000 <main>:

 8000000:       4b16            ldr     r3, [pc, #88]   ; (800005c <main+0x5c>)

 8000002:       2280            movs    r2, #128        ; 0x80

 8000004:       6959            ldr     r1, [r3, #20]

 8000006:       0292            lsls    r2, r2, #10

 8000008:       430a            orrs    r2, r1

 800000a:       b510            push    {r4, lr}

 800000c:       2180            movs    r1, #128        ; 0x80

 800000e:       615a            str     r2, [r3, #20]

现在 main() 被放到ROM最开始去了,这好象是对的?如果了解ARM Cortex-M0下就知道这样错了,因为最开始应该是中断向量表。我们还没有编写Linker Script中的 .isr_vectors 段的内容。而且,一上来初始化堆栈指针等工作都没有做就直接运行 main() 了也不合适吧?还缺少了初始化代码。

原来是这样,中断向量表在这里进行了描述,还有设置堆栈,初始化全局变量的代码,然后跳转到 main 执行。好了,这样就该差不多了。这个汇编程序是GNU AS的语法,可以用 arm-none-eabi-gcc 来直接汇编 arm-none-eabi-gcc -c startup_stm32f072.s

我这个是最简化的例子,使用最简化的软件工具,不过已经包含了基本的C语言框架。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 9
收藏 8
关注 206
成为作者 赚取收益
全部留言
0/200
  • 小董 2021-04-06 19:50
    讲的真好!
    回复
  • dy-blNlwnWV 2021-02-25 22:04
    期待继续
    回复
  • lihui710884923 2021-02-25 21:54
    很好
    回复
  • keyhei66 2020-12-16 21:59
    思路清晰,受益匪浅
    回复
  • heiha88 2020-12-03 22:43
    有点太深奥了
    回复