一、实验原理:
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件。然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。
二、实验电路图
三、代码详解:
“i2c.h”
#ifndef _I2C_H_
#define _I2C_H_
#include <reg52.h>
#include <intrins.h>
/*定义关键字*/
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
sbit SCL = P3^0;
sbit SDA = P3^1;
sbit WP = P3^4;
/*声明函数*/
void IIC_Start(void); // 起始信号
void IIC_Stop(void); // 停止信号
void IIC_Asked(void); // 应答信号
bit IIC_WaitAsk(void); // 等待应答
void IIC_NonAsked(void); // 非应答信号
void IIC_SendByte(unsigned char Data); // 发送一个字节
unsigned char IIC_ReceiveByte(void); // 接收一个字节
void WriteToEE(unsigned char Addr,unsigned char Data); // 写入EEPROM,24C01-08使用
unsigned char ReadFromEE(unsigned char Addr); // 从EEPROM读出,24C01-08使用
#endif
“i2c.c”
#include "i2c.h"
#define RdAddr 0xA1 // 从地址读出 AT24C01:1010A2A1A01(1010-000-1)
#define WrAddr 0xA0 // 写入从地址 AT24C01:1010A2A1A00(1010-000-0)
unsigned char SystemError; // I2C系统错误字
/*开始I2C相关设置 */
void Delay_us(unsigned int m) {// 延时m (us)
while(m > 0) {
_nop_();
m--;
}
}
void IIC_Start(void) {// 起始信号
EA = 0;
SDA = 1;
SCL = 1;
Delay_us(10);
SDA = 0;
Delay_us(10);
SCL = 0;
}
void IIC_Stop(void) {// 停止信号
SCL = 0;
SDA = 0;
Delay_us(10);
SCL = 1;
Delay_us(10);
SDA = 1;
EA = 1;
}
void IIC_Asked(void) {// 应答信号
SDA = 0;
Delay_us(10);
SCL = 1;
Delay_us(10);
SCL = 0;
}
bit IIC_WaitAsk(void) {// 等待应答
unsigned char ErrTime = 255;// 检测次数255
SDA = 1;
Delay_us(10);
SCL = 1;
Delay_us(10);
while(SDA) {// 如果非应答
ErrTime--;// 次数减1
if(!ErrTime) {// 如果次数到0
IIC_Stop();// 停止信号
SystemError = 0x11;// 置系统错误字=0x11
return 0;// 返回0
}
}
SCL = 0;
return 1;// 如果应答,返回1
}
void IIC_NonAsked(void) {// 非应答
SDA = 1;
Delay_us(10);
SCL = 1;
Delay_us(10);
SCL = 0;
}
void IIC_SendByte(unsigned char Data) {// 发送一个字节
unsigned char tData,i;
tData = Data;
i = 8;
while(i > 0) {
SCL = 0;
_nop_();
if((tData & 0x80) == 0x80) { SDA = 1; }
else { SDA = 0; }
tData <<= 1;
Delay_us(10);
SCL = 1;
Delay_us(10);
i--;
}
SCL = 0;
}
unsigned char IIC_ReceiveByte(void) {// 接收一个字节
unsigned char Data,i;
i = 8;
Data = 0x00;
SDA = 1;
while(i > 0) {
Data <<= 1;
SCL = 0;
Delay_us(10);
SCL = 1;
Delay_us(10);
if(SDA == 1) { Data |= 0x01; }
i--;
}
SCL = 0;
return Data;
}
// ******* 写入EEPROM *******
// ******* 输入:Addr 地址;Data 数据 *******
void WriteToEE(unsigned char Addr,unsigned char Data) {
IIC_Start(); // 发起始信号
IIC_SendByte(WrAddr); // 发写入控制字
IIC_WaitAsk(); // 等待应答
IIC_SendByte(Addr); // 发送地址
IIC_WaitAsk(); // 等待应答
IIC_SendByte(Data); // 发送数据
IIC_WaitAsk(); // 等待应答
IIC_Stop(); // 发停止信号
}
// ******* 从EEPROM读出 *******
// ******* 输入:Addr 地址;返回数据 *******
unsigned char ReadFromEE(unsigned char Addr) {
unsigned char Data;
IIC_Start(); // 发起始信号
IIC_SendByte(WrAddr); // 发写入控制字
IIC_WaitAsk(); // 等待应答
IIC_SendByte(Addr); // 发送地址
IIC_WaitAsk(); // 等待应答
IIC_Start(); // 发重复起始信号
IIC_SendByte(RdAddr); // 发读出控制字
IIC_WaitAsk(); // 等待应答
Data = IIC_ReceiveByte(); // 接收一个字节到Data
IIC_NonAsked(); // 非应答信号
IIC_Stop(); // 发停止信号
return Data; // 返回Data
}
**注意:**这个工程项目中去掉应答信号函数void IIC_Asked(void)也行,因为没用上。写的目的是提供大家以后参考使用。
也因为多余了这个应答函数,因此编译时,编译器会报一个warning: SEGMENT: ?PR?IIC_ASKED?I2C。意思是有个函数声明了却未使用。但不影响实验结果。
“main.c”
#include <reg52.h>
#include "i2c.h"
unsigned char code seg[16]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
unsigned char code pos[4]=
{0xfe,0xfd,0xfb,0xf7};
unsigned char disbuf[4]={0,0,0,0};
unsigned char counter = 0;//定时器中断计数器
unsigned char index = 0;//地址索引
void delay(unsigned int x) {
unsigned char i;
while(x--) {
for(i=0;i<125;i++) ;
}
}
void display(void){
unsigned char i;
for(i=0;i<4;i++){
P0 = seg[disbuf[i]]; //段码
P1 = pos[i]; //位选码
delay(2);
P1=0xff; //消隐
}
}
void main(void){
TMOD=0x01;
TH0=(65536-10000)/256;
TL0=(65536-10000)%256;
EA=1;
ET0=1;
TR0=1;
WP=0;
for(index=0;index<=0x1f;index++){// 连续写入24C01
WriteToEE(index,index);// index地址写入index
delay(9);//必须等待写周期结束
}
index=0;
WP=1;
while(1){ display(); }
}
void timer0() interrupt 1{
unsigned char Data=0;
TH0=(65536-10000)/256;
TL0=(65536-10000)%256;
counter++;
if (counter==100){
counter=0;
Data= ReadFromEE(index);// 读出index地址数据
disbuf[3]=index/0x10; //地址、数据写入显示缓冲器
disbuf[2]=index%0x10;
disbuf[1]=Data/0x10;
disbuf[0]=Data%0x10;
index++;
if(index==0x2f){index=0;}
}
}
仿真结果:
实验结果:单片机成功将0到15这十六个数写入AT24C01的存储区中,并通过I2C总线的读取方式,将存放在AT24C01存储区的这十六个数依次读取出来并在数码管上显示出来。
上图为单片机成功写入16个数进入AT24C01存储区并依次读取数据并在数码管上显示的图片。