搜索
bottom↓
回复: 18

关于与AVR内存空间和代码长度效率的讨论.

[复制链接]

出0入0汤圆

发表于 2008-6-16 17:42:48 | 显示全部楼层 |阅读模式
以前一直用51的,后来由于需要用上AVR M8,最近一直为一件事困惑:编写的程序感觉比用51实现同样功能,所用程序空间要大。后来通过GCC编译出来的汇编看,其SRAM都用LDS等4字节指令,这样变量多时就要比2字节指令占用更多的程序空间。但AVR手册中说0x60-0xff的SRAM可用LD等指令,这个应该是2字节的啊。为什么GCC不会选择2字节的指令,这样可以省出很多空间来。

    查了一些资料,可用register .... ams("r4") 这样的方法将一些变量整到通用寄存器,曾试过,一个UINT变量,程序中用两次,用这个方法限定后省了30BYTES的空间,相当可观。 但通用寄存器比较有限,几十个或近百个数组变量,就无法这样做了。但是0x60-0xff,有160个,对于一般程序将变量放到这边,再用如LD类的2字节指令,可省很多空间的。GCC默认都是使用LDS等4字节指令,有什么办法让GCC在这个范围内使用2字节指令?

    同时问下,M88内部0x60-0xff已经是扩展IO寄存器了,但有很多是未用的,是否可以作为SRAM来用,GCC在编译M88时变量自动从0x100开始了。想把M88的变量定义到0x60-0xff的未用单元,再用2字节指令,这样可以省很多空间。望知情者不吝赐教!在此先谢过。
==================================================================================================


以上是一个网友的问题和疑惑,有一定的代表性,涉及到AVR本身的架构和内存空间等概念.因此我给出一些相关的解释如下:

说来话长了,让我慢慢道来.(个人观点,仅供参考)

1.首先要明确一个概念.51的指令是CSIC(复杂指令),AVR是RISC(精简指令).其有个区别是,CSIC的指令是不定长的,有1/2/3/4个字节的指令,而RISC是定长的,每个指令的长度相同.对于AVR讲,其大部分的指令是1个字(2字节),还有些指令是双字(4个字节).他们各自的优点和缺点这里不讨论,但要明白,所谓的"精简指令"不是指生成的代码短了,而是将一些去掉(精简)了一些复杂功能的指令,使指令系统比较简单.这样方便了内部的取指令操作,指令译码,使得单周期执行一条指令成为可能,另外提高了取指令的可靠性,同时速度也提高了.

2.那么如何看待生成代码的长短呢?这个不能拿采用某一条指令做对比的方法.因为AVR的一条指令最少是2字节,而51有单字节的指令.如果是实现一个简单的操作,51可能就是1个字节,而AVR就需要2个字节了.因此最好的比较是对比一组能完成特定算法或功能的指令段,而其指标也不是只看整个代码的长度,而是一个综合的评价.如在相同的系统时钟情况下,完成该算法的时间,以及代码长度等.下面是ATMAL公司早年在推广AVR时的给出的一个比较例子,尽管不一定全面,但可以作为参考.

***对几种常见单片机的一个简单性能对比实验
    用高级语言C编写一段选取最大值的代码,从几种市场上常见单片机的运行情况对它们的性能做一个简单的比较。

/*一个小的C函数
/* Return the maximum value of a table of 16 integers */
int max(int *array)
{
  char a;
  int maximum=-32768;
  for (a=0;a<16;a++)
     if (array[a]>maximum)
        maximum=array[a];
  return (maximum);
}

AVR 汇编输出:      Code Size: 46 Bytes, Execution time: 335 cycles
C51 汇编输出:      Code Size: 112 Bytes, Execution time: 9384 cycles
HC11 汇编输出:     Code Size: 57 Bytes, Execution time: 5244 cycles
PIC16C74 汇编输出: Code Size: 87 Bytes, Execution time: 2492 cycles

运行条件:       (系统时钟频率)
AT90S8515              8MHz
80C51                  24MHz
68HC11A8               12MHz
PIC16C74               20MHz

