STM32单片机+MPU6050设计一个电子水平仪 原创

DS小龙哥
发布于 2025-2-8 09:51
1.8w浏览
0收藏

一、前言

1.1 项目开发背景

在现代电子技术的快速发展中,各种智能设备的精确度和便捷性不断提升,电子水平仪作为一种常见的测量工具,广泛应用于建筑、家居安装、机械制造等领域。传统的水平仪通常依赖机械原理,如气泡管或机械刻度盘,虽然简单直观,但在一些精度要求较高的场合,传统工具往往难以满足需求。因此,电子水平仪应运而生,凭借其数字化显示和高精度测量,逐渐取代了传统水平仪,成为更加智能化的测量工具。

本项目基于STM32F103C8T6单片机设计一款电子水平仪。STM32F103C8T6作为一种性能强大的微控制器,具有较高的计算能力和丰富的外设接口,能够满足项目对实时数据处理和显示控制的需求。该单片机将与MPU6050传感器相结合,利用其内置的加速度计和陀螺仪,实现设备的倾斜角度测量。MPU6050传感器能够高精度地检测设备在三个轴向上的加速度信息,从而计算出设备在左右方向(X轴)和前后方向(Y轴)的倾斜角度。

为了更直观地展示测量结果,本项目选用了一款高分辨率的OLED显示屏。该显示屏可以清晰地呈现实时的角度数据,并且提供了足够的分辨率来实现可视化的图形展示。通过中心点圆圈和滚动小球的设计,用户可以轻松识别设备当前的水平状态。这种可视化的反馈方式不仅更加直观,而且提升了用户体验,特别适用于需要频繁调整水平状态的场景。

随着智能化技术的普及,越来越多的传统工具开始向数字化、智能化方向发展。电子水平仪作为数字工具的一种,其精度、稳定性和用户友好性成为了设计的核心目标。项目通过结合MPU6050传感器的高精度检测与STM32F103C8T6单片机的强大处理能力,提供了一种新型的、可视化的电子水平仪设计方案。这种方案不仅满足了基础的角度测量功能,还通过创新的显示方式,使得用户能够更加高效和精准地进行水平调整和测量。

STM32单片机+MPU6050设计一个电子水平仪-鸿蒙开发者社区

核心的部件:

STM32单片机+MPU6050设计一个电子水平仪-鸿蒙开发者社区

1.2 设计实现的功能

基于STM32设计一款电子水平仪

功能要求:
1. 本地一个SPI协议接口的OLED显示屏,展示当前 设备左右方向的倾斜角度,设备前后方向的倾斜角度。
2. 能够通过一个中心点圆圈+一个滚动的小球展示可视化展示当前的水平状态。
3. 支持按键校准,校准水平仪的参数
硬件要求:
1. 单片机采用STM32F103C8T6
2. 水平状态监测采用MPU6050
3. 显示屏采用高分辨率的OLED显示屏
4. 供电采用可充电锂电池供电,方便便携式携带
5. 一个按钮,进行复位参数校准。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

(1) 倾斜角度测量

通过 MPU6050 传感器,实时测量设备在 X 轴(左右方向)和 Y 轴(前后方向)的倾斜角度。系统能够精准地计算设备相对于水平的角度,并将其显示在 OLED 屏幕上,提供清晰的数值反馈,帮助用户了解设备当前的水平状态。

(2) 实时水平状态可视化显示

在 OLED 显示屏上,采用图形化方式展示设备的水平状态。通过中心圆圈和滚动的小球,用户可以直观地看到设备的水平偏差。小球会根据设备的倾斜方向和角度实时滚动,偏离水平时会向对应方向移动,水平时则停留在圆圈中心,增强用户体验。

(3) 数据滤波与角度计算

在数据处理方面,设计了合适的滤波算法,如卡尔曼滤波或简单平均滤波,用于去除传感器数据中的噪声和提高计算的准确性。通过这些算法,系统能够更加稳定和精准地输出设备的倾斜角度。

(4) 校准功能

系统提供校准功能,用户可以通过按下按钮来重置设备的零点,校准设备的水平状态。当按下按钮时,设备的当前角度被重置为零,确保在每次使用时都能从准确的水平状态开始。此功能特别适用于设备多次使用后的精度校正,保证每次测量的精度和可靠性。

(5) OLED 显示角度数值

在 OLED 屏幕上,实时显示设备的具体倾斜角度,分别显示 X 轴(左右方向)和 Y 轴(前后方向)的倾斜角度数值。通过直观的数字显示,用户可以准确了解当前设备的水平状态,并在需要时进行调整。

(6) 便携性与充电功能

该电子水平仪设计为便携式设备,配备了可充电锂电池,用户可以在不依赖外部电源的情况下,长时间使用设备。通过电池管理模块,支持 USB 充电接口,方便充电并延长使用时间。电池的可充电特性使得设备在户外、工地等环境下更加便捷。

