STM32学习笔记(十)--I2C、IIC总线协议详解

概述:Inter Integrated Circuit,一组多从 多组多从 有应答

是一种同步(具有时钟线需要同步时钟SCL)、串行(一位一位的往一个方向发送)、半双工(发送接收存在一种)通信总线。

(1)硬件电路 所有I2C设备的SCL连接在一起,SDA连在一起
                        设备的SCL和SDA均要配置成开漏输出模式
                        SCL跟SDA各添加一个上拉电阻,阻值一般为4.7K欧姆左右

                            

(2)I2C时序基本单元
起始条件;SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

(3)IIC 的基本的读写通讯过程

主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的 数据。主机接着发送从机地址+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机 在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号

主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,
都是由主机发出起始信号,接着发送从机地址+1(
读操作)组成的 8bit 数据,从机接收到数据验证是否是自身的地址。那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回 8bit 数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。

(4)软件读取I2C 的代码程序部分(主要是如何写时序复现时序的代码)

void MyI2C_W_SCL(uint8_t BitValue)//写SCL时钟电平
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)//写SDA数据电平
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
}



uint8_t MyI2C_R_SDA(void)//读SDA数据电平
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

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);一定要先拉高电平
}
//在SCL为低电平时 SDA可以改变电平状态 也就是 SCL拉低时 可写
//在SCL为高电平时 SDA可以不可以改变电平状态 也就是 SCL拉高时 可读
void MyI2C_Start(void)//时序开始;SDA要由高到低,SCL拉高 拉低为了可以改变数据SDA
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)//时序结束;SDA由低到高,SCL拉高
{
	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));//由于开始的时候把SCL拉低了 所以可以直接写
		MyI2C_W_SCL(1);//由于写完了一位 SCL立马拉高电平保证数据稳定写入
		MyI2C_W_SCL(0);//写入后 SCL变回低电平 利于循环遍历 方便下次改变SDA
	}
}
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);//设置I2C总线上的SDA线为高电平,准备接收数据。
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1);//SCL线为高电平,表示开始读取一个位的数据
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
//条件语句判断SDA线的状态,如果为高电平,则将对应的位(根据循环次数)置为1,使用位操作符|=将对应的位设置为1。
		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;
	MyI2C_W_SDA(1);//准备接收数据
	MyI2C_W_SCL(1);//开始接收数据
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);//结束数据接收
	return AckBit;
}
以下为I2C读取MPU6050
#define MPU6050_ADDRESS		0xD0 //模块地址 查手册
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();//应答
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();//应答
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();//应答
	MyI2C_Stop();
}


uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();//应答
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();//应答
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//1为读 0为写
	MyI2C_ReceiveAck();//应答
	Data = MyI2C_ReceiveByte();//函数用于接收应答信号,表示设备是否成功接收到地址。
	MyI2C_SendAck(1);//用于发送应答信号,表示继续读取下一个字节。
	MyI2C_Stop();
	
	return Data;
}

void MPU6050_Init(void)
{
	MyI2C_Init();
	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);//用来开启工作状态
}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

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;
}

以下为硬件读取I2C部分
配置步骤 (主要是使用引脚本身的IIC相关功能 而不像软件模拟通过IO方式)
1.开启RCC外设时钟 开启GPIO以及I2C外设
2.初始化GPIO 配置为复用开漏
3.配置I2C初始化结构体
4.开启I2C

  • 58
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7yewh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值