目录
引言
学习的内容是基于b站江协科技等的视频(图片来源于江协科技)
此篇用于记录学习感受和学习代码
一、理论
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
两根通信线:串行时钟线SCL (Serial Clock)、串行数据线SDA(Serial Data)
工作模式:同步,半双工
特点:带数据应答,支持总线挂载多设备(一主多从、多主多从)
一主多从:一个单片机作为主机,挂载一个或多个模块作为从机
主机控制着SCL信号线,从机无改变和控制SCL信号线的权力
从机访问SDA信号线时,需要得到主机的许可。
多主多从:需要进行时钟同步
1.1 硬件电路设计要求
所有的I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7k左右
(图片来源于江协科技)
一主多从模式下:
主机的SCL可以配置成推挽输出,所有从机的SCL都要配置成浮空输入或者上拉输入
主机和从机的SDA在发送时是输出,在接收时是输入,为防止一个输出高电平一个输出低电平导致电源短路,为避免这个情况出现,I2C协议禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构。
注:开漏输出兼容输入,输入时,将输出值设置为1,读取输入寄存器即可
1.2 软件时许基本单元
1.2.1 起始&终止条件和基本规则
起始条件:SCL在高电平期间,SDA从高电平切换到低电平
终止条件:SCL在高电平期间,SDA从低电平切换到高电平
(图片来源于江协科技)
空闲状态下的两条数据线都处于高电平状态,SCL和SDA由外挂的上拉电阻拉高致高电平
起始条件后的第一个操作应该是把SCL总线拉至低电平,方便后面时序单元的拼接,同时在后面的时序中保证,每个时序单元的SCL都是以低电平开始,低电平结束,在终止条件前,需要先把SCL总线拉至高电平。
起始和终止,都是由主机产生的,从机不允许产生起始和终止
1.2.2 发送一个字节
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
(图片来源于江协科技)
从机为了尽快读取数据,一般在SCL上升沿期间就开始读取数据
这个过程中,SCL和SDA全程被主机控制,从机只能被动读取
1.2.3 接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环以上过程8次,即可以接收一个字节(主机在接收之前,需要释放SDA(释放SDA相当于切换为输入模式))
(图片来源于江协科技)
由于SCL线是主机控制的,所以从机要在SCL下降沿后马上改变SDA线
1.2.4 应答机制的设计
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
(图片来源于江协科技)
即主机给应答了,从机就会继续发,给非应答了,从机就不发了
1.3 I2C时序
从机设备地址,在I2C协议标准里分为7位地址和十位地址
一般在每个设备出厂时,其公司会给它自动设置一个地址。同型号芯片设备的地址一般相同,但是其有可变地址部分,可以更改该部分的高低电平改变地址。
1.3.1 指定地址写一个字节
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
(图片来源于江协科技)
指定设备即选择的从机设备的地址。
指定地址即选择的从机设备内部的寄存器地址。
写入数据即在这个寄存器中写入的Data数据。
写入过程:
首先主机在SCL高电平期间,拉低SDA,(即执行的起始条件,起始条件和需要发送一个字节的数据,字节内容为7位从机地址+1位读写位(最低位为读写位:0写1读)),然后拉低SCL,此时改变SDA数据,再拉高SCL,反复8次。然后跟着的单位为接收从机的应答位。
第二个字节为从机设备自己设定的应答位。
例如:MPU6050定义的第二个字节就是寄存器地址、AD转换器定义的第二个字节可能是指令控制字。存储器第二个字节可能就是存储器地址。
后面的数据即为Data数据。
在停止条件前,先拉低SDA,然后执行停止条件(先释放SCL,再释放SDA)
1.3.2 当前地址读一个字节
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
(图片来源于江协科技)
指定设备即选择的从机设备的地址。
当前地址指针即在从机中的指针变量,上电默认为0,每写入和读出一个字节后,该指针会自动自增一次,移动到下一个位置(列如在0x19写入了一个字节数据,该指针就会自动改变为0x1A,再调用一次当前地址读的位置,就会返回0x1A下的数据)。
读过程:
首先主机在SCL高电平期间,拉低SDA,(即执行的起始条件,起始条件和需要发送一个字节的数据,字节内容为7位从机地址+1位读写位(最低位为读写位:0写1读)),然后拉低SCL,此时改变SDA数据,再拉高SCL,反复8次。然后跟着的单位为接收从机的应答位。
读取的第二个字节必须是从机数据
1.3.3 指定地址读一个字节
对于指定设备Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
(图片来源于江协科技)
即把指定地址写和当前地址读复合起来的复合格式。
起始条件+设备地址+读写位+应答+指定地址+应答+起始条件+设备地址+读写位+读取数据+非应答位+结束条件
二、硬件I2C介绍
2.1 简介
硬件I2C和软件I2C的区别之一:资源的区别,硬件I2C一般会固定的几个引脚,软件I2C可以选择任意两个正常工作的引脚。
以STM32为例。
stm32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
主持多主机模型
支持7位/10位地址模式
支持不同的通讯速度,标准速度(高达100kHz),快速(高达400kHz)
支持DMA(可以提高多字节读写传输速度)
兼容SMbus协议(系统管理总线,是基于I2C改进而来,一般用于电源管理系统)
STM32F103C8T6硬件I2C资源:I2C1 、 I2C2
2.2 一主多从模式
固定一个主机,多个从机,主机根据从机的设备地址进行通信
2.3 多主多从模式
2.3.1 固定多主多从模式:
固定的多个主机和多个从机
主机间先通过总线仲裁获取总线控制权,再根据从机的设备地址进行通信
2.3.2 可变的多主机模式:
默认全设备为从设备,当有设备间需要通信时,需要通信的其中一个设备出来担当主机角色,再根据需要通信的设备地址进行通信。当多个设备需要当主机时,为总线冲突状态,这时需要进行总线仲裁。
2.4 7位地址和10位地址的协议区别
主要区别是起始条件后的第一第二个字节
对于7位地址来说,起始位后的第一个字节为 地址位+读写位 一般为11010 00 读写位,11010代表七位寻址。
对于10位地址来说,起始后的第一第二个字节均为 地址位+标志位,一般为11110 + 2位地址位 + 读写位 + 8位地址,即11110代表10位寻址。
2.5 内部电路
(图片来源于江协科技)
2.5.1 发送数据
当需要发送数据时,可以把1个字节数据写到数据寄存器DR( DATA REGISTER),当移位寄存器没有数据时,数据寄存器DR的值就会存储到移位寄存器中交由移位寄存器进行移位等操作。
当数据由数据寄存器DR转移到移位寄存器时,就会置状态寄存器的TXE位为1,表示发送寄存器为空
2.5.2 接收数据
当需要接收数据时,SDA线上的数据通过数据控制线一位一位的将数据移位组合到移位数据寄存器中,当一个字节数据收齐后再整体存储到数据寄存器DR中。
同时置标志位RXNE,表示接收寄存器为非空
2.5.3 从机模式下使用的寄存器
2.5.3.1 自身地址寄存器
作为从机设备下的设备地址,可以自定一个地址写入该寄存器
2.5.3.2 双地址寄存器
STM32支持同时响应两个寄存器地址
2.5.3.3 比较器
当STM32作为从机,响应外部主机召唤,就是通过比较器比较自身地址是否相同
2.6 基本结构图(一主多从)
(图片来源于江协科技)
GPIO的配置:两个GPIO口都要配置为复用开漏输出模式
复用:即GPIO口的状态交由片上外设去执行
2.7 主机接收的操作流程
(图片来源于江协科技)
注:10位寻址模式下是11110 + 两位地址 + 读写位 + 8位地址
应答位配置:写0 不给应答 写1 接收一个字节就给一个应答
EV7:RxNE = 1 (数据寄存器非空),读DR寄存器清楚该事件
不想接收数据时,设ACK = 0和STOP请求
2.8 主机发送的操作流程
(图片来源于江协科技)
上电后,STM32默认为从设备,在控制寄存器1的START位中写入1即产生起始条件,之和,STM32自动从 从模式转为主模式,该START位会被硬件自动清0。
EV事件相当于一个标志位。
EV5:SB = 1(代表起始条件已发送),写数据寄存器DR时,硬件会自动将该位清0
EV6:ADDR = 1(主模式下1代表地址发送结束,0代表地址发送但没有结束)
EV8_1:TxE = 1 ,移位寄存器空,数据寄存器空,写DR寄存器,当写DR寄存器时,会自动将数据转运到移位寄存器,使触发EV8事件。
EV8:TxE = 1 ,移位寄存器非空,数据寄存器空,写入DR将清除该事件。
EV8_2:TxE = 1 ,BTF = 1(字节发送结束标志位),移位寄存器空时,找数据寄存器移动数据,结果数据寄存器也空
停止条件:在状态寄存器1中的STOP位置1,作用:在当前字节传输或在起始条件发出后产生停止条件。
三、一主多从的软件IIC代码
对于代码,大多数是借鉴江协科技的。
注释添加了自己学习时的理解。
3.1 MyIIC.c文件
#include "stm32f10x.h"
#include "MyIIC.h"
#include "Delay.h"
/*
* 写SCL引脚电平
* 参数(uint8_t bitValue):电平状态 0 或 1
*/
void MyIIC_W_SCL(uint8_t bitValue)
{
GPIO_WriteBit(IIC_SCL_GPIOx, IIC_SCL_Pin, (BitAction)bitValue);
Delay_us(10);//避免电平翻转过快导致从设备响应不上
}
/*
* 写SDA引脚电平
* 参数(uint8_t bitValue):电平状态 0 或 1
*/
void MyIIC_W_SDA(uint8_t bitValue)
{
GPIO_WriteBit(IIC_SDA_GPIOx, IIC_SDA_Pin, (BitAction)bitValue);
Delay_us(10);//避免电平翻转过快导致从设备响应不上
}
/*
* 获取SDA引脚电平
* 返回值:0 或 1
*/
uint8_t MyIIC_R_SDA(void)
{
uint8_t bitValue;
bitValue = GPIO_ReadInputDataBit(IIC_SDA_GPIOx, IIC_SDA_Pin);
Delay_us(10);//避免电平翻转过快导致从设备响应不上
return bitValue;
}
/*
* 初始化IIC引脚(软件IIC)
*/
void MyIIC_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(IIC_SCL_RCC, ENABLE);//使能时钟
RCC_APB2PeriphClockCmd(IIC_SDA_RCC, ENABLE);//使能时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出模式,该模式兼容输入输出,输入时,设置输出为1,读取输入寄存器值就可以实现
GPIO_InitStructure.GPIO_Pin = IIC_SCL_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SCL_GPIOx,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IIC_SDA_Pin;
GPIO_Init(IIC_SDA_GPIOx,&GPIO_InitStructure);
MyIIC_W_SCL(1);
MyIIC_W_SDA(1);
}
/*
* IIC起始条件:SCL线和SDA线先是高电平,然后先拉低SDA,再拉低SCL
*/
void MyIIC_Start(void)
{
/* 释放SCL线和SDA线,达成IIC起始条件的先决条件,
* 释放SDA要放在释放SCL前面,防止跟结束条件冲突
* 和兼容重复起始条件
*/
MyIIC_W_SDA(1);
MyIIC_W_SCL(1);
/* 协议标准:先拉低SDA,再拉低SCL */
MyIIC_W_SDA(0);
MyIIC_W_SCL(0);
}
/*
* IIC停止条件:先拉低SDA,然后再释放SCL,然后再释放SDA
*/
void MyIIC_Stop(void)
{
MyIIC_W_SDA(0);
/* 协议标准:先释放SCL,再释放SDA */
MyIIC_W_SCL(1);
MyIIC_W_SDA(1);
}
/*
* 写入一个字节数据
* 参数(uint8_t byte):需要输入的一个字节数据
*/
void MyIIC_SendByte(uint8_t byte)
{
uint8_t i;
for(i = 0; i < 8; i ++)
{
/* MyIIC_W_SDA函数有非0即1的特性
* 使用byte & (0x80 >> i)可以从高位到地位获取该位的状态
* 0x80 >> i 的输出分别为:0x80 0x40 0x20 0x10 0x08 0x04 0x02 0x01
* 对应:(1000 0000) (0100 0000) (0010 0000) ~ (0000 0010) (0000 0001)
*/
MyIIC_W_SDA(byte & (0x80 >> i));
/* 手动处理SCL时序 */
MyIIC_W_SCL(1);
MyIIC_W_SCL(0);
}
}
/*
* 读取一个字节数据
* 返回值:返回接收到的一个字节数据
*/
uint8_t MyIIC_ReceiveByte(void)
{
uint8_t i,byte = 0x00;
MyIIC_W_SDA(1);//放开SDA
for(i = 0; i < 8; i ++)
{
/*
* 接收一个字节的时序:SCL高电平期间读取数据,读取完后把SCL拉低,期间需要先放开SDA
* 使用权限。
*/
MyIIC_W_SCL(1);
/* 如果当前电平为1 说明有数据,则将byte的当前位为1 否则,保持默认的低电平不操作 */
if(MyIIC_R_SDA() == 1)
{
byte |= (0x80 >> i);
}
MyIIC_W_SCL(0);
}
return byte;
}
/*
* 发送应答
* 参数(uint8_t ackBit):应答位 0 或 1
* 0 继续
* 1 停止
*/
void MyIIC_SendAck(uint8_t ackBit)
{
MyIIC_W_SDA(ackBit);
/* 手动处理SCL时序 */
MyIIC_W_SCL(1);
MyIIC_W_SCL(0);
}
/*
* 读取一个字节数据
* 返回值:返回接收到的应答 0 或 1
* 0 :接收到继续应答,继续
* 1 :接收到结束应答 或无应答 ,结束
*/
uint8_t MyIIC_ReceiveAck(void)
{
uint8_t ackBit;
MyIIC_W_SDA(1);//放开SDA
MyIIC_W_SCL(1);
ackBit = MyIIC_R_SDA();
MyIIC_W_SCL(0);
return ackBit;
}
3.2 MyIIC.h文件
#ifndef __MYIIC_H
#define __MYIIC_H
#include "stm32f10x.h"
#include "MyIIC.h"
#include "Delay.h"
#define IIC_SCL_RCC RCC_APB2Periph_GPIOB//使用的IIC的引脚的时钟
#define IIC_SDA_RCC RCC_APB2Periph_GPIOB//使用的IIC的引脚的时钟
#define IIC_SCL_GPIOx GPIOB
#define IIC_SDA_GPIOx GPIOB
#define IIC_SCL_Pin GPIO_Pin_10
#define IIC_SDA_Pin GPIO_Pin_11
/*
* 写SCL引脚电平
* 参数(uint8_t bitValue):电平状态 0 或 1
*/
void MyIIC_W_SCL(uint8_t bitValue);
/*
* 写SDA引脚电平
* 参数(uint8_t bitValue):电平状态 0 或 1
*/
void MyIIC_W_SDA(uint8_t bitValue);
/*
* 获取SDA引脚电平
* 返回值:0 或 1
*/
uint8_t MyIIC_R_SDA(void);
/*
* 初始化IIC引脚(软件IIC)
*/
void MyIIC_init(void);
/*
* IIC起始条件:SCL线和SDA线先是高电平,然后先拉低SDA,再拉低SCL
*/
void MyIIC_Start(void);
/*
* IIC停止条件:先拉低SDA,然后再释放SCL,然后再释放SDA
*/
void MyIIC_Stop(void);
/*
* 写入一个字节数据
* 参数(uint8_t byte):需要输入的一个字节数据
*/
void MyIIC_SendByte(uint8_t byte);
/*
* 读取一个字节数据
* 返回值:返回接收到的一个字节数据
*/
uint8_t MyIIC_ReceiveByte(void);
/*
* 发送应答
* 参数(uint8_t ackBit):应答位 0 或 1
* 0 继续
* 1 停止
*/
void MyIIC_SendAck(uint8_t ackBit);
/*
* 读取一个字节数据
* 返回值:返回接收到的应答 0 或 1
* 0 :接收到继续应答,继续
* 1 :接收到结束应答 或无应答 ,结束
*/
uint8_t MyIIC_ReceiveAck(void);
#endif
3.3 MyMPU6050.c文件
#include "MyMPU6050.h"
/*
* 指定地址写函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 参数(uint8_t Data):需要写的一个字节数据
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回0 成功写入数据
* 返回-1 写入失败,具体失败原因可以添加测试函数测试
*/
int8_t MyMPU6050_WriteReg(uint8_t regAddress, uint8_t Data)
{
MyIIC_Start();
MyIIC_SendByte(MPU6050_ADDRESS);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
return -1;
}
MyIIC_SendByte(regAddress);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
return -1;
}
MyIIC_SendByte(Data);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
return -1;
}
MyIIC_Stop();
return 0;
}
/*
* 指定地址读函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回数据
*
*/
uint8_t MyMPU6050_ReadReg(uint8_t regAddress)
{
uint8_t data;
MyIIC_Start();
MyIIC_SendByte(MPU6050_ADDRESS);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
}
MyIIC_SendByte(regAddress);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
}
MyIIC_Start();
MyIIC_SendByte(MPU6050_ADDRESS | 0x01);
//接收应答,判断从机是否响应,应答0即继续进行,1为无响应
if(MyIIC_ReceiveAck())
{
//留出作错误报告
}
data = MyIIC_ReceiveByte();
/* 发送应答 */
MyIIC_SendAck(1);
MyIIC_Stop();
return data;
}
3.4 MyMPU6050.h文件
#ifndef __MYMPU6050_H
#define __MYMPU6050_H
#include "stm32f10x.h"
#include "MyIIC.h"
#include "Delay.h"
#define MPU6050_ADDRESS 0xD0 //MPU设备地址
/*
* 指定地址写函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 参数(uint8_t Data):需要写的一个字节数据
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回0 成功写入数据
* 返回-1 写入失败,具体失败原因可以添加测试函数测试
*/
int8_t MyMPU6050_WriteReg(uint8_t regAddress, uint8_t Data);
/*
* 指定地址读函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回数据
*
*/
uint8_t MyMPU6050_ReadReg(uint8_t regAddress);
#endif
四、一主多从的硬件IIC代码
4.1 MyMPU6050.c文件
#include "MyMPU6050.h"
/*
* 超时等待函数,防止意外卡死
* 参数:同I2C_CheckEvent
*/
void MyMPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t TimeOut = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
TimeOut --;
if(TimeOut == 0)
break ;
}
}
/*
* 指定地址写函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 参数(uint8_t Data):需要写的一个字节数据
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
*/
void MyMPU6050_WriteReg(uint8_t regAddress, uint8_t Data)
{
/* 起始条件 */
I2C_GenerateSTART(I2C2, ENABLE);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
/* 指定地址 */
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
/* 发送一个字节数据 */
I2C_SendData(I2C2, regAddress);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
/* 发送一个字节数据 */
I2C_SendData(I2C2, Data);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
/*
* 指定地址读函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回数据
*
*/
uint8_t MyMPU6050_ReadReg(uint8_t regAddress)
{
uint8_t data;
/* 复合格式指定地址*/
/* 起始条件 */
I2C_GenerateSTART(I2C2, ENABLE);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
/* 指定地址 */
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
/* 发送一个字节数据 */
I2C_SendData(I2C2, regAddress);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_SLAVE_BYTE_TRANSMITTED);
/* 读数据 */
/* 重复起始条件 */
I2C_GenerateSTART(I2C2, ENABLE);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
/* 指定地址 */
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
/* 在读完字节之前要提前设置ACK = 0和STOP条件 */
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
/* 监测事件 */
MyMPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
/* 读取数据 */
data = I2C_ReceiveData(I2C2);
/* 修改ACK配置 */
I2C_AcknowledgeConfig(I2C2, ENABLE);
return data;
}
void MyMPU6050_Init(void)
{
/* 硬件I2C初始化 */
GPIO_InitTypeDef GPIO_InitStucture;//定义结构体。引用结构体的所有变量
I2C_InitTypeDef I2C_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStucture.GPIO_Mode = GPIO_Mode_AF_OD;//上拉输入
GPIO_InitStucture.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Speed_50MHz指给这个配置50MHz的速度(常用)
GPIO_Init(GPIOB, &GPIO_InitStucture);
/* 初始化硬件IIC外设 */
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;//I2C模式
I2C_InitStruct.I2C_ClockSpeed = 50000;//50KHz
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比,低于100kHz是标准频率,占空比一般为1:1,快速模式下是2:1
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;//默认给应答
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//作为从机模式下能响应的地址位数
I2C_InitStruct.I2C_OwnAddress1 = 0x00;//用于指定STM32作为从机时的自身设备地址
I2C_Init(I2C2, &I2C_InitStruct);
/* 使能I2C2外设 */
I2C_Cmd(I2C2, ENABLE);
}
4.2 MyMPU6050.h文件
#ifndef __MYMPU6050_H
#define __MYMPU6050_H
#include "stm32f10x.h"
#include "Delay.h"
#define MPU6050_ADDRESS 0xD0 //MPU设备地址
/*
* 指定地址写函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 参数(uint8_t Data):需要写的一个字节数据
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回0 成功写入数据
* 返回-1 写入失败,具体失败原因可以添加测试函数测试
*/
void MyMPU6050_WriteReg(uint8_t regAddress, uint8_t Data);
/*
* 指定地址读函数,数据量:一个字节
* 参数(uint8_t regAddress):指定的地址
* 发送的设备地址:头文件定义的 MPU6050_ADDRESS
* 返回值:返回数据
*
*/
uint8_t MyMPU6050_ReadReg(uint8_t regAddress);
#endif