江科大51单片机AT24C02(I2C总线)学习笔记

ROM  不会掉电丢失,速度慢

RAM 会掉电丢失,速度快

这里的简化模型就是控制节点短路或断路

这是两种让地址总线和数据总线连接的方式

I2C 总线(Inter-Integrated Circuit,内部整合电路)是由 Philips 公司开发的一种简单、双向二线制同步串行总线。它只需要两根线(SCL,SDA)即可在连接于总线上的器件之间传送信息。

同步,半双工

所以的I2C设备SCL连在一,SDA连在一起

设备是SCL和SDA均要配置成开漏输出模式(即开关打开为1,关闭为0)

开漏输出和上拉电阻的共同作用实现了线与"的功能,此设计主要为了解决多机通信互相干扰的问题(如被控IC1在使用时其它设备断开)

如同四个人拉一个杆,谁发送数据谁拉杆给别人看

地址+读写标志位为一个字节

AT24C02的固定地址为1010,可配置地址本开发板上为000
所以SLAVEADDRESS+W为0xA0,SLAVEADDRESS+R为0xA1

I2C数据帧的应答机制主要有以下几个作用和原因:

  1. 确认数据接收
    • 在I2C通信中,每传输一个字节(8位数据)后,接收方需要通过应答位来通知发送方该字节已成功接收。如果接收方正确接收了数据,会在第9个时钟周期将SDA(数据线)拉低,表示应答。
  2. 错误检测
    • 如果接收方未正确接收数据(如由于噪声或其它原因导致数据出错),它会在第9个时钟周期保持SDA线为高电平,表示不应答(NACK,Not Acknowledge)。发送方收到NACK后,可以采取重发数据等措施,确保数据正确传输。
  3. 控制数据流
    • 在主从模式中,主设备和从设备之间通过应答来控制数据流。当主设备发送数据时,如果从设备在接收过程中发现它的接收缓冲区已满或处理不过来,它可以通过不应答来告诉主设备暂时停止数据发送。
  4. 结束通信
    • 主设备在发送完所有数据后,可以通过发送一个NACK信号来表示数据传输结束,然后释放总线(SDA和SCL线),从而允许其他设备进行通信。

AT24C02 是一个 2K 位串行 CMOS E2PROM, 内部含有 256 个 8 位字节,CATALYST 公司的先进 CMOS 技术实质上减少了器件的功耗。AT24C02 有一个 16 字节页写缓冲器。该器件通过 IIC 总线接口进行操作,有一个专门的写保护功能。

然后我们用操作系统的思想来写程序,I2C.C用来实现字节写和随机读的底层,而AT24C02.C用来把I2C.C中的函数串起来实现字节写和随机读的函数,main再调用这个函数

首先实现I2C.C

#include <REGX52.H>
#include "Delay.h"

sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;

void I2C_Start(void)
{
	I2C_SDA = 1;
	I2C_SCL = 1;
	I2C_SDA = 0;
	I2C_SCL = 0;
}

void I2C_Stop(void)
{
	I2C_SDA = 0;
	I2C_SCL = 1;
	I2C_SDA = 1;
}

void I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for (i = 0; i < 8; i++)
	{
		I2C_SCL = 0;
		I2C_SDA = Byte&(0x80>>i);
		I2C_SCL = 1;
		I2C_SCL = 0;
	}
}

unsigned char I2C_ReceiveByte(void)
{
	unsigned char Byte,i;
	I2C_SDA = 1;
	for (i = 0; i < 8; i++)
	{
		I2C_SCL = 1;
		if(I2C_SDA){Byte|=0x80>>i;}		
		I2C_SCL = 0;
	}
	return Byte;
}

void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA = AckBit;
	I2C_SCL = 1;
	I2C_SCL = 0;
}

unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit;
	I2C_SDA = 1;
	I2C_SCL = 1;
	AckBit = I2C_SDA;
	I2C_SCL = 0;
	return AckBit;
}

然后再实现AT24C02.C

#include <REGX52.H>
#include "I2C.h"

#define AT24C02_ADDRESS 0xA0 

void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}

unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();

	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS|0x01);
	I2C_ReceiveAck();
	Data = I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	return Data;
}

这样就可以写入和读取了,可以用这个程序测试

#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"

unsigned char Data;

void main()
{
	LCD_Init();
	AT24C02_WriteByte(1,66);
	Delay(5);
	Data = AT24C02_ReadByte(1);
	LCD_ShowNum(2,1,Data,3);
	while(1)
	{
		
	}
}
#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"

unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)	//K1按键,Num自增
		{
			Num++;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==2)	//K2按键,Num自减
		{
			Num--;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==3)	//K3按键,向AT24C02写入数据
		{
			AT24C02_WriteByte(0,Num%256);
			Delay(5);
			AT24C02_WriteByte(1,Num/256);
			Delay(5);
			LCD_ShowString(2,1,"Write OK");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
		if(KeyNum==4)	//K4按键,从AT24C02读取数据
		{
			Num=AT24C02_ReadByte(0);
			Num|=AT24C02_ReadByte(1)<<8;
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK ");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
	}
}

