网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 61IC中国电子在线 >> 技术文库 >> 嵌入式 >> 文章正文
  AVR GCC FAQ            【字体:
AVR GCC FAQ
作者:61IC录入    文章来源:本站原创    点击数:    更新时间:2006-4-7    

折腾了几个小时,刚翻完一半……拿出来让大家帮忙抓抓 bug……

没什么原则问题的话(笑),剩下的翻完了会在后面跟贴……

另外,总觉得这东西里面废话不少……可能是老外的好习惯吧……不过作为技术文档的话,很多东西确实没必要逐字逐句较清楚……当然了,如果有人认为“水忆秋你这这想法不对,翻译就应该尊重原文”……那个……也无所谓,如果建议合情合理的话,我会更正的,大家探讨嘛……

---------------我是分隔线------------------

1、我的程序不承认中断程序中的变量更新?

原因:编译器认为所有未经特殊声明的变量其值不会自动更改(赋值或等效语句除外)
解决方法:在可能被中断程序修改的全局变量说明前,加上 volatile 保留字,例如:

volatile uint8_t flag;

2、在使用类似 sin 一类的函数时,我被告知“未定义的标识符”……

原因:math.h 头文件中定义的函数需要 libm.a 库文件支持
解决方法:在连接时增加 -lm 标志(例如在 makefile 的 LDFLAGS = 后面追加 -lm)

3、如何让一个变量总是使用寄存器存储?

解决方法:在变量声明时增加 register,例如:

register unsigned char counter asm("r3");

(注意即使如此,当 MCU 寄存器数量不足的时候,这类变量仍然会按照编译器划分的优先级被逐一踢出寄存器的,而且大量使用这个保留字会严重影响编译器效率,强烈建议不用,另外,无论如何,AVRGCC 会在 AVR 的寄存器空间里保留 16 个字节提供给内嵌式汇编代码专用,所以即使使用这个保留字也不会有多高效率的)

4、如何在你程序的最开头对 MCUCR 或 WDTCR 进行修改?

当前版本中,对于程序的前期初始化显得比以前的版本要有弹性了许多(插话,以往这是 GCC 最大的弱点——不*直接*允许任何代码在 GCC 自己的初始化代码前面运行,不过即使现在的样子,仍然略显麻烦——不过想想别的编译器也不过是在 IDE 环境中提供这部分初始化,所以也就没什么感觉了……)

基本上,写一小段如下的汇编代码就能解决问题:

;; begin xram.S

#include <avr/io.h>

        .section .init1,"ax",@progbits

        ldi r16,_BV(SRE) | _BV(SRW)
        out _SFR_IO_ADDR(MCUCR),r16

;; end xram.S

汇编它,然后把得到的 xram.o 连接到你的程序中,这样这段程序将被附着在初始化代码中,并且正好在 reset 完成后开始执行,详情请参阅连接器脚本中,.initN 部分的注释(插话,别问我要……)

这样做的好处是,你可以在初始化代码的最开头(记住这是真正的最开头,没有堆栈、没有寄存器归零)插入你想要的任何内容而不会导致程序空间的浪费

除非遇到一些非常特殊的情况,基本上这种操作并不需要对连接器脚本进行任何修改(插话,其实原本的 GCC 就可以做到这部分,但是那需要手工调整连接器脚本,而这些脚本至少在我看来,是比 GCC 本身的源代码更加难于理解的东西……><)。最好丢弃 __stack 的初始值(内部 SRAM 的结尾处,并且因为勘误表的缘故,在类似 ATmega161 等设备上是必须的——插话,这里提到勘误表,什么勘误表?谁知道吱一声?),然后添加 -Wl,-Tdata,0x801100 以保证数据段在堆栈上方。

有关更多的信息,包括如何在 C 代码中使用这些,参阅 mem_sections.html#mem_sections

5、_BV() 到底做了什么?

(插话,这段废话多多,直接说重点)
虽然实际上,作为宏,_BV() 就是一次普通的左移运算,但是,使用这个操作仍然至少能够让你的代码容易被人读懂一些(个人觉得很重要……非常重要),而且由于编译器的作用,它并不会导致任何性能上的降低。

例如,设置定时器2为完全的 IO 时钟、比较匹配时 OC2 输出、并且比较匹配时清空定时器,以及把 OC2 设定为输出端口,那么,你既可以用:

CS2x = 0b001;
COM2x = 0b01;
CTC2 = 1;
DDRD = 0x80;

也可以用:

TCCR2 = _BV(COM20)|_BV(CTC2)|_BV(CS20);
DDRD = _BV(PD7);

孰优孰劣,一目了然了吧?

6、AVR 可不可以使用 C++?

基本上 C++ 是被支持的(当然,只要并且只有你的编译器被配置并且编译为支持 C++……),若源程序以 .cc .cpp 或者 .C (注意是大写的)后缀结尾,则 C++ 编译器被自动调用,当然,你也可以直接以 avr-c++ 的名字调用 C++ 编译器。

不过,libstdc++ 现在并不支持,这个标准支持库需要完整的 C++ 工具,因此如果想要编译一个 C++ 程序,就必须遵循许多额外的约束条件,其中包括:

