学海荡舟手机网
导航

主页 > 电子设备技术 > 电器电路基础 > > 详细内容

AVR单片机入门----C语言高效设计实践(八)

 ATMEAGl6L的内部EEPROM读写

  BATmegal6L片内有512个字节的EEP-ROM,它作为一个独立的数据空间存在。ATmegal6L的EEPROM采用独立线性编址,其地址范围为O~511。

 ATmegal6L通过对相关寄存器的操作实现对EEPROM按字节进行读写。ATmegal6L的EEPROM至少可以撩写100000次。  

 ATmegal6L的EEPROM的写入时间约花数毫秒,取决于V的。电压越低,写越长。

 与EEPROM相关的寄存器

 1.EEPROM地址寄存器—EEARH、EEARL

 a. EEARH

 b. EEARL

 EEPROM地址寄存器EEARH、EEARL用于指定某个EEPROM单元的地址。512个字节的EEPROM线性编址为O~511。地址寄存器EEARH、EEARL可读可写,初始值没有定义,访问前必须赋予正确的地址。

 2.EEPROM数据寄存器一EEDR

 EEPROM数据寄存器EEDR用于存放即将写入EEPROM或者从EEPROM读出的某个单元的数据。写入或者读出的地址由EEPROM的地址寄存器EEARH、EEARL给出。EEPROM按字节进行读写。EEPROM数据寄存器EEDR可读可写,初始值为OxOO。

 3.EEPROM控制寄存器—EECR

 EEPROM数据寄存器EEDR用于存放即将写入EEPROM或者从EEPROM读出的某个单元的数据。写入或者读出的地址由EEPROM的地址寄存器EEARH、EEPROM的操作。

 位7~4:保留位。读这些位时总为0。

 位3:EERIE位为EEPROM中断准备好使能位。当EERIE位置1而且全局中断标志位(SREG的1位)置位时,如果EEWE为0,则单片机产生一个中断,表明这些操作完成。

 位2:EEMWE位为EEPROM主写使能位。只有EEMWE置位时,置位EEWE才能将数据寄存器EEDR中的内容写入由EEAR选择好的地址空间中。如果EEMWE=0,置位EEWE不会产生写操作。EEMWE在被用户置位后的四个时钟周期后被硬件清除。

 位1:EEWE位为EEPROM写使能位。当EEPROM的数据和地址被正确设置后)如果EEMWE被置位,则置位EEWE将执行写操作。EEPROM写操作时序如下:

 (1)等待EEWE变为0;

 (2)等待SPMCSR中的SPMEN位变为0;

 (3)把新的EEPROM地址写入EEAR中(可选);

 (4)把新的数据写入EEDR中(可选);

 (5)置位EEMWE同时清零EEWE;

  (6)在EEMWE置位后的4个时钟周期内置位EEWE;在EEWE置位后的2.5mS~4mS后,EEWE被硬件清零,用户可以通过查询此位判断写操作是否完成。应注意的是,在写EEPROM时,最好关闭全局中断标志位1。

 如果在步骤5~6之间响应中断将导致写操作失败。

 位0:EERE位为EEPROM的读使能位。当EEPROM的数据和地址被正确设置后,置位EERE将执行读操作。

 用户在读取EEPROM时应该检测EEWE位,如果一个写操作正在进行,则无法读取。

 由于AVR单片机硬件上的原因,ATMEL公司建议EEPROM中地址为0的存储空间尽量不要使用。支持AVR的所有C编译器,如CAVR、GCCAVR、IAR和CVAVR都是从地址为1的空间开始存放数据。

 ATMEAG16L内部EEPROM编程实践

 1.写入ATmega16L内部EEPROM一个数,然后读出在数码营上显示进行EEPROM读写需要2个参数:一个16位的地址和一个8位的数据。这里地址我们选488,数据选21。

 右边4位显示从EEPROM中读出的数据。

 在我的文档中新建一个acl3的文件夹。建立一个acl3.prj的工程项目,最后建立源程序文件acl3.c。输入下面的程序:

#include <ioml6v.h>//包含头文件

    #define uchar unsigned char//变量类型的宏定

    # define uint unsigned int

    uchar const SEG7[10]={Ox3f.OxO6Ox5b//共阴极数

码管 O~9的字形码

    Ox4f, OX 66, Ox6d, Ox7d, Ox07, Ox7f, Ox6f}

    uchar const ACT[4]=(Oxfe0xfdOxfbOxf7)//4位共阴

极数码管的位选码

    uchar valDispBuff[4]//定义全局变量及数组

   //* * * * * * * * * * * *  * * * * * * * * * * * * * *

       void delay_ms(uint k)//延时子函数

    {

    uint i, j;

    for(i=0i<ki++)

    {

    for(j= 0; j< 1140; j++)

 

    while(EECR&(1<<EEWS))//等待前一次写操作完成

    EEAR=add//设定单元地址

    EECR=(1<<EERE      //开始 EEPROM 写操作

    return EEDR          //返回读出的数据

    }

    //*********数据转换子函数 ************

    void conv(uchari)//将变量 i 分解成待显数并存人数

    {

    Disp Buff[3]= i/ 1 000;

    Disp Buff[2]=(i% 1 000)/ 1 00;

    Disp Buff[1]=(i% 1 00)/ 1 0;

    Disp Buff10l= i% 1 0;

    }

 

    }

    }

    //********* EEPROM 子函数 **********

    void W_EEP(uint adduchar dat)//dat 为待写数据,

add EEPROM 的某单元地址

    {

    while(EECR&(1<<EEWE))//等待前一次写操作完成

 

    // 设定单元地址

//将数据写入 EEDR

//允许EEPROM操作

// 开始EEPROM 写操作

 

void display(uchar p[4])将数组扫描到数码管上显示

{

PORTA= SEG 7[p[0]];

PORTC= ACT [0];

Delay_ms(1);

PORTA= SEG 7[P[1]];

PORTC= ACT [1];

delay_ms(1);

PORTA= SEG 7[P[2]];

PORTC= ACT [2];

Delay_ms(1);

PORTA= SEG 7[P[3]];

PORTC= ACT [3];

Delay_ms(1);

}

 

    //*******EEPROM子函数*****-****

    uchar R_EEP{uint add)// add EEPROM 的某单元

地址

    {

 

void port_init(void)  端口初始化子函数

{

POBTA = OxFF        //  PA 端口初始化输出

 

11111111

DDRA=0xFF                            //PA端口设为输出

PORTB=0xFF                            //PB端口初始化输出

11111111

    DDRB=0xFF                             //PB端口设为输出

    PORTC=0xFF                                  //PC端口初始化输 

1111111

    DDRC=0xFF                            //PC端口设为输出

    PORTD=0xFF                            //PD端口初始化输出

11111111

    DDRD=0xFF                            //PD端口设为输出

}

//***************************

void main(void)                                  //定义主函数

 编译通过后,将ac13.hex文件下载到AVR单片机综合实验板上。注意,标示“MOD_DISP”、“LED-MOD_COM”的双排针插上短路块。我们看到右侧4个数码管显示0021。

 2.自己用按键S1、S2选定一个0-255之间的数(在左边4位数码管上显示),点按S3后写入ATmega16L内部EEPROM的一个单元(这里我们选地址为200),然后点按S4键读出在右边4位数码管上显示。

 在我的文档中新建一个ac14的文件夹。建立一个ac14.prj的工程项目,最后建立源程序文件ac14.c。输入下面的程序:

# include<iom16v.h>                        //包含头文件

# define uchar unsigned char                //变量类型的宏定义

# define uint unsigned int

uchar const SEG7[10]={0x3f,0x06,0x5b,      //共阴极数码管0-9的字型码

0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

uchar const ACT[4]={0xfe,0xfd,0xfb,0xf7};  //4位共阴极数码管的位选码

uchar x,y;                                //定义全局变量

//*****************************************

void delay_ms(uint k)                //延时子函数

{

uint i,j;

  for(i=0;i<k;i++)

  {

for(j=0;j<1140;j++)

;

}

}

//***********EEPROM子函数************

void W_EEP(uint add,uchar dat)//dat为待写数据,addEEPROM的某单元地址

{

while(EECR&(1<<EEWE));//等待前一次写操作完成

EEAR=add                  //设定单位地址

EEDR=dat                  //将数据写入EEDR

EECR=1<<EEMWE);      //允许EEPROM操作

EECR=1<<EEWE);        //开始EEPROM写操作

}

//****************EEPROM子函数**********************

uchar R_EEP(uint add)//addEEPROM的某单元地址

{

while (EECR&(1<<EEWE))  //等待前一次写操作完成

EEAR=add                //设定单元地址

EECR=1<<EERE);      //开始EEPROM写操作

return EEDR            //返回读出的数据

}