比较结果:

                Code Size(Bytes)     Function ExecutionTime(μS)     Current Consumption(mA)     Executions /S/Mw
AT90S8515(AVR)      46(1)                     42(1)                    11(1)                         434(1)
80C51               112(2.4)                  391(9)                   16(1.5)                       32(0.07)
68HC11              57(1.2)                   437(10)                  27(2.5)                       17(0.04)
PIC16C74            87(1.9)                   125(3)                   13.5(1.2)                     119(0.27)

比较分析:
8MHz AVR等于224MHz 80C51。
68HC11:代码效率高,但处理能力只有AVR的1/10,功耗高2.5倍。
PIC速度也比较快,但是在相同功耗下,AVR性能比其高3.5倍。

3.其实,对于一些简单的小型系统,用51实现生成代码是比AVR少,因为简单的小系统使用的指令多是最常用的,计算也不多,对于51讲,通常是1/2字节的指令,而AVR则肯定是2字节,还有4字节指令,如绝对转移指令等.因此,如果对指令代码长短非常敏感的话,设计小的比较简单的系统可能使用汇编最好,但前提条件是必须对AVR的汇编熟悉,否则或许还不如采用C.

4.现在看看AVR的内存结构.早期的AVR内存空间由三部分构成:前32个寄存器组,64个I/O寄存器,后面是RAM.对于32个寄存器和64个I/O寄存器,都有专用的大量的操作指令,均为1个字的长度.而AVR将他们整合在一个RAM空间中,也使得可以使用对RAM操作的指令对前96个寄存器操作,但这时的指令长度是2个字.这样的处理是方便C编译器,使得硬件结构更加贴近C语言的操作.后来,由于AVR内部的功能和接口越来越多和复杂,64个I/O寄存器不够用了,所以出现了某2个I/O寄存器对应一个地址的情况和对I/O空间进行扩展.扩展I/O寄存器空间就是把I/O寄存器映射到RAM的前面.可是问题来了,由于指令系统已经不能改动了,所以对于扩展的I/O空间的操作只能采用对RAM操作的指令(2个字).
   The ATmega48/88/168 is a complex microcontroller with more peripheral units than can be supported within the 64 locations reserved in the Opcode for the IN and OUT instructions. For the Extended I/O space from 0x60 - 0xFF in SRAM, only the ST/STS/STD and LD/LDS/LDD instructions can be used.

5.再后来,AVR发现由于扩展的I/O空间对于不同的芯片,其大小不同,因此RAM开始的地址也不同,比较麻烦,所以发展到现在,AVR索性就把扩展I/O空间定义到0X00FFH,即前32个为寄存器组,后64个为I/O空间,再后面的160全部是I/O扩展空间.RAM统一从0100H开始.这样做的另外一个优点是,I/O寄存器的地址比较统一了,方便了不同AVR芯片的代码移植.从最新推出的芯片可以看到这样的变化.

6.实际上,并不是所有的160个I/O空间都被使用了,有些功能比较简单的芯片,其扩展空间是用不掉的,这些不使用的I/O空间就作为备用,实际上内部即没有寄存器,也没有RAM,是不能使用的.因此你从M48的I/O寄存器分配表中可以发现后面大批的地址是保留的.所谓保留,就是在本芯片中不使用.

7.最后如何优化代码空间呢?当然如果你的汇编能力强的话,采用汇编.但这实际是又走回老路了,是没有办法的办法了.因此可行的办法有:a/采用好的编译器,如IAR.b/将在系统中频繁操作的变量(或小的数组)定义成全局变量,分配在32个工作寄存器中.c/采用合适的优化选项,如采用代码优先而不是速度优先的优化方案.d/程序设计本身的优化,如尽量使用字节变量等短的数据类型等,这就是体现个人的水平了.

有许多人认为,会51就会AVR.我个人是不同意这种观点的.我曾经做这样的比喻,51相当与普通的桑车,而AVR是F1赛车.会开普桑,或许也能驾驶F1,但要把F1开到赛车场上,真正发挥它性能还是不行的.因为许多会51的工程师,其掌握的基础、理念、方法都是落后的,停止不前的,如果不认真继续的努力,是不能真正用好AVR的,更不要说32位的系统了。

