首页 > 其他分享 >STM32学习笔记(十)--I2C、IIC总线协议详解

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

时间:2024-06-20 09:02:05浏览次数:22  
标签:SCL MyI2C -- void MPU6050 STM32 SDA IIC GPIO

概述: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

标签:SCL,MyI2C,--,void,MPU6050,STM32,SDA,IIC,GPIO
From: https://blog.csdn.net/weixin_64593595/article/details/139790136

相关文章

  • react基本概念
    React基本概念前言React是一个由Facebook开发并维护的前端库,用于构建用户界面。它采用组件化的设计思想,使开发者可以通过组合组件构建复杂的应用程序。本篇文章将介绍React的基本概念,帮助初学者快速上手。 1.什么是React?React是一个用于构建用户界面的JavaScrip......
  • Error: Insufficient memory (Failed to allocate 16588800 bytes) in void* cv::OutO
    这个错误信息表明在运行OpenCV库时遇到了内存不足的问题。具体来说,OpenCV尝试分配大约16,588,800字节(16MB)的内存,但操作系统无法满足这个请求。这可能是由于以下原因:内存限制:你的设备或系统可能没有足够的内存来处理当前操作。这在资源有限的设备(如某些嵌入式系统或旧手机)上尤......
  • 【金融安全新防线:黑龙江等保测评在金融行业安全强化中的关键作用】
    在数字化时代,金融行业作为国家经济的命脉,承载着巨大的资金流动与敏感信息处理任务,其信息安全的重要性不言而喻。面对日益复杂的网络威胁环境,黑龙江省内的金融企业正以前所未有的力度加强信息安全防护体系,其中,信息安全等级保护测评(简称“等保测评”)成为了构建金融安全新防线的关......
  • 借助浏览器实现一个录屏插件?
    说在前面......
  • 一个案例,剖析攻防演练中威胁溯源的正确姿势
    一年一度的攻防演练即将拉开帷幕。“威胁溯源”一直是演练活动中一个十分重要的工作项,它不仅有助于理解和分析攻击的来源、方法和动机,还能够显著提升整体安全防护水位,提升组件与人员的联动协作能力。在真实的攻击场景中,溯源工作还能造成对攻击者的威慑,进一步净化网络安全空间。......
  • Nodejs基本概念
     Node.js基本概念前言Node.js是一个基于ChromeV8引擎的JavaScript运行环境,主要用于构建服务器端应用。由于其高效的事件驱动和非阻塞I/O模型,Node.js在处理高并发和实时应用方面具有显著优势。本篇文章将介绍Node.js的基本概念,帮助初学者快速上手。1.什么是No......
  • 基于SpringBoot的冬奥会科普平台
    采用技术基于SpringBoot的冬奥会科普平台的设计与实现~开发语言:Java数据库:MySQL技术:SpringBoot+MyBatis工具:IDEA/Ecilpse、Navicat、Maven页面展示效果登录注册登录用户注册系统功能首页冬奥会运行页面精彩视频页面冬奥会论坛管理员页面首页用户管......
  • ThinkPHP 的老漏洞仍然被攻击者钟情
    研究人员发现安全领域出现了令人不安的趋势:攻击者不仅对新披露的漏洞十分感兴趣,对已知的漏洞也丝毫不放过,尽管有些漏洞已经存在了好些年头,攻击者仍然能够通过老漏洞成功完成攻击。典型的例子就是ThinkPHP远程代码执行漏洞CVE-2018-20062和CVE-2019-9082,距今已有六年的时......
  • Columbus:一个基于API实现的子域名发现服务工具
    关于ColumbusColumbus是一款功能强大的子域名发现与枚举工具,该工具基于API实现其功能,并且还提供了很多其他的高级功能。在该工具的帮助下,广大研究人员可以快速且高效地实现子域名枚举任务。前端架构HTML+tailwindcss+DaisyUI想要了解网络安全,或者想学习网络安全知识的......
  • Fluent UDF(四)数据访问宏(2)
    上一节介绍了网格节点和网格面数据获取宏,本节继续介绍网格单元数据获取宏。网格单元(cell)存储着许多几何参数和物理参数(单元中心、温度、压力等变量),这些参数均可利用对应的单元数据获取宏进行数据访问和操作,单元宏以C_前缀开头,获取的变量均以国际单位制(SI)返回,对于某......