当前位置:   article > 正文

STM32开发之模拟I2C与MPU6050通信_模拟i2c delay.h

模拟i2c delay.h

相信大家在I2C与MPU6050通信时总会碰到一些问题,作者的问题是可以读ID号,但是无法对MPU6050寄存器进行写入,读取寄存器的数据总是0

问题

 我们可以看到,数据一直是0,作者在网上看到部分帖子,但是按照上述解决办法没有解决,作者会将文章的链接给出,希望对大家有帮助:MPU6050可以读取ID值,温度值和原始数据值为零问题解决 - Darren_pty - 博客园 (cnblogs.com)

 嵌入式C语言应用

do-while

do-while循环是一个后测试循环,它在执行循环内部的代码之后检查循环条件。这意味着循环内部的代码至少会被执行一次,即使循环条件一开始也达不到满足。

  1. do {
  2. // 循环体内的代码
  3. // 这部分代码会至少被执行一次
  4. } while (循环条件);

 C语言代码换行

  1. do {
  2. // 循环体内的代码
  3. // 这部分代码会至少被执行一次} \(\表示换行,并且注意在'\'后仅能跟回车,不能加其他字符,否则编译报错)
  4. while (循环条件);

通信协议前置知识

串行通信与并行通信

1.并行通信的特点为同时传输多bit位数据,优点:传输效率高;缺点:数据线多,设计复杂

 2.串行通信特点是只能一位一位传输

 异步串行通信与同步串行通信

视频:你知道同步通信和异步通信有什么区别吗?_哔哩哔哩_bilibili

同步:发送方发出数据后,等接收方发回响应才能发送下一个数据包的通信方式

异步:发送方发送数据后,不等接收方发回响应接着发送下个数据包的通信方式

1.同步串行通信

 2.异步串行通信

 单工、半双工、全双工

I2C前置理论知识

支持总线挂载多设备(一主多从、多主多从),在多主多从模式下会进行总线仲裁,时钟线也是由主机控制的,多主机模式下还需要进行时钟的同步

硬件电路

1.SCL线:对于SCL时钟线,在任何时刻都只能被动的读取,从机不允许控制SCL

2.因为主机拥有SCL的绝对控制权,所以主机的SCL可以设置为推挽输出模式,所有的从机的SCL都配置成浮空输入或者上拉输入

3.SDA线:主机在发送的时候是输出,在接收的时候是输入,同样从机的SDA也会在输入和输出之间反复切换(如果输入输出的时序遇到问题,就会出现麻烦,极有可能发生两个引脚同时处于输出状态,如果这时应该输出高电平,一个输出低电平就是电源短路)

4.避免短路设计:I2C的设计是禁止所有设备输出强上拉的高电平,采用外置弱上拉的电阻+开漏输出的电路结构

 线与逻辑:什么叫线与逻辑? - NewLook的回答 - 知乎 https://www.zhihu.com/question/534999506/answer/3081799468

I2C时序分析

本文借鉴了以下文章:STM32软件I2C驱动MPU6050 - Sightseer观光客 - 博客园 (cnblogs.com)

起始条件和终止条件

起始条件:SCL高电平期间,拉低SDA;终止条件:SCL高电平期间,拉高SDA

 起始条件与终止条件的程序

黄线部分是我们的重复起始条件:在SCL高电平期间,拉低SDA

 我们为什么需要在重复开始条件前,先释放SDA再释放SCL(准确来说,只有先拉高了SDA,就可以拒绝一切的影响,拉高SCL是为了创造起始条件)

终止条件是:在SCL高电平期间,释放SDA

 下面的文字说错了:一个是把SDA拉低,就可以解决无效的终止条件

 我们为什么要在终止条件前先把SDA拉低,是为了解决重复的终止条件,再拉高是为了创造终止条件的环境;最后一步拉低SDA才是正确的终止条件

  1. //起始条件
  2. void MyI2C_Start(){
  3. //为保证兼容重复开始条件,先释放SDA再释放SCL
  4. SDA_SET(1);
  5. SCL_SET(1);
  6. /*
  7. 起始条件是:先拉低SDA,再拉低SCL
  8. */
  9. SDA_SET(0);
  10. SCL_SET(0);
  11. }
  12. //终止条件
  13. void MyI2C_Stop(){
  14. //为了避免在释放前SDA是高电平,我们需要先拉低SDA,再释放SCL,再释放SDA
  15. SDA_SET(0);
  16. SCL_SET(1);
  17. SDA_SET(1);
  18. }

 发送一个字节

主机发送,从机读取;一般是在SCL高电平期间,从机才能读取数据(但是为了尽快读取,一般从机在SCL上升沿期间就读取了SDA的数据,因为时钟是主机控制的,从机并不知道什么时候SCL就是下降沿了,所以上升沿就赶快进行读取)

发送一个字节的代码

  1. void MyI2C_SendByte(uint8_t byte){
  2. //除了终止条件,SCL以高电平结束,所有的单元我们都会保证SCL以低电平结束,这样方便各个单元的拼接
  3. /*这里我们是高位先行,我们就需要按位与取出来最高位
  4. 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*/
  5. //为了避免重复书写,我们使用for循环,然后每次右移1为,再与
  6. for(uint8_t i = 0;i < 8;i++){
  7. SDA_SET(byte & (0x80 >> i)); //SDA写数据,I2C是高位先行
  8. SCL_SET(1); //给SCL高电平,让从机把SDA的数据读走
  9. SCL_SET(0); //再拉低,发送下一位
  10. }
  11. }

接收一个字节

从机发送,主机接收:在SCL低电平期间,从机把数据放到SDA上,高电平期间主机读取数据(主机在接收之前,需要先释放SDA(因为线与的特点,只要有一个是低电平,就是都是低电平,释放SDA相当于切换为输入模式)

接收一个字节的代码

  1. uint8_t MyI2C_ReceiveByte(){
  2. uint8_t byte = 0x00;
  3. SDA_SET(1); //先释放SDA
  4. for(uint8_t i = 0; i < 8;i++){
  5. SCL_SET(1); //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
  6. if(READ_SDA() == 1){
  7. /*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
  8. 就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
  9. 第一次为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
  10. 第二次为0:byte=(1 0 0 0 0 0 0 0)
  11. 第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
  12. */
  13. byte |= (0x80 >> i);
  14. } //由高到低位读SDA
  15. SCL_SET(0); //设置SCL为低,一个时钟结束
  16. }
  17. return byte;
  18. }

接收应答与发送应答

接收应答与发送应答的代码

  1. //发送应答
  2. void MyI2C_SendACK(uint8_t ackbit){
  3. SDA_SET(ackbit); //把应答位放在SDA上
  4. SCL_SET(1); //SCL高电平,从机读取应答
  5. SCL_SET(0); //SCL低电平,进入下一个时序单元,完毕
  6. }
  7. //接收应答其实就是接收一个字节的一位
  8. uint8_t MyI2C_ReceiveACK(){
  9. /*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
  10. 同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
  11. */
  12. uint8_t ackbit;
  13. SDA_SET(1); //释放SDA
  14. SCL_SET(1); //给SCL一个脉冲,让从机把应答位写到SDA上
  15. ackbit = READ_SDA();
  16. SCL_SET(0); //设置SCL为低,一个时钟结束
  17. return ackbit;
  18. }

 I2C时序之指定地址写

指定地址写 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

  I2C时序之当前地址读

当前地址读 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

读写位:0表示写,1表示读

 当前地址指针:在RA接收应答之后,SDA的控制权就要交给从机了,I2C协议的规定是,主机进行寻址时,一旦读写标志位给1了,下一个字节就要立马转换为读的时序,所以主机还来不及指定我想要读那个寄存器,就得开始接收了,所以这里就没有指定地址这个环节,(主机并没有指定地址,那么从机到底一个发那个寄存器的数据呢,这就需要当前地址指针了)。

在从机中,所有的寄存器被分配到了一个线性区域中,并且会有一个单独的指针变量,指示着这个寄存器,这个指针上电默认,一般指向0地址(并且每写入一个字节和读出一个字节后,这个指针就会自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定地址,从机就会返回当前指针指向的寄存器的值);

假如我刚刚调用了在指定地址写的时序,在0x19的位置写入了0XAA,那么指针就会+1,移动到0X1A的位置,我再调用当前地址读的时序,返回的就是0X1A地址下的值,如果再调用一次就是0x1B下的值

I2C时序之指定地址读

 

 就是指定地址读的时序了

 最开始依旧是启动条件,然后发送一个字节进行寻址,指定从机地址是1101000,读写标志位是0,代表写的操作,进过从机应答之后,再发送一个字节;第二个字节用来指定地址(从机的寄存器的地址),然后ox19这个地址就写入到了从机的地址指针中去了,从机接收到0x19之后,它的寄存器指针就指向了0x19;之后我们直接重新起始条件,指定好从机地址为读,这时指针已经指向了0x19,后面就直接读就行了

模拟I2C的全部代码

1.MyI2C.c

  1. #include "stm32f10x.h" // Device header
  2. #include "MYI2C.h"
  3. #include "Delay.h"
  4. //设置I2C引脚端口,注意如端口号修改,时钟使能也要修改
  5. #define SCL_PORT GPIOB
  6. #define SCL_LINE GPIO_Pin_10
  7. #define SDA_PORT GPIOB
  8. #define SDA_LINE GPIO_Pin_11
  9. //设置I2C操作延迟(速度)
  10. #define I2C_DELAY do{Delay_us(10);}while(0);
  11. //I2C引脚电平写
  12. #define SCL_SET(x) do{GPIO_WriteBit(SCL_PORT,SCL_LINE,(BitAction)(x)); I2C_DELAY;} \
  13. while(0);
  14. #define SDA_SET(x) do{GPIO_WriteBit(SDA_PORT,SDA_LINE,(BitAction)(x)); I2C_DELAY;} \
  15. while(0);
  16. //I2C引脚电平读
  17. uint8_t READ_SDA(void){
  18. uint8_t val;
  19. val = GPIO_ReadInputDataBit(SDA_PORT,SDA_LINE);
  20. I2C_DELAY;
  21. return val;
  22. }
  23. void MyI2C_Init(){
  24. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  25. GPIO_InitTypeDef GPIO_InitStructure;
  26. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  27. GPIO_InitStructure.GPIO_Pin = SCL_LINE | SDA_LINE;
  28. GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
  29. GPIO_Init(GPIOB,&GPIO_InitStructure);
  30. }
  31. void MyI2C_Start(){
  32. //为保证兼容重复开始条件,先释放SDA再释放SCL
  33. SDA_SET(1);
  34. SCL_SET(1);
  35. SDA_SET(0);
  36. SCL_SET(0);
  37. }
  38. void MyI2C_Stop(){
  39. SDA_SET(0);
  40. SCL_SET(1);
  41. SDA_SET(1);
  42. }
  43. void MyI2C_SendByte(uint8_t byte){
  44. for(uint8_t i = 0;i < 8;i++){
  45. SDA_SET(byte & (0x80 >> i)); //SDA写数据,I2C是高位先行
  46. SCL_SET(1); SCL_SET(0); //给SCL一个脉冲,让从机把SDA的数据读走
  47. }
  48. }
  49. uint8_t MyI2C_ReceiveByte(){
  50. uint8_t byte = 0x00;
  51. SDA_SET(1); //先释放SDA
  52. for(uint8_t i = 0; i < 8;i++){
  53. SCL_SET(1); //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
  54. if(READ_SDA() == 1){
  55. /*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
  56. 就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
  57. 第一次为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
  58. 第二次为0:byte=(1 0 0 0 0 0 0 0)
  59. 第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
  60. */
  61. byte |= (0x80 >> i);
  62. } //由高到低位读SDA
  63. SCL_SET(0); //设置SCL为低,一个时钟结束
  64. }
  65. return byte;
  66. }
  67. //发送应答
  68. void MyI2C_SendACK(uint8_t ackbit){
  69. SDA_SET(ackbit); //把应答位放在SDA上
  70. SCL_SET(1); //SCL高电平,从机读取应答
  71. SCL_SET(0); //SCL低电平,进入下一个时序单元,完毕
  72. }
  73. //接收应答其实就是接收一个字节的一位
  74. uint8_t MyI2C_ReceiveACK(){
  75. /*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
  76. 同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
  77. */
  78. uint8_t ackbit;
  79. SDA_SET(1); //释放SDA
  80. SCL_SET(1); //给SCL一个脉冲,让从机把应答位写到SDA上
  81. ackbit = READ_SDA();
  82. SCL_SET(0); //设置SCL为低,一个时钟结束
  83. return ackbit;
  84. }

2.MyI2C.H

  1. #ifndef _MYI2C_H
  2. #define _MYI2C_H
  3. void MyI2C_Init(void);
  4. void MyI2C_Start(void);
  5. void MyI2C_Stop(void);
  6. void MyI2C_SendByte(uint8_t byte);
  7. uint8_t MyI2C_ReceiveByte(void);
  8. void MyI2C_SendACK(uint8_t ackbit);
  9. uint8_t MyI2C_ReceiveACK(void);
  10. #endif

MPU6050代码分析

指定地址读+指定地址写

  1. //指定地址读寄存器
  2. uint8_t MPU6050_ReadReg(uint8_t reg_addr){
  3. uint8_t data;
  4. MyI2C_Start(); //起始位
  5. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
  6. MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
  7. //寻址找到从机之后就可以发送下一个字节了
  8. MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
  9. //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
  10. MyI2C_ReceiveACK(); //接收应答
  11. MyI2C_Start(); //再来一个起始条件
  12. MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
  13. MyI2C_ReceiveACK(); //主机接收应答
  14. data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
  15. MyI2C_SendACK(1); //主机接收后,发送应答
  16. MyI2C_Stop(); //结束
  17. return data;
  18. }
  19. //指定地址写寄存器
  20. void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
  21. MyI2C_Start(); 起始位
  22. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
  23. MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
  24. MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
  25. MyI2C_ReceiveACK(); //接收应答
  26. MyI2C_SendByte(data); //写入的数据
  27. MyI2C_ReceiveACK(); //接收应答
  28. MyI2C_Stop(); //结束
  29. }

MPU6050代码

1.MPU6050.c

  1. #include "stm32f10x.h" // Device header
  2. #include "MPU6050_Reg.h"
  3. #include "MYI2C.h"
  4. #include "stdio.h"
  5. #include "stdlib.h"
  6. #define MPU6050_I2C_ADDR (0x68)
  7. #define MPU6050_WRITE_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x00) //写
  8. #define MPU6050_READ_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x01) //读
  9. //指定地址读寄存器
  10. uint8_t MPU6050_ReadReg(uint8_t reg_addr){
  11. uint8_t data;
  12. MyI2C_Start(); //起始位
  13. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
  14. MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
  15. //寻址找到从机之后就可以发送下一个字节了
  16. MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
  17. //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
  18. MyI2C_ReceiveACK(); //接收应答
  19. MyI2C_Start(); //再来一个起始条件
  20. MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
  21. MyI2C_ReceiveACK(); //主机接收应答
  22. data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
  23. MyI2C_SendACK(1); //主机接收后,发送应答
  24. MyI2C_Stop(); //结束
  25. return data;
  26. }
  27. //指定地址写寄存器
  28. void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
  29. MyI2C_Start(); 起始位
  30. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
  31. MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
  32. MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
  33. MyI2C_ReceiveACK(); //接收应答
  34. MyI2C_SendByte(data); //写入的数据
  35. MyI2C_ReceiveACK(); //接收应答
  36. MyI2C_Stop(); //结束
  37. }
  38. void MPU6050_Init(){
  39. MyI2C_Init();
  40. MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //关闭睡眠模式
  41. MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
  42. MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
  43. MPU6050_WriteReg(MPU6050_CONFIG,0x06);
  44. MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
  45. MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
  46. }
  47. void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
  48. int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
  49. ){
  50. uint8_t DataH, DataL;
  51. DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
  52. DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
  53. *AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换
  54. DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
  55. DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
  56. *AccY = (DataH << 8) | DataL;
  57. DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
  58. DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
  59. *AccZ = (DataH << 8) | DataL;
  60. DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
  61. DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
  62. *GyroX = (DataH << 8) | DataL;
  63. DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
  64. DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
  65. *GyroY = (DataH << 8) | DataL;
  66. DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
  67. DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
  68. *GyroZ = (DataH << 8) | DataL;
  69. }

 2.MPU6050.H

  1. #ifndef _MPU6050_H
  2. #define _MPU6050_H
  3. uint8_t MPU6050_ReadReg(uint8_t reg_addr);
  4. void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
  5. void MPU6050_Init();
  6. void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
  7. int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
  8. );
  9. #endif

MPU6050寄存器文件

1.MPU6050_Reg.h

  1. #ifndef __MPU6050_REG_H
  2. #define __MPU6050_REG_H
  3. #define MPU6050_SMPLRT_DIV 0x19
  4. #define MPU6050_CONFIG 0x1A
  5. #define MPU6050_GYRO_CONFIG 0x1B
  6. #define MPU6050_ACCEL_CONFIG 0x1C
  7. #define MPU6050_ACCEL_XOUT_H 0x3B
  8. #define MPU6050_ACCEL_XOUT_L 0x3C
  9. #define MPU6050_ACCEL_YOUT_H 0x3D
  10. #define MPU6050_ACCEL_YOUT_L 0x3E
  11. #define MPU6050_ACCEL_ZOUT_H 0x3F
  12. #define MPU6050_ACCEL_ZOUT_L 0x40
  13. #define MPU6050_TEMP_OUT_H 0x41
  14. #define MPU6050_TEMP_OUT_L 0x42
  15. #define MPU6050_GYRO_XOUT_H 0x43
  16. #define MPU6050_GYRO_XOUT_L 0x44
  17. #define MPU6050_GYRO_YOUT_H 0x45
  18. #define MPU6050_GYRO_YOUT_L 0x46
  19. #define MPU6050_GYRO_ZOUT_H 0x47
  20. #define MPU6050_GYRO_ZOUT_L 0x48
  21. #define MPU6050_PWR_MGMT_1 0x6B
  22. #define MPU6050_PWR_MGMT_2 0x6C
  23. #define MPU6050_WHO_AM_I 0x75
  24. #endif

最后

我们想使用不同的方式来完成对加速度计和陀螺仪六个数据的读取

/*在C语言中,函数的返回值只有一个,我们想要返回多个数据,就是多返回值的设计
1.使用全局变量
2.使用指针,进行变量的地址传递,来实现多返回值
3.用结构体对多个变量进行打包,然后再统一进行传递
*/

使用指针的方式

MUC6050.c

  1. void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
  2. int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
  3. ){
  4. uint8_t DataH, DataL;
  5. DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
  6. DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
  7. *AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换
  8. DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
  9. DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
  10. *AccY = (DataH << 8) | DataL;
  11. DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
  12. DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
  13. *AccZ = (DataH << 8) | DataL;
  14. DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
  15. DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
  16. *GyroX = (DataH << 8) | DataL;
  17. DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
  18. DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
  19. *GyroY = (DataH << 8) | DataL;
  20. DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
  21. DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
  22. *GyroZ = (DataH << 8) | DataL;
  23. }

main.c

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "USART.h"
  5. #include "MYI2C.h"
  6. #include "MPU6050.h"
  7. #include "LED.h"
  8. int16_t AX, AY, AZ, GX, GY, GZ;
  9. uint8_t ID;
  10. int main(void)
  11. {
  12. Usart_Init();
  13. MyI2C_Init();
  14. MPU6050_Init();
  15. while(1)
  16. {
  17. MPU6050_GetDate(&AX, &AY, &AZ, &GX, &GY, &GZ);
  18. printf("AX=%d,AY=%d,AZ=%d,GX=%d,GY=%d,GZ=%d\n",AX,AY,AZ,GX,GY,GZ);
  19. Delay_ms(200);
  20. }
  21. }

使用结构体方式

1.MPU6050.c

  1. #include "stm32f10x.h" // Device header
  2. #include "MPU6050_Reg.h"
  3. #include "MYI2C.h"
  4. #include "stdlib.h"
  5. #include "MPU6050.h"
  6. #define MPU6050_I2C_ADDR (0x68)
  7. #define MPU6050_WRITE_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x00) //写
  8. #define MPU6050_READ_ADDR (((MPU6050_I2C_ADDR) << 1) | 0x01) //读
  9. //指定地址读寄存器
  10. extern MPU6050_DATA;
  11. MPU6050_DATA pDATA;
  12. uint8_t MPU6050_ReadReg(uint8_t reg_addr){
  13. uint8_t data;
  14. MyI2C_Start(); //起始位
  15. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+读
  16. MyI2C_ReceiveACK(); //接收应答,这里有应答位可以判断从机有没有接收到数据
  17. //寻址找到从机之后就可以发送下一个字节了
  18. MyI2C_SendByte(reg_addr); //用来指定我写的地址,这个地址就写到从机的地址指针中了
  19. //从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
  20. MyI2C_ReceiveACK(); //接收应答
  21. MyI2C_Start(); //再来一个起始条件
  22. MyI2C_SendByte(MPU6050_READ_ADDR); //再次指定设备的ID号,这次我们读
  23. MyI2C_ReceiveACK(); //主机接收应答
  24. data = MyI2C_ReceiveByte(); //指针指向的寄存器的数据
  25. MyI2C_SendACK(1); //主机接收后,发送应答
  26. MyI2C_Stop(); //结束
  27. return data;
  28. }
  29. //指定地址写寄存器
  30. void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
  31. MyI2C_Start(); 起始位
  32. MyI2C_SendByte(MPU6050_WRITE_ADDR); //从机地址+写
  33. MyI2C_ReceiveACK(); 接收应答,这里有应答位可以判断从机有没有接收到数据
  34. MyI2C_SendByte(reg_addr); // //用来指定我写的寄存器地址
  35. MyI2C_ReceiveACK(); //接收应答
  36. MyI2C_SendByte(data); //写入的数据
  37. MyI2C_ReceiveACK(); //接收应答
  38. MyI2C_Stop(); //结束
  39. }
  40. void MPU6050_Init(){
  41. MyI2C_Init();
  42. MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //关闭睡眠模式
  43. MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
  44. MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
  45. MPU6050_WriteReg(MPU6050_CONFIG,0x06);
  46. MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
  47. MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
  48. }
  49. // 读取MPU6050数据并存储在指定结构体中
  50. void MPU6050_GetData(MPU6050_DATA* pDATA) {
  51. uint16_t dataH, dataL;
  52. // AccX
  53. dataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
  54. dataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
  55. pDATA->AccX = (dataH << 8) | dataL;
  56. // AccY
  57. dataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
  58. dataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
  59. pDATA->AccY = (dataH << 8) | dataL;
  60. // AccZ
  61. dataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
  62. dataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
  63. pDATA->AccZ = (dataH << 8) | dataL;
  64. // GyroX
  65. dataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
  66. dataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
  67. pDATA->GyroX = (dataH << 8) | dataL;
  68. // GyroY
  69. dataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
  70. dataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
  71. pDATA->GyroY = (dataH << 8) | dataL;
  72. // GyroZ
  73. dataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
  74. dataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
  75. pDATA->GyroZ = (dataH << 8) | dataL;
  76. // TEMP
  77. dataH = MPU6050_ReadReg(MPU6050_TEMP_OUT_H);
  78. dataL = MPU6050_ReadReg(MPU6050_TEMP_OUT_L);
  79. pDATA->Temp = (dataH << 8) | dataL;
  80. }