A、所有的 C++ 标准函数、类、模版都是无法使用的;
B、保留字 new 和 delete 目前无法使用,对它们的调用会导致连接器提示“未定义的外部引用”,不过以后可能会加入对它们的支持(插话:作为内存分配工具,new 和 delete 在大内存工作时的效率极高,不过 AVR 的 RAM 空间相对之下……实在有些施展不开,估计以后即使支持,也会要求 mega64 以上版本或外扩至少 2KB RAM 吧)
C、一些被支持的头文件在 C++ 中并不安全,建议把它们替换成类似如下的形式:
extern "C" { . . . }
当然,这个问题肯定会被修复
D、不支持异常(程控中断)。不过由于异常是 C++ 明确支持的内容,所以必须利用 -fno-exceptions 编译选项显式的关闭异常,否则连接器将会报 __gxx_personality_sj0 错误。(插话,AVR 缺乏 INT 指令,并且也不提供类似除零错误中断之类的只与软件直接相关的中断——这两点着实令人难于理解……实现这两类指令并不需要太多的技术,但是却可以使得编程工作变得容易和有系统的多……没办法……期待以后吧)

对象建立及摧毁仍然有效,包括那些全局的内容

使用 C++ 在空间及时间敏感的环境下编程时(例如 MCU 编程),要小心避免有可能导致有害副作用的 C++ 约定,例如函数调用中的结构暗示复制等。此类操作可能导致相当多的 CPU 时间和存储器空间浪费。使用 -S 编译选项不定期的检查程序生成的汇编代码可能会有所帮助。

7、我可不可以预置我所有的变量?

(按惯例,废话不说只说重点)
所有全局变量和静态变量都会被预置为 0 或 NULL 或 0.0,但是其它变量如果不在程序中进行说明,则它在 FLASH 和 EEPROM 中都不会占据任何位置——当然,一旦它开始被使用,则 SRAM 一定会被占用。

因此,全局变量和静态变量实际上要占用两份空间——一份是 SRAM 中的,一份是 FLASH 中的初始化代码。另外,显式初始化的局部变量也同样如此。对于有着大内存空间的系统(例如 PC)来说,这并不是什么引人注意的开销,但对于按字节计费的 MCU 来说,不必要的空间开销就是越少越好,所以有些对于 PC 编程者来说的好习惯,在这里就只能让位给内存不足了……

8、为何一些 16位计时器寄存器有时会被废弃?

原因:一些与计时器有关的 16位寄存器需要使用一个临时寄存器 TEMP 以便保证在一次原子操作中对两个 8 位进行传输,例如对 TCNTn 和 ICRn 的读写操作以及对 OCRnM 的写操作。详情请参考各种设备的数据手册中涉及 TEMP 寄存器的内容。

当在主程序中访问一个使用 TEMP 的寄存器,并且同时发生了中断的时候,就要小心保证中断上下文并没有对 TEMP 造成影响。

解决方法:通常来说,为了保护一个中断例程不受其他中断例程的影响,可以采用 SIGNAL() 宏对该中断函数进行说明,此时,在这个中断例程的执行过程中,全局中断允许位被清零,从而保证了任何 16位寄存器的访问是原子操作。

而在主程序中,为了达到同样的效果,可以把相关的语句置于 cli() 和 sti() 两个宏中间。例如:

uint16_t
read_timer1(void)
{
        uint8_t sreg;
        uint16_t val;

        sreg = SREG;
        cli();
        val = TCNT1;
        SREG = sreg;

        return val;
}

9、我如何在内嵌汇编中使用 #define 定义的常量?

症状:
当使用 asm volatile("sbi 0x18,0x07;"); 时,工作正常,但当使用

asm volatile("sbi PORTB,0x07;"); 时,编译器报 Error: constant value required 错误。

原因:
PORTB 是一个在头文件中定义的,经由预编译器处理的符号,然而预编译器并不会对内嵌汇编的部分进行处理

解决方法:
使用如下语句即可:

asm volatile("sbi %0, 0x07" : "I" (PORTB):);

10、为什么在使用 avr-gdb 单步执行的时候,程序指针会乱跳?

利用 avrgcc,同时使用最优化和调试信息两个选项编译一个程序以后,调试器中看到的代码是最优化的代码。尽管并非肯定,但通常这样生成的代码与仅使用最优化选项生成的代码是一样的,就如同调试信息选项并未被选择。

这会导致一些有害的副作用。因为只要语义不变,编译器可以自由重组语句的执行。代码经常被重组,以便在一个条件运算中只使用一条分支语句。分支语句只能指向一个很小的范围,因此如果一次条件运算语句不能直接使用一条分支语句,则编译器会在其周围使用一条跳跃指令及一条 rjmp 指令,这将需要额外的 ROM 空间。

最优化的另一个副作用是,一个变量只在它实际被使用的代码中有效。因此如果在函数开始的时候,一个变量被放入寄存器,那么当编译器注意到该变量已经不再使用的时候,这个寄存器就可以被其他变量占用。此时,如果试图在调试时监视变量的值,就有可能造成混淆。

为了避免这些副作用,建议在调试时关闭最优化选项。

               欢迎点击进入:TI德州中文网   (国内唯一针对TI应用的中文技术网站)    文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
    没有相关文章
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    站长:61IC 湘ICP备05002478号