搜索
bottom↓
回复: 50

[专题][交流]AVR绝对定位面面观——代码篇

[复制链接]

出0入296汤圆

发表于 2012-2-27 13:13:29 | 显示全部楼层 |阅读模式

    AVR Mega系列单片机是广大电子爱好者所熟悉和喜爱的。在后51时代,
它以易开发——使用以C和BASIC为代表的高级语言;易使用——内部集成
了大量常用的外设模块;高性能——同等时钟下是传统C51执行效率的12
倍;低功耗——用水果电池就可以驱动等特点占据了相当的市场份额,得
到了广泛的应用。

<font color=blue>什么是AVR的绝对定位呢?

    简单说就是在C语言环境下将工程中某个函数或者数据放在AVR存储器
中自己“心仪”的位置。对代码来说,定位的最小单位是函数;对数据来
说,定位的最小对象是全局变量、静态局部变量和全局常量。举例来说,
在下面的代码片断里,全局常量g_strBuffer和全局变量g_hwDataBuffer以
及函数Example中的静态局部变量s_bFlag都是可以进行绝对定位的。

    C语言中,变量的空间分配方式有两种:静态分配和动态分配。所谓静
态分配就是指在程序编译的时候,变量所占用的空间大小以及他们在存储
器中的具体位置就已经确定了——除非我们特殊指定(也就是绝对定位),
否则编译器会自动替我们做好这些工作。前面的例子中,全局变量和静态
局部变量都是静态分配的。所谓动态分配,是指在程序运行的时候,由程
序自己通过某种机制动态管理存储器的分配方式。就局部变量n来说,在函
数Example被调用时n获得属于自己的空间,而在函数退出时这些空间会被
自动释放。可见,n所使用的存储器是重复使用的,其地址也总是动态变
化的,对这样的局部变量进行绝对定位就如同刻舟求剑一般——是不可能
做到的。

    const char g_strBuffer[] = “Hello world”;
    unsigned int g_hwDataBuffer[10] = {0};

    void Example(void)
    {
        unsigned char n = 0;
        static unsigned char s_bFlag = 0;
        …
    }

    结论:只有代码和静态分配的变量才能参与绝对定位。
    推论:C语言中只有函数、全局变量和静态局部变量才能参与绝对定位。


什么场合下需要使用绝对定位呢?
    通常在什么场合下我们会需要使用绝对定位技术呢?
    1、 比如我们希望编写一段程序能够通过与外界通讯实现自我升
        级——也就是不借助编程器而实现Flash的更新。对于这样的应用,
        AVR要求用户必须把更新Flash相关的某些代码放置到专门的Boot区
        域中。如何编写Bootloader并不在本文的讨论范围之内。
    2、 某些应用中,我们希望把一些变量放置在指定的位置,比如使用外
        扩SRAM的时候,我们希望把一些体积庞大的数组放置到扩展的SRAM
        空间中,而保留更多的内部SRAM供系统使用。
    3、 对于某些需要大量查表的应用,如果表格本身的数据是常量,在SRAM
        吃紧的情况下,为了节省空间,我们往往会希望把这些表格从SRAM
        挪到Flash中,从而留下更多的可用内存空间。

    如此等等,不一而足。总结说来我们往往会因为“需要实现某些硬件功能”,
“ 需要获取更大的连续存储空间”,“ 需要节省空间”或者“满足某些特殊
的应用需求”,而试图借助绝对定位技术实现所需的功能。

绝对定位技术的实现
    既然有这么多场合牵扯到绝对定位技术,那么我们该如何去做呢?有统一
的方法没有?很遗憾,没有。代码和变量的绝对定位并不是C/C++标准的一部分,
不仅不同的编译器使用的方法不同,同一个编译器在不同版本中处理的方法也
有可能大相径庭。笔者习惯于将绝对定位问题分成三类,分别是:代码的绝对
定位、同一存储器内变量的绝对定位和跨存储器的变量绝对定位。在后面的讨
论中,我们会按照这样的划分将问题展开,以常用的三种编译环境(ICC、IAR
和GCC)为平台,分别介绍它们具体的实现方法。

    在具体介绍之前,有一个有趣的问题不得不提:什么是地址空间?可以从