(7) 低功耗设计

为了提高设备的使用时长,设计中采用了低功耗模式。STM32F103C8T6 单片机与 MPU6050 传感器均支持低功耗操作,系统在不进行操作时可以进入低功耗待机状态,减少能耗,延长电池使用时间。

(8) 用户友好界面

通过简洁直观的 OLED 显示屏界面,用户可以轻松获取设备的实时倾斜角度和水平状态。显示内容清晰、易于阅读,设计了视觉化的反馈方式,使得设备使用者能够快速理解设备的水平偏差并进行相应调整。

1.3 项目硬件模块组成

(1) STM32F103C8T6 单片机

STM32F103C8T6 是本项目的核心控制单元,负责数据处理、控制显示屏和执行用户输入的操作。该单片机具备多种外设接口,如 I2C 和 GPIO,能够与 MPU6050 传感器和 OLED 显示屏进行通信。此外,它具有较强的运算能力,能够进行角度计算、校准功能以及实时显示更新。

(2) MPU6050 传感器

MPU6050 是集成了 3 轴加速度计和 3 轴陀螺仪的传感器,用于检测设备在 X 轴(左右方向)和 Y 轴(前后方向)的加速度,进而计算出设备的倾斜角度。通过 I2C 协议将数据传输给 STM32F103C8T6 单片机,用于后续角度计算和显示。

(3) OLED 显示屏

OLED 显示屏用于显示设备的倾斜角度和水平状态。高分辨率的 OLED 显示屏能清晰地呈现数值角度和可视化图形(如中心圆圈和滚动小球)。通过 I2C 连接 STM32F103C8T6 单片机,实时显示 X、Y 轴的倾斜角度以及水平状态的图形反馈。

(4) 可充电锂电池

可充电锂电池提供电子水平仪系统所需的电力,支持便携式使用。它通常提供 3.7V 的电压,满足 STM32F103C8T6、MPU6050 传感器和 OLED 显示屏的工作需求。电池可以通过充电电路进行充电,以便用户在外出时使用。

(5) 电池充电管理模块

为了方便充电,配备电池充电管理模块(TP4056)用于给可充电锂电池充电。该模块能够通过 Micro-USB 或其他充电接口连接电源,并在充电时提供电池保护,防止过充或过放。

(6) 按键模块

一个按键用于用户与电子水平仪的交互,具体功能包括校准或复位。按键可以触发 STM32F103C8T6 的GPIO引脚,在长按时启动水平仪的参数校准功能,重新设置设备的零点,确保在不同使用环境下仍能获得准确的测量结果。

1.4 设计思路

本项目的设计思路主要是通过结合硬件和软件的紧密配合,开发一款精准且便于携带的电子水平仪。该电子水平仪通过 STM32F103C8T6 单片机作为核心处理单元,利用 MPU6050 传感器获取设备的加速度数据,实时计算设备的倾斜角度,并通过高分辨率的 OLED 显示屏直观地呈现出来。设计的关键在于如何准确捕捉设备的水平状态并通过用户友好的方式展示出来,同时确保系统的便携性和易操作性。

系统的核心是 STM32F103C8T6 单片机,作为高性能的微控制器,它具有多种外设接口和足够的运算能力,能够进行实时数据处理。通过 I2C 协议,单片机与 MPU6050 传感器进行通信,获取加速度计和陀螺仪数据。这些数据将用于计算设备的倾斜角度,并进一步进行校准和调整,确保在任何情况下都能提供精准的水平测量。

在传感器数据处理方面,设计的关键是如何精确地计算设备的角度。MPU6050 传感器提供了 X、Y 和 Z 轴的加速度数据,我们将使用这些数据计算设备相对于水平面(X轴和Y轴的倾斜角度)的变化。为了减少噪声和提高测量精度,设计时引入了数据滤波算法,以保证角度计算的稳定性和准确性。

OLED 显示屏作为用户与设备交互的界面,提供了丰富的显示功能。设计中通过实时显示当前设备的左右方向(X轴)和前后方向(Y轴)的倾斜角度,用户可以清晰地了解设备的水平状态。此外,为了增加可视化的体验,我们设计了一个中心点圆圈,并通过小球的滚动来展示设备的倾斜程度。当设备完全水平时,小球将停留在中心圆圈内,偏离水平时,小球会向对应的方向移动。这样直观的显示方式让用户能在瞬间理解设备的水平情况。

为了确保设备能够适应不同的环境和使用场景,本设计还加入了校准功能。通过设置一个按键,用户可以在需要时对设备进行参数复位和重新校准。当按下按钮时,STM32 单片机会清除当前的角度偏差,并重置为零点,确保在多次使用过程中系统依然保持高精度。

便携性是本项目的另一个重要设计方向。采用可充电锂电池为电源,配备电池管理模块,以支持系统在无外部电源的情况下长时间工作。电池的充电功能使得用户可以在野外或移动环境中随时使用,并且通过 USB 充电接口方便充电,解决了传统水平仪无法持续工作的问题。

