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

目录

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

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


void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
} 
MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机

        SCL全程由主机控制

综上所述:  不管是主机发送数据, 还是接受数据; 都是在SCL低电平期间放数据,  高电平期间读取数据;   不过接就是在主机读取数据是要把SDA的控制权交给从机---SDA置1;         全部的程序我们只需要操作主机, 从机不需要我们操作, ( 我们只写主机的代码)


void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
} 
MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机

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

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

猜你喜欢

转载自blog.csdn.net/m0_74739916/article/details/132766707