0x0000开始计数的地址都属于同一个空间,这个空间就是地址空间。SRAM的地址
可以从0x0000开始计数,因此存在SRAM地址空间;FLASH可以从0x0000开始计数,
因此存在FLASH地址空间;同样EEPROM可以从0x0000开始,也就存在EEPROM地址
空间。同一个地址,比如0x1234,在不同的地址空间中表达的意义当然也不一样:

    在SRAM里表示一个RAM单元,在FLASH里表示一个FLASH字节;在EEPROM里表示
一个ROM单元。如果觉得抽象,你大可以把一个地址空间想象成一张纸,所以SRAM
有一张纸,FLASH有一张纸,EEPROM也有一张纸。把这样三张纸叠放在一起,即
便你一笔戳下去戳出同样的窟窿,三个窟窿也是分别落在不同平面上的。这可以
用来解释了为什么下面的代码运行起来往往得到的是错误的结果:

    //假设这里的FLASH是一个宏,作用是告诉编译器其修饰的变量应该存放在Flash中
    FLASH unsigned char strHello[] = “Hello world”;               
    …
    extern void LCD_OUT(unsigned char *pchBuffer, unsigned char chLength);
    …
    LCD_PRINT((unsigned char *) strHello, sizeof(strHello));

    首先,很容易看出代码的作者是想在LCD上输出一个“Hello world”,但
实际运行的结果往往是LCD上出现了一串与“Hello world”等长的乱码。这是
因为,字符串数组strHello是放置在Flash中的,其地址属于Flash地址空间;
函数LCD_ PRINT需要的参数pchBuffer是一个指向RAM地址空间的指针,也就是
说这个指针指向的数据应该是存放在RAM中的。代码的作者也注意到,如果单纯
使用
LCD_ PRINT (strHello, sizeof(strHello));
来调用函数肯定会遇到编译器指针不匹配的错误(strHello的地址是“FLASH
unsigned char *”型的,而函数LCD_ PRINT需要的是一个“unsigned char *”
的地址),因此,代码作者试图通过强制类型转换让编译器“闭嘴”,这就好
比把FLASH地址空间和SRAM地址空间叠在一起,用strHello的地址在两张纸上戳
了一个洞——FLASH空间中,对应的位置存储的的确是“Hello world”,但谁
也没有规定在SRAM相同的位置存储的也是“Hello world”。所谓张冠李戴,
不过如此。

    介绍了这么多,今天我们就先从代码的绝对定位技术开始演练。

>> 程序代码的绝对定位
    所谓代码的绝对定位,就是将指定的函数放到Flash中指定的地方。

    与“立即绝对定位”相对,还有一种被称之为“范围绝对定位”的技术在ICC,
IAR和GCC中得到更普遍的应用。取字面上的意思,所谓“范围绝对定位”是指将
某些函数放置到某一个指定的范围内,在这个范围内,这些函数之间的位置关系
是相对的,或者说是它们之间的位置关系我们并不关心——函数的空间分配和定
位全部交由编译器来负责。你可以理解为,“范围绝对定位”就好比是定义了一
个瓶子:瓶子的容量(也就是范围的大小)决定了能容下多少函数;而把瓶子放
哪儿,就是需要我们指定(绝对定位)的了。这里必须要注意:通常情况下一个
范围不能跨越两个不同的地址空间。

    “范围绝对定位” 的使用分为两个部分:首先,在指定的地址空间中定义范
围;其次,告诉编译器哪些函数应该被放入到这个范围内。为了便于讲解,我们
将根据编译环境的不同分别进行介绍。假设,我们需要定一个名叫MyZone的范围,
定位在Flash地址0x4000上,并将函数MyFunctionA和MyFunctionB放置到该范围内。

1 GCC环境
    借助AVR Studio4,我们可以利用图形化的界面定义一个范围。假设,我们已
经使用AVR Studio4建立了一个GCC工程,此时,依次选择菜单
Project->Configuration Options打开工程设置窗口(如图1所示);在窗口左侧
的悬浮列表中选择Memory Settings。单击Add按钮,添加一个新的范围,这里也称
为段(Segment),如图2所示:



    在弹出的对话框Add New Memory Segment中,第一项用来设置新段的地址空间,
