目录
接线图部分
I2C程序的整体架构
1:用OLED框架,在Hardware文件夹中建立MyI2C.C和MyI2C.H的文件
2:在通讯层写好I2CGPIO的初始化
2:6个基本时序单元:起始条件,终止条件,发送一个一个字节,接收一个字节,发送应答,输出应答
3: 建立MPU6050模块,基于I2C模块,建立指定地址读,指定地址写
4:实现写寄存器对芯片的配置,读寄存器得到传感器配置
5:在main函数里得到MPU6050模块,初始化,读寄存器得到数据
实现功能解释部分
任务一:把SCL和SDA的GPIO口转换为开漏输出模式
任务二:把SCL和SDA置为高电平
根据引脚接线图,I2C的GPIO置为B口
GPIO的初始化,把Pin_10和Pin_11口置高电平,使总线处于空闲状态,(注意输出方式要改成复用开漏输出,否则会使后续的接收应答变成001)
对SCL和SDA的高低电平进行封装
BitAction:是一个用于位操作的枚举类型和数据类型,也可以理解为,指定GPIO引脚的具体电平值,
对SDA的读寄存器封装
起始条件和终止条件
Sr部分为重复起始条件,必须先把SDA置为高点平,才可以让起始条件兼容两个
在I2C时序图最后的P部分是终止条件,根据终止条件,设置SCL和SDA的高低电平
发送一个字节
用包装函数,用for循环来发送数据位,总共发8位,用0x80来从最高位开始发送,把SCL拉低,再释放,循环8次。
接收一个字节
先包装函数,定义Byte的初始值为16进制的0x00,主机刚刚进来时,SCL为低位,把SDA置1,防止干扰SCL。
接着把SCL置1,让主机可以开始读取数据,如果SDA读为1,字节位右移,开始读取,读取字节1,否则SCL写0,从机不再读取数据,这个过程循环8次。
发送和接收应答
发送和接收应答就是发送和接送字节的简化版
发送一个字节,收8位,接收应答,收1位就行
发送应答就是ACKBit直接读取应答位。
接收应答就是在SCL为低电平的时候把SDA置1,再把SCL拉高,让SDA读到的值赋给ACKBit,最后把SCL拉低,进入下一个时序单元。
把GPIO的初始化和6个时序单元放到头文件中,没啥操作难度
地址名为1101 0000
000代表模块会进行响应
地址名位11010010
模块响应
写入寄存器
在主函数中,初始化MyI2C_Init();让整个系统开始,发送字节地址,十六进制字节的最后一个0代表即将写入,用来测试MPU6050的功能,随后定义一个8字节数Ack来接收应答,再用OLED函数显示出来
根据I2C时序,写入指定设备,指定地址,指定数据,记得设置应答位。
首先先用宏定义,定义一个MPU6050的地址,为0xD0,换算成二进制为1101 0000,最后一位为0,指定为写地址,随后设置应答位,然后再发送指定地址RegAddress的字节地址指针位,设置应答位,最后写入8为字节的指定数据Data。
指定读的地址,指定设备和指定地址都不变,要开启第二个接收字节地址,将宏定义的0xD0的值和0x01相与,变成读地址,设置应答位,接收一个字节之后,咋讲接收到的数据赋值给Data,给一个非应答MyI2C_SendAck(1),非应答,引用停止位,代表一个字节的接收完成
读出寄存器
在main函数里,#include"MPU6050.h",初始化,然后定义一个8字节的16进制数ID,就可以利用MPU6050的ReadReg函数(这个函数在前面有定义为I2C的通讯,功能为读出一个字节)来将16进制数0x75传递在OLED屏幕上
OLED_ShowHex(1,1,ID,3);表示在显示屏的1行1列显示ID,I2C传递过来的0x75,值为68
写寄存器
利用WriteReg();在电源管理寄存器写入0x00,地址为0X6B解除睡眠模式,再利用手册里的采样频率,0x19,指定寄存器的地址,然后再定义要写入的数据,MPU6050_WriteReg(指定寄存器地址,要写入的数据可以是任何值),利用ID函数可以在OLED屏幕显示出来
把手册上的寄存器地址全部新建一个文件,名称叫MPU6050.h定义下来
江科大并没有给出这些MPU6050寄存器的注释,我自己加上去了,请用
接着,设置封装函数,这部分函数是寄存器中读取的值,用指针来定义加速度计和陀螺仪的指针值,字节位为16,先定义个8字节的高8位和高8位的值,高8位左移8位,与低8位相与,然后再把他放入16位的指针*AccX当中.
陀螺仪的值同理。
其实还有一种用I2C通信的方法用循环函数表示出来,会更加高级,但我还不会,有会的大佬可以指导一下
定义整形16字节的6个变量,把6个变量的地址传进去,再在OLED屏幕上显示出来
左边为MPU6050所受的重力加速度值,单位为g,右边表示角速度值
读取的数据/32768 = x/满量程
这里的z轴的加速度值是1966
计算出来的值是0.96g左右,目前在福建厦门
最后,用一个8字节的函数来读取ID,识别寄存器的ID,确认寄存器的设备类型
在主函数里面定义一个ID字节,然后把读到的设备标识寄存器赋值给ID,最终在OLED屏幕上显示出来。
最终效果
ID号:68,左边:加速度值。右边:角速度值
完整代码
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MPU6050.h"
#include "OLED.h"
uint8_t ID;
int16_t AX, AY ,AZ, GX, GY, GZ;
int main(void)
{
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);
}
}
MyI2C.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue)
{
//BitAction要写入GPIO引脚的具体电平值
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //用于位操作的枚举类型和数据类型
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
//要写入GPIO引脚的具体电平值
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;//置输出端,可以为输入端,输1,再读取寄存器值
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_SCL(1);//注意要把SDA放前面,为了兼容起始条件和重复起始条件
MyI2C_W_SDA(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
//终止条件
MyI2C_W_SDA(0);//注意要把SDA放前面,为了兼容起始条件和重复起始条件
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)//发送一个字节
{
uint8_t i;
for(i=0;i<8;i++)//发8位为一个字节
{
MyI2C_W_SDA(!!(Byte & (0x80 >> i))); //取Byte的最高位
MyI2C_W_SCL(1); //释放
MyI2C_W_SCL(0); //拉低
}
}
uint8_t MyI2C_ReceiveByte(void)//接收一个字节
{
uint8_t i, Byte=0x00;
MyI2C_W_SDA(1); //高电平为读,低电平为写
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1); //SCL写入置高,主机才能读取数据
if(MyI2C_R_SDA()){Byte |= (0x80 >> i);} //字节位右移一位一位地读取数据1}
MyI2C_W_SCL(0);
}
return Byte; //返回接收Byte
}
//发送应答
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1); //从机读取应答
MyI2C_W_SCL(0); //拉低,进入下一个时序单元
}
//接收应答
uint8_t MyI2C_ReceiveAck(void)//接收一个字节
{
//函数刚刚进来,SCL为低电平
uint8_t AckBit;
MyI2C_W_SDA(1); //主机释放SDA,防止从机干扰,并不是把SDA置高电平,从机仍然在继续工作
MyI2C_W_SCL(1); //主机读取应答位
AckBit = MyI2C_R_SDA(); //读到的值,直接赋给ACKBit
MyI2C_W_SCL(0); //拉低,进入下一个时序单元
return AckBit;
}
MyI2C.h
#ifndef __MYI2C_H
#define __MYI2C_H
uint8_t MyI2C_R_SDA(void);
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.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();
}
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); //1101 0001末尾变1变成读地址
MyI2C_ReceiveAck(); //总线控制权交给从机
Data = MyI2C_ReceiveByte(); //接收一个字节
MyI2C_SendAck(1);//给非应答 //接收一个字节以后,要给从机发送一个应答,收到应答之后就会继续发送数据
//发送应答了才会继续发送数据,给1是为了不给从机应答
MyI2C_Stop();
return Data;
}
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//选择一个x轴的陀螺仪时钟,其他都为0
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//6个轴均不待机
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); //读取高8位的值,放在DataH中
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取低8位的值,
*AccX = (DataH << 8)| DataL;//用补码表示的有符号数,返回赋值给int6_t
//原理:高8位左移8位,或低8位,就变成指针中的16字节数
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8)| DataL;//加速计变成16位数据
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;
}
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_H
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX,int16_t *AccY, int16_t *AccZ,
int16_t *GyroX,int16_t *GyroY, int16_t *GyroZ);
#endif
MPU6050_Reg.h
#ifndef MPU6050_H
#define MPU6050_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 //加速度计X轴输出高字节寄存器
#define MPU6050_ACCEL_XOUT_L 0x3C //加速度计X轴输出低字节寄存器
#define MPU6050_ACCEL_YOUT_H 0x3D //加速度计Y轴输出高字节寄存器
#define MPU6050_ACCEL_YOUT_L 0x3E //加速度计Y轴输出低字节寄存器
#define MPU6050_ACCEL_ZOUT_H 0x3F //加速度计Z轴输出高字节寄存器
#define MPU6050_ACCEL_ZOUT_L 0x40 //加速度计Z轴输出低字节寄存器
#define MPU6050_TEMP_OUT_H 0x41 //温度传感器输出高字节寄存器
#define MPU6050_TEMP_OUT_L 0x42 //温度传感器输出低字节寄存器
#define MPU6050_GYRO_XOUT_H 0x43 //陀螺仪X轴输出高字节寄存器
#define MPU6050_GYRO_XOUT_L 0x44 //陀螺仪X轴输出低字节寄存器
#define MPU6050_GYRO_YOUT_H 0x45 //陀螺仪Y轴输出高字节寄存器
#define MPU6050_GYRO_YOUT_L 0x46 // 陀螺仪Y轴输出低字节寄存器
#define MPU6050_GYRO_ZOUT_H 0x47 //陀螺仪Z轴输出高字节寄存器
#define MPU6050_GYRO_ZOUT_L 0x48 //陀螺仪Z轴输出低字节寄存器
#define MPU6050_PWR_MGMT_1 0x6B //电源管理寄存器1,用于控制设备的电源模式
#define MPU6050_PWR_MGMT_2 0x6C //电源管理寄存器2,用于进一步配置电源管理
#define MPU6050_WHO_AM_I 0x75 //设备标识寄存器,用于确认设备类型
#endif
OLED和DELAY的引用部分的代码大家就自行找江科大开源软件了