通过这些硬件和软件的结合,整个电子水平仪系统实现了精确、直观和便捷的功能。系统设计不仅具备高精度的水平测量功能,还通过简洁的用户界面和便携设计使得用户能够在各种环境下使用,提供了一个全新的测量工具,取代了传统的机械水平仪。

1.5 系统功能总结

功能编号 功能描述 实现方式
(1) 倾斜角度测量 通过 MPU6050 传感器测量 X 轴和 Y 轴的倾斜角度,并进行计算显示。
(2) 实时水平状态可视化显示 在 OLED 屏幕上展示中心点圆圈及滚动小球,直观展示设备的水平状态。
(3) 数据滤波与角度计算 使用卡尔曼滤波或简单平均滤波去除噪声,计算设备的精确倾斜角度。
(4) 校准功能 通过按键触发校准,重置设备角度为零,确保每次使用时从准确零点开始。
(5) OLED 显示角度数值 在 OLED 屏幕上实时显示设备的 X 轴和 Y 轴倾斜角度的数值。
(6) 便携性与充电功能 配备可充电锂电池,支持 USB 充电,适合便携式使用,解决外部电源依赖问题。
(7) 低功耗设计 系统支持低功耗模式,确保设备在待机时减少能耗,延长使用时间。
(8) 用户友好界面 OLED 显示屏提供清晰直观的角度数值和图形化的反馈,简化操作和交互。

1.6 开发工具的选择

开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。

STM32单片机+MPU6050设计一个电子水平仪-鸿蒙开发者社区

1.7 MPU6050模块介绍

MPU6050 是一款由 Invensense 公司生产的六轴传感器模块,它结合了三轴加速度计和三轴陀螺仪,广泛应用于运动追踪、姿态检测、电子稳定、虚拟现实等领域。MPU6050 采用 MEMS(微机电系统)技术,能够提供高精度的角度和加速度数据,适用于需要实时动态感知的应用。

该传感器模块内置了一个三轴加速度计和一个三轴陀螺仪。三轴加速度计能够测量物体在 X、Y 和 Z 轴上的加速度变化,单位为“g”(地球重力加速度),可以感知物体的静态或动态加速度变化。三轴陀螺仪则用于测量物体在三个轴上的角速度,单位通常为“度/秒”(°/s),可提供物体旋转的速率信息。通过这些数据,MPU6050 可以检测物体的运动、旋转以及倾斜角度,并为其他系统提供准确的传感信息。

MPU6050 的最大特点之一是它内置了一个数字运动处理单元(DMP,Digital Motion Processor),它能够对加速度计和陀螺仪的数据进行实时处理,从而减轻主处理器的计算负担。DMP 还可以进行传感器数据的滤波、姿态估算和融合处理,这使得 MPU6050 在传感器输出的稳定性和准确性上具有显著优势。此外,DMP 支持直接输出已经处理和融合的运动数据,减少了主控单元的数据处理量,从而提升了整体系统的性能。

MPU6050 模块支持 I2C 通信协议,具有两线串行总线接口,便于与主控单元(如 STM32F103C8T6、Arduino 等)进行数据传输。其通信速率最高可达 400 kHz,因此可以在需要高频率数据采集的应用中提供实时数据反馈。通过该接口,开发者能够读取加速度计和陀螺仪的数据,还能够配置模块的工作模式、灵敏度等参数,以满足特定应用需求。

在实际应用中,MPU6050 具有较高的灵敏度和较低的功耗,适合用于移动设备、机器人、无人机、车载系统等对体积、功耗有要求的场景。其内置的低通滤波器和数字处理单元,使得它能够有效地抑制噪声,提高测量数据的稳定性,尤其在动态环境下,能够准确地感知物体的运动状态和姿态变化。

此外,MPU6050 还具有一些自检功能,可以通过内部自检机制判断传感器是否正常工作。模块中还有内置温度传感器,用于补偿温度对加速度计和陀螺仪的影响,确保测量精度不会受到环境温度变化的影响。

MPU6050 以其小巧的体积、高精度的传感能力、内置数字运动处理单元(DMP)以及低功耗特点,成为许多运动监测和姿态检测系统中不可或缺的核心传感器。它不仅能够提供高质量的数据,还能减少对主控制单元的计算压力,使得整体系统更加高效、稳定。

二、代码设计

模块代码可以去网盘下载学习:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink

2.1 工作原理

在电子水平仪中,MPU6050 传感器的主要作用是通过测量加速度和角速度来计算设备的倾斜角度。它集成了三轴加速度计和三轴陀螺仪,这两者协同工作,实现对设备在各个方向上的姿态和运动的监测。

MPU6050 在水平仪中的工作原理如下:

