分类: C/C++
2006-09-14 11:05:24
分析ATmega8启动代码需要准备的资料和工具:
l ICCAVR6.31——AVR系列单片机的C编译器,启动代码从该处获得;
l AVRStudio4.10——AVR系列单片机的模拟器,借助它可以观看代码运行效果;
l ATmega8的数据手册。
由ICCAVR生成的启动代码如程序清单1.1所示:
1 ATmega8的启动代码
.org 0
jmp __start
__text_start: .text
__start:
0013 E5CF LDI R28,0x
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
0018 40D0 SBCI R29,0 sbci R29,>hwstk_size //设置软件堆栈指针
0019 EA
001B 2400 CLR R0 clr R0
001D E
001E E010 LDI R17,0 ldi R17,>__bss_end //装载bss区地址
init_loop:
0020
0022 9201 ST R0,Z+ st Z+,R0
0023 CFFB RJMP 0x
init_done:
0024 8300 STD Z+0,R16 std Z+0,R16 //在软件堆栈底部设置哨兵
//把存在Flash中的全局常量拷贝到SRAM的Data区。
0025 E2E6 LDI R30,0x26 ldi R30,<__idata_start
0026 E
0027 E
0028 E0B0 LDI R27,0 ldi R27,>__data_start
0029 E010 LDI R17,0 ldi R17,>__idata_end
copy_loop:
002B
002D
002E 9631 ADIW R30,1 adiw R30,1
0030 CFF9 RJMP 0x
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函数退出点
CPU上电并正常复位后一定会从复位地址处开始执行第一条指令的,复位地址由CPU设计者决定,一般来说是0x0000,ATmega8的中断复位地址,也就是中断复位向量表如图1.1所示:
1 ATmega8的中断复位地址
从图1.1中可以知道,如果ATmega8正常启动(不从Boot区启动)的话,复位地址是0x0000, 所以在该地址处放置了一条跳转到代码区(.text)的指令jmp __start。
那为什么__start标号即代码区的起始地址被安排在0x0013处呢?这是因为中断向量表占用了0x0000—0x0012的空间,代码区从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.
一般来说,在储存器的程序映像可以分为4个区:
Ø .text区用以保存程序代码,属性为只读;
Ø .data区用以保存被初始化的数据,属性为可读可写;
Ø .bss区用以保存未被初始化的数据,属性为可读可写。“bss”这个名字来源于一种名叫“Block Started by Symbol”的古老汇编程序助记符,这种助记符用于分配未初始化的数据空间。初始化和未初始化数据间的区别在于:初始化数据是在程序编译时已经声明有一个初始值的全局或静态变量;未初始化数据是没有明确声明初始值的全局或静态变量。
Ø .rodata区,顾名思义是用以保存只读数据的,属性为只读。
虽然bss区中的数据属性为未初始化,但初始化程序的通常做法是把bss区全初始化为0。该段功能代码是0x001B~0x0023。
现在已经解决了C语言中没有明确声明初始值的全局或静态变量,那已经声明有一个初始值的全局或静态变量该怎么办呢?
对于没有明确声明初始值的全局或静态变量,C编译器仅为它们开辟一个存储空间;而对于已经声明有一个初始值的全局或静态变量,C编译器会把这些变量的初始值一同编译到代码文件(hex或bin文件)中去,然后下载到储存器中。
对于ATmega8来说,ICCAVR会把全局或静态变量的初始值和代码一起一同下载到Flash存储器中,然后在启动代码中,把这些数据装载(Load)到对应的SRAM区。
对此,可以做一个小实验,定义一个全局变量i并赋予初始值0x55aa,然后定义一个全局数组,赋予初始值”Hello:)”,如下所示:
int i = 0x55aa;
char a[] = "hello:)";
通过AVRStudio可以看到程序区中的全局变量初始值,如图1.2所示:
2 程序存储器中的全局变量初始值
到此,我们明白了为什么要从idata 区拷贝初始化数据到直接寻址数据区data 区了。由于全局或静态变量的初始化数据是和代码放在一起的,所以全局或静态变量的初始化数据越多,生成的hex或bin文件就越大。
初始化好堆栈,解决了C程序中函数参数、局部变量和函数返回值的存储问题;
初始化好bss,为C程序中的未初始化的全局或静态变量开辟了存储空间;
把与程序一起下载到存储器(Flash)的全局或静态变量的初始值拷贝到相应的数据区(.data),完成了全局或静态变量的初始化。经历以上三步后,就基本构建出了C程序的运行时环境(C runtime environment)了。
接下来要做的事情就是调用main函数和为main函数创建一个返回点。
main函数退出以后会做些什么事情呢?如果main是由操作系统调用的,那么它返回后会先释放自己占用的资源,然后把CPU的控制权还给操作系统。而这里的main函数并不是由操作系统调用的,所以它不需要把CPU的控制权还给谁,也不需要归还使用的资源,所以返回后,只需要原地空转就行了。
综上分析,ATmega8启动代码的功能是:
建立运行时内存映像,为C语言创建运行时环境