上一节,我们完成对IIC通信的时序以及IIC的通信的讲解和代码实现,接下来,我们正式进入,利用上一节软件实现的IIC通信协议来对外设MPU6050进行读写操作。(本节IIC代码在上节)
本节,目的很明确,就是利用软件IIC通信协议来实现STM32与MPU6050的通信,说白了,就是我们就是要控制MPU6050,来实现我们的既定目标,(目标:读取MPU6050的6个轴传感器姿态数值)。那么,接下来跟着Whappy步伐。(源码在最后)
本程序的逻辑框架
视频现象:
本人总结:
本项目,主要目的STM32和MPU6050要进行通信。
第一,解决通信问题
因为MPU6050属于STM32的外部外设设,本身MPU6050是支持IIC接口的,STM32也是具备IIC硬件通信资源的接口的,但是,为了让我自己更加对IIC有深刻的认识,我软件实现了IIC时序,并利用软件模拟的时序与MPU6050交互,通过STM32的任意两个GPIO来模拟(GPIO设置成开漏输出模式)
第二,解决MPU6050驱动问题
MPU6050是STM32内部集成外的外设设备,是STM32部内部外的外设,我们是无法通过,stm32内部总线和库函数去操作这个设备的,因此,我们需要自己写MPU6050的驱动函数,所谓的写驱动函数,就是通过配置寄存器来控制和改变硬件电路的链路状态,寄存器是一个字节的二进制位(如下图)组合起来控制电路的开关,里面都是控制这个外设内部资源的开关电路,比如下图,我们给位6一个1(二进制位),此时芯片就会进入睡眠模式,寄存器可以理解成我们房子里的电闸控制系统,有控制厨房的灯,卧室的灯等等 , 寄存器如何配置是通过查找外设的数据手册规格书和寄存器操作映射操作说明书,两者结合起来配置我们需要的功能(如:测量获取,x,y,z加速计的值),查看数据手册是一件极为重要的事情,手册里包含了此外设的所用功能,电气特性,应用等,对于这个外设最最最基本操作就是读写寄存器(操作每个外部外设的基本操作单元就是读写寄存器),要操作读写寄存器,(寄存器地址通常为一个字节)我们就必须实现读写寄存器函数,要实现读写寄存器,我们就要有通信方式,这里就是利用的IIC通信,如下图2,利用IIC通信
将我们需要的字节数据写入对应的寄存器地址,在对应的寄存器地址,写入对应的数据,这个对应的数据就是要控制电路的开关了,如下图3,图4是寄存器内一个字节数据每一个对应的硬件电路,第一个函数MPU6050_Register_W(MPU_PWR_MGMT1_REG, 0x01);这个函数就是在对应的寄存器地址写入了对应的数据,这个数据就是我们要控制的那些需要的电路功能状态,功能就是将MPU6050的电源管理寄存器 1 设置为 0x01
,使MPU6050进入睡眠模式。这个操作是为了节省功耗,或者是配置MPU6050的状态。同样的,我们也要读取一些我们需要的数据,如,当我们向寄存器写入了一个字节数据,该字节的功能就是测量x轴的加速计,我们写好之后,这个外设就会控制执行相对应的操作(如1秒种测刷新一次x轴的测量数据),如何读取呢?也是同样的写一个函数读取相对应的寄存器地址就行了,一般处理好的数据就会放在这里寄存器对应的数据缓冲区里,如图5,通过读取X轴的高八位和低八位整合成一个完整数据(测量的数据是16位的 -32767~32768)
这就是写驱动函数,过程一定要完全遵循IIC通信(6步,开始,写一个字节,读一个字节,应答,非应答,停止,如本文章代码)(下图6是一个完整的写一个字节的IIC通信过程)
图2
图3
图4
图5
图6
第三,应用层。
业务逻辑非常简单,实现的目标也很简单就是测6个轴的传感器值
简要总结:
外设与内部通信的区别:
- MPU6050是一个外部设备,它并不是STM32内部的外设,因此不能直接通过STM32的内部总线(如SPI、UART等)操作。
- 为了与MPU6050通信,我们必须使用一种通信协议——I2C(Inter-Integrated Circuit),这是一种常用的串行总线协议,用于连接外部设备与微控制器。
读写寄存器的意义:
- 寄存器控制着外设内部的硬件电路。每个寄存器通常是一个字节(8位),每一位代表某个硬件功能的开关(如进入睡眠模式、启用传感器、选择时钟源等)。
- 通过向寄存器写入数据(比如
0x01
、0x00
等),可以改变外设的工作模式(如唤醒、休眠、启动数据采集等)。 - 寄存器地址和寄存器位的详细说明通常都可以在设备的数据手册中找到。
如何编写驱动函数:
- I2C通信过程:
- 通过I2C协议与MPU6050设备通信,一般的I2C操作包括:发送设备地址、发送寄存器地址、读取或写入数据、接收确认信号(ACK)和非确认信号(NACK)。
- 写入数据时,通过发送设备的I2C地址、寄存器地址、以及要写入的数据字节来配置设备的工作状态。
- 读取数据时,向设备发送寄存器地址后,再读取设备返回的数据字节(如获取传感器的加速度数据)。
- I2C通信过程:
驱动函数的工作原理:
- 驱动程序的核心就是寄存器操作,我们通过查阅设备的手册来确定每个寄存器的地址和功能,然后编写代码与外设进行通信。
- 例如,
MPU6050_Register_W(MPU_PWR_MGMT1_REG, 0x01);
这行代码的作用是通过I2C将0x01
写入MPU_PWR_MGMT1_REG
,使MPU6050进入休眠模式。 - 对于获取数据,我们通过I2C读取特定寄存器(如加速度计X轴的数据寄存器)并合并高低字节形成完整的16位数据。
I2C通信流程:
- 开始信号:通过I2C总线发送起始信号,通知通信开始。
- 发送设备地址:发送目标设备的I2C地址。
- 发送寄存器地址:指定我们要操作的寄存器地址。
- 读取/写入数据:根据操作类型(读或写),发送数据字节或读取数据字节。
- 应答与非应答:在每次字节传输后,接收或发送应答信号(ACK)或非应答信号(NACK)。
- 停止信号:完成通信后,发送停止信号,结束本次I2C通信。
主要步骤:
- 初始化I2C接口:配置STM32的I2C引脚和相关参数。
- 编写寄存器操作函数:
- 写寄存器(
MPU6050_Register_W
):通过I2C将数据写入MPU6050的特定寄存器,控制外设工作模式。 - 读寄存器(
MPU6050_Register_R
):通过I2C从MPU6050读取寄存器的数据,获取传感器的测量值。
- 写寄存器(
- 驱动功能封装:通过编写高层驱动接口(如
MPU6050_Init
、MPU6050_Struct_Data
等),简化外设控制和数据获取的操作。
I2C通信详细步骤(示例):
写操作:
- 发送 开始信号。
- 发送 设备地址(如MPU6050的地址,通常为
0xD0
或0xD1
)。 - 发送 寄存器地址(如
MPU_PWR_MGMT1_REG
)。 - 发送 数据(如
0x01
)。 - 接收 应答信号。
- 发送 停止信号。
读操作:
- 发送 开始信号。
- 发送 设备地址(写操作,低7位地址
0x68
)。 - 发送 寄存器地址(如
MPU_ACCEL_XOUTH_REG
)。 - 发送 重新启动(Re-start信号)。
- 发送 设备地址(读操作,
0x69
或0x68
)。 - 读取 数据字节(如读取加速度计X轴的高字节和低字节)。
- 接收 应答信号。
- 发送 停止信号。
总结:
- 驱动程序编写的核心是根据外设的寄存器地址和功能位配置设备,通过I2C通信协议读写数据。
- 寄存器控制硬件的各种功能和状态,就像控制电器开关一样。
- I2C通信流程确保了正确的数据交换,在STM32与MPU6050之间进行有效的读写操作。
- 驱动程序的写作需要深入理解设备手册,从中获取寄存器配置和设备操作的细节。
MPU6050资源免费共享!
【免费】中文版MPU-6000/MPU-6050寄存器映射与功能详解资源-CSDN文库
MPU-6000/MPU-6050运动传感技术规格及应用解析资源-CSDN文库
void MPU6050_Init(void);
作用:对所需硬件的配置及初始化
2.uint8_t MPU6050_Regiter_R(uint8_t MPU6050_Reg_Addr);
作用:向MPU6050读出相关配置后的数据
3.void MPU6050_Register_W(uint8_t Register_Address, uint8_t Data);
作用:向MPU6050写入相关配置
以下是 MPU6050 模块的主要函数及其作用说明,附带示例代码以便参考和应用。
1. void MPU6050_Init(void);
作用:
用于初始化 MPU6050 传感器和相关硬件环境,包括 I²C 总线初始化和传感器寄存器的基本配置。它确保传感器处于工作状态,设定必要的测量参数(如采样率、滤波器设置、量程范围等),为后续数据读取和处理打下基础。
示例:
void MPU6050_Init(void) {
MyIIC_Init(); // 初始化 I²C 总线
MPU6050_Register_W(0x6B, 0x00); // 写入电源管理寄存器,关闭休眠模式
MPU6050_Register_W(0x1A, 0x03); // 设置低通滤波器(配置采样频率)
MPU6050_Register_W(0x1B, 0x10); // 设置陀螺仪量程为 ±1000°/s
MPU6050_Register_W(0x1C, 0x08); // 设置加速度计量程为 ±4g
}
- 解释:此函数调用多个寄存器写入操作完成传感器初始化,例如取消休眠模式、设置滤波器和量程范围。完成后,MPU6050 即可正常工作。
2. uint8_t MPU6050_Regiter_R(uint8_t MPU6050_Reg_Addr);
作用:
从 MPU6050 的指定寄存器读取数据,用于获取配置或测量结果。例如,读取加速度计、陀螺仪的输出数据,或检查特定寄存器的状态。
参数说明:
reg
:要读取的寄存器地址。data
:读取到的数据存储位置。
示例:
uint8_t who_am_i;
MPU6050_Register_R(0x75, &who_am_i); // 读取 WHO_AM_I 寄存器
if (who_am_i == 0x68) {
printf("MPU6050 已正确连接。\n");
} else {
printf("MPU6050 连接失败。\n");
}
- 解释:WHO_AM_I 寄存器用于验证 MPU6050 是否正常连接,返回值
0x68
是设备的标识码。
3.void MPU6050_Register_W(uint8_t Register_Address, uint8_t Data);
作用:
向 MPU6050 的指定寄存器写入配置值,用于修改传感器的设置或发送命令。例如,设置电源管理模式、调整量程范围或启用特定功能。
参数说明:
reg
:目标寄存器地址。value
:要写入的值。
示例:
MPU6050_Register_W(0x6B, 0x40); // 设置 MPU6050 进入休眠模式
printf("传感器已进入休眠模式。\n");
- 解释:通过向电源管理寄存器写入
0x40
,MPU6050 被配置为休眠状态,减少功耗。
总结
这三个函数是 MPU6050 驱动程序的核心:
MPU6050_Init
负责硬件和传感器的初始化。MPU6050_Register_R
用于读取指定寄存器的数据(如验证连接或获取测量结果)。MPU6050_Register_W
用于向寄存器写入配置值(如设定传感器的工作模式)。
通过这些函数的分工,可以方便地实现对 MPU6050 的控制和数据交互。
本节是操作寄存器的一节,一定要知道寄存器中的每一位都对应着我们的硬件电路,可以理解,寄存器每一个为都像我们电路中导线连接着关键电路。寄存器的每一位都和我们硬件电路互动,掌握好读写寄存器,我们就能通过寄存器来控制电路了。
本节内容深入讲解了寄存器的操作,掌握这一部分是硬件编程的基础。寄存器可以看作是微控制器内部的一组存储单元,每个寄存器通常由多个二进制位(bit)组成,而这些二进制位直接控制硬件的不同部分。可以将寄存器中的每一位比作电路中导线的开关,开启或关闭这些开关,将直接影响到硬件电路的工作状态。理解寄存器中的每一位及其作用,使得我们能够精准地控制硬件行为。
举个例子:
假设我们在操作一个 MPU6050 传感器的 电源管理寄存器(PWR_MGMT_1),该寄存器的地址是 0x6B
。这个寄存器的第6位是 SLEEP 位,它控制着传感器是否处于睡眠模式。当 SLEEP 位为1 时,设备会进入低功耗模式;当 SLEEP 位为0 时,设备恢复正常工作模式。如果我们通过 I2C 总线设置该位为0,传感器就会从睡眠模式中唤醒,开始采集数据。
另一个常见的例子是 MPU6050 的时钟源选择。同样在 PWR_MGMT_1 寄存器 中, CLKSEL 位(位0到位2)用于选择设备的时钟源。我们可以通过修改这三位的值来指定时钟源。例如,选择内部8MHz振荡器、外部时钟源,或者通过陀螺仪输出作为时钟源。这些设置直接影响着传感器的工作频率和稳定性。
更具体的电路类比:
想象一下电路中有一个开关控制着某个电路的通断,当开关关闭时,电路就停止工作;而当开关打开时,电路开始运作。寄存器中的每一位就像这些开关,每一位都负责控制电路中的特定功能或状态。例如,一个寄存器的第5位可能控制一个 LED 灯的亮灭,第2位可能控制某个传感器的数据采集启动与否。通过设置这些位的值,我们能够精确地控制整个系统的行为。
寄存器位的组合:
寄存器中的各个位通常不是孤立工作的,它们通常是配合使用的。例如,MPU6050 的 PWR_MGMT_1 寄存器 中除了有 SLEEP 和 CLKSEL 位之外,还可能有其他控制位,如 TEMP_DIS(禁用温度传感器)等。这些位的组合决定了设备的行为,我们需要根据应用需求正确设置这些位,以便得到最佳的硬件配置。
结论:
在嵌入式编程和硬件开发中,寄存器的操作不仅是对硬件控制的基础,也是实现精细化管理的关键。通过掌握每一位的意义和功能,我们能够实现对硬件的精准控制,从而满足不同应用场景的需求。这就像是拥有了控制整个电路的开关,通过合理的组合和设置,灵活调整硬件的状态,达到理想的效果。
C语言实现多返回值的方法
三种函数返回多个返回值的方法:
1.全局变量法(慎用)
2.数组指针法
3.结构体指针
c语言||一个函数能return好几个?(产生多个返回值)_c语言return返回多个值-CSDN博客
IIC时序参考:4.STM32之通信接口《精讲》之IIC通信---软件实现IIC《深入浅出》面试必备!_stm32的sda接口-CSDN博客
总结:本节用了多层的模块架构,分层设计,能够让程序更具有模块化,代码
接下来就是代码实现模块:
MyIIC.h
#ifndef __MYIIC_H #define __MYIIC_H #include "stm32f10x.h" // 引入STM32F10x的设备头文件,包含对STM32F10系列微控制器的所有定义 // I2C接口相关的函数声明: // 初始化I2C接口 void MyIIC_Init(void); // 产生I2C起始信号 void MyIIC_Start(void); // 产生I2C停止信号 void MyIIC_Stop(void); // 发送一个字节的数据到I2C总线 void MyIIC_SendByte(uint8_t Byte); // 从I2C总线接收一个字节的数据 uint8_t MyIIC_ReveiveByte(void); // 向I2C总线发送ACK(确认)或NACK(非确认)信号,AckBit为0表示NACK,为1表示ACK void MyIIC_SendAck(uint8_t AckBit); // 从I2C总线接收ACK(确认)或NACK(非确认)信号 uint8_t MyIIC_ReceiveAck(void); #endif // 结束头文件保护
注释解释:
#ifndef __MYIIC_H
/#define __MYIIC_H
:防止头文件被重复包含,__MYIIC_H
是该头文件的宏定义。#include "stm32f10x.h"
:引入 STM32F10x 微控制器的头文件,包含 STM32F10x 系列相关的硬件寄存器、外设定义等。MyIIC_Init
:初始化 I2C 总线的相关设置,包括设置引脚、时钟等。MyIIC_Start
:生成 I2C 起始条件,通知从设备开始通信。MyIIC_Stop
:生成 I2C 停止条件,结束通信。MyIIC_SendByte
:向 I2C 总线上发送一个字节的数据。MyIIC_ReveiveByte
:从 I2C 总线上接收一个字节的数据。MyIIC_SendAck
:向 I2C 总线上发送确认(ACK)或非确认(NACK)信号,用于响应从设备是否成功接收到数据。MyIIC_ReceiveAck
:从 I2C 总线上接收 ACK 或 NACK 信号,确认数据是否被成功接收。这份头文件声明了I2C通信中常用的操作函数接口,实际实现则可能在
MyIIC.c
文件中。
MyIIC.c
#include "stm32f10x.h" // 引入 STM32F10x 微控制器的设备头文件,包含 STM32F10x 系列相关的硬件寄存器、外设定义等 #include "MyIIC.h" #include "Delay.h" // 写SCL信号到GPIO(时钟线),Bit为0或1表示低或高 void MyIIC_SCL_W(uint8_t Bit) { GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)Bit); // 写入SCL引脚(GPIOB Pin 10) Delay_us(10); // 延时10微秒 } // 写SDA信号到GPIO(数据线),Bit为0或1表示低或高 void MyIIC_SDA_W(uint8_t Bit) { GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)Bit); // 写入SDA引脚(GPIOB Pin 11) Delay_us(10); // 延时10微秒 } // 读取SDA引脚的状态(即I2C的数据线) uint8_t MyIIC_SDA_R(void) { uint8_t Bit; Bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); // 读取SDA引脚(GPIOB Pin 11)的电平状态 Delay_us(10); // 延时10微秒 return Bit; // 返回读取的状态 } // 初始化I2C通信接口的GPIO引脚 void MyIIC_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 设置为开漏输出模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; // 配置GPIOB的Pin 10(SCL)和Pin 11(SDA) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置GPIO速度为50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB的引脚 GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 设置SCL和SDA为高电平(空闲状态) } // 产生I2C总线的起始条件 void MyIIC_Start(void) { MyIIC_SDA_W(1); // SDA保持高电平 MyIIC_SCL_W(1); // SCL保持高电平 MyIIC_SDA_W(0); // SDA拉低 MyIIC_SCL_W(0); // SCL拉低,准备开始通信 } // 产生I2C总线的停止条件 void MyIIC_Stop(void) { MyIIC_SDA_W(0); // SDA拉低 MyIIC_SCL_W(1); // SCL拉高 MyIIC_SDA_W(1); // SDA拉高,产生停止条件 } // 向I2C总线发送一个字节的数据(8位) void MyIIC_SendByte(uint8_t Byte) { uint8_t i; for (i = 0; i < 8; i++) // 遍历每一位 { MyIIC_SDA_W(Byte & (0x80 >> i)); // 通过位运算确定当前位并发送 MyIIC_SCL_W(1); // 拉高时钟线SCL MyIIC_SCL_W(0); // 拉低时钟线SCL,完成一个时钟周期 } } // 从I2C总线接收一个字节的数据(8位) uint8_t MyIIC_ReveiveByte(void) { uint8_t i, Byte = 0x00; MyIIC_SDA_W(1); // 设置SDA为输入模式,准备接收数据 for (i = 0; i < 8; i++) // 遍历每一位 { MyIIC_SCL_W(1); // 拉高时钟线SCL,开始接收 if (MyIIC_SDA_R() == 1) // 读取SDA数据线的电平,接收当前位 { Byte |= (0x80 >> i); // 将接收到的位存入字节 } MyIIC_SCL_W(0); // 拉低时钟线SCL,准备接收下一个位 } return Byte; // 返回接收到的字节 } // 向I2C总线发送ACK(确认)或NACK(非确认)信号 void MyIIC_SendAck(uint8_t AckBit) { MyIIC_SDA_W(AckBit); // 设置ACK或NACK,AckBit为0表示NACK,为1表示ACK MyIIC_SCL_W(1); // 拉高时钟线SCL,准备发送 MyIIC_SCL_W(0); // 拉低时钟线SCL,完成一个时钟周期 } // 从I2C总线接收ACK(确认)或NACK(非确认)信号 uint8_t MyIIC_ReceiveAck(void) { uint8_t AckBit; MyIIC_SDA_W(1); // 设置SDA为输入模式,准备接收ACK信号 MyIIC_SCL_W(1); // 拉高时钟线SCL,准备接收ACK AckBit = MyIIC_SDA_R(); // 读取ACK信号(SDA线的状态) MyIIC_SCL_W(0); // 拉低时钟线SCL,完成接收 return AckBit; // 返回接收到的ACK或NACK }
注释说明:
MyIIC_SCL_W
和MyIIC_SDA_W
:
- 这两个函数用于控制I2C时钟线(SCL)和数据线(SDA)的电平。
Bit
是传入的电平值(0 或 1),它控制SCL或SDA的高低电平。- 每次设置完电平后,延时10微秒,以确保信号稳定。
MyIIC_SDA_R
:
- 该函数用于读取SDA线的电平,即获取I2C总线数据线的状态。
MyIIC_Init
:
- 用于初始化I2C的GPIO引脚(SCL和SDA)。在此函数中,通过配置GPIOB的引脚10和11为开漏输出模式来实现I2C的通讯。
- 使能GPIOB时钟,并将SCL和SDA初始化为高电平,处于空闲状态。
MyIIC_Start
和MyIIC_Stop
:
MyIIC_Start
:通过控制SDA和SCL的电平,产生I2C总线的起始条件。MyIIC_Stop
:通过控制SDA和SCL的电平,产生I2C总线的停止条件。
MyIIC_SendByte
:
- 该函数用于发送一个字节的数据到I2C总线上。数据是按位发送的,逐位控制SDA线的电平,并通过时钟线(SCL)来同步数据传输。
MyIIC_ReveiveByte
:
- 该函数用于从I2C总线上接收一个字节的数据。每接收一位,都会拉高SCL线,然后读取SDA线的状态,再拉低SCL线。
MyIIC_SendAck
和MyIIC_ReceiveAck
:
MyIIC_SendAck
:用于发送ACK(确认)或NACK(非确认)信号,告知从设备是否成功接收到数据。MyIIC_ReceiveAck
:接收从设备发送的ACK或NACK信号,表示接收是否成功。作用:
本代码实现了I2C总线的基本操作函数,包括起始和停止信号的产生、字节的发送和接收、ACK信号的发送与接收。通过这些基本的I2C操作函数,可以与I2C从设备进行通信。
MPU6050.h
#ifndef __MPU6050_H #define __MPU6050_H #include "stm32f10x.h" // 引入 STM32F10x 微控制器的设备头文件,包含相关硬件寄存器和外设定义 // 定义一个结构体类型,用于存储MPU6050传感器的六个数据值(加速度和陀螺仪) typedef struct { int16_t Acce_X; // X轴加速度数据 int16_t Acce_Y; // Y轴加速度数据 int16_t Acce_Z; // Z轴加速度数据 int16_t Gyro_X; // X轴陀螺仪数据 int16_t Gyro_Y; // Y轴陀螺仪数据 int16_t Gyro_Z; // Z轴陀螺仪数据 } MPU6050_Data, *pMPU6050_Data; // 定义结构体类型MPU6050_Data,并定义其指针类型pMPU6050_Data // 函数声明:初始化MPU6050传感器 void MPU6050_Init(void); // 函数声明:向MPU6050的指定寄存器写入一个字节的数据 void MPU6050_Register_W(uint8_t Register_Address, uint8_t Data); // 函数声明:读取MPU6050指定寄存器的一个字节数据 uint8_t MPU6050_Regiter_R(uint8_t MPU6050_Reg_Addr); // 函数声明:读取MPU6050的加速度计和陀螺仪数据 void MPU6050_RegState_Data(int16_t* Acce_X, int16_t* Acce_Y, int16_t* Acce_Z, int16_t* GYRO_X, int16_t* GYRO_Y, int16_t* GYRO_Z); // 函数声明:获取MPU6050的设备ID uint8_t MPU6050_ID(void); // 函数声明:获取MPU6050的传感器数据并返回一个指向MPU6050_Data结构体的指针 MPU6050_Data* MPU6050_Struct_Data(void); #endif
注释说明:
#ifndef __MPU6050_H
和#define __MPU6050_H
:
- 这两行代码用于防止头文件被重复包含。当该头文件第一次包含时,宏
__MPU6050_H
未定义,编译器会执行包含内容。若再次包含该头文件,__MPU6050_H
会被定义,内容将被忽略,防止重复定义。
typedef struct { ... } MPU6050_Data, *pMPU6050_Data;
:
- 定义了一个结构体
MPU6050_Data
,用于存储MPU6050传感器的加速度计和陀螺仪数据。结构体中包括六个int16_t
类型的变量,分别表示三个加速度计轴(X, Y, Z)和三个陀螺仪轴(X, Y, Z)。pMPU6050_Data
是该结构体的指针类型,允许通过指针访问传感器数据。
MPU6050_Init
:
- 用于初始化MPU6050传感器。初始化过程通常包括配置传感器的工作模式、采样率、增益等参数。
MPU6050_Register_W
:
- 用于向MPU6050的指定寄存器写入一个字节的数据。传感器的配置和数据读取都通过此函数进行。
MPU6050_Regiter_R
:
- 用于读取MPU6050指定寄存器的一个字节数据。返回值是寄存器的数据,通常用于获取传感器的状态或传感器的测量结果。
MPU6050_RegState_Data
:
- 该函数用于获取MPU6050传感器的加速度计和陀螺仪的数据,并通过指针参数返回六个数据值(加速度和陀螺仪的X, Y, Z轴数据)。
- 该函数从传感器的寄存器读取加速度计和陀螺仪的原始数据,并将它们转换成16位的整型值。
MPU6050_ID
:
- 用于读取MPU6050传感器的设备ID。通过该ID可以验证设备是否正常工作。
MPU6050_Struct_Data
:
- 该函数返回一个指向
MPU6050_Data
结构体的指针,该结构体包含传感器的加速度计和陀螺仪数据。- 通过返回结构体的指针,可以在主程序中访问和处理传感器数据。
总结:
该头文件定义了与MPU6050传感器交互的基本函数,包括初始化、寄存器读写、数据读取和传感器ID获取。结构体
MPU6050_Data
用于存储传感器的加速度计和陀螺仪数据,并通过函数接口返回这些数据。
MPU6050.c
#include "stm32f10x.h" // 引入 STM32F10x 微控制器的设备头文件,包含 STM32F10x 系列相关的硬件寄存器、外设定义等 #include "MyIIC.h" // 引入自定义的I2C通信接口头文件 #include "Delay.h" // 引入延时函数的头文件 // 写SCL信号到GPIO(时钟线),Bit为0或1表示低或高 void MyIIC_SCL_W(uint8_t Bit) { GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)Bit); // 写入SCL引脚(GPIOB Pin 10),用于控制I2C时钟信号 Delay_us(10); // 延时10微秒,确保信号稳定 } // 写SDA信号到GPIO(数据线),Bit为0或1表示低或高 void MyIIC_SDA_W(uint8_t Bit) { GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)Bit); // 写入SDA引脚(GPIOB Pin 11),用于控制I2C数据线 Delay_us(10); // 延时10微秒,确保信号稳定 } // 读取SDA引脚的状态(即I2C的数据线) uint8_t MyIIC_SDA_R(void) { uint8_t Bit; Bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); // 读取SDA引脚(GPIOB Pin 11)的电平状态 Delay_us(10); // 延时10微秒,确保读取稳定 return Bit; // 返回读取的电平状态 } // 初始化I2C通信接口的GPIO引脚 void MyIIC_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟,确保GPIOB的引脚可以使用 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 设置为开漏输出模式(Open-Drain),适用于I2C通信 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; // 配置GPIOB的Pin 10(SCL)和Pin 11(SDA) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置GPIO速度为50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB的引脚,配置为I2C功能 GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 设置SCL和SDA为高电平(空闲状态),I2C处于空闲状态时两线都是高电平 } // 产生I2C总线的起始条件 void MyIIC_Start(void) { MyIIC_SDA_W(1); // SDA保持高电平 MyIIC_SCL_W(1); // SCL保持高电平 MyIIC_SDA_W(0); // SDA拉低,生成起始信号的第一个部分 MyIIC_SCL_W(0); // SCL拉低,生成起始信号的第二个部分,开始通信 } // 产生I2C总线的停止条件 void MyIIC_Stop(void) { MyIIC_SDA_W(0); // SDA拉低 MyIIC_SCL_W(1); // SCL拉高 MyIIC_SDA_W(1); // SDA拉高,产生停止条件,表示通信结束 } // 向I2C总线发送一个字节的数据(8位) void MyIIC_SendByte(uint8_t Byte) { uint8_t i; for (i = 0; i < 8; i++) // 遍历每一位(8位数据) { MyIIC_SDA_W(Byte & (0x80 >> i)); // 通过位运算确定当前位并发送到SDA线上 MyIIC_SCL_W(1); // 拉高时钟线SCL,通知数据有效 MyIIC_SCL_W(0); // 拉低时钟线SCL,完成一个时钟周期,数据被接收 } } // 从I2C总线接收一个字节的数据(8位) uint8_t MyIIC_ReveiveByte(void) { uint8_t i, Byte = 0x00; MyIIC_SDA_W(1); // 设置SDA为输入模式,准备接收数据 for (i = 0; i < 8; i++) // 遍历每一位(接收8位数据) { MyIIC_SCL_W(1); // 拉高时钟线SCL,开始接收数据 if (MyIIC_SDA_R() == 1) // 读取SDA线的电平状态,接收当前位的数据 { Byte |= (0x80 >> i); // 将接收到的位存储到字节中 } MyIIC_SCL_W(0); // 拉低时钟线SCL,准备接收下一个位 } return Byte; // 返回接收到的完整字节 } // 向I2C总线发送ACK(确认)或NACK(非确认)信号 void MyIIC_SendAck(uint8_t AckBit) { MyIIC_SDA_W(AckBit); // 设置SDA为ACK(0)或NACK(1) MyIIC_SCL_W(1); // 拉高时钟线SCL,发送ACK信号 MyIIC_SCL_W(0); // 拉低时钟线SCL,完成一个时钟周期 } // 从I2C总线接收ACK(确认)或NACK(非确认)信号 uint8_t MyIIC_ReceiveAck(void) { uint8_t AckBit; MyIIC_SDA_W(1); // 设置SDA为输入模式,准备接收ACK信号 MyIIC_SCL_W(1); // 拉高时钟线SCL,准备接收ACK AckBit = MyIIC_SDA_R(); // 读取SDA线的电平状态,接收ACK信号(0表示ACK,1表示NACK) MyIIC_SCL_W(0); // 拉低时钟线SCL,完成接收过程 return AckBit; // 返回接收到的ACK或NACK }
详细注释说明:
引入的头文件:
#include "stm32f10x.h"
:STM32F10x系列微控制器的硬件寄存器和外设相关定义。#include "MyIIC.h"
:自定义的I2C通信接口头文件,提供I2C通信相关函数的声明。#include "Delay.h"
:延时函数头文件,用于提供精确的延时功能。
MyIIC_SCL_W
:该函数用于向I2C总线的时钟线(SCL)写入0或1的值,控制时钟信号的状态。每次操作后延时10微秒,以确保信号稳定。
MyIIC_SDA_W
:该函数用于向I2C总线的数据线(SDA)写入0或1的值,控制数据线的电平。每次操作后延时10微秒。
MyIIC_SDA_R
:该函数用于读取I2C总线数据线(SDA)的电平,返回SDA引脚的状态。读取完成后延时10微秒。
MyIIC_Init
:初始化I2C通信所需的GPIO引脚(SCL和SDA)。该函数启用了GPIOB的时钟,并设置Pin 10(SCL)和Pin 11(SDA)为开漏输出模式,并将SCL和SDA初始化为高电平(空闲状态)。
MyIIC_Start
:生成I2C总线的起始条件(Start Condition)。首先将SDA拉高,然后SCL拉高,再将SDA拉低,最后将SCL拉低,完成起始信号。
MyIIC_Stop
:生成I2C总线的停止条件(Stop Condition)。首先将SDA拉低,然后SCL拉高,最后将SDA拉高,完成停止信号。
MyIIC_SendByte
:该函数通过I2C总线发送一个字节数据。通过遍历每一位,依次将数据位发送到SDA线,并控制SCL时钟线完成一个时钟周期。
MyIIC_ReveiveByte
:该函数通过I2C总线接收一个字节数据。它通过逐位读取SDA线的数据,并根据SCL时钟的变化完成数据接收。
MyIIC_SendAck
:该函数向I2C总线发送一个ACK(确认)或NACK(非确认)信号。根据AckBit
的值(0表示NACK,1表示ACK),它通过SDA线发送相应的信号,并产生一个时钟周期(通过控制SCL的高低电平)。
MyIIC_ReceiveAck
:该函数从I2C总线接收ACK或NACK信号。它将SDA线设置为输入模式,读取SDA线的电平状态(0表示ACK,1表示NACK),然后返回读取的ACK信号。此函数用于确认数据接收是否成功。代码流程:
起始条件(Start Condition):在I2C通信开始之前,必须产生一个起始条件。
MyIIC_Start
函数完成了这一任务,即通过拉低SDA并产生时钟信号(SCL拉低),来启动通信。停止条件(Stop Condition):在I2C通信结束时,必须产生一个停止条件,告知设备通信结束。
MyIIC_Stop
函数实现了这一功能,它通过SCL拉高并将SDA拉高来终止通信。总结:
这段代码提供了I2C通信的基本操作,包括启动、停止、发送数据、接收数据以及ACK/NACK响应的实现。它通过STM32的GPIO引脚模拟了I2C协议的通信过程,并且支持数据的读取和写入。延时函数
Delay_us
在每次信号变化后使用,确保信号的稳定性与时序的准确性。数据传输(Data Transfer):数据通过SDA线传输,每次传输8位数据。
MyIIC_SendByte
函数将一个字节的数据通过SDA线发送出去,MyIIC_ReveiveByte
函数则用来接收来自SDA的数据。每发送或接收一位数据时,都会同步地通过SCL产生时钟脉冲。ACK/NACK:在I2C协议中,数据接收方通常需要发送一个ACK信号(表示数据正确接收),或者在某些情况下发送NACK信号(表示数据接收失败或已接收完毕)。
MyIIC_SendAck
和MyIIC_ReceiveAck
函数分别用来发送和接收ACK/NACK信号。
MPU6050_Reg_Cofig.h
#ifndef __MPU6050_Reg_Config_H #define __MPU6050_Reg_Config_H #include "stm32f10x.h" // Device header,包含STM32F10x微控制器的头文件 // 部分宏定义数据,用于访问MPU6050寄存器的地址和控制引脚 // MPU6050 AD0控制脚 #define MPU_AD0_CTRL PAout(15) // 控制AD0电平, 从而控制MPU的I2C地址 // 一些未用到的寄存器,可能是参考手册中的保留寄存器 //#define MPU_ACCEL_OFFS_REG 0X06 // 加速度计偏移寄存器, 可读取版本号, 寄存器手册未提到 //#define MPU_PROD_ID_REG 0X0C // 产品ID寄存器, 在寄存器手册未提到 // MPU6050自检寄存器(X、Y、Z轴以及加速度计的自检) #define MPU_SELF_TESTX_REG 0X0D // 自检寄存器X #define MPU_SELF_TESTY_REG 0X0E // 自检寄存器Y #define MPU_SELF_TESTZ_REG 0X0F // 自检寄存器Z #define MPU_SELF_TESTA_REG 0X10 // 自检寄存器A // 采样率和配置相关寄存器 #define MPU_SAMPLE_RATE_REG 0X19 // 采样频率分频器,设置采样速率 #define MPU_CFG_REG 0X1A // 配置寄存器,控制低通滤波器等配置 #define MPU_GYRO_CFG_REG 0X1B // 陀螺仪配置寄存器,设置陀螺仪的量程 #define MPU_ACCEL_CFG_REG 0X1C // 加速度计配置寄存器,设置加速度计的量程 // 运动检测、FIFO等寄存器 #define MPU_MOTION_DET_REG 0X1F // 运动检测阀值设置寄存器 #define MPU_FIFO_EN_REG 0X23 // FIFO使能寄存器,启用FIFO存储 #define MPU_I2CMST_CTRL_REG 0X24 // I2C主机控制寄存器 #define MPU_I2CSLV0_ADDR_REG 0X25 // I2C从机0器件地址寄存器 #define MPU_I2CSLV0_REG 0X26 // I2C从机0数据地址寄存器 #define MPU_I2CSLV0_CTRL_REG 0X27 // I2C从机0控制寄存器 // 更多I2C从机寄存器 #define MPU_I2CSLV1_ADDR_REG 0X28 // I2C从机1器件地址寄存器 #define MPU_I2CSLV1_REG 0X29 // I2C从机1数据地址寄存器 #define MPU_I2CSLV1_CTRL_REG 0X2A // I2C从机1控制寄存器 #define MPU_I2CSLV2_ADDR_REG 0X2B // I2C从机2器件地址寄存器 #define MPU_I2CSLV2_REG 0X2C // I2C从机2数据地址寄存器 #define MPU_I2CSLV2_CTRL_REG 0X2D // I2C从机2控制寄存器 #define MPU_I2CSLV3_ADDR_REG 0X2E // I2C从机3器件地址寄存器 #define MPU_I2CSLV3_REG 0X2F // I2C从机3数据地址寄存器 #define MPU_I2CSLV3_CTRL_REG 0X30 // I2C从机3控制寄存器 #define MPU_I2CSLV4_ADDR_REG 0X31 // I2C从机4器件地址寄存器 #define MPU_I2CSLV4_REG 0X32 // I2C从机4数据地址寄存器 #define MPU_I2CSLV4_DO_REG 0X33 // I2C从机4写数据寄存器 #define MPU_I2CSLV4_CTRL_REG 0X34 // I2C从机4控制寄存器 #define MPU_I2CSLV4_DI_REG 0X35 // I2C从机4读数据寄存器 // I2C主机状态、配置等寄存器 #define MPU_I2CMST_STA_REG 0X36 // I2C主机状态寄存器 #define MPU_INTBP_CFG_REG 0X37 // 中断/旁路设置寄存器,配置中断和旁路功能 #define MPU_INT_EN_REG 0X38 // 中断使能寄存器,启用不同的中断 #define MPU_INT_STA_REG 0X3A // 中断状态寄存器,读取中断状态 // 加速度计和温度传感器的数据寄存器 #define MPU_ACCEL_XOUTH_REG 0X3B // 加速度值X轴高8位寄存器 #define MPU_ACCEL_XOUTL_REG 0X3C // 加速度值X轴低8位寄存器 #define MPU_ACCEL_YOUTH_REG 0X3D // 加速度值Y轴高8位寄存器 #define MPU_ACCEL_YOUTL_REG 0X3E // 加速度值Y轴低8位寄存器 #define MPU_ACCEL_ZOUTH_REG 0X3F // 加速度值Z轴高8位寄存器 #define MPU_ACCEL_ZOUTL_REG 0X40 // 加速度值Z轴低8位寄存器 #define MPU_TEMP_OUTH_REG 0X41 // 温度值高8位寄存器 #define MPU_TEMP_OUTL_REG 0X42 // 温度值低8位寄存器 // 陀螺仪数据寄存器 #define MPU_GYRO_XOUTH_REG 0X43 // 陀螺仪X轴高8位寄存器 #define MPU_GYRO_XOUTL_REG 0X44 // 陀螺仪X轴低8位寄存器 #define MPU_GYRO_YOUTH_REG 0X45 // 陀螺仪Y轴高8位寄存器 #define MPU_GYRO_YOUTL_REG 0X46 // 陀螺仪Y轴低8位寄存器 #define MPU_GYRO_ZOUTH_REG 0X47 // 陀螺仪Z轴高8位寄存器 #define MPU_GYRO_ZOUTL_REG 0X48 // 陀螺仪Z轴低8位寄存器 // I2C从机数据寄存器 #define MPU_I2CSLV0_DO_REG 0X63 // I2C从机0数据寄存器 #define MPU_I2CSLV1_DO_REG 0X64 // I2C从机1数据寄存器 #define MPU_I2CSLV2_DO_REG 0X65 // I2C从机2数据寄存器 #define MPU_I2CSLV3_DO_REG 0X66 // I2C从机3数据寄存器 // 一些额外的控制和配置寄存器 #define MPU_I2CMST_DELAY_REG 0X67 // I2C主机延时管理寄存器 #define MPU_SIGPATH_RST_REG 0X68 // 信号通道复位寄存器 #define MPU_MDETECT_CTRL_REG 0X69 // 运动检测控制寄存器 #define MPU_USER_CTRL_REG 0X6A // 用户控制寄存器,启用/禁用各种功能 #define MPU_PWR_MGMT1_REG 0X6B // 电源管理寄存器1,用于设置设备的电源模式 #define MPU_PWR_MGMT2_REG 0X6C // 电源管理寄存器2,控制其他电源管理设置 // FIFO相关寄存器 #define MPU_FIFO_CNTH_REG 0X72 // FIFO计数寄存器高8位 #define MPU_FIFO_CNTL_REG 0X73 // FIFO计数寄存器低8位 #define MPU_FIFO_RW_REG 0X74 // FIFO读写寄存器 // 设备ID寄存器 #define MPU_DEVICE_ID_REG 0X75 // 设备ID寄存器,读取MPU6050的ID // I2C地址设置:如果AD0脚接地,I2C地址为0x68;如果接V3.3,I2C地址为0x69 #define MPU_ADDR 0X68 // 默认I2C地址为0x68 #endif
注释说明:
- MPU6050寄存器配置:代码中定义了MPU6050模块
main.c
#include "stm32f10x.h" // 引入STM32F10x系列微控制器的硬件抽象层库,提供硬件相关函数。 #include "Delay.h" // 引入延时函数库。 #include "OLED.h" // 引入OLED显示屏的驱动库。 #include "MPU6050.h" // 引入MPU6050陀螺仪/加速度传感器的驱动库。 #include "Serial.h" // 引入串口通信库(用于串口调试或输出)。 // 定义加速度计(AX, AY, AZ)和陀螺仪(GX, GY, GZ)数据的变量 int16_t AX, AY, AZ, GX, GY, GZ; int main(void) { // 初始化OLED显示屏 OLED_Init(); // 初始化串口通信 Serial_Init(); // 初始化MPU6050传感器 MPU6050_Init(); // 在OLED上显示"ID:",并显示MPU6050的设备ID OLED_ShowString(1, 1, "ID:"); OLED_ShowHexNum(1, 4, MPU6050_ID(), 2); // 显示MPU6050的ID(2个字符长度) // 无限循环,持续获取和显示传感器数据 while (1) { // 获取MPU6050传感器的加速度和陀螺仪数据 pMPU6050_Data Sensor_Data = MPU6050_Struct_Data(); // 获取传感器数据结构体 // 在OLED显示屏上显示加速度数据(单位:g) OLED_ShowSignedNum(2, 2, (*Sensor_Data).Acce_X, 5); // 显示X轴加速度 OLED_ShowSignedNum(3, 2, Sensor_Data->Acce_Y, 5); // 显示Y轴加速度 OLED_ShowSignedNum(4, 2, Sensor_Data->Acce_Z, 5); // 显示Z轴加速度 // 在OLED显示屏上显示陀螺仪数据(单位:°/s) OLED_ShowSignedNum(2, 9, Sensor_Data->Gyro_X, 5); // 显示X轴角速度 OLED_ShowSignedNum(3, 9, Sensor_Data->Gyro_Y, 5); // 显示Y轴角速度 OLED_ShowSignedNum(4, 9, Sensor_Data->Gyro_Z, 5); // 显示Z轴角速度 // 这里可以加入延时等操作,确保显示数据稳定且不闪烁 } }
主要功能:
初始化:
OLED_Init()
:初始化OLED显示屏,准备显示内容。Serial_Init()
:初始化串口通信,可能用于调试。MPU6050_Init()
:初始化MPU6050传感器,确保它能够开始工作。获取MPU6050数据:
MPU6050_Struct_Data()
:函数用于从MPU6050传感器获取加速度和陀螺仪的数据。返回的数据结构体包含X、Y、Z轴上的加速度和角速度(陀螺仪数据)。显示数据:
- 使用
OLED_ShowSignedNum()
函数在OLED屏幕上显示加速度计(Acce_X, Acce_Y, Acce_Z)和陀螺仪(Gyro_X, Gyro_Y, Gyro_Z)的数据。这里的数字显示格式为有符号的5位数字。不断更新:
- 主循环(
while(1)
)会持续从MPU6050读取数据,并将这些数据不断显示在OLED屏幕上。可能需要补充的部分:
OLED_ShowString
和OLED_ShowHexNum
等函数用来显示字符串和十六进制数。MPU6050_Struct_Data
和相关的传感器初始化及数据获取函数(如MPU6050_Init()
、MPU6050_ID()
)应在外部定义,用于实际操作MPU6050芯片。
2. 代码嵌套关系
主程序:
main()
-> 调用初始化函数(OLED_Init()
,Serial_Init()
,MPU6050_Init()
)。while(1)
-> 在主循环中不断获取传感器数据并显示。
函数嵌套:
- I2C通信:底层I2C操作函数(如
MyIIC_Start()
,MyIIC_SendByte()
)被驱动层(如MPU6050_Register_W()
,MPU6050_Regiter_R()
)和应用层(如MPU6050_Struct_Data()
)调用。 - MPU6050驱动:
MPU6050_Init()
,MPU6050_RegState_Data()
,MPU6050_Struct_Data()
等函数负责与MPU6050传感器交互。 - OLED显示:通过
OLED_ShowString()
、OLED_ShowHexNum()
和OLED_ShowSignedNum()
在显示屏上更新数据。
- I2C通信:底层I2C操作函数(如
3. 函数功能
MyIIC_Init()
: 初始化I2C通信,配置SCL和SDA引脚。MPU6050_Init()
: 初始化MPU6050传感器,配置电源管理、采样率、低通滤波器和量程设置。MPU6050_Register_W()
: 向MPU6050写入数据,通过I2C发送寄存器地址和值。MPU6050_Regiter_R()
: 从MPU6050读取寄存器的数据。MPU6050_Struct_Data()
: 获取MPU6050的加速度和陀螺仪数据,并返回数据结构体的指针。OLED_ShowString()
: 在OLED上显示字符串。OLED_ShowHexNum()
: 在OLED上以16进制格式显示数值(例如MPU6050的设备ID)。OLED_ShowSignedNum()
: 显示带符号的数字(用于显示加速度和陀螺仪数据)。
4. 分层关系
这个程序可以分为三个主要层次:
底层(硬件抽象层):
- MyIIC通信层:通过I2C协议与MPU6050进行通信,负责读写数据。包括初始化I2C接口、发送和接收字节、控制时钟和数据线。
驱动层:
- MPU6050驱动层:通过I2C控制MPU6050,配置设备并读取加速度和陀螺仪数据。它封装了对MPU6050的操作,并通过寄存器配置和数据读取函数与底层I2C通信进行交互。
应用层:
- 数据获取与显示:从MPU6050获取数据后,应用层负责将这些数据通过OLED显示屏展示出来。这里还包含串口调试功能(例如显示MPU6050的ID)以及对OLED显示的格式化和更新。
5. 底层MyIIC通信
在程序中,底层使用I2C协议与MPU6050进行通信。MyIIC层负责:
- I2C初始化:配置GPIO引脚(SCL和SDA)并启用I2C时钟。
- 数据传输:通过
MyIIC_Start()
,MyIIC_SendByte()
,MyIIC_ReveiveByte()
等函数传输数据。包括发送和接收字节,以及产生启动和停止条件。
6. 驱动层MPU6050
MPU6050驱动层的功能包括:
- 初始化:
MPU6050_Init()
函数通过I2C配置MPU6050的各个寄存器,设置工作模式、采样率、量程等。 - 数据读取:
MPU6050_Struct_Data()
函数通过I2C读取加速度计和陀螺仪的数据,返回一个包含数据的结构体。
7. 应用层
应用层的任务包括:
- 数据获取与格式化:通过
MPU6050_Struct_Data()
函数获取传感器数据,格式化并显示在OLED上。 - OLED显示更新:通过
OLED_ShowString()
,OLED_ShowHexNum()
,OLED_ShowSignedNum()
等函数,实时显示传感器数据。
8. 总结
该程序完成了从MPU6050传感器读取加速度和陀螺仪数据,并通过OLED显示屏实时展示数据。通过I2C总线与MPU6050进行通信,数据被封装到结构体中,简化了后续的数据处理与显示。OLED显示功能增强了交互性,使得传感器的数据更加直观。
9. 经验
- 模块化设计:分层结构(底层、驱动层、应用层)清晰,易于维护和扩展。每个层次只关注自己的任务,避免了代码耦合。
- 硬件抽象:通过I2C协议与MPU6050交互时,底层I2C接口提供了良好的硬件抽象,避免了直接操作硬件寄存器。
- 数据展示:OLED显示功能可以实时显示数据,适合实时嵌入式项目。
10. 待改进
- 错误处理:当前代码没有实现I2C通信失败或MPU6050寄存器访问失败的错误处理。建议增加超时检测和错误恢复机制。
- 数据滤波:目前未对传感器数据进行滤波处理(例如低通滤波或卡尔曼滤波),这对于提高数据的稳定性和准确性非常重要。
- 低功耗设计:如果应用需要低功耗,可以考虑让MPU6050进入低功耗模式或定时获取数据。
- 更高的刷新率:考虑使用定时器中断来代替
Delay_ms()
,从而获得更高的刷新率。
11. 本程序主要用到的实现方法
- I2C通信:使用I2C协议与MPU6050传感器通信,进行数据读取和配置。
- OLED显示:使用OLED显示屏实时展示传感器数据。
- 数据格式化:将传感器的原始数据通过位运算合并高低字节,然后通过OLED显示。
- 模块化设计:程序的硬件操作、传感器交互和数据展示层次清晰,便于扩展和维护。
1. I2C通信函数(MyIIC.c)
函数 | 作用 | 调用关系 | 层级 |
---|---|---|---|
MyIIC_SCL_W(uint8_t Bit) |
控制SCL信号线的高低电平 | 被 MyIIC_Start() , MyIIC_SendByte() 等调用 |
I2C操作层 |
MyIIC_SDA_W(uint8_t Bit) |
控制SDA信号线的高低电平 | 被 MyIIC_Start() , MyIIC_SendByte() 等调用 |
I2C操作层 |
MyIIC_SDA_R(void) |
读取SDA信号线的电平 | 被 MyIIC_ReveiveByte() 调用 |
I2C操作层 |
MyIIC_Init(void) |
初始化I2C通信的GPIO引脚 | 直接调用 | I2C初始化层 |
MyIIC_Start(void) |
产生I2C总线的起始条件(SDA和SCL拉低) | 被 MPU6050_Register_W() , MPU6050_Regiter_R() 等调用 |
I2C操作层 |
MyIIC_Stop(void) |
产生I2C总线的停止条件(SDA和SCL拉高) | 被 MPU6050_Register_W() , MPU6050_Regiter_R() 等调用 |
I2C操作层 |
MyIIC_SendByte(uint8_t Byte) |
向I2C总线发送一个字节的数据(通过SDA) | 被 MPU6050_Register_W() 等调用 |
I2C操作层 |
MyIIC_ReveiveByte(void) |
从I2C总线接收一个字节的数据(通过SDA) | 被 MPU6050_Regiter_R() 等调用 |
I2C操作层 |
MyIIC_SendAck(uint8_t AckBit) |
发送I2C的ACK(确认)或NACK(非确认)信号 | 被 MPU6050_Register_W() 调用 |
I2C操作层 |
MyIIC_ReceiveAck(void) |
接收I2C的ACK或NACK信号 | 被 MPU6050_Regiter_R() 调用 |
I2C操作层 |
3. 主程序(main.c)
函数 | 作用 | 调用关系 | 层级 |
---|---|---|---|
main(void) |
主程序,初始化OLED显示、串口通信、MPU6050并循环读取传感器数据 | 调用 OLED_Init() , Serial_Init() , MPU6050_Init() , MPU6050_Struct_Data() , OLED_ShowString() , OLED_ShowHexNum() , OLED_ShowSignedNum() |
应用层 |
4. OLED显示函数(OLED.c)
函数 | 作用 | 调用关系 | 层级 |
---|---|---|---|
OLED_Init(void) |
初始化OLED显示模块 | 直接调用 | OLED初始化层 |
OLED_ShowString(uint8_t x, uint8_t y, char* str) |
在OLED上显示字符串 | 被 main() 调用 |
OLED显示层 |
OLED_ShowHexNum(uint8_t x, uint8_t y, uint8_t num, uint8_t len) |
在OLED上显示16进制数字 | 被 main() 调用 |
OLED显示层 |
OLED_ShowSignedNum(uint8_t x, uint8_t y, int16_t num, uint8_t len) |
在OLED上显示带符号的数字 | 被 main() 调用 |
OLED显示层 |
5. 串口通信函数(Serial.c)
函数 | 作用 | 调用关系 | 层级 |
---|---|---|---|
Serial_Init(void) |
初始化串口通信 | 被 main() 调用 |
串口初始化层 |
Serial_SendByte(uint8_t Byte) |
发送一个字节的数据到串口 | 直接调用 | 串口通信层 |
嵌套和分层关系分析
1. MyIIC函数嵌套关系
MyIIC_Init()
是I2C通信初始化的起点,初始化GPIO引脚。MyIIC_Start()
和MyIIC_Stop()
控制I2C总线的开始和停止。MyIIC_SendByte()
和MyIIC_ReveiveByte()
用于数据的发送和接收。MyIIC_SCL_W()
和MyIIC_SDA_W()
用于控制SCL和SDA线。
2. MPU6050函数嵌套关系
MPU6050_Init()
调用MPU6050_Register_W()
配置传感器。MPU6050_Struct_Data()
调用MPU6050_Regiter_R()
读取传感器数据。MPU6050_Register_W()
和MPU6050_Regiter_R()
调用 MyIIC 函数进行具体的I2C通信。
3. 主程序
- 主程序 (
main()
) 调用各种I2C和OLED显示函数,实时获取传感器数据并通过OLED显示。
4. 分层结构
- I2C层:实现了底层的I2C总线操作。
- MPU6050传感器层:负责与MPU6050传感器进行通信和数据交互。
- 应用层:负责业务逻辑和外设控制,如显示传感器数据等。
总结
这些函数的分层结构清晰,I2C通信层通过底层的 MyIIC
函数实现了与MPU6050的通信,MPU6050数据读取层通过I2C协议与传感器交互并返回数据,最后应用层处理这些数据并通过OLED显示出来。
STM32与MPU6050的通信及I2C协议利用总结
1. I2C协议概述
I2C(Inter-Integrated Circuit)是一种常用的串行总线协议,广泛应用于低速外设的通信。I2C协议具有以下特点:
- 主从模式:一条总线上可以有多个设备,主设备(STM32)发起通信,多个从设备(如MPU6050)响应。
- 双线通信:使用两根线进行通信,SCL(时钟线)和SDA(数据线)。
- 地址通信:每个从设备通过唯一的地址来进行识别。
STM32作为主设备,通过I2C协议与MPU6050传感器进行数据交换。STM32的I2C接口通过适当的配置和操作,读取或写入MPU6050的寄存器,控制传感器并获取其加速度和陀螺仪数据。
2. STM32与MPU6050通信流程
- I2C初始化:通过配置STM32的GPIO和I2C硬件接口,使其能够支持I2C通信。此过程包括:
- 启用I2C的时钟。
- 配置I2C的SCL和SDA引脚为开漏输出模式。
- 配置I2C的通信速率、从设备地址等。
- 通信操作:主要包括以下几个步骤:
- 启动信号(START):发送起始信号,表明I2C通信开始。
- 发送设备地址:STM32向MPU6050发送其I2C地址。
- 发送寄存器地址:指定操作的寄存器地址。
- 读/写数据:根据需要,STM32可以向MPU6050写入数据,或从其寄存器读取数据。
- 应答信号(ACK/NACK):每次数据传输后,设备会发送应答信号,主设备(STM32)根据从设备(MPU6050)的响应继续或结束操作。
- 停止信号(STOP):结束I2C通信。
3. STM32 I2C通信的函数封装
STM32与MPU6050的通信主要涉及到I2C的基本操作函数。以下是一些关键的通信函数封装示例:
函数名 | 作用 | 函数功能 |
---|---|---|
MyIIC_Init() |
初始化I2C通信,配置GPIO引脚和I2C硬件接口 | 配置I2C的时钟线和数据线的GPIO引脚,设置I2C的通信速率与地址 |
MyIIC_Start() |
生成I2C总线的起始条件(START信号) | 设置SDA、SCL信号为适当的高低电平,发起I2C通信 |
MyIIC_Stop() |
生成I2C总线的停止条件(STOP信号) | 设置SDA、SCL信号为适当的高低电平,结束I2C通信 |
MyIIC_SDA_W() |
控制SDA信号的输出(0或1) | 向SDA线写入低电平或高电平数据 |
MyIIC_SCL_W() |
控制SCL信号的输出(0或1) | 向SCL线写入低电平或高电平数据 |
MyIIC_SDA_R() |
读取SDA信号的输入(0或1) | 读取SDA线的电平状态,作为接收数据的输入 |
MyIIC_SendByte() |
向I2C总线发送一个字节的数据 | 将一个字节按位写入SDA线,并通过SCL生成时钟周期发送该字节 |
MyIIC_ReveiveByte() |
从I2C总线接收一个字节的数据 | 读取SDA线上的数据,按位接收并返回字节数据 |
MyIIC_SendAck() |
向I2C总线发送ACK(确认)或NACK(非确认)信号 | 向SDA线发送ACK或NACK,表示数据传输成功或失败 |
MyIIC_ReceiveAck() |
从I2C总线接收ACK(确认)或NACK(非确认)信号 | 读取SDA线的状态,返回ACK或NACK值,表示是否接收到有效数据 |
4. MPU6050的寄存器操作
MPU6050内部具有多个寄存器,用于配置和读取加速度计、陀螺仪以及其他传感器的状态信息。常见的寄存器操作包括:
- 写寄存器操作:通过I2C将数据写入MPU6050的寄存器,如设置电源管理、采样率、加速度计量程等。
- 读寄存器操作:通过I2C读取MPU6050寄存器的数据,获取传感器的实时加速度和陀螺仪数据。
关键寄存器:
MPU_PWR_MGMT1_REG
:电源管理寄存器,用于控制MPU6050的工作模式。MPU_SAMPLE_RATE_REG
:采样率寄存器,控制数据的采样频率。MPU_ACCEL_CFG_REG
:加速度计配置寄存器,设置加速度计的量程。MPU_GYRO_CFG_REG
:陀螺仪配置寄存器,设置陀螺仪的量程。
5. MPU6050驱动封装
MPU6050的驱动程序主要通过寄存器操作来完成传感器初始化、数据读取等任务。具体的封装步骤如下:
- 初始化:通过
MPU6050_Init()
函数初始化MPU6050传感器,设置工作模式、采样率、传感器量程等参数。 - 寄存器写操作:通过
MPU6050_Register_W()
函数,向指定的寄存器地址写入配置数据。 - 寄存器读操作:通过
MPU6050_Regiter_R()
函数,读取MPU6050的寄存器数据,例如获取加速度计和陀螺仪的测量值。 - 数据读取:通过
MPU6050_RegState_Data()
或MPU6050_Struct_Data()
获取传感器的加速度和陀螺仪数据,并返回给主控制系统。
6. STM32与MPU6050通信的关键点
- 时序要求:I2C通信要求主设备和从设备之间按照时序规定正确发送和接收数据。
- 数据完整性:确保每次读取数据后,都进行ACK/NACK响应确认,以确保数据传输的可靠性。
- 中断与延时:I2C通信过程中可能需要适当的延时来确保通信稳定,尤其在传感器数据读取时,适当的延时有助于确保数据的准确性。
总结
STM32与MPU6050的通信主要通过I2C协议进行。STM32作为I2C主设备,通过相应的I2C初始化、启动、停止、数据发送与接收等操作,控制MPU6050的配置和数据获取。MPU6050的驱动程序封装则包括初始化、配置寄存器以及获取传感器数据的函数。通过这些封装与优化,可以方便地获取加速度计和陀螺仪数据,为后续应用提供传感器数据支持。