MPU6050 内部的三轴加速度计可以测量设备在三个方向(X、Y、Z轴)上的加速度。加速度计的基本工作原理是根据物体受到的力(如重力)产生的加速度来推算设备的运动状态。在一个理想的水平状态下,重力仅作用于Z轴(即垂直方向)。因此,设备的倾斜会导致加速度的分布发生变化,从而改变X轴和Y轴的加速度值。

当设备倾斜时,重力的作用方向将不再完全指向 Z 轴,而是分布到 X 和 Y 轴上,产生一定的加速度。这些加速度值可以用来计算设备的倾斜角度。通过对 X 和 Y 轴加速度值的计算,可以推算出设备的俯仰角度(pitch)和滚转角度(roll)。例如,通过以下公式,可以计算出设备的俯仰角(pitch)和滚转角(roll):

  • 俯仰角(Pitch):

    \[ \text{pitch} = \arctan\left(\frac{accelX}{\sqrt{accelY^2 + accelZ^2}}\right) \]

  • 滚转角(Roll):

    \[ \text{roll} = \arctan\left(\frac{-accelY}{\sqrt{accelX^2 + accelZ^2}}\right) \]

通过这些计算,可以获取设备的实时倾斜角度。

陀螺仪工作原理

MPU6050 内部的三轴陀螺仪可以测量设备的旋转速率,也就是角速度。陀螺仪通过检测设备在各个轴上的旋转速率(单位:度/秒或 rad/s)来捕捉设备的动态变化。与加速度计不同,陀螺仪主要检测旋转的角速度,能反映设备在实时运动中的变化。

在水平仪中,陀螺仪的工作原理通常用于跟踪设备的快速变化。例如,当设备发生快速旋转或倾斜时,陀螺仪的角速度数据可以帮助补充加速度计测量的数据,增强系统对动态变化的响应能力。特别是在静态情况下,加速度计能够提供较为稳定的角度测量,而在快速运动或旋转时,陀螺仪可以提供更快的反应数据。

数据融合与姿态计算

虽然加速度计和陀螺仪都能提供关于设备倾斜的不同信息,但它们各自存在局限性。加速度计受到重力和运动加速度的影响,可能会产生一定的噪声;而陀螺仪在长时间使用中可能会受到漂移的影响。因此,在水平仪中,通常会采用传感器融合算法,如互补滤波卡尔曼滤波,来结合加速度计和陀螺仪的数据,从而提高系统的精度和稳定性。

  • 互补滤波: 这种方法结合了加速度计和陀螺仪的优势,通过加速度计提供的稳定低频信息和陀螺仪提供的高频旋转信息,实现一个平衡的结果。
  • 卡尔曼滤波:卡尔曼滤波是一种更复杂的算法,它能够基于信号的不确定性,计算出加速度计和陀螺仪数据的最优融合,提供更为精确和稳定的角度计算。

在水平仪中,结合加速度计和陀螺仪的数据后,可以获得非常精确的设备俯仰和滚转角度,从而在 OLED 屏幕上显示出设备的水平状态,直观地向用户反馈设备是否处于水平状态。

工作流程总结

  1. 加速度计:用于检测设备在 X、Y 和 Z 轴上的加速度,并通过计算设备的俯仰角和滚转角来推算其在水平面上的倾斜角度。
  2. 陀螺仪:用于检测设备在各个轴上的旋转速率,帮助系统在快速运动和变化时实时更新角度信息。
  3. 数据融合:结合加速度计和陀螺仪的输出数据,通过传感器融合算法(如互补滤波或卡尔曼滤波),获取更为准确的设备角度数据。
  4. 角度计算:根据加速度计和陀螺仪数据计算出设备的俯仰角和滚转角,从而实时监控和显示设备的倾斜状态,向用户反馈设备是否处于水平状态。

2.2 MPU6050的驱动代码

#include "mpu6050.h"
#include "sys.h"
#include "delay.h"
#include <stdio.h>

/*--------------------------------------------------------------------IIC协议底层模拟时序--------------------------------------------------------------------------------*/

/*
函数功能:MPU IIC 延时函数
*/
void MPU6050_IIC_Delay(void)
{
	DelayUs(2);
}

/*
函数功能: 初始化IIC
*/
void MPU6050_IIC_Init(void)
{					     
 	RCC->APB2ENR|=1<<3;		//先使能外设IO PORTB时钟 							 
	GPIOB->CRL&=0X00FFFFFF;	//PB6/7 推挽输出
	GPIOB->CRL|=0X33000000;	   
	GPIOB->ODR|=3<<6;     	//PB6,7 输出高
}

/*
函数功能: 产生IIC起始信号
*/
void MPU6050_IIC_Start(void)
{
	MPU6050_SDA_OUT();     //sda线输出
	MPU6050_IIC_SDA=1;	  	  
	MPU6050_IIC_SCL=1;
	MPU6050_IIC_Delay();
 	MPU6050_IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}

