AVR 单片机存储器组织结构

发布于 2014-11-28  1.5k 次阅读


AVR 系列单片机内部有三种类型的被独立编址的存储器,它们分别为: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 定义文件的包含:

io.h 文件在编译器包含路径下的avr 目录下,由于AVR 各器件间存在同名寄存器地址有不同的问题,io.h 文件不直接定义SFR 寄存器宏,它根据在命令行给出的 –mmcu 选项再包含合适的 ioxxxx.h 文件。在器件对应的ioxxxx.h 文件中定义了器件SFR 的预处理宏,在程序中直接对它赋值或引用的方式读写SFR,如:

从io.h 和其总包含的头文件sfr_defs.h 可以追溯宏PORTB 的原型在io2313.h 中定义:

在sfr_defs.h 中定义:

这样PORTB=0XFF; 就等同于 *(volatile unsigned char *)(0x38)=0xff;0x38 在器件AT90S2313 中PORTB 的地址对SFR 的定义宏进一步说明了SFR 与SRAM 操作的相同点。

关键字volatile 确保本条指令不会因C 编译器的优化而被省略。

2. SRAM 内变量的使用

一个没有其它属性修饰的 C 变量定义将被指定到内部SRAM,avr-libc 提供一个整数类型定义文件inttype.h,其中定义了常用的整数类型如下表:

定义值长度(字节) 值范围

根据习惯,在程序中可使用以上的整数定义。定义、初始化和引用

如下示例:

 

寄存器变量:

C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,这样可提高效率。

寄存器变量是个临时变量,当调用完之后,会自动释放,以节约内存空间,提高程序效率。寄存器变量没有地址,没有地址就不能用指针变量指向它

寄存器变量的说明符是register,对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。(根据COA原理,局部变量会更多的被使用,所以一般编译器进行优化的时候,都会被变量放到寄存器中来加快访问速度,所以一般不推荐将变量定义为寄存器变量。当然如果你在编译器中设置优化为无时,就可以自己来编写属于自己的最高效率的代码。)

C语言可以把变量定义为寄存器类型的,将数据直接存放在CPU的寄存器中,使用关键字register定义变量。

例如register a=123;定义a为寄存器类型变量。

对于register变量需要注意的几点:

(1)使用register定义的变量尽可能存放到寄存器中,但不绝对。

(2)定义的变量一般整数(int)为宜。

(3)定义的变量,只要涉及到该变量的地址时,编译器都会报错,如"&a",数组首地址a。

(4)也可以定义指针类型的变量,如register ptr=&c,c="abcde",c不是register变量。ptr='a',ptr++移动4个字节。

(5)不能使用sizeof(register)。

注意:由于一般编译器都会对寄存器的使用进行优化,所以不建议使用寄存器型变量。

实验笔记:在Arduino 1.5.8以及VS2013环境下,使用寄存器变量,并指针指向他并不会报错。

3. 在程序中访问FLASH 程序存储器
 

avr-libc 支持头文件:pgmspace.h

在程序存储器内的数据定义使用关键字 attribute((progmem))。在pgmspace.h中它被定义成符号 PROGMEM。

(1). FLASH 区整数常量应用

定义格式:

数据类型 常量名 PROGMEM = 值 ;

如:

对于不同长度的整数类型 avr-libc 提供对应的读取函数:

另外在pgmspace.h 中定义的8 位整数类型 prog_char prog_uchar 分别指定在FLASH 内的8 位有符号整数和8 位无符号整数。应用方式如下:

对于应用程序FLASH 常量是不可改变的,因此定义时加关键字const 是个好的习惯。

(2). FLASH 区数组应用:

定义:

另外一种形式

读取示例:

(3).FLASH 区字符串常量的应用

全局定义形式:

函数内定义形式:

以下为一个FLASH 字符串应用示例

 

  1. EEPROM 数据存储器操作
       #include <avr/eeprom.h>
       头文件声明了avr-libc 提供的操作EEPROM 存储器的API 函数。
       这些函数有:
    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 读写就绪
eeprom_write_byte(0,0xaa); //将0xaa 写入到EEPORM 0 地址处
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 等格式的文件。


公交车司机终于在众人的指责中将座位让给了老太太