10:STM32------I2C通信

news2024/11/25 1:04:51

目录​​​​​​​

一:I2C通信协议

1:I2C简历

2:硬件电路

3:I2C时序基本单元

A : 开/ 终条件

2:发送一个字节

3:接收一个字节

4:应答机制 

4:I2C时序 

1:指定地址写

2:当前地址读

3: 指定地址读

二:MPU6050

1:简历

2:参数

3:硬件电路

4:框图

5:寄存器地址

三:案例

A:软件I2C读写 MPU6050

1:连接图

2:代码

B:硬件I2C读写 MPU6050

1:简介

2:l2C框图

3:l2C基本结构

4:主机发送

5:主机接收

6:连接图

7:函数介绍

8:代码


一:I2C通信协议

软件l2C的读写

1:I2C简历

I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线

两根通信线:SCL(Serial Clock)、SDA(Serial Data)

同步,半双工

带数据应答

支持总线挂载多设备(一主多从、多主多从)

        许多外设都遵守I2C的通信协议,eg:  上面图片中的: MPU6050,OLED,  AT24C02, DS3231模块

        SCL: 时钟     SDA:数据  主机对CS具有完全的控制权

        半双工 : 一根数据线负责发送和接收数据,   eg:I2C通信的SDA线

        同步: 接收方可以在时钟信号的指引下进行采样

        我们一般使用的为一主多从,下面讲的也为一主多从

2:硬件电路

所有I2C设备的SCL连在一起,SDA连在一起

设备的SCL和SDA均要配置成开漏输出模式

SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

下面为一主多从模式下的I2C通信

 主机:

        A : 对SCL线的完全控制--------任何时候,都是主机完全掌控SCL线

        B: 在空闲状态下,主机可以主动发起对SDA的控制,     只有在从机发送数据和从机应答的时候,  主机才会转交SDA的控制权给从机

从机:

         A  : 从机不允许控制SCL线------对于SCL时钟线,在任何时刻都只能被动的读取

        B : 对于SDA数据线,从机不允许主动发起对SDA的控制, -----只有在主机发送读取从机的命令后,或者从机应答的时候,  从机才能短暂地取得SDA的控制权

 弱上拉---弹簧杆子模型

        只允许向下拉,不允许向上拉.

        向下拉为--低电频;   不拉----高电频

线与现象:

        A : 只要有任意一个或多个从机设备输出了低电平,  总线就处手低电平

        B : 只有所有设备都输出高电平, 总线才处手高电平

优点

        辟免了引脚模式的频繁切换

        完全杜绝了电源短路现象,保证电路的安全

3:I2C时序基本单元

A : 开/ 终条件

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

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

        起始和终止,都是由主机产生的 ,  从机不允许产生起始和终止.  在总线空闲状态时,从机必须始终双手放开,   不允许主动跳出来,去碰总线

2:发送一个字节

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

注意: 这里是高位先行,  所以第一位是一个字节的最高位B7, 然后依次是次高B6 B5..........B0

        和串口是不一样的,  串口时序是低位先行,这里I2C是高位先行

        由于这整个时序是主机发送一个字节---------所以在这个单元里,SCL和SDA全程都由主机掌控------只有在从机发送数据和从机应答的时候,  主机才会转交SDA的控制权给从机----- 从机不允许控制SCL线------对于SCL时钟线,在任何时刻都只能被动的读取

        SCL全程由主机控制

3:接收一个字节

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

         主机在接收之前,需要释放SDA------这时候是从机发送数据需要在SCL低电频期间把数据放在SDA上面, 把SDA的控制器交给从机

        SCL全程由主机控制

        

4:应答机制 

谁发送数据,谁接收应答

        主机发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据(应答数据),数据0表示应答,数据1表示非应答

          从机给主机发送数据, 主机需要应答从机,  看还需不需要从机继续给主机发送数据

        数据0表示应答,数据1表示非应答------0把主机SCL时钟线拉低,,使得从机可以继续把数据放在SDA数据上;     1:把主机SCL时钟线拉高,  从机不能在SDA数据线上放数据. 主机可以读取数据,    主机也可以在CSL高电频期间给SDA一个上升沿结束

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

        主机给从机发送数据, 从机给主机应答, 看还需不需要主机继续给从机发送数据

        数据0表示应答,数据1表示非应答------0把主机SCL时钟线拉低,,使得主机可以继续把数据放在SDA数据上;     1:把SCL时钟线拉高,  从机不能在SDA数据线上读取数据. 从机读取数据,   主机也可以在CSL高电频期间给SDA一个上升沿结束

