ImageCraft下的AVR启动代码

        以前经常想自己使用编译器编写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:如需转载请注明原文出处。


天空不曾留下翅膀的痕迹,但鸟儿已飞过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值