首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

iic时序结构和AT24C02储存器

  • 25-04-24 20:01
  • 4652
  • 8750
blog.csdn.net

声明:本人跟随b站江科大学习,本文章是观看完视频后的一些个人总结和经验分享。(仅仅是个人的理解和看法,可能有误,也希望大家能帮忙纠正,谢谢大家❤️)

目录

课前问题

正文部分

一、IIC和AT24C02理论部分

1.基本介绍

2.电路规范

1)所有的I2C设备的SCL连在一起,SDA连在一起:

2)设备的SCL和SDA均要配置成开漏输出模式:

3)SCL和SDA各添加一个上拉电阻,阻值一般为4.7千欧左右:

3.线与

4.主机和从机和从机的区别

5.时序结构

        解释为什么需要释放SDA,以及为什么SDA置1就是释放总线

        那么为什么SDA置1就是释放总线呢?

        那为什么只有接受字节和接受应答才需要释放总线呢?

6.数据帧

iic数据帧

AT24C02的数据帧

二、IIC和AT24C02代码部分

        1.iic

        2.at24c02


相信各位小伙伴看完江科大这期AT24C02(I2C总线)的视频还是会有点疑惑的,首先肯定是不知道iic的电路规范为什么是这样的,其次是时序结构里面出现了“释放总线”这个初学比较抽象的概念,对主机和从机的概念也比较模糊,然后就是iic和AT24C02的数据帧及其代码的敲写。

在介绍下面内容之前先给大家留几个问题,在看完文章之后,可以问一下自己这几个问题,觉得没问题,那恭喜你这篇文章你理解的差不多了💪💪💪

课前问题

  • SCL和SDA分别是什么
  • “线与”的作用
  • 为什么要使用开漏输出和上拉电阻
  • 为什么接受字节和接受应答需要释放SDA,发送字节和发送应答呢
  • SDA置1为什么就能够释放总线

正文部分

一、IIC和AT24C02理论部分

1.基本介绍

b54eaa6e281c48549b5963c585172254.png

 负责同步作用的是我们的SCL串行时间线,半双工则是因为接受和传输都只用SDA串行数据线这一根线,带数据应答则是发送字节和接受字节这里都会产生应答,表示有没有接收到数据(后面还会细说)。

PS:同步和,异步和半双工不太清楚的小伙伴可以去阅读一下这篇文章,以加强理解。单片机学习笔记---串口通信(1)_单片机db9-CSDN博客

2.电路规范

553bfb1aa9224c8abf6025faea1f8710.png

1)所有的I2C设备的SCL连在一起,SDA连在一起:

        在这里是为区别于串口通信使TXD和RXD交叉连接

2)设备的SCL和SDA均要配置成开漏输出模式:

        在了解开漏输出之前,先给大家介绍一下弱上拉(我们的IO口其实就是一种弱上拉,而且其实也是一种开漏输出,只不过加了一个弱上拉电阻而已),目的是增强IO口的驱动能力,那为什么能够增强IO口的驱动能力呢?

47146606512846c1b7b808d37a8c11b4.png11f2090cf2ae451a813ef2cb900316a7.jpeg

看这个图,其中R为弱上拉电阻,当S处给0时,开关闭合,给1开关断开(可能是因为是三极管中的PNP类型),如果开关闭合,则会连通GND,M端输出则为0,如果开关断开,则直接连接与VCC,M端输出为1,由于高电平驱动力弱,低电平驱动能力强,所以当S处给0时,IO口的驱动能力更强。

那我们的开漏输出其实就是没有这个弱上拉电阻

094ca890105840caabded8898239410f.png

那这个时候我们S给0,M端输出还是0,但是当S给1时,M端就处于一种名为高阻态的状态,即引脚浮空,输出既不是1也不是0。那么显而易见上拉电阻那部分电路的作用其实就出来了,即实现外部上拉,让我们的IO口具有输出1的能力。

PS:通常开漏输出和推挽输出的区别就在于,推挽输出有上拉电阻那部分的电路,使该输出模式具有输出1的能力。

