0x0B I2C

本文的大部分内容来自B站up主 江协科技, 此文只供本人学习记录用途, 侵删

一、I2C

  • I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步,半双工
  • 带数据应答
  • 支持总线挂载多设备(一主多从、多主多从)

二、I2C硬件

  • 所有I2C设备的SCL连在一起,SDA连在一起
  • 设备的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右(属于弱上拉)
  • 如果不是开漏而是推挽, 那么输出引脚的高电平就完全来自于电源正极, 若此时的接收的引脚正好是低电平, 那就会照成电源短路, 所以输出引脚全部都得开漏, 但是高电平并不能完全浮空, I2C协议要求闲置时, 两根通讯线都得是高电平, 所以给他们接了个阻值很高的上拉电阻, 既安全又能达到功能.

image-20231102125014618

设备内部输出电路, 其实就是个开漏输出的电路, 输出高电平时, 引脚浮空, 输出低电平时, 引脚被强下拉

image-20231102143853674

三、I2C时序

说明; 以下时序实线为主机控制, 虚线为从机控制

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平

在这里插入图片描述

  • 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

image-20231102150346150

  • 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

image-20231102150615260

  • 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
  • 接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

在这里插入图片描述

四、软件I2C代码

纯粹的GPIO模拟出I2C时序

#include "stm32f10x.h"                  // Device header
#include "delay.h"
void MYI2C_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure); //配置为开漏输出(低电平有驱动能力)
	GPIO_SetBits(GPIOA,GPIO_Pin_8|GPIO_Pin_9); //两个引脚设为高电平
}
//写SCL
void MYI2C_W_SCL(uint8_t BitValue){
	GPIO_WriteBit(GPIOA,GPIO_Pin_8,(BitAction)BitValue);
	Delay_us(10);//防止引脚翻转过快, 外设可能会跟不上
}
//写SDA
void MYI2C_W_SDA(uint8_t BitValue){
	GPIO_WriteBit(GPIOA,GPIO_Pin_9,(BitAction)BitValue);
	Delay_us(10);
}
//读SDA
uint8_t MYI2C_R_SDA(void){
	uint8_t BitValue = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9);
	Delay_us(10);
	return BitValue;
}
//开始信号
void MYI2C_Start(void){
	MYI2C_W_SDA(1);
	MYI2C_W_SCL(1);
	MYI2C_W_SDA(0);
	MYI2C_W_SCL(0);
}
//结束信号
void MYI2C_End(void){
	MYI2C_W_SDA(0);
	MYI2C_W_SCL(1);
	MYI2C_W_SDA(1);
}
//发送一个字节
void MYI2C_SendByte(uint8_t Byte){
	for(uint8_t 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 Byte = 0;
	MYI2C_W_SDA(1);
	for(uint8_t i=0;i<8;i++){
		MYI2C_W_SCL(1);
		if(MYI2C_R_SDA())Byte |= (0x80>>i);
		MYI2C_W_SCL(0);
	}
	return Byte;
}
//发送应答
void MYI2C_SendAck(uint8_t Ack){
	MYI2C_W_SDA(Ack);
	MYI2C_W_SCL(1);
	MYI2C_W_SCL(0);
}
//接收应答
uint8_t MYI2C_ReceiveAck(void){
	uint8_t Ack;
	MYI2C_W_SDA(1);
	MYI2C_W_SCL(1);
	Ack=MYI2C_R_SDA();
	MYI2C_W_SCL(0);
	return Ack;
}

五、MPU6050

  • MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
  • 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
  • 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
  • 加速度计具有静态稳定性, 只有在静态时才能测量出准确的角度
  • 陀螺仪传感器具有动态稳定性, 和加速度计结合就能得出稳定的姿态角 , 但本节只是I2C的应用, 只读出数据, 不会涉及到姿态解算的知识

六、软件I2C配置MPU6050

我们通过I2C控制MPU6050的寄存器, 至于为什么这么控制, 这么发的作用, 不是本节的重点

重点是看看怎么应用I2C:

这是MPU6050读写寄存器的时序

image-20231102183353464

image-20231102183434309

这是代码:

#include "stm32f10x.h"                  // Device header
#include "MYI2C.H"
#include "MPU6050_REG.H"
#define MPU_ADDRESS 0XD0  //MPU6050的设备地址

//写寄存器, 传入地址和数据
void MPU_WriteReg(uint8_t RegAddress,uint8_t Byte){
    //I2C开始
	MYI2C_Start();
    //发送从机写地址
	MYI2C_SendByte(MPU_ADDRESS);
	MYI2C_ReceiveAck();
	//发送寄存器地址
	MYI2C_SendByte(RegAddress);
	MYI2C_ReceiveAck();
	//发送字节
	MYI2C_SendByte(Byte);
	MYI2C_ReceiveAck();
	//结束通讯
	MYI2C_End();
}
//读寄存器, 传入地址和数据
uint8_t MPU_ReadReg(uint8_t RegAddress){
    //I2C开始
	uint8_t Byte;
	MYI2C_Start();
	//发送从机写地址
	MYI2C_SendByte(MPU_ADDRESS);
	MYI2C_ReceiveAck();
	//发送寄存器地址
	MYI2C_SendByte(RegAddress);
	MYI2C_ReceiveAck();
	//I2C开始
	MYI2C_Start();
	//发送从机读地址
	MYI2C_SendByte(MPU_ADDRESS|0x01);
	MYI2C_ReceiveAck();
	//接收字节
	Byte=MYI2C_ReceiveByte();
	MYI2C_SendAck(1);
	MYI2C_End();
	return Byte;
}

void MPU_Init(void){
	MYI2C_Init();//初始化I2C
	MPU_WriteReg(MPU6050_PWR_MGMT_1,0x08);//电源管理寄存器1
	MPU_WriteReg(MPU6050_PWR_MGMT_2,0x00);//电源管理寄存器2
	MPU_WriteReg(MPU6050_SMPLRT_DIV,0x09); //陀螺仪输出速率(分频)
	MPU_WriteReg(MPU6050_CONFIG,0x06);     //配置低通滤波器
	MPU_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪配置
	MPU_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计配置
}


void MPU_ReadValue(int16_t* AX,int16_t* AY,int16_t * AZ,
					int16_t* GX,int16_t* GY,int16_t* GZ)
{
	//整体流程都是读取数据的低八位和高八位, 然后装到传入的参数地址中
	int16_t DataH,DataL;
	DataH = MPU_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AX=(DataH<<8) | DataL;
	
	DataH = MPU_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AY=(DataH<<8) | DataL;
	
	DataH = MPU_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AZ=(DataH<<8) | DataL;
	
	
	DataH = MPU_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU_ReadReg(MPU6050_GYRO_XOUT_L);
	*GX=(DataH<<8) | DataL;
	
	DataH = MPU_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU_ReadReg(MPU6050_GYRO_YOUT_L);
	*GY=(DataH<<8) | DataL;
	
	DataH = MPU_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GZ=(DataH<<8) | DataL;
}
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
//这个头文件描述了MPU6050中寄存器的地址

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif

七、STM32中的I2C硬件外设

  • STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担

  • 支持多主机模型支持7位/10位地址模式

  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)

  • 支持DMA兼容SMBus协议

  • STM32F103C8T6 硬件I2C资源:I2C1、I2C2

    工作流程:

    和USART类似, 我们只需关注DR(数据寄存器), CR(配置寄存器), SR(状态寄存器) ,配置完写数据, 读取状态位就完事了

    发送时:

    把数据写入DR, 如果发送移位寄存器没有数据在移位, 就会把DR的数据放到发送移位寄存器中, 准备发送,此时将TXE置1, 此时我们就可以写入下个数据了

    接收时:

    数据进来, 在接收控制器的驱动下, 数据一位一位进入接收移位寄存器, 当接收移位寄存器接收了8个位, 这一个字节的数据会一下子被放进DR中, 此时将RXNE置1,我们就可以读取数据了

    在这里插入图片描述

    image-20231102185007130

    image-20231102190949676