4:I2C时序 

1:指定地址写

       指定地址写----主机在指定的地址对从机写入数据

        对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

        首先把每个从设备都确定一个唯一的设备地址,  从机设备地址就相当于每个设备的名字,   主机在起始条件之后,要先发送一个字节叫一下从机名字,   所有从机都会收到第一个字节,和自己的名字进行比较

        从机设备地址,在12C协议标准里分为7位地址和10位地址--说下7位地址的模式

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

        第一个字节: 前七位写入要写从机的地址, 确定给谁写入数据.  第8位: 读写位---0表示,之后的时序主机要进行写入操作,   1表示,之后的时序主机要进行读出操作.

        应答位 : 在每一个字节完成后都需要一个应答位,    这里是主机给从机发送数据, 从机给主机应答, 看还需不需要主机继续给从机发送数据-----------在这个时刻,主机要释放SDA,释放SDA之后,引脚电平回弹到高电平,上升沿;   从机要在这个位拉低SDA,下降沿;   根据线与的特点为低电频.   这个过程,就代表从机产生了应答-------最终高电平期间,主机读取SDA,发现是0,就说明,我进行寻址,有人给我应答了,传输没有问题;   如果主机读取SDA,发现是1,说明进行寻址,应答位期间,我松手了,没人拽住它,没人给我应答,直接产生停止条件吧,并提示一些信息

        第二个字节 : 就可以送到指定设备的内部了, 从机设备可以自己定义第二个字节和后续字节的用途.    一般第二个字节可以是寄存器地址或者是指令控制字等  eg:主机向从机发送了0x19这个数据,  在MPU6050里,就表示要操作你0x19地址下的寄存器了

        第三个字节 :这个字节就是主机想要写入到0x19地址下寄存器的内容了

这个数据帧的目的就是,对于指定从机地址为1101000的设备,  在其内部0x19地址的寄存器中,写入0xAA这个数据

2:当前地址读

        当前地址读-----------主机在当前地址读取从机的数据

        对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

由手当前地址读并不能指定读的地址,  所以这个时序用的不是很多

3: 指定地址读

指定地址读----主机在指定地址读取从机的数据

对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data) 

又叫做复合模式   :   也就是把指定地址写的前2个字节Copy过来,  不用结束, 但是需要重新开始.   在重新写第一个字节,   因为第一个字节的最后一位决定了我们的操作为读取数据还是写入数据;   然后就正常的读取从机的数据

二:MPU6050

1:简历

        MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景

        3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度

        3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

2:参数

16位ADC采集传感器的模拟信号,量化范围:-32768~32767

加速度计满量程选择:±2、±4、±8、±16(g)----------ACCEL_CONFIG

陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)------GYRO_CONFIG

可配置的数字低通滤波器

可配置的时钟源

可配置的采样分频---SMPLRT_DIV

I2C从机地址:1101000(AD0=0)             1101001(AD0=1)

3:硬件电路

        AD0: 通过改变7位从机地址的最后一位,  l来改变从机的地址

        AD0=0  7位从机地址=1101000

        AD0=1    7位从机地址=1101001

4:框图

5:寄存器地址

16进制表示寄存器的地址10进制表示寄存器的地址寄存器名称读写权限第7位第6位

...

.................

SMPLRT_DIV----采样分频器

CONFIG-------配置寄存器

GYRO_CONFIG--------陀螺仪配置寄存器

ACCEL_CONFIG----加速度配置寄存器

ACCEL---加速度;      _H:高8位,    _L:低8位

TEMP----温度传感器

GYRO------陀螺仪传感器

PWR_MGMT_1----电源管理寄存器1;     PWR_MGMT_2:电源管理寄存器2

WHO_AM_I----器件的ID号码

三:案例

