写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.04.10
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
原理图如下
1、开发板原理图
2、STM32F103C6和51对比
3、STM32F103C6核心板
视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。
下图是实物图
引用
【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
数据手册
解答和科普
一、I2C硬件
1、 介绍协议规则,然后用软件模拟的形式来实现协议;因为I2C是同步时序,软件模拟协议也非常方便;
2、 介绍STM32的I2C外设,然后用硬件来实现协议;
51单片机使用的是AT24C02这个存储器模块来学习I2C的,这里会使用MPU6050这个陀螺仪,加速度传感器来学习I2C。
代码一个是:软件I2C读写MPU6050,另一个是硬件I2C读写MPU6050,两个代码实现的效果是一样的。
软件是通过软件I2C通信,对MPU6050芯片内部的寄存器进行读写,写入到配置寄存器,就可以对外挂的这个模块进行配置, 读出寄存器,就可以获取外挂模块的数据,这就是I2C通信的目的。最终,我们读取的数据会显示在这个OLED上,其中最上面的数据是,设备的ID号,MPU6050的ID号固定为0x68,一般我们可以读出这个ID号,看看是不是0x68,用来测试I2C读取数据的功能是不是正常,左边三个,是加速度传感器的输出数据,分别是X轴、Y轴、Z轴的加速度,右边3个,是陀螺仪传感器的输出数据,分别是X轴,Y轴,Z轴的角速度,我们可以改变MPU6050传感器的姿态,这6个数据就会对应变化。
串口通信:就是从TX引脚向RX引脚发送数据流,数据流以字节为单位,可以组合多个字节,变成多字节的数据包传输,设计为,一条发送线,一条接收线,可以直接通过内部的数据总线来实现,直接用指针操作就行,不需要我们操心,但是,现在这个模块的寄存器在单片机的外面,你要是直接把单片机内部的数据总线拽出来,把两个芯片合而为一体,那可能不太现实,所以设计一种通信协议,在单片机和外部模块连接少量的几根线,实现单片机读写外部模块寄存器的功能,这时你回想,这不太简单了,用这个串口的数据包通信就可以完成任务,比如就用这个HEX数据包,定义一个3个字节的数据包,从单片机向外挂模块发过去,第一个字节,表示读写,发送0,表示这是一个写数据包,发送1,表示这是一个读数据包,第二个字节,表示读写的地址,第三个字节表示写入的数据,比如发送数据包为:0x00,0x06,0Xaa,这就表示在0x06的地址下写入0Xaa,模块收到后,就执行这个写入操作,如果发送数据包为:0x01,0x06,0x 00;表示要读取0x06地址下的数据,注意,这个读的数据包第三个字节无效,模块收到之后,就要再给我发送一个字节,返回0x06地址下的数据,这样就行了,是不是完美完成任务;目前串口设计,需要两根通信线的全双工协议,我们这个流程是一种基于对话的形式来进行的,在整个工程中并不需要同事进行发送或接收,发送的时候不需要接收,接收的时候不需要发送,这样就会导致始终有一条线处于空闲状态,删掉一根线,只能在同一个根线上进行发送和接收,也就是把全双工变成半双工,我们这个协议并没有一个应答机制,也就是单片机发送了一个数据,对方有没有收到,单片机是完全不了解的,所以为了安全起见,要求增加应答机制,要求每发一个字节,对方要给我一个应答,每接收到一个字节,我也要对方一个应答;要求3线上可以接多个外挂设备,单片机可以指定,和任意一个模块通信,同时单片机在和某个模块进行通信时,其他模块不能对正常通信产生干扰,第四个要求:串口是异步的时序来通信,也就是发送发和接收方约定的传输速率是非常严格的,时钟不能有过大的偏差,也不能说是,在传输过程中,单片机有点事,进中断了,这个时序能不能暂停一下,对于异步时序来说,这时不行的,你单片机一个字节发一半暂停了,接收方可能是不知道的,它仍然会按照原来那个约定的速率读取,这就会导致传输出错,所以异步时序的缺点就是,非常依赖硬件外设的支持,必须要有USART电路才能方便地使用,如果没有USART硬件电路的支持,那么串口是很难用软件来模拟的,虽然说软件模拟串口通信也是行得通的,但是异步时序对时间要求很严格,一般我们很少使用软件来模拟串口通信,要把这个协议改成同步的协议,另外加一条时钟线来指导对方读写,由于存在时钟线,对传输的时间要求就不高了,单片机可以随时暂停处理其他事情,因为暂停的同时,时钟线也暂停了,所以传输双方都能定格在暂停的时刻,可以过一段时间再来继续,不会对传输造成影响,这就是同步时序的好处,使用同步时序就可以极大地降低单片机对硬件电路的依赖,即使没有硬件电路的支持,也可以很方便地使用软件手动翻转电平来实现通信,比如之前51单片机里,那个单片机就没有I2C的硬件外设,但是同样不影响51单片机进行软件模拟的I2C通信,异步时序的好处就是省一根时钟线,节省资源,缺点就是对时间要求严格,对硬件电路的依赖比较严重,同步时序的好处就是,反过来,对时间要求不严格,对硬件电路不怎么依赖,在一些低端单片机,没有硬件资源的情况下,也很容易使用软件来模拟时序,缺点就是多一根时钟线,这就是同步和异步的区别,所以需要一个同步的协议。
通过通信线,实现单片机读写外挂模块寄存器的功能,其中至少要实现,在指定的位置写寄存器,和在指定的位置读寄存器这两个功能。实现了读写寄存器,就是实现了对这个外挂模块的完全控制。
MPU6050、OLED、AT24C02、DS3231
同步的时序,降低对硬件的依赖,同时同步的时序稳定性也比异步时序更高,然后只有一根SDA数据线,满足了大公司提出的要求1,变全双工为半双工,一根线兼具发送和接收,最大化利用资源,带数据应答,支持挂载多个设备。
在总线冲突中,I2C协议会进行仲裁,仲裁胜利的一方取得总线控制权,失败的一方自动变为从机,当然时钟线也是有主机控制的,所以在多主机的模型下,还要进行时钟同步,多主机的情况下,协议是比较复杂的。
这是一个一主多从的模型,左边CPU就我我们的单片机,作为总线的主机,主机的权利很大,包括,对SCL线的完全控制,任何时候都是主机完全掌握SCL线,另外在空闲状态下,主机可以主动发起对SDA的控制, 只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机,这就是主机的权利;
下面是一系列被控IC,也就是挂载I2C总线上了从机,这些从机可以是姿态传感器、OLED、存储器、时钟模块等等,从机的权利比较小,对于SCL时钟线,在任何时刻都只能被动的读取, 从机不允许控制SCL线, 对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后,或者从机应答的时候,从机才能短暂地取得SDA的控制权,这就是一主多从模型中协议的规定;
看接线,所有I2C设备的SCL连在一起,SDA连在一起;如何规定每个设备SCL和SDA的输入输出模式呢,SCL应该好规定,因为现在是一主多从,主机拥有SCL的绝对控制权,所以主机的SCL可以配置成推挽输出,所有从机的SCL都配置成浮空输入或者上拉输入,数据流向是,主机发送,所有从机接收,但是到SDA线这里,就比较麻烦了,因为这是半双工的协议,所以主机的SDA在发送的时候是输出,在接收的时候是输入,同样从机的SDA也会在输入和输出之间反复切换,如果你能协调好输入输出的切换时间,也没问题,不然极有可能发生两个引脚同时处于输出的状态,如果这时又刚好是一个输出高电平,一个输出低电平,那这个状态就是电源短路,这个状态是要极力避免的,所以为了避免总线协议没协调好导致电源短路这个问题,I2C的设计是,禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构。
首先,引脚进来,都可以通过一个数据缓冲器或者是施密特触发器,进行输入,因为输入对电路没有任何影响,所以任何设备在任何时刻都是可以输入的,但是在输出的这部分,采用的是开漏输出的配置;
正常的推挽输出是这样的,所以是强上拉和强下拉的模式:
开漏输出:就是去掉上面的开关管,输出低电平时,下管导通,是强下拉,输出高电平时,下管断开,但是没有上管了,此时引脚处于浮空啥的状态,这就是开漏输出。
输出低电平,这个开关管导通,引脚直接接地,是强下拉,输出高电平,这个开关管断开,引脚什么都不接,处于浮空状态,这样的话,所有的设备都只能输出低电平而不能输出高电平,为了避免高电平造成的引脚浮空,这时就需要在总线外面,SCL和SDA各外置一个上拉电阻,这时一个通过电阻拉高到高电平的,所以这是一个弱上拉,用我们之前讲的弹簧和杆子的模型来解释就是,SCL或SDA就是一根杆子,为了防止有人向上推杆子,有人向下拉杆子,造成冲突,我们就规定,所有的人,不准向上推杆子,只能选择向下拉或者放手,然后我们再外置一个弹簧向上拉,你要输出低电平,就往下拽,这根弹簧肯定拽不赢你,所以弹簧被拉伸,杆子处于低电平状态,你要输出高电平,就放手,杆子在弹簧的拉力下,回弹到高电平,这就是一个弱上拉的高电平,但是完全不影响数据传输,这样做有什么好处:第一,完全杜绝了电源短路现象,保证电路的安全,你看所有人无论怎么拉杆子或者放手,杆子都不会处于一个被同事强拉和强推的状态,即使有多个人同时向下拉杆子,也没问题,第二,避免了引脚模式的频繁切换,开漏加上弱上拉的模式,同时兼具了输入和输出的功能,你要是想输出,就去拉杆子或放手,操作杆子变化就行了,你要是相输入,就直接放手,然后观察杆子高低就行了,因为开漏模式下,输出高电平就相当于断开引脚,所以在输入之前,可以直接输出高电平,不需要再切换成输入模式了,第三就是,这个模式会有一个“线与”的现象,就是只要有任意一个或多个设备输出低电平,总线就处于低电平,只有所用设备都输出高电平,总线才是高电平,I2C可以利用这个点了特性,执行多主机模式下的时钟同步和总线仲裁,所以SCL虽然在一主多从模式下可以用推挽输出,但是他仍然使用了开漏加上上拉输出的模式,因为在多主机模式下会利用这个特性,以上就是I2C的硬件电路设计。
二、I2C软件
软件:时序
在I2C总线处于空闲状态时,SCL和SDA都处于高电平状态,也就是没有任何一个设备去碰SCL和SDA,SCL和SDA由外挂的上拉电阻拉高至高电平,总线处于平静的高电平状态,当主机需要进行数据收发时,首先就要打破总线的宁静,产生一个起始条件,这个起始条件就是,SCL处于高电平不去动它,然后SDA拽下来,产生一个下降沿,当从机捕获到这个SCL高电平,SDA下降沿信号时,就会进行自身的复位,等待主机的召唤,然后在SDA下降沿之后,主机要再把SCL拽下来,拽下来SCL,一方面是占用这个总线,另一方面也是为了方便我们这些基本单元的拼接,就是之后我们会保证,除了起始和终止条件,每个时序单元的SCL都是以低电平开始,低电平结束,这样这些单元拼接起来,SCL才能续得上;
终止条件是SCL高电平期间SDA从低电平切换到高电平:也就是SCL先放手,回弹到高电平,SDA再放手,回弹高电平,产生一个上升沿,这个上升沿触发终止条件,同时终止条件之后,SCL和SDA都是高电平,回归到最初的平静状态,这个起始条件和终止条件就类似串口时序里的起始位和停止位,一个完整的数据帧,总是以起始条件开始,终止条件结束,另外,其实和终止,都是由主机产生的,从机不允许产生起始和终止,所以在总线空闲状态时,从机必须始终双手放开,不允许主动跳出来,去碰总线,如果允许的话,那就是多主机模型了;
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。起始条件后,第一个字节,也必须是主机发送的,主机如何发送呢,就是最开始,SCL低电平,主机如果想发送0,就拉低SDA到低电平,如果想发送1,就放手,SDA回弹到高电平,在SCL低电平期间允许改变SDA的电平,当这一位放好之后,主机就松手时钟线,SCL回弹到高电平,在高电平期间,是从机读取SDA的时候,所以高电平期间,SDA不允许变化,SCL处于高电平之后,从机需要尽快地读取SDA,一般都是在上升沿这个时刻,从机就已经读取完成了,因为时钟是主机控制的,从机并不知道什么时候就会产生下降沿了,你从机要是磨磨唧唧的,主机可不会等你的,所以从机在上升沿时,就会把数据读走,主机在放手SCL一段时间后,就可以继续拉低SCL,传输下一位了,主机也需要在SCL下降沿之后尽快把数据放在SDA上,但是主机有时钟的控制权,所以主机并不需要这么着急,只需要在低电平的任意时刻把数据放在SDA上就行了,晚点也没关系,数据放完之后,主机再松手SCL,SCL高电平,从机读取这一位;主机拉低SCL,把数据放在SDA上,主机松开SCL,从机读取SDA的数据,在SCL的同步下,依次进行主机发送和从机接收,循环8次,就发了8位数据,也就是一个字节,另外注意,这里是高位先行,所以第一位是一个字节的最高位B7(和串口不同);如果突然暂停,SCL和SDA电平都暂停变化,传输也完全暂停,等中断结束之后,主机回来继续操作,传输仍然不会出问题,这就是同步时序的好处,最后就是,由于这整个时序是主机发送一个字节, 所以在这个单元里,SCL和SDA全程都由主机掌控,从机只能被动读取,这就是发送一个字节的时序;
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
主机在接收之前,需要释放SDA,释放SDA起始就相当于切换成输入模式,或者这样来理解,所有设备包括主机都始终处于输入模式,当主机需要发送的时候,就可以主动去拉低SDA,而主机在被动接受的时候,就必须先释放SDA,不要去动它,以免影响别人发送,因为总线是线与的特征,任何一个设备拉低了,总线就是低电平,如果你接受的时候,还拽着SDA不放手,那别人无论发什么数据,总线都始终是低电平,你自己给它拽着不放,还让别人怎么发送呢,所以主机在接收之前,需要释放SDA,从流程上来看,接收一个字节和发送一个字节是非常相似的,区别就是发送一个字节是,低电平主机放数据,高电平从机读数据;而接受一个字节是:低电平从机放数据,高电平主机读数据;然后看一下下面的时序,和上面基本一样,区别就是SDA线,主机在接收之前要释放SDA,然后这时从机就取得了SDA的控制权,从机需要发送0,就把SDA拉低,从机需要发送1,就放手,SDA回弹高电平,然后同样的,低电平变换数据,高电平读取数据,这里实线部分便是主机控制的电平,虚线部分表示从机控制的电平,SCL由主机控制,SDA主机在接收前要释放,交由从机控制,之后还是一样,因为SCL时钟是由主机控制的,所以从机的数据变换基本上都是贴着SCL下降沿进行的,而主机可以在SCL高电平的任意时刻读取,这就是接收一个字节的时序。
首先应答机制分为发送应答和接受应答,它们的时序分别和发送一个字节、接收一个字节的其中一位是相同的,你可以理解为发送一位和接收一位,这一位就用来作为应答。
首先是发送应答,是主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答;
然后是接受应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA),我们在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据,如果从机收到了,那在应答位这里,主机释放SDA的时候,从机就应该瞬间把SDA拉下来,然后在SCL高电平期间,主机读取应答位,如果应答位为0,就说明从机确实收到了,这个场景就是,主机发送一个字节,然后说,有没有人收到呀,我现在把SDA放手了,如果有人收到的话,你就把SDA拽下来,然后主机高电平读取数据,发现,确实有人给它拽下来了,那就说明有人收到了,如果主机发现,我松手了,结果这个SDA就跟着回弹到高电平了,那就说明没有人回应我,刚发的一个字节可能没人收到,或者它收到了,但是没给我回应,这就是发送一个字节,接收应答的流程,同理,在接收一个字节后,我们也要给从机发送一个应答位,发送应答的目的是告诉从机,你是不是还有继续发,如果从机发送一个数据后,得到了主机的应答,那从机就还会继续发送,如果从机没得到主机的应答,那从机就会认为,我发送了一个数据,但是主机不理我,可能主机不想要了吧,这时从机就会乖乖地释放SDA,交出SDA的控制权,防止干扰主机之后的操作,这就是应答位的执行逻辑,好,那到这里,我们I2C的6块拼图就已经集齐了,分别是,起始条件,终止条件,发送一个字节,接受一个字节,发送应答和接受应答。接下来,我们就来拼接这些基本单元,组成一个完整的数据帧,I2C的完整时序,主要有指定地址写,当前地址读和指定地址读这三种。
我们这个I2C是一主多从的模型,主机可以访问总线上的任何一个设备,那如何来发出指令,来确定要访问的是那个设备呢,这就需要把每个从设备都确定一个唯一的设备地址,从机设备地址就相当于每个设备的名字,主机在起始条件之后,要先发送一个字节叫一下从机名字,所有从机都回收到第一个字节,和自己的名字进行比较,如果不一样,则认为主机没有叫我,之后的时序我就不管了,如果一样,就说明,主机现在在叫我,那我就相应之后主机的读写操作,在同一条I2C总线里,挂载的每个设备地址必须不一样,否则,主机叫一个地址,有多个设备都响应,那不就乱套了,从机设备地址,在I2C协议标准里分为7位地址和10位地址,我们目前只讲了7位地址的模式,因为7位地址比较简单而且应用范围最广,
那在每个I2C设备出厂时,厂商都会为它分配一个7位的地址,这个地址具体是什么,可以在芯片手册里找到,比如我们MPU6050这个芯片的7位地址是1101 000,之前学习的AT24C02的7位地址是1010 000一般不同型号的芯片地址都是不同的,相同型号的芯片地址都是一样的,那如果有相同的芯片挂载在同一条总线上怎么办呢,这就需要用到地址中的可变部分了,一般器件地址的最后几位是可以在电路中改变的,比如MPU6050地址的最后一位,就可以由这个板子上的AD0引脚确定,这个引脚接低电平,那它的地址就是1101 000,如果接高电平,那他的地址就是1101 0001,比如AT24C02地址的最后三位,都可以分别由这个板子上的A0、A1、A2引脚确定,一般I2C的从机设备地址,高位都是由厂商确定的,低位可以由引脚来灵活切换,这样,即使相同型号的芯片,挂载在同一个总线上,也可以通过切换地址低位的方式,保证每个设备的地址都不一样,这就是I2C设备的从机地址,然后看一下I2C的时序:
指定地址写:完成的任务是,对于指定设备,指定设备,通过Slave Address,从机地址来确定,在指定地址下,这个指定地址就是某个设备内部的Reg Address,寄存器地址,写入指定数据,就是要在这个寄存器中写入Data数据,然后看一下下面的时序,这个时序是我在示波器下实际抓拍到的波形,大家也可以用逻辑分析仪抓这个波形,而且逻辑分析仪还自带协议解析的功能,还是非常方便的,在这里,上面的线是SCL,下面的线是SDA,空闲状态,它俩都死高电平,然后主机需要给从机写入数据的时候,首先,SCL高电平期间,拉低SDA,产生起始条件(Start,S),在起始条件之后,紧跟着的时序,必须是发送一个字节的时序,字节的内容,必须是从机地址+读写位,正好从机地址是7位,读写位是1位,加起来是一个字节8位,发送从机地址,就是确定通信的对象,发送读写位,就是确定我接下来要写入还是读出,具体发送的时候呢,在这里,低电平期间,SDA变换数据,高电平期间,从机读取SDA,这里我用绿色的线,来标明了从机读到的数据。第一个数据表示,高7位,表示从机地址,1101 000,最后一位0,表示是写,1表示,之后的时序主机要进行读出操作。然后跟着的单元,就得是接收从机的应答位(Receive Ack),在这个时刻,主机要释放SDA,所以单看主机的波形,应该是这样,释放SDA之后,引脚电平回弹到高电平,但是根据协议规定,从机要在这个位拉低SDA,所以单看从机的波形,应该是这样,该应答的时候,从机立刻拽住SDA,然后应答结束之后,从机再放开SDA,现在综合两者的波形,结合线与的特性,在主机释放SDA,由于SDA也被从机拽住了,所以主机松手后,SDA并没有回弹高电平,这个过程,就代表从机产生了应答,最终高电平期间,主机读取SDA,发现是0,就说明,我进行寻址,有人给我应答了,传输没问题,如果主机读取SDA,发现是1,就说明,我进行寻址,应答期间,我松手了,但是没人拽住它,也没用人给我应答,那就直接产生停止条件吧,并提示一些信息,这就是应答位,这个上升沿,就是应答位结束后,从机释放SDA产生的,从机交出了SDA的控制权,因为从机要在低电平尽快变换数据,所以这个上升沿和SCL下降沿,几乎是同时发生的,然后继续往后,由于之前我们读写位给了0,所以应答结束后,我们要继续发送一个字节,同样的时序再来一遍,第二个字节,就可以送到指定设备的内部了,就表示,我要在0x19地址下,写入0x AA,最后是接收应答位,如果主机不需要继续传输了,就可以产生停止条件,在停止条件之前,先拉低SDA,为后续SDA的上升沿做准备,然后释放SCL,再释放SDA,这样就产生了SCL高电平期间,SDA的上升沿,这样一个完整的数据帧就拼接完成了;
从机应答之后,从这里开始数据的传输方向就要反过来了,因为刚才主机发了读的指令,所以这之后,主机就不能继续发送了,要把SDA的控制权交给从机,主机调用接收一个字节的时序,进行接收操作,然后到这一块,从机就得到了主机的允许,可以在SCL低电平期间写入SDA,然后主机在SCL高电平期间读取SDA,那最终,主机在SCL高电平期间依次读取8位,就接受到了从机发送的一个字节数据,0000 1111,那这个0x 0F是从哪个寄存器色数据呢,可以看到,在读的时序中,I2C协议的规定是,主机进行寻址时,一但读写标志位给1了,下一个字节就要立马转为读的时序,所以主机还来不及指定,我想要读哪个寄存器,就得开始接收了,所以这里就没有指定地址这个环节,那主机并没有指定寄存器的地址。
从机到低该发那个寄存器的数据呢,这就需要用到我们上面说的当前地址指针了,在从机中,所有的寄存器被分配到了一个线性区域中, 并且有单独的指针变量,指示着其中一个寄存器,这个指针上电默认,一般指向0地址,并且,每写入一个字节和读取一个字节后,这个指针就会自动自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定要读那个地址,从机就会返回当前指针指向的寄存器的值,那假设,我刚刚调用了这个指定地址写的时序,在0x19的位置写入了0xAA,那么指针就会+1,移动到0x1A的位置,我再调用这个当前读的时序,返回的就是0x1A地址下的值,如果再调用一次呢,返回的就是0x1B地址下的值,这就是当前地址读时序的操作逻辑。由于当前地址读并不能指定读的地址,所以这个时序用的不是很多。
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data),那这个时序为什么能指定读的地址呢,前面的部分是指定地址写,后面的部分是当前地址读,
因为刚才制定了地址,所以再调用当前地址读,两者加在一起就是指定地址读了,所以,指定地址读的时序会复杂一些;
首先是起始条件,然后发送一个字节,进行寻址,这里指定从机地址是1101 0000,读写标志位是0,代表我要进行写的操作,进过从机应答之后,再发送一个字节,第二个字节,用来指定地址,这个数据就写入到了从机的地址指针里了,也就是说,从机接收到这个数据之后,它的寄存器指针就指向了0x19这个位置,之后,我们要写入的数据,不给它发,而是直接再来一个起始条件,这个SR(Start Repeat)的意思就是重复起始条件,相当于另起一个时序,因为制定读写标志位只能是跟着起始条件的第一个字节,所以如果想切换读写方向,只能再来个起始条件,然后起始条件后,重新寻址并且指定读写标志位,此时读写标志位是1,代表我要开始读了,接着主机接收一个字节,这个字节是不是就是0x19,地址下的数据,这就是指定地址读,另外在这里,你也可以加一个停止条件,这样也行,这样的话,就是两个完整的时序了,先起始,写入地址,停止,因为写入的地址会存在地址指针里面,所以这个地址并不会因为时序的停止而消失,我们就可以再起始,读当前位置,停止,这样两条时序也可以完成任务,但是I2C协议规定的符合格式是一整个数据帧,就是先起始,再重复起始,再停止,相当于把两条时序拼接在一条了,这就是3个I2C完整时序的介绍了。
现在介绍的:指定地址写,只是写一个字节,当前地址读和指定地址读,也都是读一个字节,那进阶版本就是指定地址读多个字节,当前地址读多个字节,和指定地址读多个字节,时序上和这些都非常相似,只需要增加一些小细节就行:在这里,指定地址,然后写入一个字节,如果你只想写一个字节,那就停止,就行了,如果你想写多个字节,就可以把最后一部分,多重复几次,比如这里,重复三遍发送一个字节和接受应答, 这样第一个数据就写入到了指定地址0x19的位置,然后不要忘了刚才说的,写入一次数据后,地址指针会自动加1,变成0X1A,所以这第二个数据就写入到了0x1A的位置,同理第三个数据就写入的是0x1B的位置,这样这个时序就进阶为在指定的位置开始,按顺序连续写入多个字节,比如你需要连续写入多个寄存器,就可以考虑这样来操作,这样在一条数据帧里,就可以同时写入多个字节,执行效率比较高,然后同理,当前位置读和指定位置读,也可以多次执行这最后一部分时序,由于地址指针在读后也会自增,所以这样就可以连续读出一片区域的寄存器,效率也会非常高。
如果你只想读一个字节就停止的话,在读完一个字节之后,一定要给从机发个非应答,非应答就是该主机应答的时候,主机不把SDA拉低,从机读到SDA为1,就代表主机没有应答,从机收到非应答之后,就知道主机不想要继续了,从机就会释放总线,把SDA控制器交还给主机,如果主机读完仍然给从机应答了,从机就会认为主机还想要数据,就会继续发送下一个数据,而这时,主机如果想产生停止条件,SDA可能就会因为从机被拽住了,而不能正常弹回高电平,这个注意一下,如果主机想连续读多个字节,就需要在最后一个字节给非应答,而之前的所用字节都要给应答,简单来说就是主机给应答 ,从机就会继续发,主机给非应答了,从机就不会再发了,交出SDA的控制权,从机控制SDA发送一个字节的权利,开始于读写标志为去,结束于主机给应答为1,这就是主机给从机发送应答位的作用。
问题
总结
本节课主要是了I2C的协议内容,硬件的层面和软件的层面。