以前经常想自己使用编译器编写MCU的C代码,编译器帮我们做了什么。编译器是如何分配变量和代码的。所以就闲着没事去看编译器的安装路径下有什么东东。工作中使用的是ICCAVR编译器和Atmel的atmega64.所以我倒腾的就是这款编译器和MCU~~~。
说实话ICCAVR编译器确实非常简捷方便,但是功能强大(当然了,我没用过其它的编译器o(╯□╰)o)。对于它的基本使用再次不再赘述。在编译器环境中点击帮助菜单会弹出Show Library Source Code passwd,然后点击会弹出一个小提示框:password is ICCAVR.来到ICCAVR的安装目录中会看到有一个压缩包libsrc.zip,它在libsrc.avr文件夹内。呵呵想必你已经知道这个压缩包的解压密码了。里边有常用的C库函数源代码和常用函数的汇编代码。
在libsrc.avr文件夹下有个init.s文件,这个文件是mega系列mcu初始化的通用文件。里边是几十行汇编代码。在MCU上电时首先执行的代码就是这些代码,而并不是你编写的代码~~~代码如下:
; init.s
;
; to be included by the crt*.s files
;
; initialize stacks
;
; set the hardware stack to ram_end, and the software stack some
; bytes below that
ldi R28,<ram_end
ldi R29,>ram_end
out $3D,R28
out $3E,R29
subi R28,<hwstk_size
sbci R29,>hwstk_size
ldi R16,0xAA
; sentenial
std Y+0,R16
; put sentenial at bottom of HW stack
clr R0
ldi R30,<__bss_start
ldi R31,>__bss_start
ldi R17,>__bss_end
; this loop zeros out all the data in the bss area
;
init_loop:
cpi R30,<__bss_end
cpc R31,R17
breq init_done
st Z+,R0
rjmp init_loop
init_done:
std Z+0,R16
; put sentenial at bottom of SW stack
; copy data from idata to data
; idata contains the initialized values of the global variables
ldi R30,<__idata_start
ldi R31,>__idata_start
ldi R26,<__data_start
ldi R27,>__data_start
ldi R17,>__idata_end
; set RAMPZ always. If this is a main app, then RAMPZ needs to reset to
; zero if invoked through the bootloader
ldi R16,USE_ELPM
out 0x3B,R16
copy_loop:
cpi R30,<__idata_end
cpc R31,R17
breq copy_done
.if USE_ELPM
elpm ; load (RAMPZ:Z)
.else
lpm ; load (Z) byte into R0
.endif
adiw R30,1
st X+,R0
rjmp copy_loop
copy_done:
不熟悉汇编的请自己去补充,这段代码中也使用了一些ICC自己的编译器伪指令:<、>分别是对$FF进行取余和取整运算。而ram_end、hwstk_size等常量是我们在新建工程的时候选择芯片类型的时候决定的,或者可以在project的option选项中进行更改。通常下默认的hwstk_size硬件堆栈的大小为30,ram_end的大小取决于你使用的芯片AVR64则该值是10ff.这与芯片内部的存储器组织相关,它标记了MCU的sram的终端地址。.text伪指令标定了以下生成的是位于代码区,_start::标号是编译器内部开始标号,而_main才是我们的程序入口。Note:ICC编译器中的::表示外部标号,:表示内部标号。
首先使用立即数加载ldi将RAM的高端地址存入Y指针,同时将Y指针赋值给SP堆栈指针out $3D,R28中的0x3D就是SPL的地址。这样通过前四条指令就设置好了堆栈指针。然后采用相同的办法设置好堆栈尺寸。由于Y指针指向了堆栈的高端地址,然后使用subi指令减掉你在编译器环境下设置的堆栈尺寸,将Y指针指向硬件堆栈的栈底,同时硬件堆栈的栈底紧邻软件堆栈的栈顶。为了防止堆栈溢出,ICC编译器专门在该处存放了0xAA作为标记(ldi R16,0xAA std Y+0,R16)。其实你去看头文件中的宏函数检查堆栈是否溢出,它就是判断该处存放的0XAA是否给覆盖掉。
设置好堆栈之后,再进行变量的内存分配。对于变量内存的分配,编译器是这样操作的:先定义先分配,同时不会对内存进行速度优化(偶字节对其什么的都不会,因为Sram非常稀缺)。变量又分为有初值的变量和无初值的变量(变量的定义和声明),ICC将这两类变量分别存储到bss区和data区。bss区存放没有初值的变量(只有声明,没有定义的全局变量等),data区存放有初值的全局性变量(全局变量和static修饰的有初值的变量).对于bss区的处理自然非常简单
clr R0
ldi R30,<__bss_start
ldi R31,>__bss_start
ldi R17,>__bss_end
init_loop:
cpi R30,<__bss_end
cpc R31,R17
breq init_done
st Z+,R0
rjmp init_loop
利用处理器特性指令,设置好Z指针,使用指针自增存储配合跳转,将你编写的C工程中bss变量进行全部默认清零。此时执行std z+0,R16依然将0xAA标记存放到bss区顶部,在bss区和硬件堆栈区之间是软件堆栈区。是这样的:编译器在帮你规划存储器的时候,你的变量空间是先从低地址开始的,首先规划data区,然后划分bss区,剩余的就是软件堆栈区,最后是硬件堆栈区。所以当你的全局性变量太多时,硬件堆栈是一定的,那么软件堆栈势必会被挤压过小,软件堆栈用来函数调用时的入栈出栈,中断现场保护等操作。所以该情况下特别容易引起堆栈溢出~~~!!!
ldi R30,<__idata_start
ldi R31,>__idata_start
ldi R26,<__data_start
ldi R27,>__data_start
ldi R17,>__idata_end
此时又出现了idata区和data区的分别。idata区指定了你的有初值的全局变量在Flash中的存放位置,不然MCU如何记住你定义的变量初值呢?
copy_loop:
cpi R30,<__idata_end
cpc R31,R17
breq copy_done
.if USE_ELPM
elpm ; load (RAMPZ:Z)
.else
lpm ; load (Z) byte into R0
.endif
adiw R30,1
st X+,R0
rjmp copy_loop
copy_done:
这段代码就是将初值全部copy到RAM中的data区对应位置,根据芯片类型来确定是否需要elpm。
整个init.s代码就是这样,前边注释中也提到了;to be included by the crt*.s files。这段通用代码会被引用到crtxxboot.s代码中。通用的boot代码如下所示:
; make sure to assemble w/ -n flag, e.g.
; iasavr -n crt...
;
; bootloader startup file, same as crtavr.s except that vectors are routed
; to the bootloader section and use jmp for the vector
;
.include "area.s"
.text
__start:: ; entry point
; route vector
ldi R16,1
out 0x35,R16
; MCUCR = 1, unlock IVSEL
ldi R16,2
out 0x35,R16
; MCUCR = 2, set ivsel 以上的代码用来设置中断向量表位置,是位于Flash的起始位置,还是bls区(这个以后讨论)
USE_ELPM = 0;
.include "init.s"
; call user main routine
call _main 寻找你的main函数,现在编译器已经帮你把所有的都准备好了,将MCU交给你。
_exit::
rjmp _exit
; interrupt vectors. The first entry is the reset vector
;
.area vector(abs) 标定中断向量绝对地址,位于00000H处,进行一次跳转,寻找__start
.org 0
jmp __start
额,这样对于ICCAVR对MCU的初始化就完成了,这是我的理解。希望批评指正。PS:如需转载请注明原文出处。
天空不曾留下翅膀的痕迹,但鸟儿已飞过。
原文地址:http://blog.csdn.net/fengyehudie/article/details/44022729