A:软件I2C读写 MPU6050

1:连接图

        我们这个代码使用的是软件I2C,  就是使用普通的GPIO口实现反转电频的操作,  它不需要32内部的外设资源支持,所以这里的端口可以任意指定

2:代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"

/**
* @brief  每个函数都是一SCL低电频结束的,除了MYI2C_Stop函数
	使用在除了MYI2C_Stop函数以外的函数,在函数开始的时候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;
}

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(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}


/**
* @brief  主机发送一个字节给从机---主机发送字节
	在SCL低电频的时候,主机把数据放在SDA上面;然后拉高SCL从机读取数据;
	然后再拉低SCL主机继续放数据,循环8次
	
  * @param  Byte 要发送的字节
  * @retval 无
  */
void MyI2C_SendByte(uint8_t Byte)
{	
	MyI2C_W_SCL(0);
	for (uint8_t i=0;i<8;i++)
	{	
		//0x80 1000 0000 &依次取出发送字节的每一位(从高到低)I2C是高位先行
		MyI2C_W_SDA(Byte&(0x80>>i));
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

/**
* @brief  从机发送一个字节给主机----主机接收字节
	从机发送数据,使用主机需要把SDA的控制权交从机(SDA置1),
循环8次:从机把数据放在SDA上,主机可以读取数据;拉低SCL从机放数据
  * @retval 无
  */
uint8_t MyI2C_ReceiveByte(void)
{	uint8_t Byte=0x00; //0000 0000
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机
	for (uint8_t i=0;i<8;i++)
	{ // |---置1
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000
		MyI2C_W_SCL(0);
	}
	return Byte;
}

/**
* @brief  主机在接收完一个字节之后,在下一个时钟发送一位数据
  * @param   AckBit 要发送的应答
  * @retval 无
  */
void MyI2C_SendAck(uint8_t AckBit)
{	
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}
/**
* @brief  主机在发送完一个字节之后,在下一个时钟接收一位数据,
  * @retval 无
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机
	
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;

}



#include "stm32f10x.h"                  // Device header
#include "MYI2C.h"
#include "MUP6050_Rge.h"
#define MPU6050_addrees 0xD0

/**
* @brief  指定地址写
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
	 * @param  Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据
  * @retval 无
  */
	
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MYI2C_Start();
	MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
	MyI2C_ReceiveAck();
	
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();
	MYI2C_Stop();

}

/**
* @brief  指定地址读
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
  * @retval 无
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{	
		uint8_t data;
		MYI2C_Start();
		MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
		MyI2C_ReceiveAck();
		MyI2C_SendByte(RegAddress);
		MyI2C_ReceiveAck();
		
		
		MYI2C_Start();
		MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001  因为要进行写入的操作,所以最后一位要置1
		MyI2C_ReceiveAck();
		data=MyI2C_ReceiveByte();
		MyI2C_SendAck(1);
		MYI2C_Stop();
		return data;
}

/**
* @brief  读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面
  * @retval 无
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

void MPU6050_init(void)
{
	MyI2C_Init();
	//写寄存器--应该先解除芯片的睡眠模式
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}

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


#ifndef __MPU6050_RGE_H
#define __MPU6050_RGE_H

#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



int main(void)
{	
	//I2C测试
//	uint8_t ACK;
//	OLED_Init();
//	MyI2C_Init();
//	
//	MYI2C_Start();
//	MyI2C_SendByte(0XD0);
//	ACK = MyI2C_ReceiveAck();
//	MYI2C_Stop();
//	OLED_ShowNum(1,1,ACK,3);
	
	//MPU6050的MPU6050_ReadReg测试
	
//	uint8_t ID;
//	OLED_Init();
//	MPU6050_init();
//	
//	OLED_ShowString(1, 1, "ID:");
//	ID = MPU6050_ReadReg(0x75);
//	OLED_ShowHexNum(1, 4, ID, 2);
//	OLED_ShowNum(2, 4, ID, 2);
//	
//	MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
//	MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
//	MPU6050_WriteReg(0x19,0x66);
//	uint8_t num=MPU6050_ReadReg(0x19);
//	OLED_ShowHexNum(3, 4, num, 2);

//------------------------------------------------------------------------------------
	uint8_t ID;
	int16_t AX, AY, AZ, GX, GY, GZ;
		OLED_Init();
		MPU6050_init();
		OLED_ShowString(1, 1, "ID:");
		ID = MPU6050_GetID();
		OLED_ShowHexNum(1, 4, ID, 2);
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}



主机先把SDA置1, 在读取SDA有意义?

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机
	
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;

}

        第一 : I2C的引脚都是开漏输出+弱上拉的配置,  主机输出SDA为1, 并不是强制SDA为高电频, 而是释放SDA.

        第二 : I2C是在在进行通信,  主机释放SDA, 从机如果在的话,会把SDA拉低,  所以即使主机之前把SDA置1了,  之会在读取SDA的值,也可能为0. 如果读取的结果为0的话代表了从机给了应答.

不断读取SDA,没有写入,读取的结果始终相同?

uint8_t MyI2C_ReceiveByte(void)
{	uint8_t Byte=0x00; //0000 0000
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机
	for (uint8_t i=0;i<8;i++)
	{ // |---置1
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000
		MyI2C_W_SCL(0);
	}
	return Byte;
}

        I2C正在进行通信, 它是有从机的存在的,   当主机不断驱动SDA时钟时, 从机有义务改变SDA的电频;   所以主机在每次循环读取SDA的时候, 读取的数据是受从机控制的,  这个数据也是从机想要给主机发送的数据

B:硬件I2C读写 MPU6050

1:简介

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

        支持多主机模型

        支持7位/10位地址模式

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

        支持DMA

        兼容SMBus协议

        STM32F103C8T6 硬件I2C资源:I2C1、I2C2

        支持多主机模型-----STM32采用可以变多主机的模式, 谁想做主机谁跳出

        兼容SMBus协议------系统管理总线, 主要用于电源管理系统中 ,是由l2C改进而来的

2:l2C框图

        发送数据 : 是这里的数据寄存器(DATA_REGISTER)和数据移位寄存器;   需要发送数据时可以把一个字节数据写到数据寄存器DR中,   当移位寄存器没有数据移位时, 数据寄存器的值就会转到移位寄存器里面去,;   在移位的过程中就可以把新的数据放在数据寄存器里面;    一旦前一个数据移位完成,  数据就可以无锋衔接,继续发送,   当光据由数据寄存器转到移位寄存器时--------就会置状态奇存器的TXE位为1(发送寄存器位空)

        接收数据 :

        

        自身地址寄存器和双地址寄存器  : STM32采用的是可以变多主机的模式, 这个是32作为从机的时候使用的,  32在不进行通信的时候为从机

3:l2C基本结构

使用硬件l2C------GPIO复用开漏输出;      复用,就是GPIO的状态是交由片上外设来控制的,   开漏输出,这是12C协议要求的端口配置

4:主机发送

操作流程

5:主机接收

6:连接图

 硬件中的通信引脚不能随便连接需要查看引脚定义表

7:函数介绍

在stm32f10x i2c.h文件中-----初始化I2C

void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

在stm32f10x i2c.h文件中-----生成起始条件

void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)

在stm32f10x i2c.h文件中-----生成终止条件

void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)

在stm32f10x i2c.h文件中-----32作为主机时,是否给从机应答

void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)

作用: 把ACK置1;  STM作为主机时:  ACK=1 给从机应答;   ACK=0 给从机非应答 

在stm32f10x i2c.h文件中-----发送数据

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)

实际就是把data数据直接写到DR寄存器里面去 

在stm32f10x i2c.h文件中------发送7位地址的专用函数, 第一个字节

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)

在stm32f10x i2c.h文件中-----读取数据

uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

在stm32f10x i2c.h文件中-----状态监控函数

ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)

这种方式就是同时判断一个或多个标志位 ,   来确定EV几EV几这个状态是否发生

8:代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"

#define MPU6050_addrees 0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) ==ERROR)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
}

/**
* @brief  指定地址写
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
	 * @param  Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据
  * @retval 无
  */
	
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MYI2C_Start();
//	MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
//	MyI2C_ReceiveAck();
//	
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	
//	MyI2C_SendByte(Data);
//	MyI2C_ReceiveAck();
//	MYI2C_Stop();
//-----------------------------------------------------------------------------
	
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6

	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8

	I2C_SendData(I2C2,Data);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2 结束时产生EV8_2的标志位

	I2C_GenerateSTOP(I2C2,ENABLE);



}