介绍完弱上拉和开漏输出之后,那究竟为什么要将SCL和SDA全部配置成开漏输出呢?

原来是因为我们的iic能够实现多机通信,当CPU想要和其中一个从机单独进行通信时,其他从机或多或少会对通信产生影响,那么我们在与该从机通讯时,需要将其他从机全部断开,这个时候我们只需要利用开漏模式,将引脚给1,那么该引脚浮空,就当于断开了。

PS:你们可能还会有个疑问,那就是如果SDA和SCL全部采用弱上拉,引脚全部置1,由于高电平驱动能力弱,相当于断开从机,这样看似乎也行。但是要知道全部采取弱上拉的时候,弱上拉电阻不断并联,总阻值变小,上拉能力越强(大概原因是因为当电压U不变时,I=U/R,R越小I越大,上拉能力越强),此时其他从机的上拉电阻多少会对正在通信的从机产生影响,故还是采用开漏输出最好。

3)SCL和SDA各添加一个上拉电阻,阻值一般为4.7千欧左右:

上面说了开漏输出是不具备输出1的能力的,CPU(这里的CPU相当于开关S,M相当于SDA或者SCL)如果想发送1,由于没有东西能把电平拉高,我们的SDA和SCL就不能等于1,但是我们iic作为一种通信,两根通信线不可能不具备输出1的能力(即SDA=1和SCL=1),所以我们在SCL和SDA各加上一个上拉电阻,使具备输出1的能力。所以当其他从机都断开时候,我们CPU如果要发送0(即开关S=0),那么从机(从机连着GND)就连上了,这个时候M端就输出0(即SDA或者SCL等于0),如果要发送1(S=1),那从机就断开连接了,上拉电阻会自动把M端电平拉高,输出1。

我们这里所说的SDLA和SCL=1,电平拉高实际上就是拉高iic总线,SDA,SCL=0就是拉低总线

083dc985c84c4144af26129ec411aa3f.png

4)这一点我放在下面讲

其中2)和3)的理解很重要,两个的作用也是为了解决iic多机通信互相干扰的问题

3.线与

上面说开漏输出和上拉电阻共同实现了"线与"功能,那什么是“线与”呢?

这里简单介绍一下线与:是一种存在于总线上的逻辑关系,“与”顾名思义,即逻辑与“&”,举个例子,现在所有从机的输出都为1,那么1&1还是1,但是如果有一个从机输出为0,那么0&任何数都为0,那么其余从机无论输出什么,最后都会变成0。

所以我们使用开漏输出和上拉电阻后断开了其他从机,总线上只有主机和被通信从机的线与关系,由于iic支持多主机和多从机进行通信,那么如何判断谁占用了总线呢?线与机制就是用来实现总线仲裁的,判断谁占用了总线。(这里有个低电平优先原则:哪个设备先发送低电平,谁就控制总线;我是0,那么你们输出是0是1都要看我,差不多就这个意思)

PS:不清楚总线制裁的可以看看这篇文章IIC总线仲裁机制_iic仲裁机制-CSDN博客

4.主机和从机和从机的区别

主机:既能够操作SCL线也能够操作SDA(虽然能够有多个主机,但是由于总线制裁,所以只能有一个主机操作SCL)

从机:只能够操作SDA线

5.时序结构

6a6548fee5d543a9a9fcaae50f2fd6e2.png

起始和终止的时序比较简单,需要注意的地方是,“起始”里面的SCL在SDA电平切换完成后,自身需要从高电平下拉到低电平,“停止”则不需要

7337bcf3ed8d4f7c97312fe18a92ed0b.png

发送字节需要注意的点:

  • SCL低电平期间开始放数据
  • 主机将数据位依次放在SDA线上(即主机操纵SDA线)
  • 高位在前(即先读取二进制的最高位)
  • 拉高SCL,从机读取数据
  • 拉高SCL读完数据后,SCL还要拉低
  • 图中SDA有两条线,并不是真的有两条,而是有两种电平转换的情况,低转高,或者高转低