可选的内容有Flash, Sram和Eeprom;因为我们要定位的是代码,而代码只能被放
置在Flash地址空间中,因而我们选择类型Flash。第二项Name用于给新的段起名,
这里,虽然英文字符串可以是任意的,但别忘记加一个“.”。作为范例,我们使
用“.MyZone”作为自定义段的名称。最后一项Address是段落的地址。不啰嗦,迅
速填上0x4000。单击OK,我们就完成了新范围的添加。接下来,就需要把指定的函
数放置到这一区域中,语法如下:

    __attribute__ ((section(“.MyZone”))) + 函数原型
   
    比如,我们希望将函数MyFuncitonA放置到“.MyZone”中:

    __attribute__ ((section(“.MyZone”))) void MyFunctionA(void);
    或者
    __attribute__ ((section(“.MyZone”)))
    void MyFunctionA(void);

    也可以
    void MyFunctionA(void) __attribute__ ((section(“.MyZone”)));

    为了让代码看起来更容易懂一些,我们可以事先定一个宏:
    #define FLASH_MYZONE       __attribute__ ((section(“.MyZone”)));
   
    然后用这个新的宏来修饰函数,比如
    FLASH_MYZONE void MyFunctionA(void);
    或者:
    void MyFunctionA(void) FLASH_MYZONE;

    基于以上的定义,我们可以通过下面的代码完成之前的目标:

    void MyFunctionA(void) FLASH_MYZONE;   //将MyFunctionA放置到MyZone中
    void MyFunctionB(void) FLASH_MYZONE;   //将MyFunctionB放置到MyZone中
   
    从这个例子中我们容易看出,__attribute__ ((section(“.MyZone”))) 一次
只对一个函数原型有效,其放置的位置并不是非常严格。其实,每一个工程都有一
个默认的Flash段“.text”用于放置用户的代码;也就是说,如果我们没有使用
__attribute__ ((section(“.MyZone”)))来制定代码的存放位置,实际上函数就
被自动加入到“.text”段中。反过来想一想,如果我们在AVR Studio4的Memory
Settings中,定义一个同名的“.text”段并指定了地址,结果会怎样呢?有兴趣的
你可以试验一下。

2 ICC环境
    ICC环境下添加新范围要更为直接一些,打开工程编译选项,在Target选项卡中
找到“Other Options”文本框,按照如下的语法填入绝对地址:
    -b<范围名称>:<绝对地址>

    我们依然以MyZone为例,则将代码定位到0x4000地址上的写法为:
    -bMyZone:0x8000

    细心的你也许已经注意到,明明是定位到0x4000地址上,为什么这里要写作
0x8000呢?为什么看起来是一个两倍的关系呢?原因很简单,AVR的Flash实际上是
以2个字节的WORD为单位的,所有的AVR指令也都是2个字节(16位)长度的。因此,
当我们定位代码的时候,应该使用WORD(也就是2字节长度)作为基本单位,这就
能够保证每一条指令在绝对定位以后依然能够被单片机正确的识别(这种方式我们
通常称之为对齐到“字”)。ICC编译器绝对定位使用的单位是“字节”,0x4000的
“字”地址换算为“字节”也就是0x8000。
    前面我们曾提到,在代码中GCC一次只能重新定位一个函数;而ICC编译器通过
一个扩展的预编译语法结构#pragma可以批量的将一定范围内的函数都定位到目标
范围内。据体语法如下:

    #pragma text: <自定义区域名称>    // 将随后的函数都放置到自定义范围内
    ……
    #pragma text: text                // 将随后的函数都放置到默认的text范围内
    ……

    仍然以MyZone为例,对MyFunctionA和MyFunctionB的定位可以写为:
   
    #pragma text: MyZone              // 将随后的函数放置到MyZone里
    void MyFunctionA(void);
    void MyFunctionB(void);
    #pragma text: text                // 将随后的函数放置到默认的text段内,也就是恢复普通的定位

    是不是很方便?不同版本的ICC都支持这一定位方式,可以放心使用。此外,