/**
* @brief  指定地址读
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
  * @retval 无
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{	
//		uint8_t data;
//		MYI2C_Start();
//		MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
//		MyI2C_ReceiveAck();
//		MyI2C_SendByte(RegAddress);
//		MyI2C_ReceiveAck();
//		
//		
//		MYI2C_Start();
//		MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001  因为要进行写入的操作,所以最后一位要置1
//		MyI2C_ReceiveAck();
//		data=MyI2C_ReceiveByte();
//		MyI2C_SendAck(1);
//		MYI2C_Stop();
//		return data;
//-----------------------------------------------------------------------------	

	uint8_t Data;
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6
	
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2
	
	
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Receiver);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//接收时的EV6

	//进入接收模式
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);  //EV7
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答
	
	return Data;
	
}

/**
* @brief  读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面
  * @retval 无
  */

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

void MPU6050_init(void)
{	
	//MyI2C_Init();
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,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);
	
	I2C_InitTypeDef I2C_structinit;
	I2C_structinit.I2C_Ack=I2C_Ack_Enable; //应答位---操作ACK用于确定在接收一个字节后是否给从机应答
	I2C_structinit.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//32做为从机: 响应机为的地址
	I2C_structinit.I2C_ClockSpeed=50000;
	I2C_structinit.I2C_DutyCycle=I2C_DutyCycle_2;//时钟占空比参数----高电频:低电频=2:1  ; I2C_DutyCycle_16_9---高电频:低电频=16:9
	I2C_structinit.I2C_Mode=I2C_Mode_I2C;
	I2C_structinit.I2C_OwnAddress1=0x00;//自身地址1  当32为从机是, 主机呼唤它的地址;于I2C_AcknowledgedAddress关联;
	/*
	I2C_AcknowledgedAddress 给7位时;  自身地址1 I2C_OwnAddress1---写7位
	I2C_AcknowledgedAddress 给10位时;  自身地址1 I2C_OwnAddress1---写10位

	*/
	I2C_Init(I2C2,&I2C_structinit);
	I2C_Cmd(I2C2,ENABLE);
	
	//写寄存器--应该先解除芯片的睡眠模式
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}



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