找这个逻辑可以再写一个掉电不丢失的秒表

可以更改KEY.c,换一种方式消抖,降低了CPU占用率

主循环如果长时间操作会影响显示的效果和定时的精准性

基本逻辑是利用定时器每20ms读取一次按键的值

按键按下的20ms进一次,但不会引起变化,会让LastState=0,NowState=当前按键值

松手的20ms会让按键的LastState=当前按键值,LastState=0

以此做到利用定时器中断消抖

#include <REGX52.H>
#include "Delay.h"

unsigned char Key_KeyNumber;

unsigned char Key(void)
{
	unsigned char Temp;
	Temp = Key_KeyNumber;
	Key_KeyNumber = 0;
	
	return Temp;
}

unsigned char Key_GetState()
{
	unsigned char Keynumber = 0;

	if(P3_1==0){Keynumber = 1;}
	if(P3_0==0){Keynumber = 2;}
	if(P3_2==0){Keynumber = 3;}
	if(P3_3==0){Keynumber = 4;}
	return Keynumber;
}

void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState = NowState;
	NowState = Key_GetState();
	if(LastState == 1 && NowState == 0)
	{
		Key_KeyNumber = 1;
	}
	if(LastState == 2 && NowState == 0)
	{
		Key_KeyNumber = 2;
	}
	if(LastState == 3 && NowState == 0)
	{
		Key_KeyNumber = 3;
	}
	if(LastState == 4 && NowState == 0)
	{
		Key_KeyNumber = 4;
	}
}

这时候主函数这样

#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
#include "Key.h"
#include "Timer0.h"

unsigned char KeyNum;
unsigned char Temp;

void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum = Key();
		if(KeyNum) Temp = KeyNum;
		Nixie(1,Temp);
	}
}


void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count >= 20)
	{
		T0Count = 0;
		Key_Loop();
	}
}

 这样就可以利用中断来让Nixie.c自己运行显示工作,而主函数只做修改工作

再修改一下Nixie.c
#include <REGX52.H>
#include "Delay.h"	//包含Delay头文件

unsigned char Nixie_Buf[9] = {0,10,10,10,10, 10,10,10,10};

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};

void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location] = Number;
}

//数码管显示子函数
void Nixie_Scan(unsigned char Location,Number)
{	
	P0=0x00;				//段码清0,消影
	switch(Location)		//位码输出
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number];	//段码输出
}

void Nixie_Loop(void)
{
	static unsigned char i = 1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i >= 9){i = 1;}
}

这段用来测试(main.c)
#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
#include "Key.h"
#include "Timer0.h"

unsigned char KeyNum;
unsigned char Temp;

void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum = Key();
		if(KeyNum)
		{
			Nixie_SetBuf(1,KeyNum);
			Nixie_SetBuf(2,KeyNum);
			Nixie_SetBuf(3,KeyNum);
		}
		Delay(10);
	}
}


void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	T0Count2++;
	if(T0Count1 >= 20)
	{
		T0Count1 = 0;
		Key_Loop();
	}
	if(T0Count2 >= 2)
	{
		T0Count2 = 0;
		Nixie_Loop();
	}
}

然后可以来实现秒表的功能

#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
#include "Key.h"
#include "Timer0.h"
#include "AT24C02.h"

unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;

void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum = Key();
		if(KeyNum == 1)
		{
			RunFlag = !RunFlag;
		}
		if(KeyNum == 2)
		{
			Min = 0;
			Sec = 0;
			MiniSec = 0;
		}
		if(KeyNum == 3)
		{
			AT24C02_WriteByte(0,Min);
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,MiniSec);
			Delay(5);			
		}
		if(KeyNum == 4)
		{
			Min = AT24C02_ReadByte(0);
			Sec = AT24C02_ReadByte(1);
			MiniSec = AT24C02_ReadByte(2);
		}
		Nixie_SetBuf(1,Min/10);
		Nixie_SetBuf(2,Min%10);
		Nixie_SetBuf(3,11);
		Nixie_SetBuf(4,Sec/10);
		Nixie_SetBuf(5,Sec%10);
		Nixie_SetBuf(6,11);
		Nixie_SetBuf(7,MiniSec/10);
		Nixie_SetBuf(8,MiniSec%10);
	}
}

void Sec_Loop(void)
{
	if(RunFlag)
	{
		MiniSec++;
		if(MiniSec >= 100)
		{
			MiniSec = 0;
			Sec++;
			if(Sec >= 60)
			{
				Sec = 0;
				Min++;
				if(Min >= 60)
				{
					Min=0;
				}
			}
		}
	}

}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2,T0Count3;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	if(T0Count1 >= 20)
	{
		T0Count1 = 0;
		Key_Loop();
	}
	T0Count2++;
	if(T0Count2 >= 2)
	{
		T0Count2 = 0;
		Nixie_Loop();
	}
	T0Count3++;
	if(T0Count3 >= 10)
	{
		T0Count3 = 0;
		Sec_Loop();
	}

}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值