AT24C02(I2C总线)

本文介绍了如何利用AT24C02非易失性存储器通过I2C总线进行数据存储,并配合LCD1602显示屏及秒表功能。通过定时器替代传统的delay函数,解决按键消抖和显示同步问题。
摘要由CSDN通过智能技术生成


前言

介绍AT24C02和I2C总线规则,通过定时器代替delay函数消抖、消影。实现使用AT24C02写取数据、秒表案例


一、介绍部分

存储器介绍

RAM在没有电源时数据易丢失,可以快速存取。
ROM在没有电源时数据不易丢失,存储速度不快,刚开始只能读取数据不能写入,随着发展现在已经可以写入数据、删除数据和读取数据了。

在这里插入图片描述

存储器简化模型

存储器的数据读取和矩阵键盘类似,是扫描地址线,针对每条地址线的每条数据进行读取。

在这里插入图片描述

AT24C02介绍

在这里插入图片描述

引脚即原理图

官方电路图
在这里插入图片描述
完整电路图
在这里插入图片描述

引脚
在这里插入图片描述
内部结构框图
在这里插入图片描述

I2C总线

I2C总线介绍

在这里插入图片描述

I2C电路规范

在这里插入图片描述

弱上拉与开漏输出模式

在这里插入图片描述

I2C的时序结构

在这里插入图片描述
发送一个字节
在这里插入图片描述
接收一个字节
在这里插入图片描述
发送应答与接收应答
在这里插入图片描述

I2C数据帧

发送一帧数据
在这里插入图片描述
接收一帧数据
在这里插入图片描述
先发送再接收数据帧
在这里插入图片描述
字节写与随机读
在这里插入图片描述

二、使用方法

1.使用AT24C02存储数据(配合LCD1602)

将I2C的所有时序结构封装成函数放入I2C.c文件中
将字节写与随机读按照数据帧封装成函数放入AT24C02.c文件中

I2C.c内容如下

//  I2C的时序结构

#include <REGX52.H>
// 引脚声明
sbit I2C_SCL = P2^1;	// 相当于开关
sbit I2C_SDA = P2^0;	// 相当于存储数据
/**
   * @brief 	I2C开始
   * @param		无
   * @retval	无
   */
void I2C_Start(void){
	I2C_SDA = 1;
	I2C_SCL = 1;
	I2C_SDA = 0;
	I2C_SCL = 0;
}
/**
   * @brief		I2C终止
   * @param		无
   * @retval	无
   */
void I2C_Stop(void){
	I2C_SDA = 0;
	I2C_SCL = 1;
	I2C_SDA = 1;
}
/**
   * @brief		I2C发送一个字节
   * @param		Byte,要发送的字节
   * @retval	无
   */
void I2C_SendByte(unsigned char Byte){
	unsigned char i;
	for(i=0;i<8;i++){
		I2C_SDA = Byte & (0x80>>i);
		I2C_SCL = 1;	// 高电平期间读取数据
		I2C_SCL = 0;	
	}
}
/**
   * @brief		I2C接收一个字节
   * @param		无
   * @retval	Byte,从SDA读取到的数据
   */
unsigned char I2C_ReceiveByte(void){
	unsigned char i,Byte = 0x00;
	I2C_SDA = 1;	// 释放SDA
	for(i=0;i<8;i++){
		I2C_SCL = 1;	// 高电平期间读取数据
		if(I2C_SDA){
			Byte |= (0x80>>i);
		}
		I2C_SCL = 0;
	}
	return Byte;
}
/**
   * @brief		I2C发送应答
   * @param		ACK,要发送应答的内容
   * @retval	无
   */
void I2C_SendACK(unsigned char ACK){
	// 0表示应答,1表示非应答
	I2C_SDA = ACK;
	I2C_SCL = 1;
	I2C_SCL = 0;
}
/**
   * @brief		I2C接收应答
   * @param		无
   * @retval	ACK,从SDA接收到的应答
   */
unsigned char I2C_ReceiveACK(void){
	unsigned char ACK;
	I2C_SDA = 1;	// 释放SDA
	I2C_SCL = 1;
	ACK = I2C_SDA;
	I2C_SCL = 0;
	return ACK;
}

AT24C02.c内容如下

#include <REGX52.H>
#include "I2C.h"
// 从机地址
#define SLAVE_ADDRESS 0xa0
/**
   * @brief		字节写
   * @param		WordAddress,要写入数据的地址 Data,要写入的数据
   * @retval	无
   */
void AT24C02_WriteByte(unsigned char WordAddress,Data){
	I2C_Start();
	I2C_SendByte(SLAVE_ADDRESS);
	I2C_ReceiveACK();
	I2C_SendByte(WordAddress);  // 发送地址内容
	I2C_ReceiveACK();
	I2C_SendByte(Data);					// 发送数据内容
	I2C_ReceiveACK();
	I2C_Stop();
}
/**
   * @brief		随机读取
   * @param		WordAddress,从此地址读取数据
   * @retval	Data,所读取的数据
   */