还是拿开车说事。很多人习惯(或许也只有这样的水平)开自动波的车,但赛车都是手动波的。

出0入22汤圆

发表于 2008-6-16 17:59:59 | 显示全部楼层
顶,还没考虑到那么深,说明我水平太菜

出0入0汤圆

发表于 2008-6-16 18:24:30 | 显示全部楼层
地板,再看!

出0入0汤圆

发表于 2008-6-16 18:30:17 | 显示全部楼层
怎么板凳没人坐?

板凳,坐着看.

出0入0汤圆

发表于 2008-6-16 20:01:55 | 显示全部楼层
machao老师,我觉得很大部分朋友写的程序的效率问题都不是出再这里,而更多是受到个人思路上的限制.
     我是个初学者,虽然我51,avr,arm都在用,但我更多是把arm当avr用,把avr当51用.我想像我这样的人也许还不在少数,对于我们更多时候不是因为c语言汇编后的代码多少影响效率,而更多是个人编程思路制约着程序的效率.我有一个习惯,就是不断回头看自己写过的程序,如果有可能的话也不断重写自己的程序.每一次的重写都是我程序的效率得到了很大的提高.因为每次我重写的代码的时候我都有更成熟,更优的思路.
     半个月前我看了<<时间触发嵌入式系统设计模式>>这本书,尽管51我很少用,但是书中的思想却很大程度的改变了我编程的思维方式.书中的调度器,其实并不是好,至少我觉得实用性没有大家渴望的那么高,因为这个调度器使用时受到很多方面的限制,并不想我们所渴望的那种"操作系统",用了后就我爱怎么写任务就怎么写,效率操作系统都可以全部帮我解决.我再书上学到了很多为了适应这个调度器修改自己程序的方法,但是这远远不够我把这个调度器放入项目中使用.再经过半个多月的思考和参考网上众多朋友对这个调度器的讨论(特别感谢给我很大的启发傻孩子),我将状态机和面向对象的方法融入了这个调度器上,形成了新的编程风格,效率提高了10多倍以上.
     我用到很深奥的技术嘛?其实没有,我只是消除了我程序中所有的软延时(我发现这个是影响我程序效率的主要因素),单单这个地方,就已经使我写的程序有了值的飞跃了.而且我学会了如何把大任务分割成小的任务,从而模拟出了多任务并行执行的效果,以前很多无法实现的,或者很难实现的东西现在都轻松的实现了.
     说了那么多,其实我最想说的是:更多时候制约我们编程效率的并不是语言或者编译器,而更多的是我们编程的思维方式,简洁高效的用语言表现出自己的意思是提高效率的关键

出0入0汤圆

发表于 2008-6-16 21:03:31 | 显示全部楼层
+00000067:   01FC        MOVW    R30,R24          Copy register pair
+00000068:   01DB        MOVW    R26,R22          Copy register pair
17:               temp = *p;
+00000069:   8190        LDD     R25,Z+0          Load indirect with displacement
18:               *p = *u;
+0000006A:   918C        LD      R24,X            Load indirect
+0000006B:   8380        STD     Z+0,R24          Store indirect with displacement
19:               *u = temp;
+0000006C:   939C        ST      X,R25            Store indirect
+0000006D:   9508        RET  

这是gcc编译的一个变量交换的函数。

而且,楼主似乎搞错了。LD是间接加载指令,是加载X指针指向的单元的值到寄存器中,而非直接加载(那才是LDS指令)。
如果考虑到X指针通过LDI加载需要2字节*2条,再加上LD指令自身,显然是大于LDS这一条指令的。

LD - Load Indirect from data space to Register using Index X

LD r26, X+

LD r27, X+

LD r26, -X

LD r27, -X

出0入0汤圆

 楼主| 发表于 2008-6-17 15:22:30 | 显示全部楼层
4楼的朋友,我非常赞成你的观点.实际情况也的确如此.

