(EEPROM的读写)AT24C02在51单片机上的使用方法

写在前面

  这篇博客记录下我在开发板上使用AT24C02芯片的过程。
  本次用AT24C02实现的是EEPROM的功能,可以实现断电记忆功能。
  单片机是 STC12C5A60S2 平台是 普中HC6800-ES V2.0
  24C02与单片机使用的是IIC协议通信,这个协议网上的资料很多,这里就不再赘述了。下面给出各功能的代码。
  单片机告诉24C02开始发送数据:

/*******************************************************************************
* 函数名         : I2cStart()
* 函数功能		 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 起始之后SDA和SCL都为0
*******************************************************************************/
void iic_start(){
	sda = 1;
	delay1;
	scl = 1;
	delay1;
	sda = 0;
	delay1;
	scl = 0;					//拉低scl,占用scl准备发送数据
	delay1;
}

  中间的delay1是一个短延时。延时时间是IIC协议规定的。起始信号就是当SCL为高电平时,SDA从高电平降到低电平。当24C02收到这个信号之后就会做好接收准备。
  单片机写入字节到24C02:

/*******************************************************************************
* 函数名         : iic_wr(uint8 dat)
* 函数功能		 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入           : dat
* 输出         	 : none
* 备注           : 起始之后SDA和SCL都为1
*******************************************************************************/
void iic_wr(uint8 dat){
	uint8 i;
	for(i=0;i<8;i++){
		if(dat & 0x80){		
			sda = 1;
		}
		else{
			sda = 0;
		}
		dat = dat << 1;
		scl = 1;							//拉高scl,让从机接收sda
		delay1;
		scl = 0;							//占用scl,让sda允许改变
		delay1;
	}
	scl = 1;					//释放SCL和SDA
	delay1;
	sda = 1;
	delay1;
}

  单片机从24C02读一个字节:

/*******************************************************************************
* 函数名         : iic_rd()
* 函数功能		 : 使用I2c读取一个字节
* 输入           : none
* 输出         	 : dat
* 备注           : 
*******************************************************************************/
uint8 iic_rd(){
	uint8 i;
	uint8 dat;
	
	scl = 0;					//在改变sda前先确保scl为低电平
	delay1;
	sda = 1;					//主机释放sda,以便从机可以改变sda
	delay1;
	
	for(i=0;i<8;i++){
		scl = 1;				//拉高scl,接收从机发来的sda
		delay1;
		dat <<= 1;
		dat |= sda;
		scl = 0;
		delay1;				//拉低scl,让从机可以改变sda的电平
	}
	return dat;
}

  单片机检测24C02是否发出了应答信号:

/*******************************************************************************
* 函数名         : wait_ack()
* 函数功能		 : 检测24C02是否发出了应答信号
* 输入           : 无
* 输出         	 : 0 or 1
*******************************************************************************/
bit wait_ack(){
	uint8 max_time = 0;
	// scl = 1;					//释放SCL和SDA
	// delay1;
	// sda = 1;
	// delay1;					//iic_wr后scl和sda都已经释放
	while(sda){
		if(++max_time > 200){	//等待时间超过200,视为无应答,发送停止信号
			scl = 0;
			iic_stop();
			return 0;
		}
	}										
		scl = 0;							//应答正常,拉低scl形成第9个时钟脉冲
		return 1;
}

  单片机向24C02发出停止信号:

/*******************************************************************************
* 函数名         : iic_stop()
* 函数功能		 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void iic_stop(){
	sda = 0;
	delay1;
	scl = 1;
	delay1;
	sda = 1;
}

  这就是IIC通信的核心部分代码。后面的读一个字节和写一个字节到24C02上都是调用上面这些功能代码。
  往24C02某个位置写入数据
  代码如下:

/*******************************************************************************
* 函数名         : write_byte(uint8 add,uint8 dat)
* 函数功能		 : 往24c02的一个地址写入一个数据
* 输入           : add,dat
* 输出         	 : 无
*******************************************************************************/
void write_byte(uint8 add,uint8 dat){
	iic_start();
	iic_wr(0xa0);				//写入设备地址
	wait_ack();
	iic_wr(add);				//写入存放数据的地址
	wait_ack();
	iic_wr(dat);				//写入数据
	wait_ack();
	iic_stop();					//结束写数据
}

  读取24C02某个位置的数据:

/*******************************************************************************
* 函数名         : read_byte(uint8 add)
* 函数功能		 : 读取24c02的一个地址的一个数据
* 输入           : add
* 输出         	 : dat
*******************************************************************************/
uint8 read_byte(uint8 add){
	uint8 dat;
	
	iic_start();
	iic_wr(0xa0);				//写入设备地址 最后一位为0代表主机要写入数据到从机
	wait_ack();
	iic_wr(add);				//写入要读数据的地址
	wait_ack();
	iic_start();				
	iic_wr(0xa1);				//写入设备地址 最后一位为1代表主机要读从机数据
	wait_ack();
	dat = iic_rd();				//保存读出的数据
	iic_stop();					//结束读数据
	return dat;
}

  有了上面这些函数,只需要在主函数里面调用一下就能实现控制24C02来保存需要保存的数据了。
  所有代码如下:

