软件IIC和硬件IIC
原理:多个设备公用SCL和SDA信号线,支持多主机多从机。SCL是单向串行时钟线,始终由主设备控制,SDA是双向串行数据线,可以由主机和从机控制。每个设备在这个协议中都有固有的设备号。有俩个上拉电阻,当设备空闲时,由上拉电阻拉高。如果是多主机,那么会使用仲裁,避免数据冲突,有3种传输模式,100k,400k,3.4m。为什么要使用俩条信号线?节省资源。为什么scl时钟线由主设备控制?进行通信的主动
硬件层
原理:多个设备公用SCL和SDA信号线,支持多主机多从机。SCL是单向串行时钟线,始终由主设备控制,SDA是双向串行数据线,可以由主机和从机控制。每个设备在这个协议中都有固有的设备号。有俩个上拉电阻,当设备空闲时,由上拉电阻拉高。如果是多主机,那么会使用仲裁,避免数据冲突,有3种传输模式,100k,400k,3.4m。
为什么要使用俩条信号线?节省资源。为什么scl时钟线由主设备控制?进行通信的主动权是掌握在主机手中。为什么sda是双向串行数据线?因为设备和设备之间要通信,交换数据。为什么通过设备号区分?因为主设备发送数据是全部从设备都接收到了,就要通过一个特殊的id来区分,一般是厂家设置。上拉电阻的作用?避免数据冲突,如果主设备和从设备同时输出高电平,造成短路。空闲时,电平也会自动拉高。
协议层
主机发送起始信号,然后发送需要通信的从设备地址,选择读写位,等待应答,然后发送数据等待应答。传输数据需要停止时,主机发送停止信号,接收数据需要停止时,给从机发送非应答。
1、起始信号:scl高电平时,sda从高电平向低电平跳变。
2、停止信号:scl高电平时,sda从低电平向高电平跳变。
3、只有scl为高电平,sda的数据才有效。
4、读数据时,sda会被主机释放掉,由从机控制sda,主机通过sda接收数据;写数据时,sda由主机控制,从机通过sda接收;iic的设备地址有7位和11位。
5、响应,不管是主机和从机接收到数据都要发送应答,为了信号的完整性。有发送应答ack和发送非应答nack,接收到nack会停止数据发送。比如在主机发送完数据后,从机会立马得到sda的控制权,0是应答,1是非应答。
软件模拟iic(软件iic)
好处:不用依赖硬件资源,灵活,操作简单。
坏处:速度比硬件慢。
软件IIC,又称为“bit-banging IIC”,是指使用MCU的通用输入/输出(GPIO)引脚模拟IIC通信协议的方式。这种方式的优点在于,由于它不依赖于MCU的内部模块,因此它可以在任何具有GPIO功能的MCU上使用。此外,软件IIC的配置和使用通常比硬件IIC更简单。
然而,软件IIC也有其局限性。首先,由于IIC通信需要由CPU通过GPIO引脚进行模拟,因此CPU的负载较大。其次,由于GPIO引脚的性能限制,软件IIC通常不能支持和硬件IIC一样高的通信速率。
所以硬件IIC和软件IIC各有优缺点。在选择使用哪种方式时,需要根据具体的应用需求和MCU的性能进行权衡。例如,如果MCU的CPU负载较重,或者需要较高的通信速率,那么可能更适合使用硬件IIC。相反,如果MCU没有内部的IIC模块,或者需要更简单的配置和使用,那么可能更适合使用软件IIC。
#include "stm32f10x.h" // Device header
#include "Delay.h"
//参数给1或0,就可以释放或拉低SCL了。
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
//写数据
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
//读与写不是一个寄存器
//读数据
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
//IIC的SDA与SCL引脚初始化
void MyI2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//将两根线置为高电平,初始化为开漏输出且释放总线
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}
//开始
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
//结束
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
//发送单元
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//接收单元
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i,Byte = 0x00;
//MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
for (i = 0; i< 8;i++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1)
{
Byte |= (0x80>>i);
}
MyI2C_W_SCL(0);
}
return Byte;
}
//发送应答
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
//接收应答
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
//主机释放SDA
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
硬件IIC
硬件IIC是指使用微控制器(MCU)内部集成的IIC模块进行通信的方式。这种方式的优点在于,由于IIC通信由硬件模块自动完成,因此CPU的负载较小,可以同时处理其他任务。此外,硬件IIC通常能够支持更高的通信速率。
然而,硬件IIC也有其局限性。首先,由于硬件IIC使用的是MCU内部的模块,因此它的使用受到MCU的限制。其次,硬件IIC的配置和使用通常比软件IIC更复杂。
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0XD0
//带有超时退出机制的WaitEvent函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
//主机给从机写数据(单片机给MPU6050写数据)
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
//在库函数中,发送数据自带接收应答,接收数据自带发送应答。
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2,RegAddress);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2,Data);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2,ENABLE);
}
//主机读从机的数据(单片机读MPU6050的数据)
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
//在库函数中,发送数据自带接收应答,接收数据自带发送应答。
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2,RegAddress);
//问题:如果使用硬件操作I2C,在重复开始条件时,EVENT_MASTER_BYTE_TRANSMITTING这个状态正在发送数据(数据并没有发生完成),但是实验结果显示数据也能正常的接收,这是为什么?
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2,ENABLE);
return Data;
}
//获取数据寄存器的函数
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ)
{
uint8_t DataH,DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
//初始化MPU6050
void MPU6050_Init(void)
{
//第一步:开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//第二步:配置pin引脚为复用开漏输出模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//第三步:初始化I2C外设
I2C_InitTypeDef I2C_InitStructure;
//接收一个字节后是否给从机应答
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
//stm32从机可以响应几位的地址(stm32作为从机才会用到)
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 10000;
//占空比:这里指的是SCL低电平与高电平的比,因为SCL是弱上拉,所以在快速状态下,SDA波形翻转需要一些时间(低电平需要更多的时间)
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
//自身地址1(stm32作为从机才会用到),指定stm32的自身地址,方便别的主机呼叫。
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2,&I2C_InitStructure);
//第三步:I2C使能
I2C_Cmd(I2C2,ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
//获取MPU6050的ID
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
I2C协议是一种双向通信协议,需要两根线路:时钟线(SCL)和数据线(SDA)。其中:SCL是由主设备提供时钟信号来同步数据传输(SCL的作用)。SDA:用来传输数据的(双向传输)。
I2C协议的设计使得多个设备可以通过同一条总线进行通信,其中每个设备都有一个唯一的地址。这使得微控制器可以轻松地与多个设备进行通信,并能确定与哪个设备进行通信(也称寻址)。
通信协议通常包括硬件规定和软件规定两个方面:
硬件规定包括:硬件如何连接、端口地输入输出模式等
主机对SCL线任何时候都完全控制;从机任何时候都不能控制。
主机对SDA线:在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据与从机应答的时候,主机才会转交SDA的控制权给从机。
IIC原理图
所有I2C设备的SCL连在一起,SDA连在一起。
SCL和SDA各添加一个上拉电阻,阻值一般为4.7k欧左右
设备的SCL和SDA均要配置成开漏输出模式(强下拉),在通过上拉电阻实现弱上拉
开漏加若上拉模式,同时兼具了输入与输出的功能,如果想输入数据,直接观察电平变化。
在开漏模式下,输出高电平就相当于断开引脚。这么做避免了输入输出的来回切换。
IIC时序的基本单元
起始条件
SCL高电平期间,SDA从高电平切换到低电平
即SCL不动,把SDA拽下来,产生一个下降沿,当从机捕获到SCL高电平,SDA下降沿时,就会进行自身复位,等待主机召唤。
终止条件
SCL高电平期间,SDA从低电平切换到高电平
一个完整的数据帧总是以起始条件开始,终止条件结束。
起始与终止都是由主机产生,所以在空闲状态时,从机必须双手放开。
发送一个字节
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。总结:低电平切换(放)数据,高电平读取(读)数据。
接收一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后主机释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
发送应答
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据。
规则:数据0表示应答,数据1表示非应答。
发送应答是为了告诉从机:主机是否还要数据(命令从机发不发数据)。
目的:通知从机是否继续发送数据。
接收应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
规则:数据0表示应答,数据1表示非应答
从机告诉主机接收到了主机传递的数据了,主机刚发送一个字节,就立刻说有没有人收到数据(SDA高),如果有接收数据就会回应(SDA拉低)。目的:判断从机是否接收到了主机发送的数据。
通过拼接基本的时序单元,可以组成读写时序,进而实现芯片之间的通信。
IIC指定地址写时序
对于指定设备地址(Slave Address),在指定寄存器地址(Reg Address)下,写入指定数据(Data)。该图意思为:在0xD0地址的设备中,在0x19寄存器下写入0xAA数据。其中第一个字节(7位地址位+1位读写位,读:1,写:0)
在从机中,所有的寄存器被分配到一个线性区域中,且有个单独的指针变量,指示着其中的一个寄存器。上电默认指向0地址,并且每写入一个字节后与读出一个字节后,这个指针就会自动自增一次。移动到下一个位置。
注:从机设备可以自己定义第二个字节和后续字节的用途。如用途:寄存器地址;指令控制字;存储器地址等
主机首先发送一个字节(地址),所有从机都会收到这一个字节,和自己的地址进行比较,如果不是自己的地址,则认为主机没有叫它,之后的时序就不用管了。
不同型号的芯片的地址都还是不同的,相同型号的芯片地址都是一样的,如果有相同的芯片挂载到同一条总线呢?
地址中的可变部分,一般器件的最后几位可以在电路中改变的,通过接对应的电路,可以有效的改变地址。
顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→发送一个字节的数据→从机应答→结束条件。
IIC指定地址读时序
指定地址读后,会接收一个从机应答,之后,总线控制权交给从机。
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)。该图意思为:在0xD0地址的设备中,读取0x19寄存器中的数据(0xAA)。
顺序:开始条件→7位从机地址+1位写(0)位→从机应答(Receive ACK,RA)→发送寄存器地址→从机应答位→重新开始→7位地址+读(1)位→从机应答(0)→读取一个字节→主机发送非应答(1)→结束。
注:指定地址读写只能跟着起始条件的第一个字节,所以如果想切换读写方向,只能再来个起始
指定地址写多个字节
顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→发送一个字节的数据→从机应答→发送字节的数据→从机应答→结束条件。可以多次重复绿色的步骤。
只需要将发送字节的数据→从机应答重复即可。
指定地址读多个字节
顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→重新开始→7位地址+读(1)位→发送应答→读取一个字节→主机发送应答→读取一个字节→主机发送非应答→结束。可以多次重复绿色的步骤。
补充:寄存器也是存储器,普通的存储器只能写与读,没有实际的意义。寄存器的每一位数据都对应了硬件的电路状态,寄存器与外界的硬件电路是可以互动的。
IIC外设
数据收发的核心部分是数据寄存器与数据移位寄存器。
发送数据(橙色线):
当我们发送数据时,可以把一个字节的数据写到数据寄存器DR中,当数据移位寄存器没有数据移位时,这个数据寄存器的值会进一步转到移位寄存器里,在移位的过程中,我们就可以直接把下一个数据放到数据寄存器中里等着,一旦前一个数据移位完成,下一个数据就可以无缝衔接继续发送,当数据由数据寄存器转移到数据移位寄存器时,置状态寄存器TXE位为1,表示发送寄存器为空。--发送流程。
接收数据(绿色线):
输入的数据一位一位的从引脚移入到移位寄存器里,当一个字节的数据收齐之后,数据就会整体从移位寄存器转到数据寄存器,同时置标志位RXNE为1,表示接收寄存器非空,这时就可以把数据从数据寄存器读出来。在IIC中数据收发是用一组数据寄存器与数据移位寄存器。通过控制寄存器的对应位控制什么时候收什么时候发。
注:在stm32的IIC中,读与写不是同一个寄存器。
I2C外设,利用硬件电路实现I2C的通信。一般外设寄存器有:控制寄存器CR、数据寄存器DR,状态寄存器SR:
CR:控制寄存器:具有控制功能,比如开关灯等(就像,踩油门、打方向盘,控制汽车)
DR:数据寄存器:存储数据,比如汽车运行时产出的速度、温度等
SR:状态寄存器:描述当前状态,比如汽车是否启动等(通过观看仪表盘,了解汽车运行状态)
IIC外设简约框图
更多推荐
所有评论(0)