八、硬件I2C控制MPU6050

对于事件的具体参数填什么, 在库函数中有对应的表格可供查询

在这里插入图片描述

与软件I2C相比,只有初始化,写寄存器, 读寄存器的代码有改动:

void MPU_WriteReg(uint8_t RegAddress,uint8_t Byte){
    //I2C开始 等待EV5事件
	I2C_GenerateSTART(I2C2, ENABLE);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	//I2C发送7位地址(从机写地址) 等待EV6事件
	I2C_Send7bitAddress(I2C2, MPU_ADDRESS, I2C_Direction_Transmitter);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);
	//I2C发送数据(寄存器地址) 等待EV8事件	
	I2C_SendData(I2C2,RegAddress);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING )!=SUCCESS);
	//I2C发送数据(写入数据) 等待EV8事件
	I2C_SendData(I2C2,Byte);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS);
	//I2C停止通讯
	I2C_GenerateSTOP(I2C2, ENABLE);
}
uint8_t MPU_ReadReg(uint8_t RegAddress){
	uint8_t Byte;
	//I2C开始 等待EV5事件
	I2C_GenerateSTART(I2C2, ENABLE);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	//I2C发送7位地址(从机写地址) 等待EV6事件
	I2C_Send7bitAddress(I2C2, MPU_ADDRESS, I2C_Direction_Transmitter);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);
	//I2C发送数据(寄存器地址) 等待EV8事件
	I2C_SendData(I2C2,RegAddress);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING )!=SUCCESS);
	//I2C开始 等待EV5事件
	I2C_GenerateSTART(I2C2, ENABLE);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	//I2C发送7位地址(从机读地址) 等待EV6事件 注意这里的EV6是接收的EV6, 和上面不一样
	I2C_Send7bitAddress(I2C2, MPU_ADDRESS, I2C_Direction_Receiver);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )!=SUCCESS);
	//接收的前一时刻给ACK置0和STOP请求
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	//等待EV7事件发生 接收数据并给到应答
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS);
	Byte=I2C_ReceiveData(I2C2);
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	//返回接收数据
	return Byte;
}