/*
函数功能: 产生IIC停止信号
*/
void MPU6050_IIC_Stop(void)
{
	MPU6050_SDA_OUT();//sda线输出
	MPU6050_IIC_SCL=0;
	MPU6050_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
 	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=1; 
	MPU6050_IIC_SDA=1;//发送I2C总线结束信号
	MPU6050_IIC_Delay();							   	
}

/*
函数功能: 等待应答信号到来
返 回 值:1,接收应答失败
        0,接收应答成功
*/
u8 MPU6050_IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	MPU6050_SDA_IN();      //SDA设置为输入  
	MPU6050_IIC_SDA=1;MPU6050_IIC_Delay();	   
	MPU6050_IIC_SCL=1;MPU6050_IIC_Delay();	 
	while(MPU6050_READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			MPU6050_IIC_Stop();
			return 1;
		}
	}
	MPU6050_IIC_SCL=0;//时钟输出0 	   
	return 0;  
}

/*
函数功能:产生ACK应答
*/
void MPU6050_IIC_Ack(void)
{
	MPU6050_IIC_SCL=0;
	MPU6050_SDA_OUT();
	MPU6050_IIC_SDA=0;
	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=1;
	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=0;
}

/*
函数功能:不产生ACK应答	
*/	    
void MPU6050_IIC_NAck(void)
{
	MPU6050_IIC_SCL=0;
	MPU6050_SDA_OUT();
	MPU6050_IIC_SDA=1;
	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=1;
	MPU6050_IIC_Delay();
	MPU6050_IIC_SCL=0;
}

/*
函数功能:IIC发送一个字节
返回从机有无应答
1,有应答
0,无应答		
*/		  
void MPU6050_IIC_Send_Byte(u8 txd)
{                        
		u8 t;   
		MPU6050_SDA_OUT(); 	    
		MPU6050_IIC_SCL=0;//拉低时钟开始数据传输
		for(t=0;t<8;t++)
		{              
				MPU6050_IIC_SDA=(txd&0x80)>>7;
				txd<<=1; 	  
				MPU6050_IIC_SCL=1;
				MPU6050_IIC_Delay(); 
				MPU6050_IIC_SCL=0;	
				MPU6050_IIC_Delay();
		}	 
}

/*
函数功能:读1个字节,ack=1时,发送ACK,ack=0,发送nACK 
*/	
u8 MPU6050_IIC_Read_Byte(unsigned char ack)
{
		unsigned char i,receive=0;
		MPU6050_SDA_IN();//SDA设置为输入
		for(i=0;i<8;i++ )
		{
				MPU6050_IIC_SCL=0; 
				MPU6050_IIC_Delay();
				MPU6050_IIC_SCL=1;
				receive<<=1;
				if(MPU6050_READ_SDA)receive++;   
				MPU6050_IIC_Delay(); 
		}					 
		if(!ack)
				MPU6050_IIC_NAck();//发送nACK
		else
				MPU6050_IIC_Ack(); //发送ACK   
		return receive;
}


/*--------------------------------------------------------------------MPU6050底层驱动代码--------------------------------------------------------------------------------*/

/*
函数功能:初始化MPU6050
返 回 值:0,成功
		其他,错误代码
*/
u8 MPU6050_Init(void)
{ 
	u8 res;
	MPU6050_IIC_Init();//初始化IIC总线
	MPU6050_Write_Byte(MPU_PWR_MGMT1_REG,0X80);	//复位MPU6050
  
	DelayMs(100);
	MPU6050_Write_Byte(MPU_PWR_MGMT1_REG,0X00);	//唤醒MPU6050 
	MPU6050_Set_Gyro_Fsr(3);					//陀螺仪传感器,±2000dps
	MPU6050_Set_Accel_Fsr(0);					//加速度传感器,±2g
	MPU6050_Set_Rate(50);						  //设置采样率50Hz
	MPU6050_Write_Byte(MPU_INT_EN_REG,0X00);	//关闭所有中断
	MPU6050_Write_Byte(MPU_USER_CTRL_REG,0X00);	//I2C主模式关闭
	MPU6050_Write_Byte(MPU_FIFO_EN_REG,0X00);	//关闭FIFO
	MPU6050_Write_Byte(MPU_INTBP_CFG_REG,0X80);	//INT引脚低电平有效
	res=MPU6050_Read_Byte(MPU_DEVICE_ID_REG);
	if(res==MPU6050_ADDR)//器件ID正确
	{
		MPU6050_Write_Byte(MPU_PWR_MGMT1_REG,0X01);	//设置CLKSEL,PLL X轴为参考
		MPU6050_Write_Byte(MPU_PWR_MGMT2_REG,0X00);	//加速度与陀螺仪都工作
		MPU6050_Set_Rate(50);						//设置采样率为50Hz
 	}else return 1;
	return 0;
}