//********************************************************

void display(void) 将变量xy扫描到数码管上显示

{

PORTA=SEG7[y%10]

PORTC=ACT[0]

delay_ms(1)

PORTA=SEG7[(y%100)/10]

PORTC=ACT[1]

delay_ms(1)

PORTA=SEG7[y%1000]/100]

PORTC=ACT[2] 

delay_ms(1);   PORTA=SEG7[y/1 000]:   PORTC=ACT[3]:   delay_ms(1);   //-------------------------------   PORTA=SEG7[x%1 O];   PORTC=ACT[4];   delay_ms(1);   PORTA=SEG7[(x%1 00)/1 0];   PORTC=ACT[5];   delay_ms(1);   PORTA=SEGT[(x%1 000)/1 OO];   PORTC=ACT[6];   delay_ms(1):   PORTA=SEG7[x/1000];   PORTC=ACT[7];   delay_ms(1):   }   //************************************   void port_init(void)    端口初始化子函数   {   PORTA=0xFF;    //PA端口初始化输出 11111111     DDRA=OxFF;    //将PA端口设为输出     PORTB=OxFF;    //PB端口初始化输出 11 11 11 11     DDRB=OxFF;    //将PB端口设为输出     PORTC=0xFF;    //PC端口初始化输出 11111111     DDRC=0xFF;    //将PC端口设为输出     PORTD=OxFF;    //PD端口初始化输出 11111111     DDRD=0x00;    //将PD端口设为输入 //*********************************     void main(void)    //定义主函数        {uchar i;    //定义局部变量     port_init();    //调用端口初始化子函数     while(1)    //无限循环        {        if((D&0x1O)==0)  //如果按键S 1按下     {     if(x<255)x++;  //变量X递增,最大值为255     for(i=0;i<25;i++)//for语句,用来显示X     display();    //显示X、Y        }      //------------------------    if((PIND&x20)==O)  ∥如果按键S2按下        {     if(x>O)x--:    //变量X递减,最小值为 0     for(i=O:i<50;i++)//for语句,用来显示Y     display();    //显示X、Y     }        //**************************     if((PIND&Ox40)==O){W_EEP(200,x);delay_ms (10):}//如果按键S3按下,     //将变量X写入ATmegal6L内部EEPROM的200 单元     if((PIND&Ox80)==0) {V=R_EEP(200);delay_ms  (10):)//如果按键S4按下,     //从ATmega16L 内部EEPROM的200单元中读出 数据至变量v中     display();    //显示x、Y     }     }

 编译通过后,将acl4.hex文件下载到AVR单片机综合试验板上。注意,标示“LEDMOD_DISP”、“LED—MODCOM”及“KEY”的双排针应插上短路块。点按S1、S2任选一个0-255之间的数(在左边4位数码管上我们使用的ICCAVR编译器也提供了读写AVR单片机内部EEPROM的库函数,这大大加快了我们开发产品的速度,也降低了编写读写EEPROM程序的难度。在使用此库函数之前,要包含eeprom.h头文件。

 3.设计一个电子钟,在右边4个数码管上显示时、分。按S2、Sl可调整时、分。左边4位数码管显示定时设置.按S4、S3可调整定时的时、分。按下INTl键可显示);点按S3后写入ATmega16L内部EEPROM的200单元:最后点按S4键,单片机即从EEPROM的200单元读出数据并在右边4位数码管上显示。

 将定时设置值写入ATmega16L内部的EEPROM长期保存。按下INTO键定时功能启动,这时最左位的数码管小数点点亮。当定时到达后,D1点亮,表示输出一个控制信号。

 在我的文档中新建一个ac15的文件夹。建立一个ac15.prj的工程项目,最后建立源程序文件ac15.c。输入下面的程序:

 #include<iom16v.h>    //包含头文件   #include<eeprom.h>   #define uchar unsigned char  //变量类型的宏定 义   #define uint unsigned int   uchar const SEG7[1O]={Ox3f,Ox06,Ox5b,//共阴极数 码管0-9的字形码   0X4f,0x66,Ox6d,Ox7d,Ox07,Ox7f,Ox6f};   uchar const ACT[4]={Oxfe,Oxfd,Oxfb,Oxf7);//4位共阴 极数码管的位选码     #define LED1_0(PORTB=PORTB&Oxfe)  ∥端口定 义     #define S1(PIND&Ox10)     #define S2(PIND&Ox20)     #define S3(PIND&Ox40)     #define S4(PIND&Ox80)     #define SINTO(PIND&Ox04)     #define SINTl{PlND&0x08)     uchar dpw,dpt,write_flag,time_flag,cnt;//定义全局 变量    uint key_cnt,ms_cnt;   uchar sec,min,set_sec,set_min;     /********************************************** ********/   void port_init(void)    //端口初始化子函数   {    //端口初始化子函 数开始     PORTA=OxFF;    //PA端口初始化输 出11111111   DDRA=OxFF;    //将PA端口设为 输出     PORTB=OxFF;    //PB端口初始化输 出11111111     DDRB=OxFF;    //将PB端口设为输出     PORTC=OxFF;    //PC端口初始化输出 111111111   DDRC=OxFF;    //将PC端口设为输出   PORTD=OxFF;    //PD端口初始化输出 1111111 DDRD=Ox00;    //将PD端口设为输入   }    ∥端口初始化子函数结束     //**********************************   void timerO_init(void)    //定时器O初始化子函数    {   TCNTO=0x83;    //1mS的定时初值   TCCRO=Ox03;    //定时器0的计数   }   /***************************************** *****/   void init_devices(void)    //芯片的初始化子函数   {   port_init();    //调用端口初始化子函数   timerO_init();    //调用定时器0初始化子函数   SREG=Ox80;    //使能总中断   }   //*************************************   #pragma interrupt_handler timerO_ovf_isr:1O/T/CO 中断服务子函数   void timerO_ovf_isr(void)   {   TCNTO=0x83;    //重装1mS的定时初值   if(++key_cnt>300)key_cnt=O;//按键计数器计数范 围0~300     if(++cnt>7)cnt=O://8位数码管的旋转扫描 计数器     if(++ms_cnt>999){ms_cnt=O;sec++;}//计数到 1000 毫秒时,秒变量sec递增     if(sec>59) {min++;sec=O;)//秒变量sec递增到60 时,分变量min递增     if(min>59)min=59;    //分变量min最高递 增到59     switch(cnt)//switch语句,根据cnt的值分别点亮 8位数码管     {     case O:PORTA=SEG7[sec%1 O];PORTC=ACT[0]; break;     case 1:PORTA=SEG7[sec/10]:PORTC=ACT[1]; break;     case 2:PORTA=SEG7[min%10];PORTC=ACT[2]; break;     case 3:PORTA=SEG7 [min/1O];PORTC=ACT[3]: break;     case 4:if(dpw==1){PORTA=SEG7[set_sec%10] Ox80;}//如果EEPROM中写入定时     //设置值后,设置值的最低位数码管小数点点亮     else{PORTA=SEG7[set_sec%1O];}//否则设 置值的最低位数码管小数点不亮     PORTC=ACT[4];break;     case 5:PORTA=SEG7[set_sec/10];PORTC=ACT[5]: break;  case 6:PORTA=SEG7[set_min%1 O];PORTC=ACT [6];break;     case 7:if(dpt==1){PORTA=SEG7[set_min/1O]ㄧOxaO;} //如果按下INTO键定时功能     ∥启动,这时设置值的最高位数码管小数点点亮。     else{PORTA=SEG7[set_min/1O];}//否则设置值 的最高位数码管小数点不亮     PORTC=ACT[7];break:     default:break;     }     if(key_cnt==0)    //每300毫秒时检测按键     {     if(S1==0)(sec++;if(sec>59)sec=O:}//如果按键S1 按下,sec递增     if(S2==O)(min++:if(min>59)min=0;}//如果按键S2 按下,min递增     if(S3==0){set_sec++;if fset_sec>59)set_sec=0;}// 如果按键S3按下,     //set_sec递增     if(S4==O){set_min++;if(set_min>59)set_min=O;)// 如果按键S4按下,     //seLmin递增        if(SlNT0==0){time—flag=l:}//如果按键INTO按下, 定时功能标志     //time_flag启动,这时设置值的最高位数码管小数 点点亮     if(SINT1==0){write_fiag=1:}//如果按键INT1按下, 写入EEPROM标志     //write_flag启动     }     }     /********************************/     void detay(uint k)    N延时子函数     {     uint i,j;     fOr(i=O:i<k;i++)     {     for(j=O;j<140:i++):     }     }     /*****************************/   void main(void)    //定义主函数   {   init_devices();    //调用芯片初始化子函数     while(t)    ∥无限循环     {     if(write_flag==1)//如果写入标志 write_flag为1,进入if语句     {SREG=Ox00;  //共闭总中断     EEPROM_WRITE(200,set_sec);delay(1O):∥将 变量set_sec写入   //ATmega16L内部EEPROM的200单元     EEPROM_WRITE(201,set_min);delay(1O);//将 变量set_min写入     //ATmega16L内部EEPROM的201单元     write_flag=O;//清除写入标志     dpw=1:    //置位设置值数码管最低位小 数点的标志     SREGㄧ=Ox80;  //开总中断     }     if(time_flag==1)//如果定时功能标志time_flag 启动     {SREG=Ox00;  //共闭总中断     EEPROM_READ(200,set_sec);delay(1O);//从 ATmega16L内部EEPROM        //的200单元中读出数据至变量set_sec中     EEPROM_READ(201,set_min);delay(1O):∥从 ATmega16L内部EEPROM     //的201单元中读出数据至变量set—min中     SREGㄧ=Ox80;  //开总中断     dpt=1;    //置位设置值数码管最高位小 数点的标志     time_flag=O;//清除定时功能标志time_flag     }     if(dpt==1)    //如果设置值数码管最高位小 数点的标志为1     {     if((sec==set_sec)&&(min==set—min))LED1_O;// 当走时与定时设置值     ∥相等时,点亮发光D1     }     }

 编译通过后,将ac15.hex文件下载到AVR单片机综合试验板上。注意,标示“LEDMOD_DISP”、“LED-MOD_COM”及“KEY”、“INT”的双排针应插上短路块。按下S1、S2在右边的4位数码管上调整走时时间“分”、“秒”,按下S3、S4在左边的4位数码管上设置定时时间“分”、“秒”。按下INT1键将定时设置值写入ATmega16L内部的EEPROM保存(左边4位数码管上最低位数码管小数点点亮)。按下INT0键定时功能启动(左边4位数码管上最高位数码管小数点点亮)。当定时到达后,发光二极管D1点亮,达到设计的目标。


相关文章