×
单片机 > 单片机程序设计 > 详情

ImageCraft下的AVR启动代码

发布时间:2021-11-29 发布时间:
|

以前经常想自己使用编译器编写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,

ldi R29,>ram_end

out $3D,R28

out $3E,R29

subi R28,

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的初始化就完成了,这是我的理解。希望批评指正。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
8051单片机的函数发生器的设计