int main(void)
{	
	//I2C测试
//	uint8_t ACK;
//	OLED_Init();
//	MyI2C_Init();
//	
//	MYI2C_Start();
//	MyI2C_SendByte(0XD0);
//	ACK = MyI2C_ReceiveAck();
//	MYI2C_Stop();
//	OLED_ShowNum(1,1,ACK,3);
	
	//MPU6050的MPU6050_ReadReg测试
	
//	uint8_t ID;
//	OLED_Init();
//	MPU6050_init();
//	
//	OLED_ShowString(1, 1, "ID:");
//	ID = MPU6050_ReadReg(0x75);
//	OLED_ShowHexNum(1, 4, ID, 2);
//	OLED_ShowNum(2, 4, ID, 2);
//	
//	MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
//	MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
//	MPU6050_WriteReg(0x19,0x66);
//	uint8_t num=MPU6050_ReadReg(0x19);
//	OLED_ShowHexNum(3, 4, num, 2);

//------------------------------------------------------------------------------------
	uint8_t ID;
	int16_t AX, AY, AZ, GX, GY, GZ;
		OLED_Init();
		MPU6050_init();
		OLED_ShowString(1, 1, "ID:");
		ID = MPU6050_GetID();
		OLED_ShowHexNum(1, 4, ID, 2);
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

注意部分

    //进入接收模式
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);  //EV7
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答
	
	return Data;
	;

