IIC简介
IIC硬件电路
添加上拉电阻,防止总线出现主设备高电平,从设备低电平,发生短路现象
·只要有一个或多个设备输出低电平,总线就处于低电平;所有从设备输出高电平,总线才高电平
基本时序单元
如果在传输过程中发生中断,则SCL和SDA的电平状态保持不变,暂停传输,等待中断处理完毕再继续传输数据
SCL全程由主设备控制,在主设备接收数据时,释放SDA控制权给从设备
完整时序单元
指定地址写
当前地址读
当前地址指的是,存在一个指针变量指向寄存器组的某一个寄存器(该指针变量会自增),程序根据当前地址进行读操作
指定地址读
发送应答、接收应答
MPU6050简介
加速度计具有静态稳定性,不具有动态稳定性,因为受物体运动时的角度影响
陀螺仪动态稳定,静态不稳定
相关参数
从机地址分为:纯十六进制(0x68)、融入了读写位(0xD0、0xD1,1101000先左移一位,然后或操作0x01或0x00,表示读或者写)
硬件电路
在这里插入图片描述
芯片框图
接线图
My_I2C测试源码
c
#include "stm32f10x.h" // Device header
#include "MyDelay.h" //自定义延时函数
#include "Delay.h" //官方延迟函数
#include "Button.h" //按键Led驱动
#include "stdio.h"
#include "OLED.h"
#include "Button.h"
#include "MyI2C.h" //IIC驱动
int main(void){
//环境配置
OLED_Init();
//测试IIC完整时序
MyI2C_Init();
//起始
MyI2C_Start();
//AD0引脚可以改变电平 ,从而改变MPU6050地址, 最后一位代表读写位
MyI2C_SendByte(0xD0); //指定从机地址(MPU6050当前地址):1101 0000 (配合循环,可以实现从机地址扫描)
uint8_t ACK = MyI2C_ReceiveAck(); //接收数据
MyI2C_Stop(); //终止
//展示应答信号
OLED_ShowNum(1,1,ACK,3);
return 0;
}
c
#include "stm32f10x.h" //Device header
#include "Delay.h"
#include "OLED.h"
//封装操作,方便移植
//修改scl电平
void myi2c_write_scl(uint8_t BitValue){
//BitAction: 枚举变量(Reset、set)
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10) ; // 延时,确保SDA与SCL时序吻合 ,具体时间看手册
}
//修改sda电平
void myi2c_write_sda(uint8_t BitValue){
//BitAction: 枚举变量(Reset、set)
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10) ; // 延时,确保SDA与SCL时序吻合 ,具体时间看手册
}
//读取sda电平
uint8_t myi2c_read_sda(){
uint8_t BitValue;
//BitAction: 枚举变量(Reset、set)
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10) ; // 延时,确保SDA与SCL时序吻合 ,具体时间看手册
return BitValue;
}
void MyI2C_Init(void){
//GPIO初始化,SCL接PB10, SDA接PB11 ,开漏输出(能够输入输出)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// SCL和SDA端口置高电平,模仿上拉电阻 ,I2C总线处于空闲状态
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}
// iic时序: 起始、终止、发送字节、接收字节、发送应答、接收应答
void MyI2C_Start(void){
//SDA先高电平转低电平、SCL后高转低,表示起始时序
//SDA、SCL先释放总线,先释放sda是为了兼容:在新的周期开启之前,
// SCL可能为低电平暂时未释放总线 ,所以先释放sda可以兼容该情况
myi2c_write_sda(1);
myi2c_write_scl(1);
//先拉低sda,再拉低scl ,表示开启新周期
myi2c_write_sda(0);
myi2c_write_scl(0);
}
//先释放SDA,再释放SCL,表示终止一个周期
//但是释放时SDA时未必是低电平,所以先拉低SDA
//再先后释放SDA、SCL
void MyI2C_Stop(void){
myi2c_write_sda(0);
myi2c_write_sda(1);
myi2c_write_scl(1);
}
// 主机发送一个字节的数据,高位先行
// scl低电平切换发送下一位数据,SCL高电平发送
void MyI2C_SendByte(uint8_t data){
OLED_Init();
int times = 8;
uint8_t pos = 0x80;
// OLED_ShowString(3,1,"data:");
// OLED_ShowBinNum(3,5,data,8);
while(times--){
//通过按位与的操作,取出数据的某一位或几位
myi2c_write_sda(data & pos); //主机发送数据位
myi2c_write_scl(1); //发送sda的数据
myi2c_write_scl(0); //切换到下一个数据
//
// OLED_ShowString(2,1,"pos:");
// OLED_ShowBinNum(2,5,pos,8);
// Delay_ms(500);
pos >>= 1;
}
}
// 接收字节,SCL拉低电平(从机放数据),释放SCL保持高阻态,
// 主机释放SDA,从机通过切换SDA高低电平发送数据(主机读数据)
// 读写分离:SCL低电平写SDA,SCL高电平读SDA
uint8_t MyI2C_ReceiveByte(){
uint8_t r_data= 0x00, times=8;
while(times--){
myi2c_write_scl(0); //确保scl低电平,开启接收数据的流程
myi2c_write_sda(1); //主机释放sda,由从机通过sda发送数据
myi2c_write_scl(1); //拔高scl,开始读从机发送的数据
r_data |= myi2c_read_sda(); //读取从机发送的数据
myi2c_write_scl(0); //拉低scl,开启下一位数据读取
// OLED_ShowString(2,1,"r_data:");
// OLED_ShowBinNum(3,1,r_data,8);
// Delay_ms(500);
}
return r_data;
}
//发送应答: 在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
void MyI2C_SendAck(uint8_t AckBit){
myi2c_write_scl(0);
myi2c_write_scl(0);
myi2c_write_sda(AckBit); //在scl高电平时期,将应答数据写入sda
}
//接收应答: 在下一个时钟接收一位数据,数据0表示应答,数据1表示非应答
uint8_t MyI2C_ReceiveAck(void){
myi2c_write_scl(0);
myi2c_write_scl(1);
//主机释放sda控制权,从机读取sda
myi2c_write_sda(1);
uint8_t r_ack = myi2c_read_sda();
myi2c_write_scl(0); //拉低scl,开启下一个周期
return r_ack;
}
c
#ifndef My_I2C_H
#define My_I2C_H
//封装操作,方便移植
//修改scl电平
void myi2c_write_scl(uint8_t BitValue);
//修改sda电平
void myi2c_write_sda(uint8_t BitValue);
//读取sda电平
uint8_t myi2c_read_sda();
void MyI2C_Init(void);
// iic时序: 起始、终止、发送字节、接收字节、发送应答、接收应答
void MyI2C_Start(void);
//先释放SDA,再释放SCL,表示终止一个周期
//但是释放时SDA时未必是低电平,所以先拉低SDA
//再先后释放SDA、SCL
void MyI2C_Stop(void);
// 主机发送一个字节的数据,高位先行
// scl低电平切换发送下一位数据,SCL高电平发送
void MyI2C_SendByte(uint8_t data);
// 接收字节,SCL拉低电平(从机放数据),释放SCL保持高阻态,
// 主机释放SDA,从机通过切换SDA高低电平发送数据(主机读数据)
// 读写分离:SCL低电平写SDA,SCL高电平读SDA
uint8_t MyI2C_ReceiveByte();
//发送应答: 在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
void MyI2C_SendAck(uint8_t AckBit);
//接收应答: 在下一个时钟接收一位数据,数据0表示应答,数据1表示非应答
uint8_t MyI2C_ReceiveAck(void);
#endif
MPU6050测量偏转角及加速度
c
#include "stm32f10x.h"
#include "MyI2C.h"
#include "MPU6050.h"
#include "MPU6050Reg.h"
#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();
}
//指定地址读(先指定MPU6050 + 寄存器,再读取sda传送的数据)
uint8_t MPU6050_ReadReg(uint8_t RegAddress){
uint8_t Data;
//sendbyte 、 receiveack、 sendbyte/start/stop/recebyte/sendack
//先指定当前地址
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS); //写操作,从机地址最后一位为0
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
//进入读当前地址的时序
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //从机地址,读操作,最后一位改为1
MyI2C_ReceiveAck();
//主机移交sda权限,从机发送数据,主机读数据
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); //采样分频为10
MPU6050_WriteReg(MPU6050_CONFIG,0x06); //滤波参数最大
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪数值最大
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速器,数值最大
}
//获取MPU6050当前的xyz加速度值和陀螺仪值
void MPU6050_XYZ_RPY_Data(struct MPU_Data * md){
//缓存数据的高8位、低8位
uint16_t data_h, data_l;
//加速器
data_h = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
data_l = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
md->x = (data_h << 8) | data_l; //高8位左移8位,再或操作低8位,得16位数据
data_h = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
data_l = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
md->y = (data_h << 8) | data_l;
data_h = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
data_l = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
md->z = (data_h << 8) | data_l;
//偏转角: Roll(滚转角),Pitch(俯仰角),Yaw(偏航角)
data_h = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
data_l = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
md->P = (data_h << 8) | data_l; //高8位左移8位,再或操作低8位,得16位数据
data_h = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
data_l = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
md->Y = (data_h << 8) | data_l;
data_h = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
data_l = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
md->R = (data_h << 8) | data_l;
}
/**
* 函 数:MPU6050获取数据
* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 返 回 值:无
*/
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; //定义数据高8位和低8位的变量
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据
*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据
*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据
*AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据
*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据
*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据
*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
}
c
#ifndef MPU6050_H
#define MPU6050_H
//加速器和偏转角数据
typedef struct MPU_Data{
int16_t x;
int16_t y;
int16_t z;
int16_t R;
int16_t P;
int16_t Y;
};
//指定寄存器写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
//指定地址读(先指定MPU6050 + 寄存器,再读取sda传送的数据)
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
//获取MPU6050当前的xyz加速度值和陀螺仪值
void MPU6050_XYZ_RPY_Data(struct MPU_Data * md);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY,
int16_t *AccZ,int16_t *GyroX,
int16_t *GyroY, int16_t *GyroZ);
#endif
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_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
c
#include "stm32f10x.h" // Device header
#include "MyDelay.h" //自定义延时函数
#include "Delay.h" //官方延迟函数
#include "Button.h" //按键Led驱动
#include "stdio.h"
#include "OLED.h"
#include "Button.h"
#include "MPU6050.h" //IIC驱动
uint8_t ID; //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量
int main(void){
/*模块初始化*/
OLED_Init(); //OLED初始化
MPU6050_Init(); //MPU6050初始化
/*显示ID号*/
OLED_ShowString(1, 1, "ID:"); //显示静态字符串
ID = MPU6050_ReadReg(0x75); //获取MPU6050的ID号
OLED_ShowHexNum(1, 4, ID, 2); //OLED显示ID号
MPU6050_WriteReg(0x69,0xA8);
OLED_ShowString(2, 1, "Reg:"); //显示静态字符串
uint8_t Reg_data = MPU6050_ReadReg(0x69);
OLED_ShowHexNum(2, 5, Reg_data, 2); //OLED显示ID号
while (1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //获取MPU6050的数据
OLED_ShowSignedNum(2, 1, AX, 5); //OLED显示数据
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);
}
}