/*
设置MPU6050陀螺仪传感器满量程范围
fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps
返回值:0,设置成功
    其他,设置失败 
*/
u8 MPU6050_Set_Gyro_Fsr(u8 fsr)
{
	return MPU6050_Write_Byte(MPU_GYRO_CFG_REG,fsr<<3);//设置陀螺仪满量程范围  
}

/*
函数功能:设置MPU6050加速度传感器满量程范围
函数功能:fsr:0,±2g;1,±4g;2,±8g;3,±16g
返 回 值:0,设置成功
    其他,设置失败 
*/
u8 MPU6050_Set_Accel_Fsr(u8 fsr)
{
	return MPU6050_Write_Byte(MPU_ACCEL_CFG_REG,fsr<<3);//设置加速度传感器满量程范围  
}


/*
函数功能:设置MPU6050的数字低通滤波器
函数参数:lpf:数字低通滤波频率(Hz)
返 回 值:0,设置成功
          其他,设置失败 
*/
u8 MPU6050_Set_LPF(u16 lpf)
{
	u8 data=0;
	if(lpf>=188)data=1;
	else if(lpf>=98)data=2;
	else if(lpf>=42)data=3;
	else if(lpf>=20)data=4;
	else if(lpf>=10)data=5;
	else data=6; 
	return MPU6050_Write_Byte(MPU_CFG_REG,data);//设置数字低通滤波器  
}

/*
函数功能:设置MPU6050的采样率(假定Fs=1KHz)
函数参数:rate:4~1000(Hz)
返 回 值:0,设置成功
          其他,设置失败 
*/
u8 MPU6050_Set_Rate(u16 rate)
{
	u8 data;
	if(rate>1000)rate=1000;
	if(rate<4)rate=4;
	data=1000/rate-1;
	data=MPU6050_Write_Byte(MPU_SAMPLE_RATE_REG,data);	//设置数字低通滤波器
 	return MPU6050_Set_LPF(rate/2);	//自动设置LPF为采样率的一半
}

/*
函数功能:得到温度值
返 回 值:返回值:温度值(扩大了100倍)
*/
short MPU6050_Get_Temperature(void)
{
    u8 buf[2]; 
    short raw;
	float temp;
	MPU6050_Read_Len(MPU6050_ADDR,MPU_TEMP_OUTH_REG,2,buf); 
    raw=((u16)buf[0]<<8)|buf[1];  
    temp=36.53+((double)raw)/340;  
    return temp*100;;
}


/*
函数功能:得到陀螺仪值(原始值)
函数参数:gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
返 回 值:0,成功,其他,错误代码
*/
u8 MPU6050_Get_Gyroscope(short *gx,short *gy,short *gz)
{
  u8 buf[6],res;  
	res=MPU6050_Read_Len(MPU6050_ADDR,MPU_GYRO_XOUTH_REG,6,buf);
	if(res==0)
	{
		*gx=((u16)buf[0]<<8)|buf[1];  
		*gy=((u16)buf[2]<<8)|buf[3];  
		*gz=((u16)buf[4]<<8)|buf[5];
	} 	
    return res;;
}

/*
函数功能:得到加速度值(原始值)
函数参数:gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
返 回 值:0,成功,其他,错误代码
*/
u8 MPU6050_Get_Accelerometer(short *ax,short *ay,short *az)
{
  u8 buf[6],res;  
	res=MPU6050_Read_Len(MPU6050_ADDR,MPU_ACCEL_XOUTH_REG,6,buf);
	if(res==0)
	{
		*ax=((u16)buf[0]<<8)|buf[1];  
		*ay=((u16)buf[2]<<8)|buf[3];  
		*az=((u16)buf[4]<<8)|buf[5];
	} 	
    return res;;
}

/*
函数功能:IIC连续写
函数参数:
				addr:器件地址 
				reg:寄存器地址
				len:写入长度
				buf:数据区
返 回 值:0,成功,其他,错误代码
*/
u8 MPU6050_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
		u8 i; 
		MPU6050_IIC_Start(); 
		MPU6050_IIC_Send_Byte((addr<<1)|0);//发送器件地址+写命令	
		if(MPU6050_IIC_Wait_Ack())	//等待应答
		{
			MPU6050_IIC_Stop();	
				printf("等待应答失败\r\n");			
			return 1;		
		}
		MPU6050_IIC_Send_Byte(reg);	//写寄存器地址
		MPU6050_IIC_Wait_Ack();		//等待应答
		for(i=0;i<len;i++)
		{
			MPU6050_IIC_Send_Byte(buf[i]);	//发送数据
			if(MPU6050_IIC_Wait_Ack())		//等待ACK
			{
				MPU6050_IIC_Stop();
				printf("等待ACK失败\r\n");
				return 1;		 
			}		
		}    
		MPU6050_IIC_Stop();	 
		return 0;	
}