进入到主机接收的模式,  就开始接收从机发的数据波形了,   在接收一个字节时,有个EV6 1事件,   这个事件没有标志位,也不需要我们等待适合接收1个字节的情况

        读取1个字节:  也就是上面的代码 ,  恰好在EV6之后,要清除响应和停止条件的产生,  我们要把应答位ACK置0,同时把停止条件生成位STOP置1;       规定:在接收最后一个字节之前,  就要提前把ACK置0,同时设置停止位STOP

        读取多个字节:  那直接等待EV7事件,读取DR,就能收到数据了,   在接收最后一个字节之前,也就是这里的EV7-1事件,  需要提前把ACK置0,STOP置1

硬件的接收应答 :  并不需要一个函数来操作,  发送数据都自带了接收应答的过程,   同样,接收数据也自带了发送应答的过程,  所以发送和接受数据不需要我们处理应答位

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

weblogic配置证书

1.windows安装jdk JDK 可以到官网下载 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 系统变量→新建 JAVA_HOME 变量 配置环境变量&#xff1a;右击“我的电脑”–>“高级”–>“环境变量” JAVA_HOME&#xff1a;D:\Java\j…

原型链(一定要搞懂啊!!!>-<)

一、概念 1、prototype 习惯称作“显示原型”&#xff0c;只有构造函数才有的属性。 2、构造函数 能用new关键字创建的对象叫做构造函数 3、__proto__ 习惯称作“隐式原型”&#xff0c;每一个实例都有的属性&#xff0c;该属性指向他构造函数的“显示原型”。Function对象…

iwebsec靶场 文件包含漏洞通关笔记2-文件包含绕过(截断法)

目录 前言 1.%00截断 2.文件字符长度截断法&#xff08;又名超长文件截断) 方法1&#xff08;路径截断法&#xff09; 方法2&#xff08;点号截断法&#xff09; 第02关 文件包含绕过 1.打开靶场 2.源码分析 3.00文件截断原理 4.00截断的条件 5.文件包含00截断绕过 …

传统大数据迁移遇到的问题与解决方案

信息技术的进步和普及使得各个领域都在持续产生和积累大量的数据&#xff0c;这些数据蕴含了丰富的信息和价值&#xff0c;被称为大数据。据国际权威机构IDC预测&#xff0c;到2025年&#xff0c;全球数据总量将达到175ZB&#xff08;1ZB1024EB1024^7B&#xff09;&#xff0c;…

YOLOv5的常见报错总结

目录 1.安装pycocotools报错 2.Cant get attribute SPPF on module models.common 2.1原因 2.2解决方案 3.[WinError 1455] 页面文件太小&#xff0c;无法完成操作 3.1原因 3.2解决方案 4.AssertionError: Image Not Found D:\PycharmProjects\yolov5-hat\VOCdevkit\im…

centos安装flink,通过windows访问webui

1. 安装flink 1.1. flink的下载 通过flink官网下载flink安装包 https://flink.apache.org/ 下载安装包 1.2 flink在centos上的安装 将下载好的flink-1.17.1-bin-scala_2.12.tgz安装包放到centos目录下 解压文件&#xff1a; [rootlocalhost ~]# tar -zxvf flink-1.17.…

Vue3常用语法记录,基础使用看这篇就够了

1、ref const test ref<number>(8) 2、reactive const testObj reactive({test001: ,test002: }) 3、props & defineEmits defineProps({icon: String, }); const emit defineEmits([change, update:value, format-error]); emit(update:value, v); 4、wat…

DataGrip实时模板的配置2.0

印象里一直记着配置过代码实时模板&#xff0c;但是忘了换了工作电脑&#xff0c;之前配置的模板在我另一台电脑上 需要重新配置一下&#xff0c;我是笨蛋orz 配置方法和之前的一致 DataGrip实时模板的配置_王小小鸭的博客-CSDN博客https://blog.csdn.net/clover_oreo/articl…

9月11日上课内容 第二章 GFS 分布式文件系统

本章结构 前言 存储 块存储 硬盘 文件存储 nfs lvm raid ext4 ext3 centos6 xfs centos7 对象存储 GFS Ceph fastdfs 云端 OSS 阿里云存储 url链接 S3 亚马逊 …

证件照换衣服怎么换?学会这两招不用重拍证件照