unsigned char AT24C02_ReadByte(unsigned char WordAddress){
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(SLAVE_ADDRESS);
	I2C_ReceiveACK();
	I2C_SendByte(WordAddress);  // 发送地址内容
	I2C_ReceiveACK();
	I2C_Start();
	I2C_SendByte(SLAVE_ADDRESS | 0x01);
	I2C_ReceiveACK();
	Data = I2C_ReceiveByte();
	I2C_SendACK(1);
	I2C_Stop();
	return Data;
}

main.c内容如下:

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

unsigned char KeyNum;
unsigned int Data;
void main(){
	LCD_Init();
	LCD_ShowNum(1,1,Data,5);

	while(1){
		KeyNum = Key();
		// 数据+1
		if(KeyNum == 1){
			Data++;
			LCD_ShowNum(1,1,Data,5);
		}
		// 数据-1
		if(KeyNum == 2){
			Data--;
			LCD_ShowNum(1,1,Data,5);
		}
		// 将数据存入AT24C02中
		if(KeyNum == 3){
			// AT24C02的写周期为5ms,比单片机运行时间慢,需要延时才能读出
			AT24C02_WriteByte(0,Data%256);	// 高八位
			Delayms(5);
			AT24C02_WriteByte(1,Data/256);	// 低八位
			Delayms(5);
			LCD_ShowString(2,1,"write success");
			Delayms(1000);
			LCD_ShowString(2,1,"             ");
		}
		// 从AT24C02读取数据
		if(KeyNum == 4){
			Data = AT24C02_ReadByte(0);
			// char与int运算时 char会内部转化为int型参与运算  所以读出值左移八位不会溢出
			Data |= AT24C02_ReadByte(1)<<8;
			LCD_ShowNum(1,1,Data,5);
			LCD_ShowString(2,1,"read success");
			Delayms(1000);
			LCD_ShowString(2,1,"            ");
		}
	}
}

2.实现秒表

使用定时器代替delay消影、消抖

改写按键key.c

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

unsigned char KeyRealNum;	// 用于接收按键值
unsigned char Key(){
	// 中间变量接收按键值
	unsigned char Temp = KeyRealNum;
	KeyRealNum = 0;	// 先归零再进行下次判断
	return Temp;
}
// 按键状态
unsigned char Key_State(){
	unsigned char KeyNum = 0;

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

void Key_Loop(){
	static unsigned char OldKeyState,NewKeyState;
	OldKeyState = NewKeyState;
	NewKeyState = Key_State();
	
	// 表示松开了此按键
	if(NewKeyState==0 && OldKeyState==1){
		KeyRealNum = 1;
	}
	if(NewKeyState==0 && OldKeyState==2){
		KeyRealNum = 2;
	}
	if(NewKeyState==0 && OldKeyState==3){
		KeyRealNum = 3;
	}
	if(NewKeyState==0 && OldKeyState==4){
		KeyRealNum = 4;
	}
	
}

改写数码管Nixie.x

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

//数码管显示缓存区
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};

/**
  * @brief  设置显示缓存区
  * @param  Location 要设置的位置,范围:1~8
  * @param  Number 要设置的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	// 第几位显示啥
	Nixie_Buf[Location]=Number;
}

/**
  * @brief  数码管扫描显示
  * @param  Location 要显示的位置,范围:1~8
  * @param  Number 要显示的数字,范围:段码表索引范围
  * @retval 无
  */
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];	//段码输出
}

/**
  * @brief  数码管驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Nixie_Loop(void)
{
	static unsigned char i=0;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=0;}
}

秒表实现主要代码main.c

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

unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;		// 秒表停止、运行状态

void main()
{
	Time0_Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)			//K1按键按下
		{
			RunFlag=!RunFlag;	//启动标志位翻转
		}
		if(KeyNum==2)			//K2按键按下
		{
			Min=0;				//分秒清0
			Sec=0;
			MiniSec=0;
		}
		if(KeyNum==3)			//K3按键按下
		{
			AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02
			Delayms(5);
			AT24C02_WriteByte(1,Sec);
			Delayms(5);
			AT24C02_WriteByte(2,MiniSec);
			Delayms(5);
		}
		if(KeyNum==4)			//K4按键按下
		{
			Min=AT24C02_ReadByte(0);	//读出AT24C02数据
			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);
	}
}

/**
  * @brief  秒表驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
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 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
		T0Count1=0;
		Key_Loop();	//20ms调用一次按键驱动函数
	}
	T0Count2++;
	if(T0Count2>=2)
	{
		T0Count2=0;
		Nixie_Loop();//2ms调用一次数码管驱动函数
	}
	T0Count3++;
	if(T0Count3>=10)
	{
		T0Count3=0;
		Sec_Loop();	//10ms调用一次数秒表驱动函数
	}
}


补充

使用定时器代替delay消抖、消影可以对程序的运行影响更小,不会有按键一直按下时导致整个进程等待的情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值