92800c467d0c4ac4bc3aaf945e082a09.png

接收字节需要注意的点:

  • SCL低电平期间开始放数据
  • 从机将数据位依次放在SDA线上(即从机操纵SDA线)
  • 高位在前(即先读取二进制的最高位)
  • 拉高SCL,主机读取数据
  • 拉高SCL读完数据后,SCL还要拉低
  • 主机在接收之前,需要释放SDA(SDA置1)
        解释为什么需要释放SDA,以及为什么SDA置1就是释放总线

        上面我们讲过从机和主机都能操纵SDA,之间存在线与关系。但是接受字节时要求是从机操纵SDA,如果不释放SDA将SDA控制权交给从机的话,那么由于线与关系,我们主机若果输出0的话,那么无论从机发送啥,输出都会变为0,这样肯定是不行的。所以我们在主机接受数据之前要将SDA置1,将SDA控制权交给从机,是从机能够正常发送数据。

        那么为什么SDA置1就是释放总线呢?

        我们刚刚讲过弱上拉,但是在这里SDA线既是M端又是开关S。当SDA置0时,开关闭合,SDA靠的是从机或者主机的VCC将其拉低为低电平,根据低电平优先原则,那就相当于掌握了SDA的控制权;当SDA置1时,开关断开,SDA靠的是上拉电阻外部上拉使其为高电平,那么由于开关断开,你失去将SDA置0的能力了,就是失去对SDA的控制权,那么这个时候其他设备就可以根据总线制裁抢SDA的控制权,所以我们一般说SDA置1就是释放总线

        那为什么只有接受字节和接受应答才需要释放总线呢?

        其实发送字节和发送应答都也释放了总线,因为我们的SDA控制权不可能给从机之后不归还给主机,从机在发送完数据后,从机内部其实会自动释放总线,控制权就又回到了主机的手里,在代码里面我们不写从机代码,看不到从机释放总线的过程,就会误认为发送不需要释放总线

  • 图中SDA有两条线,并不是真的有两条SDA,而是有两种电平转换的情况,低转高,或者高转低

ca0908caae394432b464ef12d9451d83.png

发送应答和接受应答差不多,都是为了让主机(从机)知道从机(主机)接受到了字节,区别有以下几点:

        1)发送应答用于接收字节之后,接收应答用于发送字节之后

        2)发送应答:主机在SCL低电平时候变换SDA发送应答位,从机在SCL高电平时读取(由于此时主机变换SDA,所以操纵SDA的为主机)

              接收应答:从机在SCL低电平时候变换SDA发送应答位,主机在SCL高电平时读取(由于此时从机变换SDA,所以操纵SDA的为从机)

        3)由于在发送应答和接收应答中,操纵SDA的设备不一样,所以在进行接收应答是需要对SDA置1释放总线

6.数据帧

iic数据帧

上面的每一个时序下面都有对应方块表示,这里数据帧这样表示更方便

960bcb1a616f4f7a9fc89e1dfa1c0670.png

整个数据帧比较容易看懂,即起始(S)->发送从机地址和读写位(S:SLAVE ADDRESS+W)->接收应答(RA:0)->发送我们其他需要传输的字节(S:BYTE)->接收应答(RA:0)->……->终止(P)

需要介绍的就是从机地址和读写位:

79ac9ad41dae4a0996accb79e8ccf91a.png

前七位就是我们的从机地址,最后一位是读写位(1为读,0为写),从机地址决定我们是读去(写入)哪一个从机。

PS:前四位A6~A3如果是同样的从机,那就是是固定的,我们每个从机申请iic通信时候,公司会给该设备一个固定的数字,AT24C02就是1010,后面A2~A0三位则是用来处理一个主线挂载多个同类型的从机的情况,控制这三位就可以选择多个相同类型从机中的一个进行输出

6dcb3dbe64b546bc85d0d01966582ea0.png

