完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 zzp289228448 于 2015-1-18 14:34 编辑
AVR单片机堆栈结构 本文章由"WS"写作,如果你觉得还行请点一个赞. 本文章所有程序都是在ICCAVR上测试,在AVR STUDIO仿真查看结果。 背景:为什么我要在此研究AVR的堆栈机构,有何目的。一是为了完成UCOS在AVR单片机上移植;二是,学习单片机编程到一定的层次必要对单片机的堆栈结构有一定的了解,避免一些潜在的BUG产生。对汇编要能看懂,在某些时候我们需要对我们的C程序对应生成的汇编代码进行分析,查找隐藏很深的BUG。 一、 AVR有两个堆栈,一个为硬件堆栈(指针SP),一个软件堆栈(Y指针)。 首先了解一下AVR数据存储空间结构(仅片内) AVR单片机的数据存储器是线形的,从低地址到高地址依次是CPU寄存器区(32个通用寄存器),I/O寄存器区,数据存储区。 ICC编译器又将数据存储区划分为全局变量,软件堆栈区和硬件堆栈区三个空间。以16为例,32个通用寄存器+64个IO寄存器+1024字节RAM构成整个数据存储区,如下: ICC自动连接的启动代码中,把SP指向了内存的最高处,完成一系列初始化工作以后,调用用户的主函数(main()),此时main函数的返回地址会入栈,如下: 这里只讨论了函数返回地址,我们似乎忽略了一些重要的东西,在微处理器运行中,AVR是如何处理局部变量的,以及中断的时候如何保存现场。 再来讨论一下我们所熟悉的两条指令:POP、PUSH。出栈与进栈指令,他们以SP为指针将数据压入堆栈与弹出堆栈,AVR支持这两条指令,通常用在中断程序做现场保护,32个通用寄存器+状态寄存器。我们先来理论计算一下,在ICC编译器里面,堆栈默认大小30个字节,若产生中断,有32个通用寄存器+状态寄存器需要保护,早已溢出我们的堆栈空间,为此ICC编译器并非用POP、PUSH来保护现场,硬件堆栈(SP)只用来保存函数返回地址。其他的数据都是用软件堆栈保存,用Y(R28,R29)指针指向栈顶。 软件堆栈:软件堆栈用Y指向栈顶,在启动代码中被初始化为 SP - (硬件堆栈大小+1)。 ICC编译器在中断函数中将32个通用寄存器+状态寄存器全部保存到软件堆栈去。而对于函数调用过程中对于局部变量。经过对汇编分析,得出以下结论(不一定全对):有两种方式:一是用通用寄存器做局部变量,在函数正式执行之前,将本函数所要用到的通用寄存器先保存到软件堆栈区,通过Y指针寻址,例如:“ST -Y,R20 ”,将R20保存到软件堆栈区,在函数执行完成后,从软件堆栈区恢复自己用过的通用寄存器的原来值。注意这个地方不要理解错了,是被调函数需要用哪个寄存器就先保存哪个寄存器的原有值,然后再恢复寄存器值,不破坏调用者的数据;而不是调用者用了哪些寄存器,当它调用其他函数时就保存自己用过的寄存器。这样做可以节省资源与提高运行效率;二是直接在软件堆栈区分一块区域用作你的局部变量,通过修改Y指针分配地址空间,获取变量存储空间。一下几个小程序供大家参考: C源程序: int fun1(int dat1) { int dat2; dat1 ++; dat2 = fun2(dat1); return dat2; } 对应的汇编代码: _fun1: dat2 --> R10 // 局部变量dat2分配到R10,R11 dat1 --> R20 // 局部变量dat1分配到R20,R21 00071 940E 010C CALL push_xgset300C // 本函数使用了R10,R11,R20,R21, // 现将数据入栈保存 00073 01A8 MOVW R20,R16 (0021) } (0022) int fun1(int dat1) (0023) { (0024) int dat2; (0025) dat1 ++; 00074 5F4F SUBI R20,0xFF 00075 4F5F SBCI R21,0xFF (0026) dat2 = fun2(dat1); 00076 018A MOVW R16,R20 00077 D003 RCALL _fun2 00078 0158 MOVW R10,R16 (0027) return dat2; 00079 940C 0111 JMP pop_xgset300C // 将R10,R11,R20,R21,取出 push_xgset300C: // R10,R11,R20,R21 存 0010C 935A ST -Y,R21 0010D 934A ST -Y,R20 0010E 92BA ST -Y,R11 0010F 92AA ST -Y,R10 00110 9508 RET pop_xgset300C: // R10,R11,R20,R21 取 00111 90A9 LD R10,Y+ 00112 90B9 LD R11,Y+ 00113 9149 LD R20,Y+ 00114 9159 LD R21,Y+ 00115 9508 RET 再来看另一段代码: C源程序: int fun5(int dat1) { int buf[20],i; float f = 12.3454; dat1 ++ ; for(i = 0;i<20;i++) buf++; f += 1; return dat1; } 对应的部分汇编代码: _fun5: f --> Y,+40 // 局部变量f分配到Y指针+40后的四个字节 buf --> Y,+0 // 局部数组buf分配到Y指针后的四十个字节 i --> R22 // 局部变量i分配到R22,R23 dat1 --> R20 // 局部变量dat1分配到R20,R21 00099 940E 0116 CALL push_xgsetF000//保存自己将使用的R20,R21,R22,R23 0009B 01A8 MOVW R20,R16 0009C 97AC SBIW R28,0x2C push_xgsetF000: // R20,R21,R22,R23 入软件堆栈 00116 937A ST -Y,R23 00117 936A ST -Y,R22 00118 935A ST -Y,R21 00119 934A ST -Y,R20 0011A 9508 RET 通过这两段程序分析,我们可以对ICC编译器对局部变量的处理有一定的了解。通用寄存器的读写速度比RAM读写要快,对于局部变量诺分配到寄存器对程序的执行效率将提高,但寄存器数量有限,当局部变量很多时,寄存器无法满足要求,只能分配到软件堆栈,ICC在分配局部变量是有限使用寄存器,在寄存器不能满足的情况下分配到软件堆栈区。在C语言里有个关键字“register”,告诉编译器这个变量会频繁使用,被修饰过的字符将优先分配寄存器。在这里强调一点,对于局部变量要尽可能的少定义,要避免定义数组局部变量,特别是在UCOS系统编程里面,局部变量越多你所要分配的堆栈空间就越多。 看了ICC对函数的局部变量处理,接下来看看在中断程序中,ICC是如何保护现场的。 先来看另一段代码: C源程序: #pragma interrupt_handler T2:4 void T2(void) { Delayms(100); } 对应的汇编代码: _T2: 000DD 920A ST -Y,R0 // 首先通过Y指针将R0~R31(不包括R28,R29) 000DE 921A ST -Y,R1 保存到软件堆栈区,保护现场。 000DF 922A ST -Y,R2 000E0 923A ST -Y,R3 000E1 924A ST -Y,R4 000E2 925A ST -Y,R5 000E3 926A ST -Y,R6 000E4 927A ST -Y,R7 000E5 928A ST -Y,R8 000E6 929A ST -Y,R9 000E7 930A ST -Y,R16 000E8 931A ST -Y,R17 000E9 932A ST -Y,R18 000EA 933A ST -Y,R19 000EB 938A ST -Y,R24 000EC 939A ST -Y,R25 000ED 93AA ST -Y,R26 000EE 93BA ST -Y,R27 000EF 93EA ST -Y,R30 000F0 93FA ST -Y,R31 000F1 B60F IN R0,0x3F // 保存状态寄存器 000F2 920A ST -Y,R0 (0066) } (0067) } (0068) #pragma interrupt_handler T2:4 (0069) void T2(void) (0070) { (0071) Delayms(100); FILE: 000F3 E604 LDI R16,0x64 000F4 E010 LDI R17,0 000F5 DF5B RCALL _Delayms 000F6 9009 LD R0,Y+ // 取出状态寄存器值 000F7 BE0F OUT 0x3F,R0 000F8 91F9 LD R31,Y+ // 将R0~R31全部取出,恢复现场 000F9 91E9 LD R30,Y+ 000FA 91B9 LD R27,Y+ 000FB 91A9 LD R26,Y+ 000FC 9199 LD R25,Y+ 000FD 9189 LD R24,Y+ 000FE 9139 LD R19,Y+ 000FF 9129 LD R18,Y+ 00100 9119 LD R17,Y+ 00101 9109 LD R16,Y+ 00102 9099 LD R9,Y+ 00103 9089 LD R8,Y+ 00104 9079 LD R7,Y+ 00105 9069 LD R6,Y+ 00106 9059 LD R5,Y+ 00107 9049 LD R4,Y+ 00108 9039 LD R3,Y+ 00109 9029 LD R2,Y+ 0010A 9019 LD R1,Y+ 0010B 9009 LD R0,Y+ 0010C 9518 RETI 通过上面的代码,ICC编译器并不是通过POP、PUSH指令来保护和恢复中断现场,而是通过Y指针把数据保存到软件堆栈区。Y(R28,R29)并不需要保存, 在整个程序中它都是指向软件堆栈的底端。在AVR中有着双堆栈指针,SP指向硬件堆栈,保存函数及中断返回地址。Y指向软件堆栈区,保存局部变量、寄存器和传递的参数。 掌握这两个指针的意义,对于我们移植UCOS有非常重大的意义,才能理解任务堆栈结构的设置,才能调试程序。 |
|
相关推荐 |
|
你正在撰写讨论
如果你是对讨论或其他讨论精选点评或询问,请使用“评论”功能。
182 浏览 0 评论
如何用OpenCV的相机捕捉视频进行人脸检测--基于米尔NXP i.MX93开发板
1207 浏览 0 评论
《DNK210使用指南 -CanMV版 V1.0》第四十章 YOLO2人手检测实验
493 浏览 0 评论
嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-网络编程示例之开发板测试
420 浏览 0 评论
嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-网络编程示例之网络socket程序编程
957 浏览 0 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
11743 浏览 31 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-19 08:38 , Processed in 0.645410 second(s), Total 39, Slave 31 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号