文章概述
一、简要介绍SMBus与I2C区别
二、STM32F407的I2C寄存器
三、编程方法
这篇文章介绍一下STM32F407芯片的I2C资源,从而进一步了解I2C通讯协议。
这里再提一下SMBus总线。从概念上来讲,SMBus(系统管理总线)是一个双线制接口,器件之间可以通讯,它是以I2C工作原理为基础,SMBus可针对系统和电源管理相关的任务提供控制总线。系统管理总线规范涉及三类器件。(1)从器件,用于接收或响应命令。(2)主器件,用于发出命令、生成时钟和中止传输。(3)主机,专用的主器件,可提供连接系统 CPU 的主接口。主机必须具有 主 - 从设备功能,并且必须支持 SMBus 主机通知协议。系统中只允许存在一个主机。
SMBus与I2C的相似之处:
简单明了,2条线的协议:数据*1+时钟*1
通讯方式都是主从通讯,且主器件提供时钟
多主
两者的地址格式(7bit)相似
有相似就有区别,以下是区别:
STM32F407的硬件外设包含了3个硬件I2C资源,硬件I2C的速度比软件I2C快,通常可以达到几百kHz设置更高的速度,软件I2C达到几十kHz。
二、STM32F407的I2C相关的寄存器
2.1. I2C控制寄存器1(I2C_CR1)
位15: SWRST:软件复位
当置 1 时,I2C 处于复位状态。在复位此位之前,确保 I2C 线已释放且总线空闲。0:I2C 外设未处于复位状态 ,1:I2C 外设处于复位状态
位 8 START:生成起始位
此位由软件置 1 和清零,并可在起始位发送完成后或 PE=0 时由硬件清零。在主模式下:0:不生成起始位 1:生成重复起始位在从模式下:0:不生成起始位 1:在总线空闲时生成起始位
位 7 NOSTRETCH:禁止时钟延长 (从模式)
在从模式下,当 ADDR 或 BTF 标志置 1 时,此位用于禁止时钟延长,直到软件将其复位为止。0:使能时钟延长 1:禁止时钟延长
位 6 ENGC:广播呼叫使能 (General call enable)
0:禁止广播呼叫。不对地址 00h 应答。1:使能广播呼叫。对地址 00h 应答。
位 5 ENPEC:PEC 使能 (PEC enable)
0:禁止 PEC 计算1:使能 PEC 计算
位 4 ENARP:ARP 使能 (ARP enable)
0:禁止 ARP1:使能 ARPSMBTYPE=0 时识别 SMBus 器件默认地址SMBTYPE=1 时识别 SMBus 主机地址
位 3 SMBTYPE:SMBus 类型 (SMBus type)
0:SMBus 器件 1:SMBus 主机
位 1 SMBUS:SMBus 模式 (SMBus mode)
0:I2C 模式 1:SMBus 模式
位 0 PE:外设使能 (Peripheral enable)
0:禁止外设 1:使能外设
注意:如果此位在通信进行过程中复位,在结束本次通信后会带 IDLE 状态,外设被禁止。由于通信结束时 PE=0,所有位均会复位。在主模式下,此位不能在通信结束之前复位。
2.2. I2C控制寄存器2(I2C_CR2)
位 12 LAST:最后一次 DMA 传输 (DMA last transfer)
0:下一个 DMA EOT 不是最后一次传输 1:下一个 DMA EOT 是最后一次传输注意:此位用于主接收模式,可对最后接收的数据生成 NACK
位 11 DMAEN:DMA 请求使能 (DMA requests enable)
0:禁止 DMA 请求 1:当 TxE=1 或 RxNE =1 时使能 DMA 请求
位 10 ITBUFEN:缓冲中断使能 (Buffer interrupt enable)0:TxE = 1 或 RxNE = 1 时不生成任何中断。1:TxE = 1 或 RxNE = 1 时生成事件中断(与 DMAEN 状态无关)
位 9 ITEVTEN:事件中断使能 (Event interrupt enable)
0:禁止事件中断 1:使能事件中断满足以下条件时将生成此中断:—SB = 1(主模式)—ADDR = 1(主/从模式)—ADD10= 1(主模式)—STOPF = 1(从模式)—BTF = 1,无 TxE 或 RxNE 事件—ITBUFEN = 1 且 TxE 事件置 1—ITBUFEN = 1 且 RxNE 事件置 1
位 8 ITERREN:错误中断使能 (Error interrupt enable)
0:禁止错误中断 1:使能错误中断满足以下条件时将生成此中断:
BERR = 1
ARLO =
1AF = 1
OVR = 1
PECERR = 1
TIMEOUT = 1
SMBALERT = 1
位 5:0 FREQ[5:0]:外设时钟频率 (Peripheral clock frequency)
外设时钟频率必须使用 APB 时钟频率进行配置(I2C 外设连接到 APB)。允许的最小频率为 2 MHz,最大频率则受限于 APB 最大频率 (42 MHz) 和固有极限频率 46 MHz。0b000000:不允许0b000001:不允许0b000010:2 MHz...0b101010:42 MHz大于 0b101010:不允许
2.2. I2C自有地址寄存器1(I2C_OAR1)
位 15 ADDMODE 寻址模式 (Addressing mode)(从模式)
0:7 位从地址(无法应答 10 位地址)1:10 位从地址(无法应答 7 位地址)位 14 应通过软件始终保持为 1
位 9:8 ADD[9:8]:接口地址
7 位寻址模式:无意义10 位寻址模式:地址的第 9:8 位
位 7:1 ADD[7:1]:接口地址
地址的第 7:1 位
位 0 ADD0:接口地址
7 位寻址模式:无意义10 位寻址模式:地址的第 0 位
2.2. I2C自有地址寄存器1(I2C_OAR1)
2.3. I2C数据寄存器(I2C_DR)
位 7:0 DR[7:0] 8 位数据寄存器
接收的字节或者要发送到总线的字节。—发送模式:在 DR 寄存器中写入第一个字节时自动开始发送字节。如果在启动传送 (TxE=1) 后立即将下一个要传送的数据置于 DR 中,则可以保持连续的传送流—接收模式:将接收到的字节复 制到 DR 中 (RxNE=1)。如果在接收下一个数据字节 (RxNE=1) 之前读取 DR,则可保持连续的传送流。注意:在从模式下,地址并不会复 制到 DR 中。注意:硬件不对写冲突进行管理(TxE=0 时也可对 DR 执行写操作)。注意:如果发出 ACK 脉冲时出现 ARLO 事件,则不会将接收到的字节复 制到 DR 寄存器,因而 也无法读取字节。
2.3. I2C状态寄存器1(I2C_SR1)
位 15 SMBALERT:SMBus 报警 (SMBus alert)
在 SMBus 主机模式下:0:无 SMBALERT1:引脚上发生 SMBALERT 事件在 SMBus 从模式下:0:无 SMBALERT 响应地址头 1:接收到指示 SMBALERT 低电平的 SMBALERT 响应地址头— 由软件写入 0 来清零,或在 PE=0 时由硬件清零。
位 14 TIMEOUT:超时或 Tlow 错误 (Timeout or Tlow error)
0:无超时错误 1:SCL 低电平时长持续 25 ms(超时)或主器件累计时钟低电平延长时间超过 10 ms (Tlow:mext)或从器件累计时钟低电平延长时间超过 25 ms (Tlow:sext)— 在从模式下置 1 时:从器件复位通信且硬件释放数据线— 在主模式下置 1 时:由硬件发送停止位— 由软件写入 0 来清零,或在 PE=0 时由硬件清零。注意:此功能仅在 SMBus 模式下可用
位 12 PECERR:接收期间 PEC 错误 (PEC Error in reception)
0:无 PEC 错误:接收器在接收 PEC 后返回 ACK(如果 ACK=1)1:PEC 错误:接收器在接收 PEC 后返回 NACK(无论 ACK 什么值)—由软件写入 0 来清零,或在 PE=0 时由硬件清零。—注意:接收到错误的 CRC 时,如果在结束 CRC 接收之前 PEC 控制位没有置 1,则 PECERR 位在从模式下不会置 1。不过可以通过读取 PEC 值来判定接收到的 CRC 是否正确。
位 11 OVR:上溢/下溢 (Overrun/Underrun)
0:未发生上溢/下溢 1:上溢或下溢—在从模式下由硬件置 1,前提是满足 NOSTRETCH=1 且:—接收过程中接收到一个新字节(包括 ACK 脉冲)但尚未读取 DR 寄存器。新接收的字节将 丢失。—发送过程中将发送一个新字节但尚未向 DR 寄存器写入数据。同一字节发送两次。—由软件写入 0 来清零,或在 PE=0 时由硬件清零。注意:如果 DR 写操作时间与出现 SCL 上升沿的时间非常接近,则发出的数据不确定,并且出 现数据保持时间错误。
位 10 AF:应答失败 (Acknowledge failure)
0:未发生应答失败 1:应答失败—无应答返回时由硬件置 1。—由软件写入 0 来清零,或在 PE=0 时由硬件清零
位 9 ARLO:仲裁丢失 (Arbitration lost)(主模式)
0:未检测到仲裁丢失 1:检测到仲裁丢失当接口在竞争总线是输给另一个主设备时,由硬件将该位置 1—由软件写入 0 来清零,或在 PE=0 时由硬件清零。发生 ARLO 事件后,接口会自动切换回从模式 (M/SL=0)。注意:在 SMBUS 中,从模式下的数据仲裁仅发生在数据阶段或发送确认期间(不适用于地址 确认)。
位 8 BERR:总线错误 (Bus error)
0:无误放的起始或停止位 1:存在误放的起始或停止位—SCL 为高电平时,若接口在字节传输期间检测到某个无效位置出现 SDA 上升沿或下降沿, 则会由硬件将该位置 1。—由软件写入 0 来清零,或在 PE=0 时由硬件清零
位 7 TxE:数据寄存器为空 (Data register empty)(发送器)
0:数据寄存器非空 1:数据寄存器为空—发送过程中 DR 为空时该位置 1。TxE 不会在地址阶段置 1。—由软件写入 DR 寄存器来清零,或在出现起始、停止位或者 PE=0 时由硬件清零。如果接收到 NACK 或要发送的下一个字节为 PEC (PEC=1),TxE 将不会置 1注意:写入第一个要发送的数据或在 BTF 置 1 时写入数据都无法将 TxE 清零,因为这两种情况 下数据寄存器仍为空。
位 6 RxNE:数据寄存器非空 (Data register not empty)(接收器)
0:数据寄存器为空 1:数据寄存器非空—接收模式下数据寄存器非空时置 1。RxNE 不会在地址阶段置 1。—由软件读取或写入 DR 寄存器来清零,或在 PE=0 时由硬件清零。发生 ARLO 事件时 RxNE 不会置 1。注意:BTF 置 1 时无法通过读取数据将 RxNE 清零,因为此时数据寄存器仍为满.
位 4 STOPF:停止位检测 (Stop detection)(从模式)
0:未检测到停止位 1:检测到停止位—从设备在应答脉冲后(如果 ACK=1)检测到停止位,由硬件置 1。—由软件分别对 SR1 寄存器和 CR1 寄存器执行读操作和写操作来清零,或在 PE=0 时由硬件 清零。注意:收到 NACK 后 STOPF 位不会置 1。建议在 STOPF 置 1 后执行完整的清零序列(首先读取 SR1,然后写入 CR1)。
位 3 ADD10:发送 10 位头(主模式)
0:未发生 ADD10 事件。1:主器件已发送第一个地址字节(头)。—主器件在 10 位地址模式下已发送第一个字节时由硬件置 1。—由软件在读取 SR1 寄存器后在 DR 寄存器中写入第二个地址字节来清零,或在 PE=0 时由硬 件清零
位 2 BTF:字节传输完成 (Byte transfer finished)
0:数据字节传输未完成 1:数据字节传输成功完成—由硬件置 1,前提是满足 NOSTRETCH=0 且:—接收过程中接收到一个新字节(包括 ACK 脉冲)但尚未读取 DR 寄存器 (RxNE=1)。—发送过程中将发送一个新字节但尚未向 DR 寄存器写入数据 (TxE=1)。—由软件读或写 DR 寄存器来清零,或在发送过程中出现起始或停止位后由硬件清零,也可 以在 PE=0 时由硬件清零。注意:收到 NACK 后 BTF 位不会置 1如果下一个要发送的字节为 PEC(I2C_SR2 寄存器中的 TRA=1,I2C_CR1 寄存器中的 PEC=1),则 BTF 位不会置 1
位 1 ADDR:地址已发送(主模式)/地址匹配(从模式)
由软件在读取 SR1 寄存器后读取 SR2 寄存器来清零,或在 PE=0 时由硬件清零。
地址匹配(从模式):0:地址不匹配或未接收到地址。1:接收到的地址匹配。—当接收到的从地址与 OAR 寄存器内容、广播呼叫地址或 SMBus 器件默认地址匹配时,或者 识别到 SMBus 主机或 SMBus 报警时,该位由硬件置 1。(根据配置确定何时使能)。
地址已发送(主模式)0:地址发送未结束 1:地址发送结束—在 10 位寻址模式下,接收到第二个地址字节的 ACK 后该位置 1。—在 7 位寻址模式下,接收到地址字节的 ACK 后该位置 1。注意:收到 NACK 后 ADDR 位不会置 1
位 0 SB:起始位 (Start bit)(主模式)
0:无起始位 1:起始位已经发送。—生成启动条件时置 1。—由软件在读取 SR1 寄存器后写入 DR 寄存器来清零,或在 PE=0 时由硬件清零
2.4. I2C状态寄存器2(I2C_SR2)
位 15:8 PEC[7:0] 数据包错误校验寄存器
ENPEC=1 时,此寄存器包含内部 PEC。
位 7 DUALF:双标志 (Dual flag)(从模式)
0:接收到的地址与 OAR1 匹配 1:接收到的地址与 OAR2 匹配—出现停止位、重复起始位或 PE=0 时由硬件清零。
位 6 SMBHOST:SMBus 主机头 (SMBus host header)(从模式)
0:无 SMBus 主机地址 1:SMBTYPE=1 且 ENARP=1 时接收到 SMBus 主机地址。—出现停止位、重复起始位或 PE=0 时由硬件清零。
位 5 SMBDEFAULT:SMBus 器件默认地址 (SMBus device default address)(从模式)
0:无 SMBus 器件默认地址 1:ENARP=1 时接收到 SMBus 器件默认地址—出现停止位、重复起始位或 PE=0 时由硬件清零。
位 4 GENCALL:广播呼叫地址 (General call address)(从模式)0:无广播呼叫 1:ENGC=1 时接收到广播呼叫地址—出现停止位、重复起始位或 PE=0 时由硬件清零。
位 3 保留,必须保持复位值
位 2 TRA:发送器/接收器 (Transmitter/receiver)
0:接收器 1:发送器此位在整个地址阶段的结尾处根据地址字节的 R/W 位状态进行置 1。同样,检测到停止位 (STOPF=1)、重复起始位、总线仲裁丢失 (ARLO=1) 或当 PE=0 时该位 也由硬件清零。
位 1 BUSY:总线忙碌 (Bus busy)
0:总线上无通信 1:总线正在进行通信—检测到 SDA 或 SCL 低电平时由硬件置 1 —检测到停止位时由硬件清零。该位指示总线上是否正在进行通信。即使禁止接口 (PE=0) 后此信息也会更新。
位 0 MSL:主/从模式 (Master/slave)
0:从模式 1:主模式—接口进入主模式时 (SB=1) 由硬件置 1。—检测到总线上的停止位、仲裁丢失 (ARLO=1) 或当 PE=0 时由硬件清零
2.5. I2C时钟控制寄存器(I2C_CCR)
位 15 F/S:I2C 主模式选择 (I2C master mode selection
0:标准模式 I2C1:快速模式 I2C
位 14 DUTY:快速模式占空比 (Fast mode duty cycle
0:快速模式 tlow/thigh = 21:快速模式 tlow/thigh = 16/9
位 11:0 CCR[11:0]:快速/标准模式下的时钟控制寄存器 (Clock control register in Fast/Standard mode)(主模式)
控制主模式下的 SCL 时钟。
标准模式或 SMBus 模式:Thigh = CCR * TPCLK1Tlow = CCR * TPCLK1
快速模式:如果 DUTY = 0:Thigh = CCR * TPCLK1Tlow = 2 * CCR * TPCLK1
如果 DUTY = 1:(达到 400 kHz) Thigh = 9 * CCR * TPCLK1Tlow = 16 * CCR * TPCLK1
例如:要在标准模式下生成 100 kHz 的 SCL 频率:如果 FREQR = 08,TPCLK1 = 125 ns,则必须将 CCR 编程为 0x28(0x28 <=> 40d x 125 ns = 5000 ns)。
注意:1.允许的最小值为 0x04,但快速占空比模式例外,其最小值为 0x01.这些时间均未经过滤波。.CCR 寄存器必须仅在禁止 I2C (PE = 0) 的情况下配置。
2.6. I2C TRISE寄存器(I2C_TRISE)
这些位必须编程为 I2C 总线规范中给定的最大 SCL 上升时间加 1。例如:标准模式下允许的最大 SCL 上升时间为 1000 ns。如果 I2C_CR2 寄存器中 FREQ[5:0] 位的值等于 0x08 且 TPCLK1 = 125 ns,则 TRISE[5:0] 位 必须编程为 09h。(1000 ns / 125 ns = 8 + 1)滤波器值也可以叠加到 TRISE[5:0]。如果结果不为整数,则 TRISE[5:0] 必须编程为整数部分,以符合 tHIGH 参数要求。注意:TRISE[5:0] 必须仅在禁止 I2C (PE = 0) 的情况下配置
2.7. I2C FLTR寄存器(I2C_FLTR)
位 4 ANOFF:模拟噪声滤波器关闭 (Analog noise filter OFF)0:使
能模拟噪声滤波器1:禁止模拟噪声滤波器注意:ANOFF 必须仅在禁止 I2C (PE = 0) 的情况下配置。
位 3:0 DNF[3:0]:数字噪声滤波器 (Digital noise filter)
这些位用于配置 SDA 和 SCL 输入端的数字噪声滤波器。数字滤波器可抑制脉宽达 DNF[3:0] * TPCLK1 以下的尖峰。
0000:禁止数字噪声滤波器
0001:使能数字噪声滤波器,滤波能力可达 1* TPCLK1。
...
1111:使能数字噪声滤波器,滤波能力可达 15* TPCLK1。
注意:DNF[3:0] 必须仅在禁止 I2C (PE = 0) 的情况下配置。如果模拟滤波器也已使能,则需将 数字滤波器添加到模拟滤波器中。
三、编程方法
编程方法主要是写代码测试两个I2C设备进行的通讯是否正常,由于STM32系列的硬件I2C不是很好用,容易卡死发不出来数据,所以我们下面的代码是用软件模拟I2C通讯过程。
首先说编程步骤:
第一步,引脚初始化操作,使能对应的GPIO时钟(若使用硬件I2C需要同时使能I2C时钟),配置相应的GPIO为输出开漏模式(也可以将CLK引脚配置为GPIO为推挽模式);
第二步,根据I2C时序写对应的函数:比如I2C_Start(),I2C_Stop(),I2C_Wait_Ack(),I2C_Ack(),I2C_Nack(),
第三步,写读写函数I2C_Send_Byte(u8 data),u8 I2C_Read_Byte(u8 ack),
向I2C从设备中写数据的步骤
1)启动通信,调用I2C_Start()函数,发送Start信号,启动i2c通信;
2)发送设备地址和写操作位;
3)等待应答,等待从设备发送应答信号ACK确认地址已经成功接收,如果没有收到应答,则可能出现问题或者设备故障
4)发送寄存器地址,发送要写入的寄存器地址给从设备,指明要将数据写入到哪个寄存器中;
5)等待应答,等待从设备发送应答信号ACK确认寄存器地址已成功接收;
6)发送数据,将写入的数据发送到从设备的寄存器中
7)等待应答,等待从设备发送应答信号ACK确认数据已经成功接收;
8)如果要连续写入多个寄存器,重复步骤4-7,每次发送新的寄存器地址和相应的数据
9)结束通信,发送停止信号stop来结束I2C通信。
要从从设备寄存器中读取数据的步骤
1)启动通信,调用I2C_Start()函数,发送Start信号,启动i2c通信;
2)发送设备地址和写操作位;
3)等待应答,等待从设备发送应答信号ACK确认地址已经成功接收;
4)发送寄存器地址,告诉从设备从它的那个寄存器中读取数据,
5)等待应答,等待从设备发送应答信号ACK确认寄存器地址已成功接收;
6)重新启动通信,发送重新启动信号,把通信转为读操作
7)发送从设备地址和读操作位,
8)等待应答,等待从设备发送应答信号ACK确认寄存器地址已成功接收;
9)从设备读取数据,从从设备中读取读取寄存器的数据;根据从设备的规格和寄存器的数据格式,确定正确的读取方式,如读取一个字节或者多个字节;
10)发送不应答信号,如果只读取一个字节数据,可以发送不应答信号NACK告诉从设备这是最后一个要读取的字节;
11)结束通信,发送停止信号stop来结束I2C通信。
以下是一些关键代码实现:
void IIC_Init(void)
{
RCC->AHB1ENR|=1<<1;//使能PORTB时钟
GPIO_Set(GPIOB,PIN8,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PD);
GPIO_Set(GPIOB,PIN9,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_50M,GPIO_PUPD_PD);
IIC_SDA=1;
IIC_SCL=1;
}
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;
delay_us(4);
IIC_SCL=0; //钳住IIC总线,准备发送或接收数据
}
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);
}
u8 IIC_Wait_Ack(void)
{
u8 ucErrTtime=0;
SDA_IN();
IIC_SDA=1;
delay_us(1);
IIC_SCL=1;
delay_us(1);
while(READ_SDA)
{
ucErrTtime++;
if(ucErrTtime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1:有应答 0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 i;
SDA_OUT();
IIC_SCL=0;
for(i=0;i<8;i++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读一个字节 ack=1;发送ACK,ack=0,发送NAck
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();
for(i=0;i<8;i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
if(READ_SDA==1) //总线上是1
{
receive|=(0x80>>i);
}
else
{
receive&=~(0x80>>i);
}
// receive<<=1;
// if(READ_SDA)
// {receive++;}
delay_us(1);
}
if(!ack)
IIC_NAck();
else
IIC_Ack();
return receive;
}