硬件出身的工程师,软件设计的能力比较差.学习51的时候,也没深入学习和掌握一些好的系统软件编写的思想方法.而且目前很多的书中,在系统软件设计方法这方面还是老的一套.使用51也许可以了.但现在的芯片内部的资源越来越多,内部的接口也越来越多,而且与外部芯片的连接更多的使用串行方式,因此如何设计一个好的系统软件就显的更加重要了.

在我新编写的《AVR单片机嵌入式系统原理与应用实践》一书中,介绍的许多编程思路和方法和以往的书有比较大的区别.比如强调尽量不要使用软件延时(除非是几个us的延时),学会使用状态机的思想设计系统,采用中断+缓冲+接口的结构实现串口通信等.

现在我们面临的一个问题是:8位的芯片资源和功能越来越强大,32位也在向低端挺进,如AVR32的UC3,ARM架构的M3.在这样的硬件环境中采用原来的系统软件编写方法已经不适合不方便了.而移植一个OS,又显的太大,占用了过多的资源.因此目前是又人在设计和考虑一些折中的办法,提出和推出一些方案和办法,<<时间触发嵌入式系统设计模式>>就是其中的一种方式.另外比如"Protothreads"等.这些东西实际不是严格意义上的操作系统,只能算一个"调度器"或"微型调度器".

作为"调度器"其基本的功能是实现任务(或线程)的切换和管理,但如何与传统的中断服务相互的配合还是一个问题(UCOS中将中断也看成一个任务),例如<<时间触发嵌入式系统设计模式>>只使用一个T/C中断,作为系统调度的time tip使用,而其它的中断就不能处理了(会破坏TIME TIP的调度).最近这关于方面的讨论相对多了起来,LZ谈到的"将状态机和面向对象的方法融入了这个调度器上,形成了新的编程风格,效率提高了10多倍以上".如可能的话,能否提供大家参考学习?

出0入0汤圆

发表于 2008-6-18 10:09:21 | 显示全部楼层
我还是个学生,自学习单片机一年了,而且没有系统的理论支持,如果我说的有错误请machao老师支持,谢谢!
<<时间触发嵌入式系统设计模式>>书调度器的主要优点:
1、纯c语言构成,可移植性非常高
2、代码长度大概300行,高可读性,能迅速掌握并使用到实际项目中.
3、调度器的代码效率和ram消耗比小,mega8已经能非常流畅运行了,是一个在单片机上比较合适的"嵌入式系统".

主要缺点:
1、限制中断使用,如果使用的话可能会和时标中断冲突而使任务执行时间出现"抖动"
2、限制任务执行时间,所有同时执行的任务要在一个时标间隔内完成,否则可能出现任务丢失.
3、由于任务统一由调度器调度,那么函数间没有调用关系,传统的传参,和return等都无法使用,这对函数见的数据交换也构成一定的影响

使用调度器主要可以软实现多任务并行处理,同时由于使用统一的时标所以还可以消除系统里面的所有软延时.这样一个比较高效的系统了.但是这样还有问题,除了调度器身的缺陷外,还有一个问题,这个是基于时间片轮询的调度器,对于很多基于事件触发的事件怎么处理?这就是我引入状态机的主要目的

状态机的作用:

1:函数分割,以实现消除延时和缩短函数执行时间     把函数以执行长度或者以需要延时为标准分割成若干份并标号,然后用一个switch语句来实现分段调用,这样任务缩短了同时需要延时的时候就暂时退出任务把cpu让给其他函数(这个是内部状态机,供函数内部使用)
2:充当任务间的通信信道
函数需要修改其他函数的调用情况或者执行内容怎么办呢?就修改其他函数的状态机就好了,或者也可以添加删除其他任务,这样就完成事件触发的调用了.同时也可以把状态机当成一个全局变量来传递一些数据给其他任务(这个是外部状态机,供函数见使用)

面向对象:
引入状态机和调度器后,任何函数都将不仅仅普通函数,因为他有了执行周期,触发条件,结束条件等等属性,所以这里的函数已经可以按照面向对象的方式来进行编写了,因为这方面我并没有过多的研究,所以我无法准确的阐述,请后面的高手接力吧.

