以前经常想自己使用编译器编写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, out $3D,R28 out $3E,R29 subi R28, 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的初始化就完成了,这是我的理解。希望批评指正。
上一篇:AVR开发 Arduino方法(二) 中断子系统
下一篇:AVR第8课:独立按键
推荐阅读最新更新时间:2024-11-07 15:30
- 热门资源推荐
- 热门放大器推荐
设计资源 培训 开发板 精华推荐
- 使用 ROHM Semiconductor 的 BD5248 的参考设计
- 车用音频 / 语音提示
- LT1021CCN8-5 精密电压基准的典型应用
- RT9261B Vfm 升压 DC/DC 转换器的典型应用
- X-NUCLEO-S2868A2,基于用于 STM32 Nucleo 的 S2-LP 无线电的低于 1 GHz 868 MHz 射频扩展板
- AD9515/PCBZ,基于 AD9515 时钟分配 IC 的评估板
- FA-128_CC253x,用于 CC253x 系列低功耗 802.15.4 LR-WPAN 无线应用的外部振荡电路
- MCP1501T-10E/RW 1.024V 负参考电压的典型应用电路
- LT1108CS8掌上电脑逻辑电源微功率DC/DC转换器典型应用电路
- 使用 ON Semiconductor 的 RC4190 的参考设计
- 了解PI最新产品,评论、下载抢楼好礼送!
- 骏龙科技携手ADI有奖直播:隔离系统设计的隐藏成本
- 关注 PI 最新 SCALE-iDriver IC产品系列 答题有好礼!
- 申请ST NUCLEO-G071RB测评,给你最佳性价比体验
- 芯(E)币兑换Nucleo扩展板:让你的Nucleo变身成pyboard
- 有奖直播:走进实验室之是德科技高端新品示波器UXR-B测评
- 赢京东卡 室内空气隐患大作战——英飞凌XENSIV™PAS CO2传感器
- ADI 全新中文资料(2019 年 11 月)
- 【在线研讨会讲义下载】TOF 技术介绍及产品应用
- 有奖直播报名|瑞萨RA MCU家族成员快速增长,助力打造安全稳定的工业控制系统