单片机学习笔记---AT24C02数据存储

2024-02-13 09:44

本文主要是介绍单片机学习笔记---AT24C02数据存储,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

AT24C02数据存储

准备工作

代码讲解

I2C.c

模拟起始位置的时序

模拟发送一个字节的时序

模拟接收应答的时序

模拟接收一个字节的时序

模拟发送应答的时序

模拟结束位置的时序

I2C.h 

AT24C02.c

字节写:在WORD ADDRESS(字地址)处写入数据DATA

随机读:读出在WORD ADDRESS处的数据DATA

AT24C02.h

 main.c


上一节讲了AT24C02和I2C相关的工作原理,这一节开始代码演示!

准备工作

新创建一个工程:AT24C02数据存储

把要用到的程序模块添加进来,这些程序模块都是我前面的博客里演示过的了

然后新建文件main.c,  AT24C02.c,  AT24C02.h, I2C.c, I2C.h

代码讲解

接下来就开始代码讲解:

I2C.c

首先我们先写I2C.c

在这个文件里面我们是按照上一篇博客所讲的各部分时序来逐个定义函数,函数体的内容就是模拟每一部分时序写的。

首先我们得根据原理图重新定义一下引脚

#include <REGX52.H>sbit I2C_SCL=P2^1;//将P2^1重命名为I2C_SCL
sbit I2C_SDA=P2^0;//将P2^0重命名为I2C_SDA
模拟起始位置的时序

void I2C_Start(void)
{I2C_SDA=1;I2C_SCL=1;I2C_SDA=0;//SCL高电平期间,SDA从高电平切换到低电平I2C_SCL=0;
}
模拟发送一个字节的时序

void I2C_SendByte(unsigned char Byte)
{unsigned char i;for(i=0;i<8;i++)//一个字节循环8次发送8位数据{//SCL低电平期间,主机将数据位依次放到SDA线上(高位在前)I2C_SDA=Byte&(0x80>>i);//从最高位开始取出,依次右移一位,直到取到最低位I2C_SCL=1;//然后拉高SCL,从机将在SCL高电平期间读取数据位I2C_SCL=0;//发送完一个字节后拉低SCL(下降沿)}
}

 注意:SCL当VCC等于5V的情况下是1000kHz=1MHz,而我们单片机的IO口翻转一次最快也就1微秒(大于0.4微秒),就是500Hz,由此可见它的频率比IO口翻转一次的频率还要快,所以即使我们拉高SCL立马又拉低也不会影响它的最大时钟,它也能很快读取到数据。

模拟接收应答的时序

unsigned char I2C_ReceiveAck(void)
{unsigned char AckBit;I2C_SDA=1;//主机接收应答之前先把SDA拉高,I2C_SCL=1;//在SCL位高电平时,主机检测从机是否应答//接下来我们不管从机的时序是怎么变化的//所以这里我们没有在代码中体现从机是拉低了SDA还是默认SDA就是高电平//我们的单片机是主机,24C02是从机。//主机和从机的程序是不一样的,我们只写主机,从机是主动检测的,//从机读取数据的时候是程序自动完成的,//我们只需要把主机的时序模拟出来就好了。AckBit=I2C_SDA;//主机接收从机的应答//如果从机不想应答或者从机不存在就默认SDA还是高电平//从机应答的话就拉低了SDA赋值给AckBitI2C_SCL=0;return AckBit;
}
模拟接收一个字节的时序

上图SDA紫色部分就是从机控制总线的时候

unsigned char I2C_ReceiveByte(void)
{unsigned char i,Byte=0x00;I2C_SDA=1;//接收之前把SDA释放for(i=0;i<8;i++)//一个字节循环8次读取8位数据{//SCL低电平期间,从机将数据位依次放到SDA线上(高位在前)I2C_SCL=1;//然后拉高SCL,主机将在SCL高电平期间读取数据位if(I2C_SDA){Byte|=(0x80>>i);}//从最高位开始读,依次右移一位,直到读到最低位I2C_SCL=0;}return Byte;
}
模拟发送应答的时序

void I2C_SendAck(unsigned char AckBit)
{I2C_SDA=AckBit;//主机发送应答给从机I2C_SCL=1;//SCL高电平期间,从机检测主机是否应答I2C_SCL=0;
}
模拟结束位置的时序

void I2C_Stop(void)
{I2C_SDA=0;//不管主机/从机是否应答,都要拉低SDA。I2C_SCL=1;I2C_SDA=1;//SCL高电平期间,SDA从低电平切换到高电平
}

I2C.h 

最后在I2C.h文件中声明一下这六个函数: 

#ifndef __I2C_H__
#define __I2C_H__void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);#endif

AT24C02.c

接下来写AT24C02.c

AT24C02.c的内容主要是按我上一篇博客讲过的这个流程图来逐个调用I2C.c中的六个函数:

字节写:在WORD ADDRESS(字地址)处写入数据DATA

随机读:读出在WORD ADDRESS处的数据DATA(这其实是一种复合格式)

上一篇博客我写过AT24C02的固定地址为1010可配置地址本开发板上为000,所以从机的写地址SLAVE ADDRESS+W为0xA0,从机的读地址SLAVE ADDRESS+R为0xA1

所以我们可以先重定义从机的写地址,将从机的写地址重定义为AT24C02_ADDRESS,然后从机的读地址我们到时候直接给字节的最低位置1就可以了:

从机的写地址=0xA0=AT24C02_ADDRESS=1010 0000

从机的读地址=AT24C02_ADDRESS|0x01=1010 0000|0000 0001=1010 0001=0xA1

#include <REGX52.H>
#include "I2C.h"#define AT24C02_ADDRESS		0xA0 //将从机的写地址重定义为AT24C02_ADDRESS
字节写:在WORD ADDRESS(字地址)处写入数据DATA

