I2C同步串行通信协议
由于《深入浅出STM8单片机入门、进阶与应用实例》一书中以及其他大多数教程并没有直接使用STM8上的IIC,而是使用GPIO模拟IIC(这样适用那些没有硬件IIC的设备),如果你需要这种方法可以参见MCU远航编写的教程
因此本章节内容参考资料包括:官方文档RM0016/AN3281/官方的库函数/一篇暂未找到原出处的文章,我最早见于电子工程世界,其来源注为与非网,但并没有找到,后经搜索在源码巴士有相同的文章,其出处注为CSDN,也请知道出处者或原作者联系我进行声明修改出处信息,在此感谢
I2C概述
I2C的定义
-
I2C简介
I2C即I2C或IIC,全称Inter IC总线,用于IC之间的通信;它类似SPI外设接口,也是一种同步串行通信协议;为什么有了SPI还要再用IIC?在下文将会在各个方面探讨它们的差异
-
I2C的双线系统
IIC只需要两根通讯线:SDA与SCL,具有接口线路少、控制方式简单、器件封装小的优点;
IIC在STM8105C6上使用的两个引脚如下,这些引脚与GPIO引脚功能复用,在LQFP48封装下的引脚如下,不同封装的具体引脚要查阅数据手册
- SDA(PB5):串行数据线
- SCL(PB4):串行时钟线
与SPI相比:IIC不使用VSS来决定主从设备的角色,也不用区分MOSI和MISO,而只使用SDA传输数据,这样精简的线路得益于其特殊的工作方式
对IIC总线通信系统,可以有多个IIC总线器件同时接在IIC总线上,通过器件地址来区分各个设备,能够实现一主多从以及多主的通信系统
I2C的工作方式
-
I2C通信流程
I2C主设备发送数据到从设备的通信遵循一定的流程:
- 首先,主设备检测总线状态,等到总线是空闲状态(SDA和SCL都是高电平)时才能进行发送
- 主设备先发送一个起始信号,表示通信过程开始
- 开始通信后,主设备会发送长为7位或10位的从机地址,以及一个读写控制位R/W(用于区分要读还是写,比如R/W=0就是写操作),之后等待答应
- 从机收到地址字节后和自己的地址比较,比较结果相同则发送答应信号
- 主设备接收到答应信号后,开始发送第一个字节的数据
- 从设备接收到数据后会返回一个答应信号
- 如果要写入的数据不止一个字节,则重复5和6,主设备受到答应信号再发送一个字节,从设备收到后再答应
- 当主设备发送最后一个字节并且收到从设备的答应信号后,主设备发送一个终止信号,结束通信并释放总线
与SPI相比,这样的通信流程不仅节约了线路资源,并且有应答机制来保证设备接收到了数据,但是机制变得复杂了
-
I2C的通信有效性
在进行通信时,首先要确保数据的有效性,分两种方面:
- 电平有效性:连接倒IIC总线上的器件制造工艺可能不同,这使得它们高低电平的阈值不一致,这就需要电平转换单元确保信号的统一性
- 传输数据的有效性:在IIC总线通信中约定:数据线SDA上的数据位必须在时钟线SCK的高电平周期内保持稳定,只有在SCL低电平时SDA数据才可以变化(SCL高电平时SDA的变化不再代表数据,而是条件信号,具体见下文)
-
主从模式与总线仲裁
IIC通信中主设备的主要作用是初始化发送流程,产生起始信号、时钟信号和终止信号,从设备则在主设备的控制下被寻址和操纵进行应答,两者都既能发送又能接收数据
而因此STM8可运行于4种模式:主设备发送/接收于从设备发送/接收模式;
- 主模式下IIC接口启动数据传输并产生时钟信号(起始条件和停止条件由软件控制产生),同时能发送从机地址
- 从模式下IIC接口能识别自己的地址
IIC不仅通过从机地址区分各设备,实现了一主多从的通信,还有一个特殊之处:支持多主机,也就是IIC总线上有多于一个主机尝试控制总线的情况,在这种情况下就必须进行仲裁,只允许其中一个主机控制总线以防报文被破坏;如果仲裁识别或是
I2C的工作时序
-
工作的条件信号
在IIC的工作流程中,非常重要的一个部分便是条件信号,分别是开始通信时的起始条件、通信结束的停止条件信号、接收到数据时的答应信号
这些信号并非单纯的电平,而是电平跳变产生的边沿信号
对于有专用IIC硬件接口的微控制器(也就是STM8)来说这些条件可以直接检测到,而若没有(比如51单片机)则需要在每个时钟周期至少采样两次SDA来判断电平跳变,还要用GPIO输出来模拟IIC时序进行通信
-
起始条件信号S
SCL时钟信号为高电平,SDA数据信号由高电平置低,产生下降沿
-
停止条件信号P
SCL时钟信号为高电平,SDA数据信号由低电平置高,产生上升沿
-
答应信号ACK
发送端每发送一个字节(8位)后就需要释放数据线SDA,在9个时钟周期让接收端反馈一个低电平的答应信号(1位),如果是高电平则表示没有成功接收发送的字节
I2C的工作参数
-
在开始配置使用I2C之前,还需要了解STM8上I2C的规格参数(这可以在规格手册的I2C interface characteristics一节中查到),以在之后计算具体的配置值
-
I2C的通信速率
I2C可分为两种模式:标准模式与快速模式;不同的模式需要配置不同的外设时钟频率(通过配置I2C_FREQR决定)
- 标准模式:通信速率最高为100kHz;外设时钟频率最低为1MHz
- 快速模式:通信速率最高为400kHz(注意此时系统主频率fMASTER必须在8MHz以上);外设时钟频率最低为4MHz,并且要是10MHz的整数倍
-
I2C的上升时间
指的是从低电平到高电平转换的用时;不同的上升时间需要配置不同的
- 标准模式上升时间最大为1000ns
- 快速模式上升时间最大为400ns
I2C配置流程
I2C配置流程简述
-
配置引脚
如果使用软件模拟方式来进行通信,就要引脚配置为高速推挽输出,并且要输出高电平;但直接使用硬件时不必配置I2C的功能复用引脚(PB5和PB4),让其维持浮空输入即可
-
配置I2C的工作时序
这是最为重点的一步,正确配置多个寄存器才能让I2C产生正确的工作时序,具体可分以下几点,进行这一配置前要确定I2C处在禁止状态:
- 配置输入时钟频率寄存器I2C_FREQR,决定输入时钟频率
- 配置时钟控制寄存器I2C_CCR,其中的F/S位决定了I2C的通信速率(置1为快速模式),DUTY位配置快速模式下的SCL信号占空比,而CCR位可影响SCL时钟频率,也就是决定I2C的输出频率
- 配置上升时间寄存器I2C_TRISER,决定主模式下的最大上升时间
各个值具体如何计算与配置见下文
-
开启I2C事件中断
按需配置I2C_ITR启动对应事件发生时的中断
-
使能I2C通信
将使能位I2C_CR1_PE位置1启动IIC
-
配置地址
通过I2C_OARL和OARH寄存器设置本机的地址及其地址模式
-
产生工作时序
仅仅使能还不是通信的开始,前文提到,IIC的工作受条件信号的控制,主模式下的设备在需要通信时要将I2C_CR1的START位置1,产生起始条件
-
发送从机地址与读写操作位
在产生起始信号后,还要发送从机地址以选择通信对象,以7位地址模式为例:发送的第一个字节(8个位)包括7位地址+1位读写操作位(用于区分本次操作是读还是写,置0为写,置1为读),在此之后发送的字节才是要写入的字节
I2C相关寄存器
-
控制寄存器 I2C_CR1
主要用PE位以控制I2C的使能
-
控制寄存器 I2C_CR2
用于设置起始、停止与答应信号的产生
-
I2C_CR2_START = 1;
产生起始信号 -
I2C_CR2_STOP = 1;
产生结束信号 -
I2C_CR2_ACK = 1;
启动应答信号
-
-
频率寄存器 I2C_FREQR
其[5:0]位用于控制外设时钟频率,由此控制I2C的频率;其他位保留
其配置单位为MHz,一般来说配置为与系统时钟fMASTER频率相同
最高值可设置为110010,即50DECMHZ,全清零时表示不允许输入时钟
-
时钟控制寄存器 I2C_CCRH/L
分为高位I2C_CCRH与低位I2C_CCRL,只能在I2C禁止时才能进行配置
-
I2C_CCRL包括CCR[7:0],用于控制主模式下的SCLH时钟(SCL时钟的高电平部分频率)
-
I2C_CCRH包括CCR的[11:8]位,以及其他I2C模式配置和快速模式下占空比的配置
-
CCR位为I2C的时钟控制系数,控制SCL时钟的高电平部分,具体而言即决定SCL的高电平会维持几个系统主时钟周期,其通过改变SCL时钟的波形来影响I2C的输出频率,具体计算见后文
-
-
TRISE寄存器 I2C_TRISE
其位[5:0]保存快速/标准模式下最大上升时间,同样只能在I2C禁止时才能配置
这些位必须按I2C总线规范里给出的最大的SCL上升时间配置,简单地说,其值应该为FREQR寄存器值+1,具体计算见后文
-
自身地址寄存器 I2C_OARH/L
分OARL和OARH两个寄存器,包含本机的地址信息以及对地址处理配置位
-
I2C_OARL主要用于保存地址
-
I2C_OARH则包含了10位地址模式额外的地址位与寻址的配置
显而易见的是,10位的地址可以分配给更多的设备(1024个从机),而7位的地址更短、寻址更快,在快速模式下更建议使用7位地址;7位和10位地址之间不需要考虑转换的问题,可以混合使用
-
-
状态寄存器1 I2C_SR1
反应I2C总线各种状态
在进行I2C通信时,会经常检查这些状态以确保正常工作,例如:
while(I2C_SR1_SB==0);
等待起始条件发送完成while(I2C_SR1_ADDR==0);
等待地址发送完成while(I2C_SR1_BTF==0);
等待数据字节发送完成while(I2C_SR1_RxNE==0);
等待接收到数据
而在完成操作后要对状态寄存器进行清零,以防对判断造成干扰,具体方法见代码实现
-
状态寄存器2 I2C_SR2
-
状态寄存器3 I2C_SR3
while(I2C_SR3_BUSY==1);
等待总线空闲,可以进行通信
-
中断寄存器 I2C_ITR
配置中断,比如ITEVFEN控制的是通信时各种事件发生(起始位发送、地址匹配、传输完成)进入中断,ITERREN则控制在发生错误(总线错误、仲裁丢失、响应失败)时进入中断
-
数据寄存器 I2C_DR
用于存放接收到的数据或放置要发送到总线的数据
- 发送模式:写一个字节到DR寄存器时,自动启动数据传输,一旦传输开始(状态寄存器的TxE位=1),如果能及时把下一个需传输的数据写入DR寄存器,I2C模块将保持连续的数据流(需要注意TxE=0的情况下也能写入数据,因此发送时要检查寄存器状态避免损坏数据)
- 接收模式:接收到的字节时(RxNE=1)把其储存到DR寄存器中,当设备为从模式时,从主设备发送的地址不会被接收到本寄存器内
I2C工作时序系数的计算公式
-
时钟控制系数CCR值
CCR值决定SCL的高电平会维持几个系统主时钟周期,由此影响到SCL时钟以及I2C的输出频率,其具体的影响见下述公式:
-
标准模式:SCL周期信号的高电平信号持续时间thigh=CCR×主时钟周期tck
tlow计算方式一样,因为标准模式下SCL是方波
-
快速模式:计算方式取决于占空比的设置
- DUTY位=0时:thigh:tlow=1:2,这时thigh=CCR×tck,而tlow=CCR×2×tck
- DUTY位=1时:thigh:tlow=9:16,这时thigh=CCR×tck,而tlow=CCR×2tck
以标准模式为例:标准模式要求产生100kHz的SCL频率(tscl=10000ns),如果FREQ[5:0]的值等于0x08(即f=8MHz,tck=1/8M=125ns),此时CCR值应该满足:tscl=2×CCR×tck,得CCR=40DEC=0x28
将计算化简:CCR=fMASTER/(fI2C×2)
-
-
最大上升时间TRISER值
I2C_TRISER的配置值由输入时钟频率FREQR与规定的最大上升时间决定,公式如下:
- TRISER=trise/tck+1
以标准模式为例:最大允许的SCL上升时间为1000ns,其他条件同上,此时TRISER=1000ns/125ns+1,计算得TRISE=09,
将计算化简:TRISER=FREQR+1
如果结果不是一个整数,则将整数部分写入TRISE[5:0]以确保tHIGH参数
代码实现
-
初始化I2C
根据上文的计算得到的值配置,设主时钟频率已经设置为8MHz
//将I2C配置为标准模式 void I2C_init(void) { I2C_CR1_PE = 0;//禁用I2C,以进行修改 I2C_FREQR = 0x08;//配置输入时钟为8MHz //8MHz下100kHz的标准模式经过上文计算CCR=0x28 I2C_CCRL = 0x28; I2C_CCRH = 0x00; // F/S=0 选择标准模式 // DUTY=0 快速模式的占空比配置,使用标准模式此处无需配置 // CCR[11:8]=0 因为配置值只占据了CCR[7:0]此处无需配置 I2C_TRISER = 0x09;//设置最大上升时间 //对标准模式也可写成 I2C_TRISER = u8(I2C_FREQR+1); I2C_CR1_PE = 1;//使能I2C I2C_OARL = 0xFE//此处配置自身地址,也可以不配置 //注意,地址并不从OARL的第0位开始,而要让出一位存放读写控制位 I2C_OARH = 0x40; // ADDMODE=0 配置为7位地址 // ADDCONF=1 启动软件配置 // ADD[9:8]=0 因为选择了7位地址模式此处无需配置 }
-
I2C收发实验设计
在我们的实验中,选择AT24C系列的串行EEPROM作为通信对象
- 其SCL和SDA硬件连接STM8对应引脚
- WP引脚为写保护,连接到高电平时芯片内数据只能读
- A0/A1/A2引脚是器件地址的输入端,也就是说最多8个AT24C芯片可以级联在一起,通过这三位不同的地址来区分(000/001/010……)
地址一共有七位,根据手册,包含完整的地址加上读写控制位(发送的第一个字节)应该是:MSB:1-0-1-A0-A1-A2-R/W:LSB
在将AT24C芯片作为从机写入数据时,有两种模式:
- 字节写入模式:一次写一个字节,在写入前不仅要发送从机地址,还要发送欲将数据写入的地址,写完后应答
- 页写入模式:连续写入多个字节到页缓冲器,再把写入数据搬到指定地址(能写多少取决于型号不同,01/02的页缓冲器可以一次写入8个)
同理,读取也可以分选择读方式(选择指定地址)与连续读方式两种模式;还有一种立即地址读方式:读取的内容为最后操作字节的地址加1的那个字节
-
发送程序
以发送一个字节的数据为例
//待发送数据存放在变量data中,要写入的地址存放在DataAddress中 //7位从机地址存放在SlaveAddress中 void I2C_Send(u8 DataAddress,u8 data) { I2C_CR2_ACK = 1;//启动应答信号 while(I2C_SR3_BUSY==1);//等待总线空闲 I2C_CR2_START = 1;//发送起始信号 while(I2C_SR1_SB==0);//等待起始条件发送完成 I2C_DR = (SlaveAddress<<1) | 0; //首先不仅发送从机地址,还要加上一个读/写控制位,写操作时为0 while(I2C_SR1_ADDR==0);//等待地址发送完成 u8 temp = I2C->SR1;//清除状态标志位 temp = I2C->SR3; //对状态寄存器进行读取操作后硬件会自动清除 I2C_DR = DataAddress;//发送要进行写入的地址 while(I2C_SR1_BTF==0);//等待发送完成 temp = I2C->SR1; temp = I2C->SR3;//清除状态标志位 I2C_DR = data;//发送数据字节 while(I2C_SR1_BTF==0);//等待数据字节发送完成 temp = I2C->SR1; temp = I2C->SR3;//清除发送完成标志位 I2C_CR2_STOP = 1;//发送结束信号 }
-
接收程序
接收程序与发送是类似的,但要注意:先进行写操作发送要读取的地址,再切换为读操作,在指定地址读出一个字节
u8 I2C_Read(u8 DataAddress) { u8 data = 0; //用于存储读取的数据 while(I2C_SR3_BUSY==1);//检查BUSY位等待总线空闲 I2C_CR2_START = 1;//发送起始命令 while(I2C_SR1_SB==0);//检查SB位等待起始命令发送完成 //首先发送地址信息 I2C_DR = (SlaveAddress << 1) | 0; //发送设备地址+写操作位 while(I2C_SR1_ADDR==0);//检查ADDR位等待地址发送完成 u8 temp = I2C_SR1; temp = I2C_SR3;//清除状态标志位 I2C_DR = DataAddress;//发送要读取的地址 while(I2C_SR1_BTF==0);//等待发送完成 temp = I2C_SR1; temp = I2C_SR3;//清除状态标志位 //重新生成起始条件以开始进行读操作 I2C_CR2_START = 1;//发送起始命令 while(I2C_SR1_SB==0);//等待起始命令发送完成 I2C_DR = (SlaveAddress << 1) | 0x01; //发送设备地址+读操作位 while(I2C_SR1_ADDR==0);//检查ADDR位等待地址发送完成 temp = I2C_SR1; temp = I2C_SR3;//清除状态标志位 I2C_CR2_ACK = 1; //使能应答信号ACK以接收数据 while(I2C_SR1_RxNE==0);//等待接收 data = I2C_DR;//读取数据 I2C_CR2_ACK = 0;//关闭应答信号 I2C_CR2_STOP = 1; //生成停止条件 return data;//返回读取的数据 }
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/untit1ed/p/18682522
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
请注意: 特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析