赞
踩
相信大家在I2C与MPU6050通信时总会碰到一些问题,作者的问题是可以读ID号,但是无法对MPU6050寄存器进行写入,读取寄存器的数据总是0
我们可以看到,数据一直是0,作者在网上看到部分帖子,但是按照上述解决办法没有解决,作者会将文章的链接给出,希望对大家有帮助:MPU6050可以读取ID值,温度值和原始数据值为零问题解决 - Darren_pty - 博客园 (cnblogs.com)
do-while循环是一个后测试循环,它在执行循环内部的代码之后检查循环条件。这意味着循环内部的代码至少会被执行一次,即使循环条件一开始也达不到满足。
- do {
- // 循环体内的代码
- // 这部分代码会至少被执行一次
-
- } while (循环条件);
- do {
- // 循环体内的代码
- // 这部分代码会至少被执行一次} \(\表示换行,并且注意在'\'后仅能跟回车,不能加其他字符,否则编译报错)
- while (循环条件);
1.并行通信的特点为同时传输多bit位数据,优点:传输效率高;缺点:数据线多,设计复杂
2.串行通信特点是只能一位一位传输
视频:你知道同步通信和异步通信有什么区别吗?_哔哩哔哩_bilibili
同步:发送方发出数据后,等接收方发回响应才能发送下一个数据包的通信方式
异步:发送方发送数据后,不等接收方发回响应接着发送下个数据包的通信方式
1.同步串行通信
2.异步串行通信
支持总线挂载多设备(一主多从、多主多从),在多主多从模式下会进行总线仲裁,时钟线也是由主机控制的,多主机模式下还需要进行时钟的同步
1.SCL线:对于SCL时钟线,在任何时刻都只能被动的读取,从机不允许控制SCL
2.因为主机拥有SCL的绝对控制权,所以主机的SCL可以设置为推挽输出模式,所有的从机的SCL都配置成浮空输入或者上拉输入
3.SDA线:主机在发送的时候是输出,在接收的时候是输入,同样从机的SDA也会在输入和输出之间反复切换(如果输入输出的时序遇到问题,就会出现麻烦,极有可能发生两个引脚同时处于输出状态,如果这时应该输出高电平,一个输出低电平就是电源短路)
4.避免短路设计:I2C的设计是禁止所有设备输出强上拉的高电平,采用外置弱上拉的电阻+开漏输出的电路结构
线与逻辑:什么叫线与逻辑? - NewLook的回答 - 知乎 https://www.zhihu.com/question/534999506/answer/3081799468
本文借鉴了以下文章:STM32软件I2C驱动MPU6050 - Sightseer观光客 - 博客园 (cnblogs.com)
起始条件:SCL高电平期间,拉低SDA;终止条件:SCL高电平期间,拉高SDA
黄线部分是我们的重复起始条件:在SCL高电平期间,拉低SDA
我们为什么需要在重复开始条件前,先释放SDA再释放SCL(准确来说,只有先拉高了SDA,就可以拒绝一切的影响,拉高SCL是为了创造起始条件)
终止条件是:在SCL高电平期间,释放SDA
下面的文字说错了:一个是把SDA拉低,就可以解决无效的终止条件
我们为什么要在终止条件前先把SDA拉低,是为了解决重复的终止条件,再拉高是为了创造终止条件的环境;最后一步拉低SDA才是正确的终止条件
- //起始条件
- void MyI2C_Start(){
- //为保证兼容重复开始条件,先释放SDA再释放SCL
- SDA_SET(1);
- SCL_SET(1);
- /*
- 起始条件是:先拉低SDA,再拉低SCL
- */
- SDA_SET(0);
- SCL_SET(0);
- }
- //终止条件
- void MyI2C_Stop(){
- //为了避免在释放前SDA是高电平,我们需要先拉低SDA,再释放SCL,再释放SDA
- SDA_SET(0);
- SCL_SET(1);
- SDA_SET(1);
- }
主机发送,从机读取;一般是在SCL高电平期间,从机才能读取数据(但是为了尽快读取,一般从机在SCL上升沿期间就读取了SDA的数据,因为时钟是主机控制的,从机并不知道什么时候SCL就是下降沿了,所以上升沿就赶快进行读取)
- void MyI2C_SendByte(uint8_t byte){
- //除了终止条件,SCL以高电平结束,所有的单元我们都会保证SCL以低电平结束,这样方便各个单元的拼接
- /*这里我们是高位先行,我们就需要按位与取出来最高位
- MyI2C_W_SDA(Byte & 0x80);//(x x x x x x x x)&(1 0 0 0 0 0 0 0)=x 0 0 0 0 0 0 0*/
- //为了避免重复书写,我们使用for循环,然后每次右移1为,再与
- for(uint8_t i = 0;i < 8;i++){
- SDA_SET(byte & (0x80 >> i)); //SDA写数据,I2C是高位先行
- SCL_SET(1); //给SCL高电平,让从机把SDA的数据读走
- SCL_SET(0); //再拉低,发送下一位
- }
- }
从机发送,主机接收:在SCL低电平期间,从机把数据放到SDA上,高电平期间主机读取数据(主机在接收之前,需要先释放SDA(因为线与的特点,只要有一个是低电平,就是都是低电平,释放SDA相当于切换为输入模式)
- uint8_t MyI2C_ReceiveByte(){
- uint8_t byte = 0x00;
- SDA_SET(1); //先释放SDA
- for(uint8_t i = 0; i < 8;i++){
- SCL_SET(1); //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
- if(READ_SDA() == 1){
- /*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
- 就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
- 第一次为1:(0 0 0 0 0 0 0 0)&(1 0 0 0 0 0 0 0)=1 0 0 0 0 0 0 0
- 第二次为0:byte=(1 0 0 0 0 0 0 0)
- 第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
- */
- byte |= (0x80 >> i);
-
- } //由高到低位读SDA
- SCL_SET(0); //设置SCL为低,一个时钟结束
- }
- return byte;
- }
- //发送应答
- void MyI2C_SendACK(uint8_t ackbit){
- SDA_SET(ackbit); //把应答位放在SDA上
- SCL_SET(1); //SCL高电平,从机读取应答
- SCL_SET(0); //SCL低电平,进入下一个时序单元,完毕
- }
- //接收应答其实就是接收一个字节的一位
- uint8_t MyI2C_ReceiveACK(){
- /*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
- 同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
- */
- uint8_t ackbit;
- SDA_SET(1); //释放SDA
- SCL_SET(1); //给SCL一个脉冲,让从机把应答位写到SDA上
- ackbit = READ_SDA();
- SCL_SET(0); //设置SCL为低,一个时钟结束
- return ackbit;
- }
指定地址写 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
当前地址读 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
读写位:0表示写,1表示读
当前地址指针:在RA接收应答之后,SDA的控制权就要交给从机了,I2C协议的规定是,主机进行寻址时,一旦读写标志位给1了,下一个字节就要立马转换为读的时序,所以主机还来不及指定我想要读那个寄存器,就得开始接收了,所以这里就没有指定地址这个环节,(主机并没有指定地址,那么从机到底一个发那个寄存器的数据呢,这就需要当前地址指针了)。
在从机中,所有的寄存器被分配到了一个线性区域中,并且会有一个单独的指针变量,指示着这个寄存器,这个指针上电默认,一般指向0地址(并且每写入一个字节和读出一个字节后,这个指针就会自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定地址,从机就会返回当前指针指向的寄存器的值);
假如我刚刚调用了在指定地址写的时序,在0x19的位置写入了0XAA,那么指针就会+1,移动到0X1A的位置,我再调用当前地址读的时序,返回的就是0X1A地址下的值,如果再调用一次就是0x1B下的值
就是指定地址读的时序了
最开始依旧是启动条件,然后发送一个字节进行寻址,指定从机地址是1101000,读写标志位是0,代表写的操作,进过从机应答之后,再发送一个字节;第二个字节用来指定地址(从机的寄存器的地址),然后ox19这个地址就写入到了从机的地址指针中去了,从机接收到0x19之后,它的寄存器指针就指向了0x19;之后我们直接重新起始条件,指定好从机地址为读,这时指针已经指向了0x19,后面就直接读就行了
1.MyI2C.c
- #include "stm32f10x.h" // Device header
- #include "MYI2C.h"
- #include "Delay.h"
- //设置I2C引脚端口,注意如端口号修改,时钟使能也要修改
- #define SCL_PORT GPIOB
- #define SCL_LINE GPIO_Pin_10
- #define SDA_PORT GPIOB
- #define SDA_LINE GPIO_Pin_11
- //设置I2C操作延迟(速度)
- #define I2C_DELAY do{Delay_us(10);}while(0);
- //I2C引脚电平写
- #define SCL_SET(x) do{GPIO_WriteBit(SCL_PORT,SCL_LINE,(BitAction)(x)); I2C_DELAY;} \
- while(0);
- #define SDA_SET(x) do{GPIO_WriteBit(SDA_PORT,SDA_LINE,(BitAction)(x)); I2C_DELAY;} \
- while(0);
- //I2C引脚电平读
- uint8_t READ_SDA(void){
- uint8_t val;
- val = GPIO_ReadInputDataBit(SDA_PORT,SDA_LINE);
- I2C_DELAY;
- return val;
- }
- void MyI2C_Init(){
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
-
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
- GPIO_InitStructure.GPIO_Pin = SCL_LINE | SDA_LINE;
- GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
- GPIO_Init(GPIOB,&GPIO_InitStructure);
- }
- void MyI2C_Start(){
- //为保证兼容重复开始条件,先释放SDA再释放SCL
- SDA_SET(1);
- SCL_SET(1);
- SDA_SET(0);
- SCL_SET(0);
- }
- void MyI2C_Stop(){
- SDA_SET(0);
- SCL_SET(1);
- SDA_SET(1);
- }
- void MyI2C_SendByte(uint8_t byte){
- for(uint8_t i = 0;i < 8;i++){
- SDA_SET(byte & (0x80 >> i)); //SDA写数据,I2C是高位先行
- SCL_SET(1); SCL_SET(0); //给SCL一个脉冲,让从机把SDA的数据读走
- }
- }
-
- uint8_t MyI2C_ReceiveByte(){
- uint8_t byte = 0x00;
- SDA_SET(1); //先释放SDA
- for(uint8_t i = 0; i < 8;i++){
- SCL_SET(1); //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
- if(READ_SDA() == 1){
- /*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
- 就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
- 第一次为1:(0 0 0 0 0 0 0 0)&(1 0 0 0 0 0 0 0)=1 0 0 0 0 0 0 0
- 第二次为0:byte=(1 0 0 0 0 0 0 0)
- 第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
- */
- byte |= (0x80 >> i);
-
- } //由高到低位读SDA
- SCL_SET(0); //设置SCL为低,一个时钟结束
- }
- return byte;
- }
- //发送应答
- void MyI2C_SendACK(uint8_t ackbit){
- SDA_SET(ackbit); //把应答位放在SDA上
- SCL_SET(1); //SCL高电平,从机读取应答
- SCL_SET(0); //SCL低电平,进入下一个时序单元,完毕
- }
- //接收应答其实就是接收一个字节的一位
- uint8_t MyI2C_ReceiveACK(){
- /*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
- 同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
- */
- uint8_t ackbit;
- SDA_SET(1); //释放SDA
- SCL_SET(1); //给SCL一个脉冲,让从机把应答位写到SDA上
- ackbit = READ_SDA();
- SCL_SET(0); //设置SCL为低,一个时钟结束
- return ackbit;
- }
2.MyI2C.H
- #ifndef _MYI2C_H
- #define _MYI2C_H
-
- 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
- //指定地址读寄存器
- uint8_t MPU6050_ReadReg(uint8_t reg_addr){
- uint8_t data;
- MyI2C_Start(); //起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
- MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
-
- //寻址找到从机之后就可以发送下一个字节了
- MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
- //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
- MyI2C_ReceiveACK(); //接收应答
-
- MyI2C_Start(); //再来一个起始条件
- MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
- MyI2C_ReceiveACK(); //主机接收应答
- data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
- MyI2C_SendACK(1); //主机接收后,发送应答
- MyI2C_Stop(); //结束
-
- return data;
- }
- //指定地址写寄存器
- void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
- MyI2C_Start(); 起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
- MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
-
- MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_SendByte(data); //写入的数据
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_Stop(); //结束
- }
1.MPU6050.c
- #include "stm32f10x.h" // Device header
- #include "MPU6050_Reg.h"
- #include "MYI2C.h"
- #include "stdio.h"
- #include "stdlib.h"
- #define MPU6050_I2C_ADDR (0x68)
- #define MPU6050_WRITE_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x00) //写
- #define MPU6050_READ_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x01) //读
- //指定地址读寄存器
- uint8_t MPU6050_ReadReg(uint8_t reg_addr){
- uint8_t data;
- MyI2C_Start(); //起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
- MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
-
- //寻址找到从机之后就可以发送下一个字节了
- MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
- //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
- MyI2C_ReceiveACK(); //接收应答
-
- MyI2C_Start(); //再来一个起始条件
- MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
- MyI2C_ReceiveACK(); //主机接收应答
- data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
- MyI2C_SendACK(1); //主机接收后,发送应答
- MyI2C_Stop(); //结束
-
- return data;
- }
- //指定地址写寄存器
- void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
- MyI2C_Start(); 起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
- MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
-
- MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_SendByte(data); //写入的数据
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_Stop(); //结束
- }
- void MPU6050_Init(){
- MyI2C_Init();
- MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //关闭睡眠模式
- MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
- MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
- MPU6050_WriteReg(MPU6050_CONFIG,0x06);
- MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
- MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
-
-
- }
- void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
- int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
- ){
- uint8_t DataH, DataL;
-
- DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
- DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
- *AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换
-
- 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;
- }
2.MPU6050.H
- #ifndef _MPU6050_H
- #define _MPU6050_H
- uint8_t MPU6050_ReadReg(uint8_t reg_addr);
- void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
- void MPU6050_Init();
- void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
- int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
- );
-
- #endif
1.MPU6050_Reg.h
- #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语言中,函数的返回值只有一个,我们想要返回多个数据,就是多返回值的设计
1.使用全局变量
2.使用指针,进行变量的地址传递,来实现多返回值
3.用结构体对多个变量进行打包,然后再统一进行传递
*/
- void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
- int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
- ){
- uint8_t DataH, DataL;
-
- DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
- DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
- *AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换
-
- 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;
- }
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "USART.h"
- #include "MYI2C.h"
- #include "MPU6050.h"
- #include "LED.h"
- int16_t AX, AY, AZ, GX, GY, GZ;
- uint8_t ID;
- int main(void)
- {
- Usart_Init();
- MyI2C_Init();
- MPU6050_Init();
- while(1)
- {
- MPU6050_GetDate(&AX, &AY, &AZ, &GX, &GY, &GZ);
- printf("AX=%d,AY=%d,AZ=%d,GX=%d,GY=%d,GZ=%d\n",AX,AY,AZ,GX,GY,GZ);
- Delay_ms(200);
-
-
- }
- }
1.MPU6050.c
- #include "stm32f10x.h" // Device header
- #include "MPU6050_Reg.h"
- #include "MYI2C.h"
- #include "stdlib.h"
- #include "MPU6050.h"
- #define MPU6050_I2C_ADDR (0x68)
- #define MPU6050_WRITE_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x00) //写
- #define MPU6050_READ_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x01) //读
- //指定地址读寄存器
- extern MPU6050_DATA;
- MPU6050_DATA pDATA;
- uint8_t MPU6050_ReadReg(uint8_t reg_addr){
- uint8_t data;
- MyI2C_Start(); //起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
- MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
-
- //寻址找到从机之后就可以发送下一个字节了
- MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
- //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
- MyI2C_ReceiveACK(); //接收应答
-
- MyI2C_Start(); //再来一个起始条件
- MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
- MyI2C_ReceiveACK(); //主机接收应答
- data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
- MyI2C_SendACK(1); //主机接收后,发送应答
- MyI2C_Stop(); //结束
-
- return data;
- }
- //指定地址写寄存器
- void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
- MyI2C_Start(); 起始位
- MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
- MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
-
- MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_SendByte(data); //写入的数据
- MyI2C_ReceiveACK(); //接收应答
- MyI2C_Stop(); //结束
- }
- void MPU6050_Init(){
- MyI2C_Init();
- MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //关闭睡眠模式
- MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
- MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
- MPU6050_WriteReg(MPU6050_CONFIG,0x06);
- MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
- MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
-
- }
-
- // 读取MPU6050数据并存储在指定结构体中
- void MPU6050_GetData(MPU6050_DATA* pDATA) {
- uint16_t dataH, dataL;
- // AccX
- dataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
- pDATA->AccX = (dataH << 8) | dataL;
- // AccY
- dataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
- pDATA->AccY = (dataH << 8) | dataL;
- // AccZ
- dataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
- pDATA->AccZ = (dataH << 8) | dataL;
- // GyroX
- dataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
- pDATA->GyroX = (dataH << 8) | dataL;
- // GyroY
- dataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
- pDATA->GyroY = (dataH << 8) | dataL;
- // GyroZ
- dataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
- dataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
- pDATA->GyroZ = (dataH << 8) | dataL;
- // TEMP
- dataH = MPU6050_ReadReg(MPU6050_TEMP_OUT_H);
- dataL = MPU6050_ReadReg(MPU6050_TEMP_OUT_L);
- pDATA->Temp = (dataH << 8) | dataL;
- }
2.MPU6050.h
- #ifndef _MPU6050_H
- #define _MPU6050_H
- typedef struct{
- int16_t AccX,AccY,AccZ;
- int16_t GyroX,GyroY,GyroZ;
- int16_t Temp;
- }MPU6050_DATA;
- void MPU6050_Init();
- void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
- uint8_t MPU6050_ReadReg(uint8_t reg_addr);
- void MPU6050_GetData(MPU6050_DATA *pDATA);
- #endif
3.main.c
- #include "Delay.h"
- #include "OLED.h"
- #include "USART.h"
- #include "MPU6050.h"
- #include "LED.h"
- #include "stdio.h"
- #include <stdio.h>
- #include <math.h>
- float ConvertTemperature(int16_t rawTemp) {
- return (rawTemp / 340.0f) + 36.53f;
- }
-
- // 函数用于将原始陀螺仪数据转换为角速度(度/秒)
- float ConvertGyro(int16_t rawGyro) {
- return rawGyro / 131.0f;
- }
-
- // 函数用于将原始加速度数据转换为加速度(g)
- float ConvertAcc(int16_t rawAcc) {
- return rawAcc / 16384.0f;
- }
- int main(void){
- MPU6050_Init();
- Usart_Init();
- MPU6050_DATA mpuData;
- while (1){
- MPU6050_GetData(&mpuData);
- // 转换并打印温度
- float temperature = ConvertTemperature(mpuData.Temp);
- printf("温度是:%.2f°C\n", temperature);
-
- // 转换并打印陀螺仪数据
- float gyroX = ConvertGyro(mpuData.GyroX);
- float gyroY = ConvertGyro(mpuData.GyroY);
- float gyroZ = ConvertGyro(mpuData.GyroZ);
- printf("GyroX=%.2f°/s, GyroY=%.2f°/s, GyroZ=%.2f°/s\n", gyroX, gyroY, gyroZ);
-
- // 转换并打印加速度数据
- float accX = ConvertAcc(mpuData.AccX);
- float accY = ConvertAcc(mpuData.AccY);
- float accZ = ConvertAcc(mpuData.AccZ);
- printf("AccX=%.2fg, AccY=%.2fg, AccZ=%.2fg\n", accX, accY, accZ);
-
-
- Delay_ms(100);
- }
- }
- /*指定地址写*/
- MyI2C_Start();//开始传输
- //起始之后,主机必须首先发送一个字节,内容是从机地址+读写位,进行寻址
- MyI2C_SendByte(0xD0); //1101 000 0(mpu6050地址+读写位)
- //发送一个字节后,我们要接收一个应答位,看看从机有没有收到刚才的数据
- uint8_t Ack=MyI2C_ReceiveAck();//我们判断Ack的值就知道从机有没有给我们应答了
-
- //接收应答之后,我们要继续再发送一个字节,写入寄存器地址
- MyI2C_Stop();
- printf("Ack=%d\n",Ack);
- //测试读寄存器
- uint8_t ID;
- Usart_Init();
- MPU6050_Init();
- // who I am I寄存器,只读,地址0x75,默认是ID号,默认值就是0x60
- ID=MPU6050_ReadReg(0x75);//返回值是ID号的内容
- printf("ID=%x\n",ID);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。