如图为我们AT24C02里的一种储存介质,这里的E0,E1,E2就是我们的A0,A1,A2,可以看到都接着GND,我们就全配置为0即可

读写位就是控制向从机中输出数据还是读取从机中的数据,发送应答读写位为写

666341d4e7e0400985fe22663ae19a2d.png

fced0e5f1d3741e185999bb8679cb2b2.png

接受的数据帧也是一样的,都一个道理,这里读写位为读而已,最后的(SA:1),发送应答为0为1都可以,复合格式就是移除发送数据帧的P再加上接收数据帧

AT24C02的数据帧

 b8e253a133f54bee88fc77816bff93f1.png

先了解一下字地址(WORD ADDRESS),即AT24C02中存储空间的地址(有2^8个位置,0~255)。

我们发现与iic的数据帧相比,AT24C02多了一个需要发送字地址的过程,其他的都大差不差。

二、IIC和AT24C02代码部分

        写代码之前,给大家强调一句话,只写主机代码,不写从机代码,从机程序自动执行

        1.iic

  1. #include
  2. //引脚定义
  3. sbit I2C_SCL=P2^1;
  4. sbit I2C_SDA=P2^0;

      先写起始和终止的时序

  1. /**
  2. * @brief I2C开始
  3. * @param 无
  4. * @retval 无
  5. */
  6. void I2C_Start(void)
  7. {
  8. I2C_SDA=1;//先给SDA置1,因为不知道上一次结束时候SDA为1还是0,保证时序不出问题
  9. I2C_SCL=1;//拉高SCL
  10. I2C_SDA=0;//拉低SDA
  11. I2C_SCL=0;//最后清零
  12. }
  13. /**
  14. * @brief I2C停止
  15. * @param 无
  16. * @retval 无
  17. */
  18. void I2C_Stop(void)
  19. {
  20. I2C_SDA=0;//一样的先给SDA置0
  21. I2C_SCL=1;//拉高SCL
  22. I2C_SDA=1;//拉低SDA
  23. //那么为什么不需要给SCL清零呢,我们一定要对着时序图写代码,终止的时序最后SCL为1
  24. }

       下面写发送和接收一个字节的时序

  1. /**
  2. * @brief I2C发送一个字节
  3. * @param Byte 要发送的字节
  4. * @retval 无
  5. */
  6. //这下面写的都是主机代码
  7. void I2C_SendByte(unsigned char Byte)
  8. {
  9. unsigned char i;
  10. for(i=0;i<8;i++)//根据时序,每次发送一位,循环八次,发送一个字节
  11. {
  12. I2C_SDA=Byte&(0x80>>i);//从高到低取出写入字节的每一位赋值给SDA,
  13. //即主机将数据放在SDA线上让从机读取
  14. I2C_SCL=1;//拉高SCL让从机读取,这里从机是自动读取的,无需写从机代码
  15. I2C_SCL=0;//拉高后迅速拉低,由于51单片机速度较慢,无需延时迅速拉低没有影响,
  16. //但是若涉及到运算速度快的单片机,则应当延时
  17. }
  18. }
  19. /**
  20. * @brief I2C接收一个字节
  21. * @param 无
  22. * @retval 接收到的一个字节数据
  23. */
  24. unsigned char I2C_ReceiveByte(void)
  25. {
  26. unsigned char i,Byte=0x00;
  27. I2C_SDA=1;//先释放总线,让从机获得SDA控制权
  28. for(i=0;i<8;i++)
  29. {
  30. I2C_SCL=1;//拉高SCL,在高电平时候从机将数据放在SDA线上
  31. if(I2C_SDA){Byte|=(0x80>>i);}//如果从机放的是1,则让Byte的第最高位置1
  32. //如果放的是0,则Byte最高位默认为0
  33. //用循环实现每一位的判断与赋值
  34. I2C_SCL=0;最后拉低SCL
  35. }
  36. return Byte;
  37. }

         最后是发送应答和接收应答

  1. /**
  2. * @brief I2C发送应答
  3. * @param AckBit 应答位,0为应答,1为非应答
  4. * @retval 无
  5. */
  6. void I2C_SendAck(unsigned char AckBit)
  7. {
  8. I2C_SDA=AckBit;//主机发送应答位,将应答位放在SDA线上,让从机读取
  9. I2C_SCL=1; //拉高SCL,让从机读取,从机自动读取,无需写代码
  10. I2C_SCL=0; //最后拉低SCL
  11. }
  12. /**
  13. * @brief I2C接收应答位
  14. * @param 无
  15. * @retval 接收到的应答位,0为应答,1为非应答
  16. */
  17. unsigned char I2C_ReceiveAck(void)
  18. {
  19. unsigned char AckBit;
  20. I2C_SDA=1; //释放总线,控制权交给从机
  21. //中间从机自动将应答位放在SDA线上,不写从机代码
  22. I2C_SCL=1; //拉高SCL让主机读取
  23. AckBit=I2C_SDA;//主机读取到应答位,存入AckBit
  24. I2C_SCL=0; //拉低SCL
  25. return AckBit; //返回应答位
  26. }