当我们申请各种证件时&#xff0c;证件照往往是不可或缺的一步。这张照片会被用于各种场合&#xff0c;比如办理银行卡、信用卡、驾驶证、护照、签证等等&#xff0c;因此&#xff0c;它的重要性不言而喻。而证件照上的衣服则是影响印象的一个重要因素。所以&#xff0c;为了让…

2022年全国研究生数学建模竞赛华为杯A题移动场景超分辨定位问题求解全过程文档及程序

2022年全国研究生数学建模竞赛华为杯 A题 移动场景超分辨定位问题 原题再现&#xff1a; 在日常家庭生活中&#xff0c;人们可能需要花费大量时间去寻找随意摆放在家中某些角落里的小物品。但如果给某些重要物品贴上电路标签&#xff0c;再利用诸如扫地机器人的全屋覆盖能力&…

手动实现一个bind函数!

原文地址&#xff1a;手动实现一个bind函数&#xff01; - 知乎 1.bind函数用法 bind()方法用于创建一个新的函数&#xff0c;这个新函数接收的第一个参数代表的就是this&#xff0c;利用bind()函数我就就可以任意改变函数内部的this指向了。 官网的解释&#xff1a; bind()…

缩小检索范围、精准检索文献的方法

搜索文献也需要找到正确的方法&#xff0c;因为不正确的搜索方法直接影响搜索结果。要么发现的文献与我们需要的无关&#xff0c;要么检索到的文献很少&#xff0c;这不仅浪费时间&#xff0c;而且浪费精力。本文整理了准确检索文献、缩小文献检索范围的详细方法&#xff0c;希…

三步高效搭建在线帮助中心,这几个技巧与工具必须掌握

搭建一个高效的在线帮助中心是提供优质客户支持的重要组成部分。下面将介绍三个步骤&#xff0c;以及必须掌握的技巧和工具&#xff0c;帮助你快速搭建一个高效的在线帮助中心。 第一步&#xff1a;规划和准备 在搭建在线帮助中心之前&#xff0c;首先需要进行一些规划和准备…

python自动生成小学四则运算题目

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 题目一 1.题目要求&#xff1a; 设计一个程序&#xff0c;帮助小学生练习10以内的加法 详情: 随机生成加法题目; 学生…

《向量数据库指南》——向量数据库会是 AI 的“iPhone 时刻”吗?

最近一年,以 ChatGPT、LLaMA 为代表的大语言模型的兴起,将向量数据库的发展推向了新的高度。 向量数据库是一种在机器学习和人工智能领域日益流行的新型数据库,它能够帮助支持基于神经网络而不是关键字的新型搜索引擎。向量数据库不同于传统的关系型数据库,例如 PostgreSQ…

Vue3 Element-Plus 主题切换方案

1. .html 文件中&#xff0c;设置 <html> 标签的 “data-theme” 属性 2. 单独创建主题的样式文件 .css/.scss &#xff0c;并导入 3. 样式文件中创建不同主题对象 4. 定义不同主题中的样式变量 注意&#xff1a;左右两个主题的变量名一样&#xff0c;值不同 5. 页面样式…

【Hive SQL】统计同名路径下目录数量(基于reverse、split和substr函数)

首先&#xff0c;Hive事务表所产生的的路径信息如下&#xff1a; PS&#xff1a;其中路径信息格式为 /user/hive/warehouse/${database_name}.db/${table_name}/*/user/hive/warehouse/test.db/tran_ts/delete_delta_0000002_0000002_0000 /user/hive/warehouse/test.db/tran_…

VUE响应式

响应式 :::tip 提示 我们了解过响应式可以同步更新数据和视图&#xff0c;但是其工作原理我们最好也要了解一下。这样当你使用时遇到一些常见的错误&#xff0c;也能够快速定位是什么问题导致的。 了解响应式原理之前&#xff0c;你必须要先去了解 ES5 的 Object.defineProper…

项目管理的工作分解结构 (WBS):如何创建及使用

工作分解结构 (WBS) 是一种可视化的项目分解。通过将项目分解为更小的组件&#xff0c;WBS 可以将范围、成本和可交付成果集成到一个工具中。虽然大多数 WBS 是基于可交付成果的&#xff0c;但它们也可以是基于阶段的。下面来详细了解 WBS 可以为项目管理做些什么。 项目管理中…