挑战用一百个字节写一个闪烁灯程序!

浏览:265来源:本站时间:2023-05-12

我们使用Segger公司的Embedded Studio开发环境进行测试:在一个Cortex-M微控制器上,看看需要使用多少Flash存储器才能够完成一个LED灯的闪烁?

目标:

l   使用少于100个字节的程序完成一个闪烁应用

l   使用人眼容易看到的切换频率(即1-5Hz范围)

l   主程序用C/C++语言编写

l   使用方便得到的硬件

l   不使用或禁用工具链的运行时系统初始化

本文将大致介绍我们要使用的每一个字节和每一条指令。这是一个了解系统启动时到底发生了什么,即在到达main()函数之前底层发生什么的好途径。

简而言之:使用Embedded Studio开发环境可以在使用不到100字节的程序内完成这个工作。

硬件

每个J-Trace仿真器交付中包含该开发板,然而,在这里,我仅仅使用常规的J-Link功能下载和调试程序。用户也可以选择任何带LED的硬件测试。


生成项目

非常简单,打开Embedded Studio开发环境,从菜单中选择File -> New Project,选择第一个选项,创建可执行文件。

根据提示,选择使用默认值,单击next几次后,我最终得到了一个小项目,如下面的Project Explorer窗口中所示。

选择Build->Build Mini或按F7构建我们的程序。

Debug -> GoF5启动调试器。

我们现在没有连接硬件,所以Embedded Studio要求我们使用内置模拟器。

点击Yes或点击Enter启动模拟器。

调试器停在main()函数处,这是一个标准的 “Hello world”应用程序。

现在,为了实现最小的应用程序,我们将其简化为一个基本上是空的循环。

1

2

3

4

5

6

int main(void) {

int i;

do {

i++;

} while (1);

构建完成后,Output窗口会清楚地显示内存使用情况。

嗨,只占用了158字节的Flash。这已经非常不错了,但是在添加实际LED闪烁功能之前,我需要了解内存的占用,以及如何使我的程序最小化。

为了做到这一点,我可以查看Memory Usage Window、链接器映射文件、生成的ELF文件,或者简单地查看Project Explorer

Project Explorer窗口可以知道,这个可执行文件由3个源程序文件构成,以及它们使用了多少Code + RO空间。请注意,这些是编译器生成对像的数值。对于最终的可执行文件,链接器可以消除未使用的功能,或者在必要时添加一些结合层代码(从Flash跳到RAM或从Thumb指令跳到ARM指令)和填充(如:保证4字节对齐)。

另一个使用Flash存储器的地方,可能是从库中链接进来的代码,例如:C运行时库。然而,我们的小项目并没有使用库函数,因此我们不必考虑库代码的空间占用。

而且,Project Explorer展示了每个源文件的内存使用情况(212824字节)和项目可执行文件总的内存使用情况:158字节。 这和我们在Output窗口中看到的数值相同。

理解项目结构

这三个文件的用途?我们的应用程序只是一个简单的main()函数。为什么我还需要另外两个文件呢?

l main.c – 应用程序。

l Cortex_M_Startup.s – CPU相关代码,包含中断向量表。

l SEGGER_THUMB_Startup.s – 应用编程人员不需要修改的代码。

让我们更详细地了解它们,以揭开大家都想知道的谜团:启动代码是如何工作的?

有了这些知识,让我们看看如何缩小我们的应用程序。

main.c

main.c包含我们的应用,一个最简单的main()函数。

我们的编译器足够智能。它可以看出这个程序什么都不做,并将其优化为只使用一条指令或两个字节代码的空循环。

我怎么知道的?

我们可以看看main.o,这是编译器产生的输出。在Project Explorer中,右键单击main.c->Show Disassembly,或者展开它,双击Output files中的main.o。它揭示了主程序只有一个分支。

这是我们的主应用程序。我们已经没有办法再简化它了。

Cortex_M_Startup.s

Cortex_M_Startup.s包含了应用程序在Cortex-M硬件上执行所需要的CPU相关代码。它包含中断向量表和上电或复位时执行的函数:Reset_Handler

此文件使用了大部分Flash空间。让我们仔细看看它产生了什么。

Cortex_M_Startup.o显示其包含中断向量表 .vectors段及默认的异常处理程序实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

section .vectors

<_vectors>

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

00000000 .word 0x00000000

 

section .init.NMI_Handler

<NMI_Handler>

E7FE b 0x00000000

 

section .init.MemManage_Handler

<MemManage_Handler>

E7FE b 0x00000000

 

section .init.BusFault_Handler

<BusFault_Handler>

E7FE b 0x00000000

 

section .init.UsageFault_Handler

<UsageFault_Handler>

E7FE b 0x00000000

 

section .init.SVC_Handler

<SVC_Handler>

E7FE b 0x00000000

 

section .init.DebugMon_Handler

<DebugMon_Handler>

E7FE b 0x00000000

 

section .init.PendSV_Handler

<PendSV_Handler>

E7FE b 0x00000000

 

section .init.SysTick_Handler

<SysTick_Handler>

E7FE b 0x00000000

 

section .init.Reset_Handler

<reset_handler>

<Reset_Handler>

F7FFFFFE bl 0x00000000

F7FFBFFE b.w 0x00000004

 

section .init.HardFault_Handler

<HardFault_Handler>

4908 ldr r1,

680A ldr r2, [r1]

2A00 cmp r2, #0

<hfLoop>

D4FE bmi 0x00000006

F01E0F04 tst lr, #4

BF0C ite eq

F3EF8008 mrseq r0, msp

F3EF8009 mrsne r0, psp

F0424200 orr r2, r2, #0x80000000

600A str r2, [r1]

6981 ldr r1, [r0, #24]

3102 adds r1, #2

6181 str r1, [r0, #24]

4770 bx lr

E000ED2C .word 0xE000ED2C

这就是罪魁祸首。

ARM内核定义了向量表中的前16个表项,然后是设备外部中断的表项。该文件提供了一个有16个表项(或64个字节)的向量表。这些条目仅用于该表。

在应用程序中,我们没有处理任何故障或中断,实际上我们只需要Reset_Handler,这是复位立即执行的代码。我们还需要向量表中的第一个表项,它在复位时完成堆栈指针(SP)的初始化。

因此,我们删除所有不必要的表项,将此表删减为两个表项,同时将消除默认的异常处理程序。

我们重新生成应用程序。不错!现在应用减少为42个字节。

让我们看看输出的elf文件的内容。

0x0000 0000开始的8个字节:向量表,包含初始化SP和指向Reset_Handler的指针。

0x0000 001E 开始的8个字节: Reset_Handler,两条4字节指令。链接器插入的一条nop指令,代替SystemInit的调用(在应用程序中不存在),以及一个跳转到_start的指令。

0x0000 0008开始的20字节: SEGGER_THUMB_Startup.s的通用运行时初始化,它执行链接器生成的对来自SEGGER_init_table的初始化函数的调用,然后,调用main,如果main返回,则停在exit循环中。

0x0000 0028开始4字节:链接器生成了SEGGER_init_table

其中包含需要在main之前调用的初始化函数。它可能包含段初始化(复制初始化的数据)、段填充(用于0初始化的静态变量或堆栈的预填充)、堆初始化或全局

京ICP备:京ICP备05011254号-1 版权归北京麦克泰软件技术有限公司所有
北京麦克泰软件技术有限公司