知识点1【I2C总线的概述】
I2C:集成电路之间(Inter-Integrated Circuit),是一种硬件接口——总线
是一种由Philips公司,开发的双向二线制同步串行总线协议,广泛用于低速设备间的短距离通信。
可以实现一对多的通信。
核心概念:允许多个 ”从设备“ 通过仅仅两条共享的总线信号线 (SDA,SCL)实现 ”一对多“或”多对多“的通信。
特点:
1、有两根通信线:SCL,SDA
2、串行
3、同步
4、半双工
5、带数据应答
6、支持挂在多设备(一主多从,多主多从)
我们下面介绍的都是一主多从模式下的
知识点2【硬件要求】
- 所有设备的SCL连在一起,SDA连在一起
- 设备的SCL和SDA均要配置为开漏输出模式
-
配置成开漏输出的原因:
如果不用开漏输出,SCL很好处理:主机的SCL配置为推挽输出,从机均配置为浮空输入
但是SDA却很难处理:主机和从机共享一条SDA线(半双工),需要在发送和接收之间来回切换,配置起来很麻烦,因此这里不太建议使用推挽输出。
-
开漏输出就能够很好的地解决这个问题:外部有一个上拉电阻,弱上拉,当输出的时候,进行输入输出即可;输入的时候即观察电平的变化即可;当不操作的时候,默认处于一个若上拉不影响电路的状态(默认状态处于一个高电平)。并且此模式下有一个”线与“的特性:只要有一个设备处于低电平,总线就处于低电平——主要利用的也就是这个特性。
-
知识点3【I2C时序分析】
对于SCL线,控制权永远在主机
对于SDA,从机不允许主动发送获取SDA的控制权,只有在主动发出读取从机数据,或者从机应答的时候,才能够短暂地获取到SDA的控制权
1、起始条件 和 终止条件
与其他的区别,都是在SCL高电平期间,SDA发生电平的跳变的
条件 | 标志 |
---|---|
起始条件 | SCL高电平期间,SDA高→低 |
终止条件 | SCL高电平期间,SDA低变高 |
2、发送一个字节
在SCL低电平期间,主机将数据位依次放在SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据,SCL高电平期间,SDA不允许有电平变化(与起始和终止位区分开),循环8次,即可发送一个字节
在这个阶段中,SDA和SCL全部 由 主机掌握
3、接受一个字节
在SCL低电平期间,从机将数据位依次放在SDA线上(高位先行),然后释放SCL,主机将在SCL高点平期间读取数据,SCL高电平期间,SDA不允许变化,循环8次,即可接收一个字节
主机在接收数据前,需要释放SDA总线。
(虚线是从机控制,实线是主机控制)
4、接收应答
主机在发送完一个字节后,在下一个时钟接收一位数据,判断从机是否应答:0表示应答,1(默认电平)表示没有应答。
5、发送应答
主机接收完一个字节后,在下一个时钟发送一位数据:0表示应答,1表示未应答。
注意:
应答中的发送和接收,都是相对于主机而言的。即 发送应答:主机发送应答;接收应答:主机接收应答
因此这两张图的区分就是看,在高电平期间,谁掌握了SDA线的控制权
同步的好处:不过于依赖于外部电路,允许在发送过程中进入中断,可用软件模拟时序
异步的好处:节省一根时钟线,节省资源,对时间要求严格,对硬件依赖程度高
知识点5【时序模块的拼接】
0、概念补充
(1)一主多从模式中,主机是如何识别不同的从机呢
每个从机都有一个专属地址,主机通过从机的地址去确定要与那个从机进行通信。因此我们发送数据前需要一个识别的过程。
(2)模块不同,地址不同。那如果同一模块我们要连接多个,该如何识别呢?
地址位的某一位或者几位是可以变化的,以此达到此目的。
如MPU6050的ADD位,就是用来达到此目的的。
1、指定地址写
在指定设备(Slave Address)的指定地址(Reg Address),写入指定数据(Data)
这张图大家自行分析,一定要分析,按照我们上面所讲的 时序介绍。
依次是:
起始标志→主机发送从机地址→ 接收应答 → 主机发送指定地址 → 接收应答 → 发送数据 → 终止标志
2、指定地址读
在指定设备(Slave Address)的指定地址(Reg Address),读取指定数据(Data)
依次是:
起始标志→主机发送从机地址→ 接收应答 → 主机发送指定地址 → 接收应答
这一部分我们称为识别部分
Sr:是一个起始标志,重新开始即正式开始接收
主机释放SDA,主机发送数据 → 发送应答 → 接收数据 → 终止标志
知识点6【I2C的配置流程】
我们使用标准库进行配置,流程简单但是内部设计到的状态,需要准确记忆(bilibili:江协科技中有严格按照时序图配置的流程,大家感兴趣的可以去看看在10.3节)
我利用的I2C1,SCL对应PB6,SDA对应PB7
1、状态信息补充
(1)Flag
I2C_FLAG_BUSY
:SDA总线正在被其他设备占用时,这位会被置位
I2C_FLAG_AF
:
(2)Event
I2C_EVENT_MASTER_MODE_SELECT
:主设备请求控制总线,表示起始条件发送成功
关键事件:SCL高电平期间,SDA有高变低
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED
:指定目标从设备,表示从机地址已发送并收到从机的应答,主设备可以发送数据
I2C_EVENT_MASTER_BYTE_TRANSMITTED
:表示1byte数据以完整发送并接收到从机的应答。
可用于 指定地址 和 数据
发送完成的标志
2、代码演示
#include "i2c.h"
int i2c_timeout;
void I2C_Config(void)
{
GPIO_InitTypeDef GPIO_InitTSources={0};
I2C_InitTypeDef I2C_InitSources;
//打开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
GPIO_InitTSources.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitTSources.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitTSources.GPIO_Speed=GPIO_Speed_10MHz;
//PB6 PB7
GPIO_Init(GPIOB,&GPIO_InitTSources);
//i2c配置
I2C_InitSources.I2C_ClockSpeed=100000;//100KHZ
I2C_InitSources.I2C_Mode=I2C_Mode_I2C;//IIC模式
I2C_InitSources.I2C_OwnAddress1=0x00;
I2C_InitSources.I2C_Ack=I2C_Ack_Enable;//使能应答
I2C_InitSources.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//7bit从机地址
I2C_InitSources.I2C_DutyCycle=I2C_DutyCycle_16_9;
I2C_Init(I2C1,&I2C_InitSources);
I2C_Cmd(I2C1,ENABLE);
}
u8 IIC_Send_Data(uint8_t sla_addr,uint8_t reg_addr,uint8_t data)
{
//防止抢占冲突
i2c_timeout = I2C_MAX_TIMEOUT_MS;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) && i2c_timeout--)
{
//延时1ms
Delay_ms(1);
if(i2c_timeout == 0)
{
//返回超时信息
return I2C_TRANS_TIMEOUT;
}
}
//I2C起始 加 防超时操作
i2c_timeout = I2C_MAX_TIMEOUT_MS;
I2C_GenerateSTART(I2C1,ENABLE);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) && i2c_timeout--)
{
Delay_ms(1);
if(!i2c_timeout)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return I2C_TRANS_TIMEOUT;
}
}
//发送从设备地址,判断是否被选中
i2c_timeout = I2C_MAX_TIMEOUT_MS;
I2C_Send7bitAddress(I2C1,sla_addr,I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && i2c_timeout--)
{
Delay_ms(1);
if(!i2c_timeout)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return I2C_TRANS_TIMEOUT;
}
}
//判断接收应答 是否正确
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF))
{
//清楚标志位
I2C_ClearFlag(I2C1, I2C_FLAG_AF);
//关闭I2C
I2C_GenerateSTOP(I2C1,ENABLE);
//返回值
return I2C_TRANS_OTHERERR;
}
//发送从设备的寄存器地址
i2c_timeout = I2C_MAX_TIMEOUT_MS;
I2C_SendData(I2C1,reg_addr);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) && i2c_timeout--)
{
Delay_ms(1);
if(!i2c_timeout)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return I2C_TRANS_TIMEOUT;
}
}
//发送数据
i2c_timeout = I2C_MAX_TIMEOUT_MS;
I2C_SendData(I2C1,data);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) && i2c_timeout--)
{
Delay_ms(1);
if(!i2c_timeout)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return I2C_TRANS_TIMEOUT;
}
}
//停止
I2C_GenerateSTOP(I2C1,ENABLE);
return I2C_TRANS_SUCCESS;
}
#ifndef _I2C_H_
#define _I2C_H_
#include "stm32f10x.h"
#include "delay.h"
#define I2C_MAX_TIMEOUT_MS 1000
#define I2C_TRANS_OTHERERR 2
#define I2C_TRANS_TIMEOUT 1
#define I2C_TRANS_SUCCESS 0
u8 IIC_Send_Data(uint8_t sla_addr,uint8_t reg_addr,uint8_t data);
void I2C_Config(void);
#endif
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!