Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2305007
  • 博文数量: 318
  • 博客积分: 8752
  • 博客等级: 中将
  • 技术积分: 4944
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-23 07:56
文章分类

全部博文(318)

文章存档

2019年(1)

2017年(2)

2016年(12)

2015年(2)

2014年(1)

2013年(17)

2012年(22)

2011年(9)

2010年(37)

2009年(33)

2008年(44)

2007年(43)

2006年(95)

分类: C/C++

2006-09-14 11:05:24

分析ATmega8启动代码需要准备的资料和工具:

l         ICCAVR6.31——AVR系列单片机的C编译器,启动代码从该处获得;

l         AVRStudio4.10——AVR系列单片机的模拟器,借助它可以观看代码运行效果;

l         ATmega8的数据手册。

1.        ICCAVR生成的启动代码

ICCAVR生成的启动代码如程序清单1.1所示:

1 ATmega8的启动代码

.org 0

                                            jmp __start

__text_start:                               .text

__start:

    0013 E5CF      LDI R28,0x5F             ldi R28,       //获取RAM地址低八位

    0014 E0D4      LDI R29,4                ldi R29,>ram_end       //获取RAM地址高八位

    0015 BFCD      OUT 0x3D,R28             out $3D,R28            //设置SPL

    0016 BFDE      OUT 0x3E,R29             out $3E,R29            //设置SPH

    0017 51C0      SUBI R28,0x10             subi R28,

    0018 40D0      SBCI R29,0                sbci R29,>hwstk_size   //设置软件堆栈指针

    0019 EA0A      LDI R16,0xAA             ldi R16,0xAA           //在硬件堆栈底部

    001A 8308      STD Y+0,R16              std Y+0,R16            //设置哨兵sentinel

    001B 2400      CLR R0                   clr R0

    001C E6E0      LDI R30,0x60             ldi R30,<__bss_start

    001D E0F0      LDI R31,0                ldi R31,>__bss_start

001E E010      LDI R17,0                ldi R17,>__bss_end     //装载bss区地址

                                  init_loop:

    001F 36E0      CPI R30,0x60             cpi R30,<__bss_end

    0020 07F1      CPC R31,R17              cpc R31,R17

    0021 F011      BEQ 0x0024               breq init_done

    0022 9201      ST  R0,Z+                st Z+,R0

0023 CFFB      RJMP 0x001F               rjmp init_loop       //bss区清零

                                 init_done:

0024 8300      STD Z+0,R16              std Z+0,R16          //在软件堆栈底部设置哨兵

                                 //把存在Flash中的全局常量拷贝到SRAMData区。

    0025 E2E6      LDI R30,0x26             ldi R30,<__idata_start

    0026 E0F0      LDI R31,0                ldi R31,>__idata_start

    0027 E6A0      LDI R26,0x60             ldi R26,<__data_start

    0028 E0B0      LDI R27,0                ldi R27,>__data_start

0029 E010      LDI R17,0                ldi R17,>__idata_end

                                 copy_loop:

    002A 32E6      CPI R30,0x26             cpi R30,<__idata_end

    002B 07F1      CPC R31,R17              cpc R31,R17

    002C F021      BEQ 0x0031               breq copy_done

    002D 95C8      LPM                      lpm

    002E 9631      ADIW R30,1                adiw R30,1

    002F 920D      ST  R0,X+                st X+,R0

0030 CFF9      RJMP 0x002A               rjmp copy_loop

                               copy_done:

    0031 D017      RCALL    _main           //转到main函数

_exit:

0032 CFFF      RJMP _exit               //main函数返回后在此死循环

程序清单1.1的左边部分是反汇编后的代码,右边是源汇编代码和注释。可见,AVR的启动代码主要做了:

1.           初始化硬件和软件堆栈指针

2.           bss 区全部初始化为零

3.           idata 区拷贝初始化数据到直接寻址数据区data

4.           调用main 函数

5.           定义main函数退出点

1.1.1          初始化硬件和软件堆栈指针

CPU上电并正常复位后一定会从复位地址处开始执行第一条指令的,复位地址由CPU设计者决定,一般来说是0x0000ATmega8的中断复位地址,也就是中断复位向量表如1.1所示:

VecT

 