/**************************************************************************************
*		             		 EEPROM-IIC												  *
实现现象:下载程序后数码管后4位显示0,按K1保存显示的数据,按K2读取上次保存的数据,
		  按K3显示数据加一,按K4显示数据清零。最大能写入的数据是255.
注意事项:																				  
***************************************************************************************/
#include <reg52.h>
#include "intrins.h"
#define delay1 {_nop_();_nop_();_nop_();_nop_();_nop_();}
//短延时
typedef unsigned char uint8;
typedef unsigned int uint16;
//************************************************端口声明

sbit sda = P2^0;
sbit scl = P2^1;
	
sbit ls138A = P2^2;					//38译码器的A B C,用来选择需要显示的数码管(位选)
sbit ls138B = P2^3;
sbit ls138C = P2^4;

sbit key1 = P3^1;
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;

//************************************************变量 函数声明
uint8 code seg[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
uint8 com[4];
uint16 temp;

void iic_start();
void iic_stop();
bit wait_ack();
void iic_wr(uint8 dat);
uint8 iic_rd();
void write_byte(uint8 add,uint8 dat);
uint8 read_byte(uint8 add);
void delay(uint16 z);								//延时
void scankey();
void display();

//*****************************************************************************
//* 函数名         : Delay10us()
//* 函数功能		 :
//* 输入           : z
//* 输出         	 : 无
//*****************************************************************************/
void delay(uint16 z){
	uint8 x,y;
	for(x=z;x>0;x--){
		for(y=110;y>0;y--);
	}
}
/*******************************************************************************
* 函数名         : iic_start()
* 函数功能		 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 
*******************************************************************************/
void iic_start(){
	sda = 1;
	delay1;
	scl = 1;
	delay1;
	sda = 0;
	delay1;
	scl = 0;					//拉低scl,占用scl准备发送数据
	delay1;
}
/*******************************************************************************
* 函数名         : iic_stop()
* 函数功能		 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void iic_stop(){
	sda = 0;
	delay1;
	scl = 1;
	delay1;
	sda = 1;
}
/*******************************************************************************
* 函数名         : wait_ack()
* 函数功能		 : 等待从机拉低SDA
* 输入           : 无
* 输出         	 : 无
* 备注           : 
*******************************************************************************/
bit wait_ack(){
	uint8 max_time = 0;
	// scl = 1;					//释放SCL和SDA
	// delay1;
	// sda = 1;
	// delay1;					//iic_wr后scl和sda都已经释放
	while(sda){
		if(++max_time > 200){	//等待时间超过200,视为无应答,发送停止信号
			scl = 0;
			iic_stop();
			return 0;
		}
	}										
		scl = 0;							//应答正常,拉低scl形成第9个时钟脉冲
		return 1;
}
/*******************************************************************************
* 函数名         : iic_wr(uint8 dat)
* 函数功能		 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入           : dat
* 输出         	 : 
* 备注           : 
*******************************************************************************/
void iic_wr(uint8 dat){
	uint8 i;
	for(i=0;i<8;i++){
		if(dat & 0x80){		
			sda = 1;
		}
		else{
			sda = 0;
		}
		dat = dat << 1;
		scl = 1;							//拉高scl,让从机接收sda
		delay1;
		scl = 0;							//占用scl,让sda允许改变
		delay1;
	}
	scl = 1;					//释放SCL和SDA
	delay1;
	sda = 1;
	delay1;
}
/*******************************************************************************
* 函数名         : iic_rd()
* 函数功能		   : 使用I2c读取一个字节
* 输入           : 无
* 输出         	 : dat
* 备注           :
*******************************************************************************/
uint8 iic_rd(){
	uint8 i;
	uint8 dat;
	
	scl = 0;					//在改变sda前先确保scl为低电平
	delay1;
	sda = 1;					//主机释放sda,以便从机可以改变sda
	delay1;
	
	for(i=0;i<8;i++){
		scl = 1;				//拉高scl,接收从机发来的sda
		delay1;
		dat <<= 1;
		dat |= sda;
		scl = 0;
		delay1;				//拉低scl,让从机可以改变sda的电平
	}
	return dat;
}
/*******************************************************************************
* 函数名         : read_byte(uint8 add)
* 函数功能		 : 读取24c02的一个地址的一个数据
* 输入           : add
* 输出         	 : dat
*******************************************************************************/
uint8 read_byte(uint8 add){
	uint8 dat;
	
	iic_start();
	iic_wr(0xa0);				//写入设备地址 最后一位为0代表主机要写入数据到从机
	wait_ack();
	iic_wr(add);				//写入要读数据的地址
	wait_ack();
	iic_start();				
	iic_wr(0xa1);				//写入设备地址 最后一位为1代表主机要读从机数据
	wait_ack();
	dat = iic_rd();				//保存读出的数据
	iic_stop();					//结束读数据
	return dat;
}
/*******************************************************************************
* 函数名         : write_byte(uint8 add,uint8 dat)
* 函数功能		  : 往24c02的一个地址写入一个数据
* 输入           : add,dat
* 输出         	 : 无
*******************************************************************************/
void write_byte(uint8 add,uint8 dat){
	iic_start();
	iic_wr(0xa0);				//写入设备地址
	wait_ack();
	iic_wr(add);				//写入存放数据的地址
	wait_ack();
	iic_wr(dat);				//写入数据
	wait_ack();
	iic_stop();					//结束写数据
}
/*******************************************************************************
* 函数名         : scankey()
* 函数功能		  : 按键扫描
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
void scankey(){
	if(!key1){
		delay(1000);
		if(!key1){
			write_byte(1,temp);
			while(!key1);
		}
	}
	if(!key2){
		delay(1000);
		if(!key2){
			temp = read_byte(1);
			while(!key2);
		}
	}
	if(!key3){
		delay(1000);
		if(!key3){
			if(temp < 65536) temp++;
			while(!key3);
		}
	}
	if(!key4){
		delay(1000);
		if(!key4){
			if(temp > 0) temp--;
			while(!key4);
		}
	}
}
/*******************************************************************************
* 函数名         : display()
* 函数功能		 : 数码屏显示
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
void display(){
	uint8 i;
	com[0] = seg[temp/1000];
	com[1] = seg[temp%1000/100];
	com[2] = seg[temp%1000%100/10];
	com[3] = seg[temp%1000%100%10];
	for(i=0;i<4;i++){
		switch(i){
			case 0: ls138A = 0;ls138B = 0;ls138C = 0;break;
			case 1: ls138A = 1;ls138B = 0;ls138C = 0;break;
			case 2: ls138A = 0;ls138B = 1;ls138C = 0;break;
			case 3: ls138A = 1;ls138B = 1;ls138C = 0;break;
		}
		P0 = com[3-i];
		delay(300);
		P0 = 0x00;				//消隐
	}	
}

void main(){
	scl = 1;
	sda = 1;					//IIC初始化
	temp = 0;
	
	while(1){
		scankey();
		display();
	}
}

  实验现象没办法发动图出来,下图第一张是按K3将temp加到20,然后按K1保存到AT24C01的0x01这个地址。
  图1
  重新上电之后初始状态是这样的,temp因为掉电被清零了,所以数码管上显示0。
图2
  按下K2之后,将AT24C01中0x01地址的数据赋值给temp,经过显示程序刷新之后数码管上次重新显示0020,与图1是一样的。

  • 3
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FPGA读写EEPROM芯片AT24C02实验Verilog逻辑源码Quartus11.0工程文件, FPGA型号为CYCLONE4E系列中的EP4CE6E22C8,可以做为你的学习设计参考。 module iic_com( clk,rst_n, sw1,sw2, scl,sda, dis_data ); input clk; // 50MHz input rst_n; //复位信号,低有效 input sw1,sw2; //按键1、2,(1按下执行写入操作,2按下执行读操作) output scl; // 24C02的时钟端口 inout sda; // 24C02的数据端口 output[7:0] dis_data; //数码管显示的数据 //按键检测 reg sw1_r,sw2_r; //键值锁存寄存器,每20ms检测一次键值 reg[19:0] cnt_20ms; //20ms计数寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_20ms <= 20'd0; else cnt_20ms <= cnt_20ms+1'b1; //不断计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin sw1_r <= 1'b1; //键值寄存器复位,没有键盘按下时键值都为1 sw2_r <= 1'b1; end else if(cnt_20ms == 20'hfffff) begin sw1_r <= sw1; //按键1值锁存 sw2_r <= sw2; //按键2值锁存 end end //--------------------------------------------- //分频部分 reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间 reg[8:0] cnt_delay; //500循环计数,产生iic所需要的时钟 reg scl_r; //时钟脉冲寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_delay <= 9'd0; else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz else cnt_delay <= cnt_delay+1'b1; //时钟计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 3'd5; else begin case (cnt_delay) 9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样 9'd249: cnt <= 3'd2; //cnt=2:scl下降沿 9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化 9'd499: cnt <= 3'd0; //cnt=0:scl上升沿 default: cnt <= 3'd5; endcase end end `define SCL_POS (cnt==3'd0) //cnt=0:scl上升沿 `define SCL_HIG (cnt==3'd1) //cnt=1:scl高电平中间,用于数据采样 `define SCL_NEG (cnt==3'd2) //cnt=2:scl下降沿 `define SCL_LOW (cnt==3'd3) //cnt=3:scl低电平中间,用于数据变化 always @ (posedge clk or negedge rst_n) begin if(!rst_n) scl_r <= 1'b0; else if(cnt==3'd0) scl_r <= 1'b1; //scl信号上升沿

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值