产生代码上的疑惑,一般都是觉得不写从机代码,时序总感觉对不上,这个地方不需要纠结太多,心里知道从机会什么时候发送,什么时候接受数据就行 

这样六个部分的代码就都写好了 ,最后组合成数据帧就能够用于各种各样的iic通信中了

       2.at24c02

  1. #include
  2. #include "I2C.h"
  3. #define AT24C02_ADDRESS 0xA0//宏定义从机地址
  4. /**
  5. * @brief AT24C02写入一个字节
  6. * @param WordAddress 存储要写入字节的位置,0~255
  7. * @param Data 要写入的数据
  8. * @retval 无
  9. */
  10. void AT24C02_WriteByte(unsigned char WordAddress,Data)
  11. {
  12. I2C_Start();
  13. I2C_SendByte(AT24C02_ADDRESS);//写入从机地址和读写位
  14. I2C_ReceiveAck();
  15. I2C_SendByte(WordAddress);//写入字地址
  16. I2C_ReceiveAck();
  17. I2C_SendByte(Data);
  18. I2C_ReceiveAck();
  19. I2C_Stop();
  20. }
  21. /**
  22. * @brief AT24C02读取一个字节
  23. * @param WordAddress 存储要读出字节的位置
  24. * @retval 读出的数据
  25. */
  26. unsigned char AT24C02_ReadByte(unsigned char WordAddress)
  27. {
  28. unsigned char Data;
  29. I2C_Start();
  30. I2C_SendByte(AT24C02_ADDRESS);
  31. I2C_ReceiveAck();
  32. I2C_SendByte(WordAddress);
  33. I2C_ReceiveAck();
  34. I2C_Start();
  35. I2C_SendByte(AT24C02_ADDRESS|0x01);//这里|0x01是为了使最后一位从0变为1,从写变成读
  36. I2C_ReceiveAck();
  37. Data=I2C_ReceiveByte();//获得读到字节赋值给Data
  38. I2C_SendAck(1);
  39. I2C_Stop();
  40. return Data;
  41. }

由于AT24C02读取和写入字节一次都只能读或写一个字节,能够表示数字的大小就2^8=256,这个时候肯定不会不够用,那么如何让int类型变量存入和读取呢?大家可以看下我的上一篇博客,里面有提到51单片机中操作符的妙用-CSDN博客


本节文章就介绍这么多(大部分都是理论介绍,可能有点枯燥哈,不过我写起来倒是觉得挺有趣的),希望对大家能够有所帮助!!!以后还会继续写博客,与大家分享在学习过程中遇到一些难题和趣题,以及个人学习的经验,也欢迎大家在评论区发表自己的意见,相互交流经验,指出不足,最后谢谢大家阅读!( ˘ ³˘)♥

     7cc10d25eac34765b6bd92252b29b5ef.jpeg

注:本文转载自blog.csdn.net的瑜先森的文章"https://blog.csdn.net/2401_87412882/article/details/144462643"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top