/*
函数功能:IIC连续写
函数参数:
				IIC连续读
				addr:器件地址
				reg:要读取的寄存器地址
				len:要读取的长度
				buf:读取到的数据存储区
返 回 值:0,成功,其他,错误代码
*/
u8 MPU6050_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{ 
		MPU6050_IIC_Start(); 
		MPU6050_IIC_Send_Byte((addr<<1)|0);//发送器件地址+写命令	
		if(MPU6050_IIC_Wait_Ack())	//等待应答
		{
			MPU6050_IIC_Stop();		 
			return 1;		
		}
		MPU6050_IIC_Send_Byte(reg);	//写寄存器地址
		MPU6050_IIC_Wait_Ack();		//等待应答
		MPU6050_IIC_Start();
		MPU6050_IIC_Send_Byte((addr<<1)|1);//发送器件地址+读命令	
		MPU6050_IIC_Wait_Ack();		//等待应答 
		while(len)
		{
			if(len==1)*buf=MPU6050_IIC_Read_Byte(0);//读数据,发送nACK 
			else *buf=MPU6050_IIC_Read_Byte(1);		//读数据,发送ACK  
			len--;
			buf++; 
		}    
		MPU6050_IIC_Stop();	//产生一个停止条件 
		return 0;	
}

/*
函数功能:IIC写一个字节 
函数参数:
				reg:寄存器地址
				data:数据
返 回 值:0,成功,其他,错误代码
*/
u8 MPU6050_Write_Byte(u8 reg,u8 data) 				 
{ 
		MPU6050_IIC_Start(); 
		MPU6050_IIC_Send_Byte((MPU6050_ADDR<<1)|0);//发送器件地址+写命令	
		if(MPU6050_IIC_Wait_Ack())	//等待应答
		{
			MPU6050_IIC_Stop();		 
			return 1;		
		}
		MPU6050_IIC_Send_Byte(reg);	//写寄存器地址
		MPU6050_IIC_Wait_Ack();		//等待应答 
		MPU6050_IIC_Send_Byte(data);//发送数据
		if(MPU6050_IIC_Wait_Ack())	//等待ACK
		{
			MPU6050_IIC_Stop();	 
			return 1;		 
		}		 
		MPU6050_IIC_Stop();	 
		return 0;
}