ICC对范围的定义不光可以指定起始地址,还可以指定范围的大小,其语法为:

    -b<范围名称>:<起始地址0>.<中止地址0>:<起始地址1>.<中止地址1>:……

    比如:
    -bMyZone:0x1000.0x2000:0x3000.0x4000

    实际上将MyZone定义为一个不连续的区域,由0x1000到0x2000和0x3000到0x4000
两个区块组成。详细的使用方法可以参考ICCAVR用户手册Help->Help Topics->
PROGRAMMING THE AVR->Addressing Absolute Memory Locations 章节的描述,
这里就不再一一为您展开。


3 IAR环境
    与ICC类似,IAR环境下可以在工程选项中加入以下的语法结构实现范围的定义:

    -Z(CODE)<范围名称>=<起始地址>-<中止地址>
   
    例如:
    -Z(CODE)MyZone=8000-A000

    将MyZone定义在了0x4000至0x5000的Flash地址空间上。这里括号里面的CODE表
示代码地址空间,也就是片内Flash。同样,我们可以看出IAR的绝对定位也是以“字
节”而不是“字”为基本单位的。对于一个打开了的工程,添加范围的具体操作也
并不复杂:首先,在Workspace窗口中选中位于最顶端的工程名称,单击右键选择
Options;在弹出窗口的Category列表中选择Linker;在右边的设置窗体选择最右边
的Extra Options选项卡,选中Use command line options,并在下面的文本框中加
入: “-Z(CODE)MyZone=8000-A000”。

    IAR和GCC一样,在代码中一次只能给一个函数进行绝对定位,其语法为:

    #pragma location=”<范围名称>”
    例如:
    #pragma location=”MyZone”
    void MyFunctionA(void) {…}

    #pragma location=”MyZone”
    void MyFunctionB(void) {…}
   
    这里#pragma表达式的位置只能位于目标函数的正上方。其实IAR的范围定义非常
灵活和细致,除了通过-Z表达式以外,还可以使用-P表达式来定义范围,只不过这个
范围可以和已有的范围重叠;对于放置到该范围内的函数,系统会自动从已用范围的
牙缝中寻找一个空挡来安置它们,可谓“斤斤计较”。更为详尽的内容可以从IAR
Help目录的IAR C/C++ Compiler Reference Guide,Part1 Using Compiler的Placing
Code and Data章节获得。



为了让大家不再怀疑,特郑重标记:本篇已完结,思密达,西马斯!

出0入296汤圆

 楼主| 发表于 2012-2-27 13:14:00 | 显示全部楼层
占位

出0入0汤圆

发表于 2012-2-27 13:20:38 | 显示全部楼层
好好学习来了……

出0入0汤圆

发表于 2012-2-27 13:20:51 | 显示全部楼层
睡觉刚起来就看到傻孩子的大作。。。
好久不见了

出0入0汤圆

发表于 2012-2-27 13:33:23 | 显示全部楼层
留名

出0入0汤圆

发表于 2012-2-27 13:35:08 | 显示全部楼层
真的不错,讲的很细致

出0入0汤圆

发表于 2012-2-27 13:35:19 | 显示全部楼层
留名

出0入59汤圆

发表于 2012-2-27 13:52:40 | 显示全部楼层
傻孩子,好久不见,还在玩AVR啊,都大师级了,有时间研究一下STM8吗,与AVR有得比呢!

出0入0汤圆

发表于 2012-2-27 13:57:27 | 显示全部楼层
学习~~~

出0入0汤圆

发表于 2012-2-27 14:00:19 | 显示全部楼层
支持AVR

出0入0汤圆

发表于 2012-2-27 14:13:43 | 显示全部楼层
回复【7楼】sonna 瑞堡光电
-----------------------------------------------------------------------

哈哈,他肯定不愿意

出0入296汤圆

 楼主| 发表于 2012-2-27 14:31:24 | 显示全部楼层
to 【7楼】 sonna 瑞堡光电
to 【10楼】 hefq 何访贤
--------------------------------------
STM32我也在玩,STM8如果有项目需要也会看,很多时候无所谓用什么芯片了。这篇文章只不过是给某编辑社写的,
过了所谓的发行有效期,所以贴出来。
大家莫把我打上AVR专用的标签哦!

出0入0汤圆

发表于 2012-2-27 14:38:52 | 显示全部楼层
我也占位。

出0入0汤圆