我个人认为只要能做到多任务并发处理和无延时,那么基本是可以使效率提高到常规写法的10倍以上.我统计过我写过的一些函数,延时消耗可以高达96%的,而且这些程序不再少数.

上面是我的想法,但是状态机的引入只解决了调度器的后两个缺陷,中断的限制却没有解决,这部分我还再思考,所以只能说一下我不成熟的看法
1、任务抖动很严重嘛?如果中断执行很短的话,任务抖动仅仅是再一个时标内的,很多对这个要求不严格的场合还是可以使用中断的
2、需要中断处理的内容真的实时性要求那么高嘛?比如串口中断,其实只要再下次数据来的时候我读出了本次数据就可以了,像这样有"弹性"的
中断我认为可以全部转换成任务再调度器中以一定的时间间隔来执行
3、强实时性中断,可以这样处理:
1)使用书中提供的混合调度器,这个调度器其实不能说包含了抢占式,只能是一个带有优先级的合作式调度器
2)开启中断,并设置的比调度器使用的定时器中断更高,这样会造成任务抖动,是否可行自己判断

我研究这个调度器半个多月,水平大大提高,但是似乎还点什么,现在函数的调度有了,好像还缺乏信道部分,也许很快我将再高级的操作系统上找到答案,祈祷着这个时间不会太长.

ps:不要期望仅仅使用这个调度器和任何操作系统来提高效率,操作系统是辅助的,你必须写出高效的程序才可能从根本上提高效率

出0入0汤圆

发表于 2008-6-18 10:16:29 | 显示全部楼层
http://www.ouravr.com/bbs/bbs_content.jsp?bbs_sn=1143320&bbs_page_no=1&search_mode=3&search_text=mojinpan&bbs_id=1000
在这个帖子上有我据的例子,可能比较幼稚,但希望能对大家有一定的启发

出0入0汤圆

发表于 2008-6-18 10:52:08 | 显示全部楼层
现在大家嵌入式软件开发的整体水平都越来越高了~~很欣慰~~~

出75入4汤圆

发表于 2008-7-1 18:57:40 | 显示全部楼层
ZHANWEI.MARK

出0入0汤圆

发表于 2008-7-3 10:46:30 | 显示全部楼层
xuexi

出10入120汤圆

发表于 2008-7-3 11:11:16 | 显示全部楼层
其实代码效率一直我没感觉是什么问题,现在CPU不管从速度还是容量都远远不是以前能及的,速度慢可以换更快的CPU,容量不够可以换容量更大的型号,并且价格并不是和性能仅仅相随的,所以过多的考虑这些细节意义不是很大。

另外代码的多少和效率的高低主要是依赖于编译器,还有太小的代码无法测试编译效率的。

出0入0汤圆

发表于 2008-7-3 13:30:20 | 显示全部楼层
下面是ATMAL公司早年在推广AVR时的给出的一个比较例子,尽管不一定全面,但可以作为参考.

---------------------------------------------------------------------------
并没有什么参考价值,个人认为是ATMEL的广告。

做产品一直是够用就好,没有深究什么,(我想大部分人和我一样,没有问题时都不会去看编译后的汇编程序)。但总体写下来,总是AVR更占空间。只是AVR的性能在那放着,也就一直用了。

出0入0汤圆

发表于 2008-7-4 15:21:44 | 显示全部楼层
长见识了

出0入0汤圆

发表于 2008-7-20 21:04:38 | 显示全部楼层
我比较赞同【7楼】 mojinpan 的看法,设计上的效率应该比你编译器的效率更关键,而且更是大家的弱项

出0入0汤圆

发表于 2009-4-22 10:27:56 | 显示全部楼层
mojinpan,爱死你了,你的想法跟我的编程思想是多么地接近啊.

出0入0汤圆

发表于 2009-4-22 18:14:50 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-4-24 20:13:01 | 显示全部楼层
要顶啊!学习了!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 07:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表