存储器组织结构及内部EEPROM读写范例
存储器组织结构及内部EEPROM读写
范例
AVR存储器组织结构及内部EEPROM读写范例2010-08-12 16:55AVR系列单片机内部有三种类型的被独立编址的存储器,它们分别为:Flash程序存储器、内部SRAM数据存储器和EEPROM数据存储器。
Flash存储器为1K~128K字节,支持并行编程和串行下载,下载寿命通常可达10,000次。由于AVR指令都为16位或32位,程序计数器对它按字进行寻址,因此FLASH存储器按字组织的,但在程序中访问FLASH存储区时专用指令LPM可分别读取指定地址的高低字节。
寄存器堆(R0~R31)、I/O寄存器和SRAM被统一编址。所以对寄存器和I/O口的操作使用与访问内部SRAM同样的指令。32个通用寄存器被编址到最前,I/O寄存器占用接下来的64个地址。从0X0060开始为内部SRAM。外部SRAM被编址到内部SRAM后。
AVR单片机的内部有64~4K的EEPROM数据存储器,它们被独立编址,按字节组织。擦写寿命可达100,000次。
1.I/O寄存器操作
I/O专用寄存器(SFR)被编址到与内部SRAM同一个地址空间,为此对它的操作和SRAM变量操作类似。
SFR定义文件的包含:
#include avr/io.h io.h文件在编译器包含路径下的avr目录下,由于AVR各器件间存在同名寄存器地址有不同的问题,io.h文件不直接定义SFR寄存器宏,它根据在命令行给出的– mmcu选项再包含合适的ioxxxx.h文件。在器件对应的ioxxxx.h文件中定义了器件SFR的预处理宏,在程序中直接对它赋值或引用的方式读写SFR,如:
PORTB=0XFF;
Val=PINB;
从io.h和其总包含的头文件sfr_defs.h可以追溯宏PORTB的原型在
io2313.h中定义:
#define PORTB _SFR_IO8(0x18)
在sfr_defs.h中定义:
#define _SFR_IO8(io_addr)_MMIO_BYTE((io_addr)+0x20)
#define _MMIO_BYTE(mem_addr)(*(volatile uint8_t*)(mem_addr))
这样PORTB=0XFF;就等同于*(volatile unsigned char*)(0x38)=0xff;
0x38在器件AT90S2313中PORTB的地址对SFR的定义宏进一步说明了SFR
与SRAM操作的相同点。
关键字volatile确保本条指令不会因C编译器的优化而被省略。
2.SRAM内变量的使用
一个没有其它属性修饰的C变量定义将被指定到内部SRAM,avr-libc提供
一个整数类型定义文件inttype.h,其中定义了常用的整数类型如下表:
定义值长度(字节)值范围
int8_t 1-128~127 uint8_t 10~255 int16_t 2-32768~32767 uint16_t 20~65535 int32_t 4-2147483648~2147483647 uint32_t 40~4294967295 int64_t 8-9.22*10^18~-9.22*10^18 uint64_t 80~1.844*10^19
根据习惯,在程序中可使用以上的整数定义。定义、初始化和引用
如下示例:
uint8_t val=8;定义了一个SRAM变量并初始化成8 val=10;改变变量值 const uint8_t val=8;定义SRAM区常量
register uint8_t val=10;定义寄存器变量
3.在程序中访问FLASH程序存储器
avr-libc支持头文件:pgmspace.h
#include avr/pgmspace.h 在程序存储器内的数据定义使用关键字__attribute__((__progmem__))。
在pgmspace.h
中它被定义成符号PROGMEM。
(1).FLASH区整数常量应用
定义
:
数据类型常量名PROGMEM=值;
如:
char val8 PROGMEM=1;
int val16 PROGMEM=1;
long val32 PROGMEM=1;
对于不同长度的整数类型avr-libc提供对应的读取函数: pgm_read_byte(prog_void*addr) pgm_read-word(prg_void*addr) pgm_read_dword(prg_void*addr)
另外在pgmspace.h中定义的8位整数类型prog_char prog_uchar分别指
定在FLASH内的8位有符号整数和8位无符号整数。应用方式如下: char ram_val;//ram内的变量
const prog_char flash_val=1;//flash内常量
ram_val=pgm_read_byte(&flash_val);//读flash常量值到RAM变量 对于应用程序FLASH常量是不可改变的,因此定义时加关键字const是个
好的习惯
2).FLASH区数组应用:
定义:
const prog_uchar flash_array={0,1,2,3,4,5,6,7,8,9};//定义 另外一种形式
const unsigned char flash_array RROGMEM={0,1,2,3,4,5,6,7,8,9}; 读取示例:
unsigend char I,ram_val;
for(I=0;I 10;I++)//循环读取每一字节
{
ram_val=pgm_read_byte(flash_array+I);
……//处理
}
(3).FLASH区字符串常量的应用
全局定义形式:
const char flash_str PROGMEM="Hello,world~"; 函数内定义形式:
; const char*flash_str=PSTR("Hello,world~")以下为一个FLASH字符串应用示例
#include avr/io.h
#include avr/pgmspace.h #include stdio.h const char flash_str1 PROGMEM="全局定义字符串";
int main(void)
int I;
char*flash_str2=PSTR("函数内定义字符串"); while(1)
{
scanf("%d",&I);
printf_P(flash_str1);
printf("\n");
printf_P(flash_str2);
printf("\n");
}
}
4.EEPROM数据存储器操作
#include avr/eeprom.h
-libc提供的操作EEPROM存储器的API函数。 头文件声明了avr
这些函数有:
eeprom_is_ready()//EEPROM忙检测(返回EEWE位)
eeprom_busy_wait()//查询等待EEPROM准备就绪
uint8_t eeprom_read_byte(const uint8_t*addr)//从指定地址读一字节 uint16_t eeprom_read_word(const uint16_t*addr)//从指定地址一字 void eeprom_read_block(void*buf,const void*addr,size_t n)//读块 void eeprom_write_byte(uint8_t*addr,uint8_t val)//写一字节至指定
地址
void eeprom_write_word(uint16_t*addr,uint16_t val)//写一字到指定
地址
void eeprom_write_block(const void*buf,void*addr,size_t n)//写块 在程序中对EEPROM操作有两种方式
方式一:直接指定EERPOM地址
示例:
#include avr/io.h
#include avr/eeprom.h int main(void)
{
unsigned char val;
eeprom_busy_wait();//等待EEPROM读写就绪
om_write_byte(0,0xaa);//将0xaa写入到EEPORM 0地址处 eepr
eeprom_busy_wait();
val=eeprom_read_byte(0);//从EEPROM 0地址处读取一字节赋给RAM变
量val while(1);
}
方式二:先定义EEPROM区变量法
示例:
#include avr/io.h #include avr/eeprom.h unsigned char val1
__attribute__((section(".eeprom")));//EEPROM变量定义方式
int main(void)
{
unsigned char val2;
eeprom_busy_wait();
eeprom_write_byte(&val1,0xAA);
eeprom_busy_wait();
val2=eeprom_read_byte(&val1);
while(1);
}
在这种方式下变量在EEPROM存储器内的具体地址由编译器自动分配。相对方式一,数据在EEPROM中的具体位置是不透明的。为EEPROM变量赋的初始值,编译时被分配到.eeprom段中,可用avr-objcopy工具从.elf文件中提取并产生ihex或binary等格式的文件。
/*
*AVR内部EEPROM读写范例*
*编译器:WINAVR 20050214*
*/
/*
本程序简单的示范了如何使用ATMEGA16的EERPOM EEPROM的简介
EEPROM的写操作
EEPROM的读操作
出于简化程序考虑,各种数据没有对外输出,学习时建议使用JTAG ICE硬件仿真器。
在打开调试文件到JTAG后,打开Debug-JTAG ICE Options菜单,然后在JTAG ICE Properties中点击Dbug页面,将preserve eeprom选项选中。在每次仿真调试时候,就保护EEPROM内容了。否则,会按照默认设置擦除EEPROM的内容。
由于定义了EEPROM变量,JTAG调试时会询问是否初始化EEPROM,请选择[否],EEPROM的数据也可以在view-memory,选Eeprom窗口下察看
*/
#i nclude
#i nclude
////时钟定为内部1MHz,F_CPU=1000000时钟频率对程序的运行没什么影响
/*
GCCAVR(avr-libc)里面自带了EEPROM的读写函数。
下面列举部分常用函数(原型)
#define eeprom_is_ready()bit_is_clear(EECR,EEWE)
检测EEPROM是否准备好。OK返回1(返回EEWE位)
#define eeprom_busy_wait()do{}while(~eeprom_is_ready()) 等待EEPROM操作完成
extern uint8_t eeprom_read_byte(const uint8_t*addr); 读取指定地址的一个字节8bit的EEPROM数据
extern uint16_t eeprom_read_word(const uint16_t*addr); 读取指定地址的一个字16bit的EEPROM数据
extern void eeprom_read_block(void*buf,const void*addr,size_t n); 读取由指定地址开始的指定长度的EEPROM数据
extern void eeprom_write_byte(uint8_t*addr,uint8_t val); 向指定地址写入一个字节8bit的EEPROM数据
extern void eeprom_write_word(uint16_t*addr,uint16_t val);
向指定地址写入一个字16bit的EEPROM数据
extern void eeprom_write_block(const void*buf,void*addr,size_t n); 由指定地址开始写入指定长度的EEPROM数据,但不支持部分AVR,原文如
下:
note This library will\e not work with the following devices \
since these devices have the EEPROM IO ports at different locations: -AT90CAN128
-ATmega48
-ATmega88
-ATmega165
-ATmega168
-ATmega169
-ATmega325
-ATmega3250
-ATmega645
-ATmega6450
在程序中对EEPROM操作有两种方式:
方式一:直接指定EERPOM地址
即读写函数的地址有自己指定,用于需要特定数据排列格式的应用中 方式二:先定义EEPROM区变量法
在这种方式下变量在EEPROM存储器内的具体地址由编译器自动分配。
相对方式一,数据在EEPROM中的具体位置是不透明的。
为EEPROM变量赋的初始值,编译时被分配到.eeprom段中,可用avr-objcopy工具从.elf文件中提取并产生ihex或binary等格式的文件,从而可以使用编程器或下载线将其写入到器件的EEPROM中。实际上WINAVR中MFILE生成的MAKEFILE已经为我们做了这一切。它会自动生成以".eep"为后缀的文件,
(这次测试发现分配地址是从0x0000开始的,故增加了一个通常它是iHex格式
EEPROM变量Evalvoid[16]),如果同时使用方式1和2,请注意防止地址重叠,自己指定的地址应该选在后面。
*/
//全局变量
unsigned char EDATA;
unsigned char
ORGDATA[16]={0x00,0x02,0x04,0x06,0x08,0x0A,0x0C,0x0E,
0x01,0x03,0x05,0x07,0x09,0x0B,0x0D,0x0F};//原始数据
unsigned char CMPDATA[16];//比较数据
//仿真时在watch窗口,监控这些全局变量。
//EEPROM变量定义
unsigned char Evalvoid[16]__attribute__((section(".eeprom")));//这个没用到
unsigned char Eval[16]__attribute__((section(".eeprom")));
int main(void)
{
eeprom_write_byte(0x40,0xA5);//向EEPROM的0x40地址写入数据0xA5 EDATA=eeprom_read_byte(0x40);//读出,然后看看数据对不对?
//上面两句编译是有如下警告,但不必理会
//EEPROM_main.c:103:warning:passing arg 1of`eeprom_write_byte'makes pointer from integer without acast
//EEPROM_main.c:104:warning:passing arg 1of`eeprom_read_byte'makes pointer from integer without acast
eeprom_write_block(&ORGDATA[0],&Eval[0],16);//块写入
//看看EEPROM数据是否是能失电永久保存,可以注释上面这句程序(不写入,只是读出),然后编译,烧写,断电(一段时间),上电,调试。
eeprom_read_block(&CMPDATA[0],&Eval[0],16);//块读出,然后看看数据对不对?
while(1);
}
/*
ATmega16包含512字节的EEPROM数据存储器。它是作为一个独立的数据空间而存在的,可以按字节读写。EEPROM的寿命至少为100,000次擦除周期。EEPROM的访问由地址寄存器EEAR、数据寄存器EEDR和控制寄存器EECR决定。也可以通过ISP和JTAG及并行电缆来固化EEPROM数据。
EEPROM数据的读取:
当EEPROM地址设置好之后,需置位EERE以便将数据读入EEDR。
EEPROM数据的读取需要一条指令,且无需等待。
读取EEPROM后CPU要停止4个时钟周期才可以执行下一条指令。
注意:用户在读取EEPROM时应该检测EEWE。如果一个写操作正在进行,就无法读取EEPROM,也无法改变寄存器EEAR。
EEPROM数据的写入:
1、EEPROM的写访问时间(自定时时间,编程时间)
自定时功能可以让用户软件监测何时可以开始写下一字节。(可以采用中断方式)
经过校准的1MHz片内振荡器用于EEPROM定时,不倚赖CKSEL熔丝位的设置。
改变OSCCAL寄存器的值会影响内部RC振荡器的频率因而影响写EEPROM的时间。
EEPROM自定时时间约为8.5 ms即1MHz片内振荡器的8448个周期
注意:这个时间是硬件定时的,数值比较保险,其实真正的写入时间根本就用不了8.5mS那么长,而且跟电压有关,但芯片没有提供其他的检测编程完成的方法
这个问题表现在旧版的AT90S系列上面,由于没有自定时,数值定得太短,ATMEL给人投诉到头都爆,呵呵~
参考:《用ATmega8535替换AT90S8535》文档里面的写EEPROM定时的改进:
在AT90S8535中写EEPROM的时间取决于供电电压,通常为2.5ms@VCC=5V,4ms@VCC=2.7V。
ATmega8535中写EEPROM的时间为8448个校准过的RC振荡器周期(与系统时钟的时钟源和频率无关)。
假定校准过的RC振荡器为1.0MHz,则写时间的典型值为8.4ms,与VCC无关。
2、为了防止无意识的EEPROM写操作,需要执行一个特定的写时序(如果使
用编译器的自带函数,无须自己操心)写时序如下(第3步和第4步的次序并不
重要):
?等待EEWE位变为零
?等待SPMCSR中的SPMEN位变为零
?将新的EEPROM地址写入EEAR(可选)
?将新的EEPROM数据写入EEDR(可选)
?对EECR寄存器的EEMWE写"1",同时清零EEWE
?在置位EEMWE的4个周期内,置位EEWE
经过写访问时间之后,EEWE硬件清零。
用户可以凭借这一位判断写时序是否已经完成。
EEWE置位后,CPU要停止两个时钟周期才会运行下一条指令。 注意:
1、在CPU写Flash存储器的时候不能对EEPROM进行编程。 在启动EEPROM写操作之前软件必须检查Flash写操作是否已经完成 步骤(2)仅在软件包含引导程序并允许CPU对Flash进行编程时才有用。 如果CPU永远都不会写Flash,步骤(2)可省略。
2、如果在步骤5和6之间发生了中断,写操作将失败。 因为此时EEPROM写使能操作将超时。
如果一个操作EEPROM的中断打断了另一个EEPROM操作,EEAR或EEDR寄
存器可能被修改,引起EEPROM操作失败。
建议此时关闭全局中断标志I。
经过写访问时间之后,EEWE硬件清零。用户可以凭借这一位判断写时序是否已经完成。
EEWE置位后,CPU要停止两个时钟周期才会运行下一条指令。
在掉电休眠模式下的EEPROM写操作:
若程序执行掉电指令时EEPROM的写操作正在进行,EEPROM的写操作将继续,并在指定的写访问时间之前完成。
但写操作结束后,振荡器还将继续运行,单片机并非处于完全的掉电模式。因此在执行掉电指令之前应结束EEPROM的写操作。
防止EEPROM数据丢失:
若电源电压过低,CPU和EEPROM有可能工作不正常,造成EEPROM数据的毁坏(丢失)。
*这种情况在使用独立的EEPROM器件时也会遇到。因而需要使用相同的保护
。
由于电压过低造成EEPROM数据损坏有两种可能:一是电压低于EEPROM写操作所需要的最低电压;二是CPU本身已经无法正常工作。
EEPROM数据损坏的问题可以通过以下方法解决:当电压过低时保持AVR RESET信号为低。这可以通过使能芯片的掉电检测电路BOD来实现。如果BOD电平无法满足要求则可以使用外部复位电路。若写操作过程当中发生了复位,只要电压足够高,写操作仍将正常结束。(EEPROM在2V低压下也能进行写操作---有可以工作到1.8V的AVR芯片)
掉电检测BOD的误解:
AVR自带的BOD(Brown-out Detection)电路,作用是在电压过低(低于设定值)时产生复位信号,防止CPU意外动作。
对EEPROM的保护作用是当电压过低时保持RESET信号为低,防止CPU意外动作,错误修改了EEPROM的内容
而我们所理解的掉电检测功能是指具有预测功能的可以进行软件处理的功能。
例如,用户想在电源掉电时把SRAM数据转存到EEPROM,可行的方法是外接一个在4.5V翻转的电压比较器(VCC=5.0V,BOD=2.7V),输出接到外部中断引
或其他中断)一但电压低于4.5V,马上触发中断,在中断服务程序中把数据脚(
写到EEPROM中保护起来。
注意:写一个字节的EEPROM时间长达8mS,所以不能写入太多数据,电源滤波电容也要选大一些。
*/