发表于 2012-2-27 15:03:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-2-27 15:20:23 | 显示全部楼层
Mark!

出0入0汤圆

发表于 2012-2-27 15:57:58 | 显示全部楼层
占位

出0入0汤圆

发表于 2012-2-27 16:32:39 | 显示全部楼层
学习了

楼主好久不见了

出0入0汤圆

发表于 2012-2-27 16:38:42 | 显示全部楼层
经典

出0入0汤圆

发表于 2012-2-27 16:38:44 | 显示全部楼层
Long time no see

出0入0汤圆

发表于 2012-2-27 16:40:13 | 显示全部楼层
如果用汇编,怎样才能达到动态内存分配的目的……

出0入0汤圆

发表于 2012-2-27 16:45:31 | 显示全部楼层
可以把版本号,编译日期等信息放到指定的Flash位置,这样通过UltraEdit查看bin文件就能知道软件的版本。

出0入0汤圆

发表于 2012-2-27 16:50:00 | 显示全部楼层
占位来了

出0入296汤圆

 楼主| 发表于 2012-2-27 17:04:39 | 显示全部楼层
to 【20楼】 proguy
    是一个好办法。

to 【19楼】 liusoldier 大圣
    跑题了……

出0入0汤圆

发表于 2012-2-27 17:13:29 | 显示全部楼层
哈,又见傻孩子写东西了。。谢!

出0入8汤圆

发表于 2012-2-27 17:14:11 | 显示全部楼层
好好学习!

出0入0汤圆

发表于 2012-2-27 17:54:15 | 显示全部楼层

出0入0汤圆

发表于 2012-2-27 18:57:02 | 显示全部楼层
按个爪印

出0入0汤圆

发表于 2012-2-27 20:16:11 | 显示全部楼层
谢谢啦

出0入0汤圆

发表于 2012-2-27 23:09:05 | 显示全部楼层
有时间看看,谢谢

出0入0汤圆

发表于 2012-3-2 16:36:43 | 显示全部楼层
留个印,学习下

出0入0汤圆

发表于 2012-3-6 18:08:49 | 显示全部楼层
傻孩子的东西有份量。

出0入0汤圆

发表于 2012-3-6 19:04:24 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-3-6 19:08:27 | 显示全部楼层
学习!!

出0入0汤圆

发表于 2012-3-6 19:12:06 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-3-6 19:39:25 | 显示全部楼层
上个项目有用到 先mark,

出0入0汤圆

发表于 2012-3-6 20:07:42 | 显示全部楼层
非常好的资料呀,支持一下傻孩子。

出0入0汤圆

发表于 2012-3-6 22:36:37 | 显示全部楼层
Mark

出0入24汤圆

发表于 2012-3-6 23:03:17 | 显示全部楼层
Mark!

出15入9汤圆

发表于 2012-3-7 03:29:28 | 显示全部楼层
O what a mark

出0入0汤圆

发表于 2012-3-7 07:41:29 | 显示全部楼层
顶!傻孩子大作,原先我有用到过ICCAVR绝对定位,呵呵。

出0入0汤圆

发表于 2012-3-7 07:42:37 | 显示全部楼层
如果早些有这个好贴,我会少走不少弯路。

出0入0汤圆

发表于 2012-3-7 08:50:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-3-7 09:03:29 | 显示全部楼层
mark!

出0入0汤圆

发表于 2012-3-7 10:30:46 | 显示全部楼层
好文!

出75入4汤圆

发表于 2012-3-7 10:38:44 | 显示全部楼层
站位,好贴

出0入0汤圆

发表于 2012-3-7 12:37:44 | 显示全部楼层
mark~~

出0入0汤圆

发表于 2012-3-24 22:00:09 | 显示全部楼层
学习.....

出0入0汤圆

发表于 2013-9-30 00:09:17 | 显示全部楼层
精品贴,mark。谢谢楼主

出0入0汤圆

发表于 2013-12-17 13:51:30 | 显示全部楼层
有STM32的IAR代码地址设定吗?

出0入0汤圆

发表于 2013-12-17 22:11:28 来自手机 | 显示全部楼层
谢谢分享

出0入0汤圆

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

本版积分规则

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

GMT+8, 2024-5-3 04:15

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

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