|
折腾了几个小时,刚翻完一半……拿出来让大家帮忙抓抓 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 空间。
最优化的另一个副作用是,一个变量只在它实际被使用的代码中有效。因此如果在函数开始的时候,一个变量被放入寄存器,那么当编译器注意到该变量已经不再使用的时候,这个寄存器就可以被其他变量占用。此时,如果试图在调试时监视变量的值,就有可能造成混淆。
为了避免这些副作用,建议在调试时关闭最优化选项。
|