直接从C源代码编程AVR EEPROM


11

在AVR C源代码中包含以下代码时,显然可以直接对保险丝进行编程,而无需额外的命令或.hex文件:

#include <avr/io.h>

FUSES = {
        .low =          LFUSE_DEFAULT ,
        .high =         HFUSE_DEFAULT ,
        .extended =     EFUSE_DEFAULT ,
};

在EEPROM中编程值是否有类似的技巧?

我已经检查了/usr/lib/avr/include/avr/fuse.h在哪里可以找到有关宏的一些注释,但是找不到类似的注释,/usr/lib/avr/include/avr/eeprom.h并且解释预处理器的内容有点超出我的范围了。

如果我可以在C源代码中包括默认EEPROM值,那将非常方便。有人知道如何做到这一点吗?

编辑1:

此FUSES技巧仅在ISP时执行,而不在RUN时执行。因此,在控制器中生成的汇编代码中没有对保险丝进行编程。相反,程序员会自动在额外的FUSES编程周期中循环。

编辑2:

我在Linux上使用avr-gcc和avrdude工具链。


仅在对ISP编程时才这样吗?大多数引导程序不允许您编程保险丝,对吗?
angelatlarge

2
我目前没有时间写一个完整的答案,但是作为提示,请尝试搜索EEMEM指令。另外,您可能需要更改链接器设置,以创建程序员将使用的单独的.EPP文件。
PeterJ 2013年

@angelatlarge仅ISP编程。此设置中没有引导程序。
jippie

2
请注意,对此的答案完全取决于工具链愿意在其输出中记录什么以及程序员愿意解析什么。大多数工具链都可以配置为创建一个特殊的部分(或将数据放置在虚拟地址中),因此最终归结于程序员能够提取它,或者能够由这样做的自定义脚本来驱动。
克里斯·斯特拉顿

Answers:


7

使用avr-gcc EEMEM可以在变量的定义上使用宏,请参见libc文档此处的示例:

#include <avr/eeprom.h>
char myEepromString[] EEMEM = "Hello World!";

声明字符数组驻留在名为“ .eeprom”的节中,编译后该节告诉程序员该数据将被编程到EEPROM。根据您的程序员软件,您可能需要显式地将在构建过程中创建的“ .eep”文件的名称提供给程序员,或者它可能会隐式地自行找到它。


我使用的文件名与“应该”使用的文件名略有不同,但是这些是我从命令行(和makefile)对EEPROM进行编程的命令:
jippie 2013年

1
创建一个包含程序员使用的Intel Hex数据的文件:avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 ihex $(src).elf $(src).eeprom.hex
jippie 2013年

1
实际的编程操作是通过以下方式完成的:avrdude -p$(avrType) -c$(programmerType) -P$(programmerDev) -b$(baud) -v -U eeprom:w:$(src).eeprom.hex
jippie

7

是的,您可以在源代码中手动将默认数据写入EEPROM。首先,在带AVR的EEPROM上查看以下很棒的指南:Dean的AVR EEPROM教程。另外,我还要补充一点,最好使用makefile创建一个包含EEPROM数据的.eep文件,该文件将与源代码一起编程到设备中。但是,如果您不熟悉各种makefile和链接器操作,则仍可以从源代码文件中完成操作-它只会在电路加电后立即发生,从而使初始程序操作停滞不前。

在程序开始时(在任何类型的主循环之前),您可以执行以下操作:

#include <avr/eeprom.h>

#define ADDRESS_1 46  // This could be anything from 0 to the highest EEPROM address
#define ADDRESS_2 52  // This could be anything from 0 to the highest EEPROM address
#define ADDRESS_3 68  // This could be anything from 0 to the highest EEPROM address

uint8_t dataByte1 = 0x7F;  // Data for address 1
uint8_t dataByte2 = 0x33;  // Data for address 2
uint8_t dataByte3 = 0xCE;  // Data for address 3

eeprom_update_byte((uint8_t*)ADDRESS_1, dataByte1);
eeprom_update_byte((uint8_t*)ADDRESS_2, dataByte2);
eeprom_update_byte((uint8_t*)ADDRESS_3, dataByte3);

“更新”功能首先检查该值是否已经存在,以节省不必要的写操作,从而保持EEPROM寿命。但是,在很多位置执行此操作可能会花费大量时间。检查单个位置可能更好。如果它是所需的值,则可以完全跳过其余的更新。例如:

if(eeprom_read_byte((uint8_t*)SOME_LOCATION) != DESIRED_VALUE){
  eeprom_write_byte((uint8_t*)SOME_LOCATION, DESIRED_VALUE);
  eeprom_update_byte((uint8_t*)ADDRESS_1, dataByte1);
  eeprom_update_byte((uint8_t*)ADDRESS_2, dataByte2);
  eeprom_update_byte((uint8_t*)ADDRESS_3, dataByte3);
}

如果您要更新大量数据,请尝试使用其他功能,例如eeprom_update_block(...)。并一定要阅读该教程;它写得很好。

您可以将所有EEPROM更新语句放在一个预处理器条件语句中。这很简单:

#if defined _UPDATE_EEPROM_
  #define ADDRESS_1 46  // This could be anything from 0 to the highest EEPROM address
  uint8_t dataByte = 0x7F;  // Data for address 1
  eeprom_update_byte((uint8_t*)ADDRESS_1, dataByte1);
#endif // _UPDATE_EEPROM_

除非您执行以下操作,否则甚至不会编译这段代码:

#define _UPDATE_EEPROM_

您可以在此处留下注释,然后如果需要更改默认EEPROM值则取消注释。有关C预处理器的更多信息,请查阅此在线手册。我认为您可能对宏和条件语句的部分最感兴趣。


正确的答案似乎在链接的PDF的最后一段中。Ch. 7 Setting Initial Values
jippie

是的,你是对的。我在第一段中提到了这一点,但是如果您不熟悉.eep文件和makefile中的链接器,那么请继续!
Kurt E. Clothier

1
使用静态EEPROM地址是不好的做法。最好改用EEMEM属性,并让编译器管理地址分配。另外,我建议对每个部分实施/执行CRC检查。如果CRC失败,则相应部分包含未初始化或损坏的数据。这样,您甚至可以在数据损坏的情况下对先前的配置实施回退机制。
2013年

“使用静态EEPROM地址是一种不好的做法。” 为什么?
angelatlarge 2013年

1
使用EEMEM变量时,编译器负责管理哪个变量位于EEPROM中的位置。这样,您在访问数据时仅对指向变量的变量(由编译器生成的常量)进行操作。另一方面,如果您明确定义每个变量所在的地址,则您必须自己照顾这些地址,包括确保没有变量意外地占用相同的地址,并且相互覆盖;或重新计算的情况下,变量的存储大小改变未来等所有地址
JimmyB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.