void MPU_Init(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启时钟
	
	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); //配置PB10(I2C2_SCL) PB11(I2C2_SDA)为复用开漏
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_ClockSpeed=50000;
	I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1=0x00;
	/*
		I2C_Mode=I2C_Mode_I2C; 设置为I2C模式(而不是其他兼容的通信协议)
		I2C_Ack=I2C_Ack_Enable; 确定在接收一个字节后给从机应答
		I2C_AcknowledgedAddress_7bit;  选择七位地址
		I2C_ClockSpeed=50000;时钟速度 最高至400kHz	
		I2C_DutyCycle=I2C_DutyCycle_2; 时钟占空比 只有在100kHz以上才有用, 还有个16比9的,表示		高电平的低电平的占比时间
		I2C_OwnAddress1=0x00; 自身地址, 本节作为主机, 无所谓
	*/
	I2C_Init(I2C2, &I2C_InitStructure);
	I2C_Cmd(I2C2, ENABLE);//使能I2C2
	
    //MPU6050的寄存器配置
	MPU_WriteReg(MPU6050_PWR_MGMT_1,0x09);
	MPU_WriteReg(MPU6050_PWR_MGMT_2,0x00);
	MPU_WriteReg(MPU6050_SMPLRT_DIV,0x09);
	MPU_WriteReg(MPU6050_CONFIG,0x06);
	MPU_WriteReg(MPU6050_GYRO_CONFIG,0x18);
	MPU_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值