单片机学习笔记---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

相关文章

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

C#使用iText获取PDF的trailer数据的代码示例

《C#使用iText获取PDF的trailer数据的代码示例》开发程序debug的时候,看到了PDF有个trailer数据,挺有意思,于是考虑用代码把它读出来,那么就用到我们常用的iText框架了,所... 目录引言iText 核心概念C# 代码示例步骤 1: 确保已安装 iText步骤 2: C# 代码程

Pandas处理缺失数据的方式汇总

《Pandas处理缺失数据的方式汇总》许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解Pandas如何表示缺失数据,... 目录缺失数据约定的权衡Pandas 中的缺失数据None 作为哨兵值NaN:缺失的数值数据Panda

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

k8s搭建nfs共享存储实践

《k8s搭建nfs共享存储实践》本文介绍NFS服务端搭建与客户端配置,涵盖安装工具、目录设置及服务启动,随后讲解K8S中NFS动态存储部署,包括创建命名空间、ServiceAccount、RBAC权限... 目录1. NFS搭建1.1 部署NFS服务端1.1.1 下载nfs-utils和rpcbind1.1

Redis高性能Key-Value存储与缓存利器常见解决方案

《Redis高性能Key-Value存储与缓存利器常见解决方案》Redis是高性能内存Key-Value存储系统,支持丰富数据类型与持久化方案(RDB/AOF),本文给大家介绍Redis高性能Key-... 目录Redis:高性能Key-Value存储与缓存利器什么是Redis?为什么选择Redis?Red

python库pydantic数据验证和设置管理库的用途

《python库pydantic数据验证和设置管理库的用途》pydantic是一个用于数据验证和设置管理的Python库,它主要利用Python类型注解来定义数据模型的结构和验证规则,本文给大家介绍p... 目录主要特点和用途:Field数值验证参数总结pydantic 是一个让你能够 confidentl

JAVA实现亿级千万级数据顺序导出的示例代码

《JAVA实现亿级千万级数据顺序导出的示例代码》本文主要介绍了JAVA实现亿级千万级数据顺序导出的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 前提:主要考虑控制内存占用空间,避免出现同时导出,导致主程序OOM问题。实现思路:A.启用线程池

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建