/*
函数功能:IIC读一个字节 
函数参数:
				reg:寄存器地址
				data:数据
返 回 值:返回值:读到的数据
*/
u8 MPU6050_Read_Byte(u8 reg)
{
		u8 res;
		MPU6050_IIC_Start(); 
		MPU6050_IIC_Send_Byte((MPU6050_ADDR<<1)|0);//发送器件地址+写命令	
		MPU6050_IIC_Wait_Ack();		//等待应答 
		MPU6050_IIC_Send_Byte(reg);	//写寄存器地址
		MPU6050_IIC_Wait_Ack();		//等待应答
		MPU6050_IIC_Start();
		MPU6050_IIC_Send_Byte((MPU6050_ADDR<<1)|1);//发送器件地址+读命令	
		MPU6050_IIC_Wait_Ack();		//等待应答 
		res=MPU6050_IIC_Read_Byte(0);//读取数据,发送nACK 
		MPU6050_IIC_Stop();			//产生一个停止条件 
	return res;		
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.

2.3 整体代码框架

初始化硬件、读取传感器数据、计算倾斜角度并通过 OLED 显示屏显示结果。

#include "stm32f10x.h"
#include "mpu6050.h"
#include "oled.h"
#include "math.h"

// 常量定义
#define ALPHA 0.98          // 互补滤波的权重系数
#define DEG_TO_RAD(x) ((x) * 3.14159265358979 / 180.0)

// 全局变量定义
float pitch = 0.0f;           // 俯仰角
float roll = 0.0f;            // 滚转角
float pitch_prev = 0.0f;      // 上一周期俯仰角
float roll_prev = 0.0f;       // 上一周期滚转角
float gyroX = 0.0f, gyroY = 0.0f, gyroZ = 0.0f; // 陀螺仪数据
float accelX = 0.0f, accelY = 0.0f, accelZ = 0.0f; // 加速度计数据
float gyroXrate = 0.0f, gyroYrate = 0.0f, gyroZrate = 0.0f; // 角速度

void delay_ms(uint32_t ms);
void MPU6050_Init(void);
void ReadMPU6050(void);
void UpdateAngles(void);
void Display(void);

int main(void)
{
    // 初始化硬件
    SystemInit();           // 初始化系统时钟
    MPU6050_Init();         // 初始化MPU6050
    OLED_Init();            // 初始化OLED显示屏
    
    while (1)
    {
        // 读取MPU6050传感器数据
        ReadMPU6050();
        
        // 更新角度数据
        UpdateAngles();
        
        // 显示结果到OLED屏幕
        Display();
    }
}

// 延时函数
void delay_ms(uint32_t ms)
{
    uint32_t i;
    for(i = 0; i < ms * 1000; i++)
    {
        __NOP();
    }
}

// 初始化MPU6050传感器
void MPU6050_Init(void)
{
    // 假设已经实现了 I2C 初始化和MPU6050的配置
    MPU6050_InitI2C();   // I2C初始化
    MPU6050_Config();     // 配置MPU6050
}

// 读取MPU6050传感器数据
void ReadMPU6050(void)
{
    // 从MPU6050读取加速度和陀螺仪数据
    accelX = (float)MPU6050_ReadAccelX();  // 读取X轴加速度数据
    accelY = (float)MPU6050_ReadAccelY();  // 读取Y轴加速度数据
    accelZ = (float)MPU6050_ReadAccelZ();  // 读取Z轴加速度数据
    
    gyroX = (float)MPU6050_ReadGyroX();    // 读取X轴陀螺仪数据
    gyroY = (float)MPU6050_ReadGyroY();    // 读取Y轴陀螺仪数据
    gyroZ = (float)MPU6050_ReadGyroZ();    // 读取Z轴陀螺仪数据
    
    // 将陀螺仪的角速度转换为角度变化
    gyroXrate = gyroX * DEG_TO_RAD(1.0f);  // 角速度转化为角度
    gyroYrate = gyroY * DEG_TO_RAD(1.0f);
    gyroZrate = gyroZ * DEG_TO_RAD(1.0f);
}

// 更新俯仰角和滚转角
void UpdateAngles(void)
{
    // 加速度计计算角度(静态)
    float accel_pitch = atan2f(accelX, sqrtf(accelY * accelY + accelZ * accelZ)) * 180.0f / M_PI;
    float accel_roll = atan2f(accelY, sqrtf(accelX * accelX + accelZ * accelZ)) * 180.0f / M_PI;

    // 陀螺仪计算角度(动态)
    pitch += gyroXrate * 0.01f;   // 0.01f为采样时间间隔
    roll += gyroYrate * 0.01f;

    // 互补滤波(数据融合)
    pitch = ALPHA * (pitch_prev + gyroXrate * 0.01f) + (1 - ALPHA) * accel_pitch;
    roll = ALPHA * (roll_prev + gyroYrate * 0.01f) + (1 - ALPHA) * accel_roll;

    // 更新上一周期角度
    pitch_prev = pitch;
    roll_prev = roll;
}

// 显示数据到OLED屏幕
void Display(void)
{
    // 清屏
    OLED_Clear();
    
    // 显示俯仰角(pitch)和滚转角(roll)
    OLED_ShowString(0, 0, "Pitch: ");
    OLED_ShowFloat(60, 0, pitch, 2);  // 显示俯仰角,保留2位小数
    
    OLED_ShowString(0, 2, "Roll: ");
    OLED_ShowFloat(60, 2, roll, 2);   // 显示滚转角,保留2位小数

    // 显示中心圆圈与小球可视化(此部分需根据具体显示库实现)
    OLED_DrawCircle(64, 32, 30);  // 圆心 (64, 32),半径 30
    int ball_x = (int)(64 + 30 * sin(DEG_TO_RAD(roll)));
    int ball_y = (int)(32 + 30 * sin(DEG_TO_RAD(pitch)));
    OLED_DrawCircle(ball_x, ball_y, 3);  // 小球,半径3
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  1. 硬件初始化
    • MPU6050_Init():初始化 MPU6050 传感器。
    • OLED_Init():初始化 OLED 显示屏。
  2. MPU6050 数据读取
    • ReadMPU6050() 从 MPU6050 读取加速度计和陀螺仪数据。
    • 数据读取后,转换为设备的角速度和加速度,并存储为 gyroXrategyroYrategyroZrateaccelXaccelYaccelZ
  3. 数据融合与角度计算
    • UpdateAngles() 使用互补滤波算法融合加速度计和陀螺仪数据,更新设备的俯仰角(pitch)和滚转角(roll)。
    • 计算的俯仰角和滚转角实时更新并用于后续显示。
  4. 显示结果
    • Display() 函数清空 OLED 屏幕并显示俯仰角和滚转角。它还显示了一个可视化效果,其中包括一个圆圈和一个根据角度变化的小球。
    • 显示函数 OLED_ShowFloat()OLED_DrawCircle() 用于显示浮动数字和绘制圆形。

三、总结

本项目设计并实现了一款基于 STM32F103C8T6 单片机的电子水平仪,采用 MPU6050 传感器进行姿态检测,并通过 OLED 显示屏实时展示设备的俯仰角和滚转角。通过应用互补滤波算法,项目有效地融合了加速度计和陀螺仪的数据,提供了高效、实时且精确的角度估算,满足了设备在动态和静态环境下的姿态检测需求。

本系统设计具有高度的可扩展性,可以根据不同的应用场景进一步优化滤波算法、增加功能如按键校准、低功耗优化等。该电子水平仪不仅在电子设备的水平测量中具有广泛应用,还可以作为嵌入式系统设计中的经典实践,展示了如何结合传感器数据融合、实时显示和用户交互。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报


回复
    相关推荐