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数据帧的应答机制主要有以下几个作用和原因:
- 确认数据接收:
- 在I2C通信中,每传输一个字节(8位数据)后,接收方需要通过应答位来通知发送方该字节已成功接收。如果接收方正确接收了数据,会在第9个时钟周期将SDA(数据线)拉低,表示应答。
- 错误检测:
- 如果接收方未正确接收数据(如由于噪声或其它原因导致数据出错),它会在第9个时钟周期保持SDA线为高电平,表示不应答(NACK,Not Acknowledge)。发送方收到NACK后,可以采取重发数据等措施,确保数据正确传输。
- 控制数据流:
- 在主从模式中,主设备和从设备之间通过应答来控制数据流。当主设备发送数据时,如果从设备在接收过程中发现它的接收缓冲区已满或处理不过来,它可以通过不应答来告诉主设备暂时停止数据发送。
- 结束通信:
- 主设备在发送完所有数据后,可以通过发送一个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();
}
}