2.MPU6050.h

  1. #ifndef _MPU6050_H
  2. #define _MPU6050_H
  3. typedef struct{
  4. int16_t AccX,AccY,AccZ;
  5. int16_t GyroX,GyroY,GyroZ;
  6. int16_t Temp;
  7. }MPU6050_DATA;
  8. void MPU6050_Init();
  9. void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
  10. uint8_t MPU6050_ReadReg(uint8_t reg_addr);
  11. void MPU6050_GetData(MPU6050_DATA *pDATA);
  12. #endif

3.main.c

  1. #include "Delay.h"
  2. #include "OLED.h"
  3. #include "USART.h"
  4. #include "MPU6050.h"
  5. #include "LED.h"
  6. #include "stdio.h"
  7. #include <stdio.h>
  8. #include <math.h>
  9. float ConvertTemperature(int16_t rawTemp) {
  10. return (rawTemp / 340.0f) + 36.53f;
  11. }
  12. // 函数用于将原始陀螺仪数据转换为角速度(度/秒)
  13. float ConvertGyro(int16_t rawGyro) {
  14. return rawGyro / 131.0f;
  15. }
  16. // 函数用于将原始加速度数据转换为加速度(g)
  17. float ConvertAcc(int16_t rawAcc) {
  18. return rawAcc / 16384.0f;
  19. }
  20. int main(void){
  21. MPU6050_Init();
  22. Usart_Init();
  23. MPU6050_DATA mpuData;
  24. while (1){
  25. MPU6050_GetData(&mpuData);
  26. // 转换并打印温度
  27. float temperature = ConvertTemperature(mpuData.Temp);
  28. printf("温度是:%.2f°C\n", temperature);
  29. // 转换并打印陀螺仪数据
  30. float gyroX = ConvertGyro(mpuData.GyroX);
  31. float gyroY = ConvertGyro(mpuData.GyroY);
  32. float gyroZ = ConvertGyro(mpuData.GyroZ);
  33. printf("GyroX=%.2f°/s, GyroY=%.2f°/s, GyroZ=%.2f°/s\n", gyroX, gyroY, gyroZ);
  34. // 转换并打印加速度数据
  35. float accX = ConvertAcc(mpuData.AccX);
  36. float accY = ConvertAcc(mpuData.AccY);
  37. float accZ = ConvertAcc(mpuData.AccZ);
  38. printf("AccX=%.2fg, AccY=%.2fg, AccZ=%.2fg\n", accX, accY, accZ);
  39. Delay_ms(100);
  40. }
  41. }

数据显示结果

 测试代码

  1. /*指定地址写*/
  2. MyI2C_Start();//开始传输
  3. //起始之后,主机必须首先发送一个字节,内容是从机地址+读写位,进行寻址
  4. MyI2C_SendByte(0xD0); //1101 000 0(mpu6050地址+读写位)
  5. //发送一个字节后,我们要接收一个应答位,看看从机有没有收到刚才的数据
  6. uint8_t Ack=MyI2C_ReceiveAck();//我们判断Ack的值就知道从机有没有给我们应答了
  7. //接收应答之后,我们要继续再发送一个字节,写入寄存器地址
  8. MyI2C_Stop();
  9. printf("Ack=%d\n",Ack);
  1. //测试读寄存器
  2. uint8_t ID;
  3. Usart_Init();
  4. MPU6050_Init();
  5. // who I am I寄存器,只读,地址0x75,默认是ID号,默认值就是0x60
  6. ID=MPU6050_ReadReg(0x75);//返回值是ID号的内容
  7. printf("ID=%x\n",ID);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/462406
推荐阅读
相关标签
  

闽ICP备14008679号