1 ATmega8的中断复位地址

    从1.1中可以知道,如果ATmega8正常启动(不从Boot区启动)的话,复位地址是0x0000, 所以在该地址处放置了一条跳转到代码区(.text)的指令jmp __start

    那为什么__start标号即代码区的起始地址被安排在0x0013处呢?这是因为中断向量表占用了0x00000x0012的空间,代码区从0x0013处接续

    进入代码区中,要做的第一件事是设置堆栈指针SP。那为什么要设置SP呢?这是因为ATmega8的堆栈是向下生长的,即从高地址向低地址生长,当处理器复位后,SP的值被设置为0x60,这是SRAM的起始地址,当有PUSH操作,或有函数调用操作时,SP值会小于0x60,指向的区域已经不是SRAM了,这会导致数据或函数地址无法保存。从初始化代码中可以看出,上电复位后,SP被设置为指向SRAM的最高地址处。

      C语言的函数调用需要堆栈的支持,初始化C运行时环境中很重要一步是初始化C函数运行的堆栈。C函数栈里面保存数据有参数、局部变量和函数返回地址,操作这个堆栈的指针是什么由C编译器确定,通常C编译器都使用处理器中有指令支持且明确声明为堆栈指针的那个寄存器,但也有例外,ICCAVR就是一例。它使用SP来保存函数返回地址,而Y指针来保存参数、局部变量。保存函数返回地址的堆栈称之为硬件堆栈(Hardware Stack),保存参数、局部变量的堆栈称之为软件堆栈(Software Stack),所以,对ICCAVR来说,初始化C函数栈的任务包含了初始化硬件堆栈和软件堆栈,该功能对应的代码是0x0013~0x0018.

1.1.1          bss 区全部初始化为零

一般来说,在储存器的程序映像可以分为4个区:

Ø         .text区用以保存程序代码,属性为只读;

Ø         .data区用以保存被初始化的数据,属性为可读可写;

Ø         .bss区用以保存未被初始化的数据,属性为可读可写。“bss”这个名字来源于一种名叫“Block Started by Symbol”的古老汇编程序助记符,这种助记符用于分配未初始化的数据空间。初始化和未初始化数据间的区别在于:初始化数据是在程序编译时已经声明有一个初始值的全局或静态变量;未初始化数据是没有明确声明初始值的全局或静态变量。

Ø         .rodata区,顾名思义是用以保存只读数据的,属性为只读。

虽然bss区中的数据属性为未初始化,但初始化程序的通常做法是把bss区全初始化为0。该段功能代码是0x001B~0x0023

现在已经解决了C语言中没有明确声明初始值的全局或静态变量,那已经声明有一个初始值的全局或静态变量该怎么办呢?

1.1.2          idata 区拷贝初始化数据到直接寻址数据区data

    对于没有明确声明初始值的全局或静态变量,C编译器仅为它们开辟一个存储空间;而对于已经声明有一个初始值的全局或静态变量,C编译器会把这些变量的初始值一同编译到代码文件(hexbin文件)中去,然后下载到储存器中。

    对于ATmega8来说,ICCAVR会把全局或静态变量的初始值和代码一起一同下载到Flash存储器中,然后在启动代码中,把这些数据装载(Load)到对应的SRAM区。

    对此,可以做一个小实验,定义一个全局变量i并赋予初始值0x55aa,然后定义一个全局数组,赋予初始值”Hello:)”,如下所示:

int i = 0x55aa;

char a[] = "hello:)";

    通过AVRStudio可以看到程序区中的全局变量初始值,如1.2所示:

Mem.jpg

2 程序存储器中的全局变量初始值

    到此,我们明白了为什么要从idata 区拷贝初始化数据到直接寻址数据区data 区了。由于全局或静态变量的初始化数据是和代码放在一起的,所以全局或静态变量的初始化数据越多,生成的hexbin文件就越大。

    初始化好堆栈,解决了C程序中函数参数、局部变量和函数返回值的存储问题;

    初始化好bss,为C程序中的未初始化的全局或静态变量开辟了存储空间;

把与程序一起下载到存储器(Flash)的全局或静态变量的初始值拷贝到相应的数据区(.data),完成了全局或静态变量的初始化。经历以上三步后,就基本构建出了C程序的运行时环境(C runtime environment)了。

    接下来要做的事情就是调用main函数和为main函数创建一个返回点。

1.1.3          定义main函数退出点

      main函数退出以后会做些什么事情呢?如果main是由操作系统调用的,那么它返回后会先释放自己占用的资源,然后把CPU的控制权还给操作系统。而这里的main函数并不是由操作系统调用的,所以它不需要把CPU的控制权还给谁,也不需要归还使用的资源,所以返回后,只需要原地空转就行了。

2.        ATmega8启动代码分析小结

综上分析,ATmega8启动代码的功能是:

建立运行时内存映像,为C语言创建运行时环境

阅读(4642) | 评论(1) | 转发(0) |
0

上一篇:软\硬堆栈

下一篇:嵌入式uclinux简介

给主人留下些什么吧!~~