按照字节写的流程图写函数体:

void AT24C02_WriteByte(unsigned char WordAddress,Data)//Data的类型和wordAddress一样
{I2C_Start();//起始位置I2C_SendByte(AT24C02_ADDRESS);//发送从机地址I2C_ReceiveAck();//接收应答I2C_SendByte(WordAddress);//发送字节地址I2C_ReceiveAck();//接收应答I2C_SendByte(Data);//发送数据I2C_ReceiveAck();//接收应答I2C_Stop();//结束位置
}
随机读:读出在WORD ADDRESS处的数据DATA

按照字节写的流程图写函数体:

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);//发送从机地址,将从机的写地址的最低位置1I2C_ReceiveAck();//接收应答//前面说过从机接收了什么数据怎么接收的数据我们在代码中不体现出来,//我们只写主机的程序Data=I2C_ReceiveByte();//我们只要把从机里面那个指定的字节地址处的数据读出来赋值给DataI2C_SendAck(1);//读取完一个字节可以不用再应答从机I2C_Stop();//结束位置return Data;//返回读出来的数据
}

AT24C02.h

声明一下这两个函数

#ifndef __AT24C02_H__
#define __AT24C02_H__void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);#endif

 main.c

 接下来我们在主程序里实现在WORD ADDRESS(字地址)处写入数据DATA,然后读出在WORD ADDRESS处的数据DATA,最后在液晶屏上显示我们写入并读出来的数据,结合独立按键的功能完成这个效果

先定义两个变量:

#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"unsigned char KeyNum;//键码
unsigned int Num;//初值,16位数据,范围是0~65535

主程序(请认真结合注释理清每一句代码的逻辑意思)

void main()
{LCD_Init();LCD_ShowNum(1,1,Num,5);while(1){KeyNum=Key();if(KeyNum==1)	//K1按键,Num自增{Num++;//第一次就按K1的时候,由0变成1LCD_ShowNum(1,1,Num,5);}if(KeyNum==2)	//K2按键,Num自减{Num--;//第一次就按K2的时候,由0变成65535LCD_ShowNum(1,1,Num,5);}if(KeyNum==3)	//K3按键,向AT24C02写入数据{AT24C02_WriteByte(0,Num%256);//在字地址0处,写入Num的低八位//%256是16进制取低8位的方法//因为Num是unsigned int型占2个字节即16位数据//所以把Num的低8位取出来写入Delay(5);//写进去不能立马读出来,ROM一般要2~3ms才能写完//手册上的写周期是5ms,意味着我们每次写入之后需要Delay 5msAT24C02_WriteByte(1,Num/256);在字地址1处,写入Num的高八位// 或256是16进制取高8位的方法Delay(5);LCD_ShowString(2,1,"Write OK");Delay(1000);//延时1000ms=1sLCD_ShowString(2,1,"        ");//第2行第1列清屏}if(KeyNum==4)	//K4按键,从AT24C02读取数据{Num=AT24C02_ReadByte(0);//低八位的字地址是0,把低八位数据读出来赋值给NumNum|=AT24C02_ReadByte(1)<<8;//高八位的字地址1,把八位数据读出来每个左移8就是高八位LCD_ShowNum(1,1,Num,5);LCD_ShowString(2,1,"Read OK ");Delay(1000);LCD_ShowString(2,1,"        ");}}
}

注意:

写进去立马读出来能读到吗?

不能!

为什么不能?

我们看一下手册上的写周期是5ms

这个写周期意味着我们每次写入之后需要Delay 5ms,写的数据帧stop一旦结束,它内部要执行一些操作,把数据写出去。所以ROM要比RAM慢一些,因为ROM有个写入时间,它这个是最长5ms,经过实测写个数据大概两三毫秒就能写完,所以我们每次写入之后需要Delay 5ms。

效果展示

效果请看视频:

AT24C02数据存储

以上就是本篇内容!

之后有时间还会补充一个“秒表(定时器扫描按键数码管)”的示例代码,敬请关注!

源码会放在评论区,自取!如有问题可评论区留言。

这篇关于单片机学习笔记---AT24C02数据存储的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/705172

相关文章

MySQL InnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据

《MySQLInnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据》mysql的ibdata文件被误删、被恶意修改,没有从库和备份数据的情况下的数据恢复,不能保证数据库所有表数据... 参考:mysql Innodb表空间卸载、迁移、装载的使用方法注意!此方法只适用于innodb_fi

mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据

《mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据》文章主要介绍了如何从.frm和.ibd文件恢复MySQLInnoDB表结构和数据,需要的朋友可以参... 目录一、恢复表结构二、恢复表数据补充方法一、恢复表结构(从 .frm 文件)方法 1:使用 mysq

mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespace id不一致处理

《mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespaceid不一致处理》文章描述了公司服务器断电后数据库故障的过程,作者通过查看错误日志、重新初始化数据目录、恢复备... 周末突然接到一位一年多没联系的妹妹打来电话,“刘哥,快来救救我”,我脑海瞬间冒出妙瓦底,电信火苲马扁.

golang获取prometheus数据(prometheus/client_golang包)

《golang获取prometheus数据(prometheus/client_golang包)》本文主要介绍了使用Go语言的prometheus/client_golang包来获取Prometheu... 目录1. 创建链接1.1 语法1.2 完整示例2. 简单查询2.1 语法2.2 完整示例3. 范围值

javaScript在表单提交时获取表单数据的示例代码

《javaScript在表单提交时获取表单数据的示例代码》本文介绍了五种在JavaScript中获取表单数据的方法:使用FormData对象、手动提取表单数据、使用querySelector获取单个字... 方法 1:使用 FormData 对象FormData 是一个方便的内置对象,用于获取表单中的键值

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE