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.OxO6,Ox5b,//共阴极数 码管 O~9的字形码 Ox4f, OX 66, Ox6d, Ox7d, Ox07, Ox7f, Ox6f}; uchar const ACT[4]=(Oxfe,0xfd,Oxfb,Oxf7);//4位共阴 极数码管的位选码 uchar val,DispBuff[4];//定义全局变量及数组 //* * * * * * * * * * * * * * * * * * * * * * * * * * void delay_ms(uint k)//延时子函数 { uint i, j; for(i=0;i<k;i++) { 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 add,uchar 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为待写数据,add为EEPROM的某单元地址
{
while(EECR&(1<<EEWE));//等待前一次写操作完成
EEAR=add; //设定单位地址
EEDR=dat; //将数据写入EEDR
EECRㄧ=(1<<EEMWE); //允许EEPROM操作
EECRㄧ=(1<<EEWE); //开始EEPROM写操作
}
//****************读EEPROM子函数**********************
uchar R_EEP(uint add)//add为EEPROM的某单元地址
{
while (EECR&(1<<EEWE)); //等待前一次写操作完成
EEAR=add; //设定单元地址
EECRㄧ=(1<<EERE); //开始EEPROM写操作
return EEDR; //返回读出的数据
}
//********************************************************
void display(void) 将变量x、y扫描到数码管上显示
{
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点亮,达到设计的目标。
- 上一篇:液晶显示器基础知识
- 下一篇:新型平板显示器件OLED