ESPHome and MPU6050 Accelerometer

I’m struggling to get an MPU6050 to correctly give me a single-axis tilt angle using ESPHome and some code I’ve written that implements a Complementary filter incorporating both gyro and accelerometer data.

The challenge that I am having is that the filter code implements a discrete integration function to help smooth out the gyro data. The problem I’m having is understanding what value to set the delta-t value which should correspond to the sampling interval of the MPU6050.

Does anyone know what the sample rate of the MPU6050 actually is as implemented with the ESPHome code for this sensor? I’m also thinking that I may need to change the I2C bus frequency to match. Looking through the code supporting this component didn’t really anything, but that could just be me.

I know this sounds really esoteric, but I’m attempting to measure the tilt angle of some outside louvers which provide no position feedback.

1 Like

One minute or two, I believe.
But you can set it with update interval.

Not sure I understand this, where do you find anything about this variable?

The delta-t is a variable in the filter algorithm. It should reflect the actual update interval (in ms) of the actual sensor. But given how the sensor responds, the default sampling rate is clearly not minutes.

But just as I said before just set the update interval, isn’t that enough?

Did you ever figure this out?

I did not figure this out.

Since I was interested in building an angle sensor, I did some more research and ended up taking a simpler approach with a two-axis angle sensor that provided an analog output.

I am pleasantly surprised how stable this sensor is (even in an outdoor environment) and am performing only very basic smoothing in ESPHome for this sensor.

Which sensor did you use? Im planning on mounting some to my solar panels so I can control the angle they are at

I don’t see why you two has such issues with the MPU.
It works very nicely for us.

This is the sensor that detects what angle our kitchen TV is in.
So when the value is high then the TV is in “viewing mode”

Thats not reporting an angle though, i.e. 45° from horizontal

Sure it does.
In a different measurement.

It measure the gravity so it can be 0 m/s2 to 9.82 m/s2.
Any anything in between is the angle between 0 and 90.

As long as you position it in a horizontal way then you will just need a simple template to make it degrees.

2 Likes

The sensor I am using is this somewhat obscure GY-25A device.

In this case, I use it to track the angle of my pergola blades with a D1 Mini and some pretty simple code:

# Set up angle sensor
sensor:
  - platform: adc
    pin: A0
#    name: "Pergola Angle Voltage"
    id: angle_voltage
    update_interval: 10ms
    accuracy_decimals: 3

# Calculate angle from sensor      
  - platform: template
    id: pergola_angle
    name: "Pergola Angle"
    update_interval: 100ms
    accuracy_decimals: 0

# x-axis 0-360 deg is proportional to 0-1 volt, so this is an easy calculation
    lambda: return ((id (angle_voltage).state - id (angle_sensor_offset).state) * 360);
    unit_of_measurement: "°"

The angle_sensor_offset is simply a calibration value that I store that represents the voltage at zero degrees. That saved me from having to get too precise with how I mounted the device. The calibration value can either be written to the D1 Mini’s storage, or in my case, a persistent MQTT message.

The sensor itself is tiny, and I mounted it in a very small weatherproof box to make it easier to install.

Like I said before, very simple and rock solid.

1 Like

Wait…
You have a device that sends out a voltage of 0-3.3 volts depending on the angle and you can only use 0-1 volt because of the ADC of the ESP8266?

I really don’t see how this is better than the MPU.

When I was prototyping this sensor, I discovered that the AliExpress information is incorrect as the GY-25A device only outputs a maximum voltage of 1.0 volts.

Regardless, even if it did output 3.3 volts, that’s an easy problem to solve with a single resistor.

Still doesn’t make any sense in my opinion.
Your adding layers of potential errors.
Creating a voltage from a value has an error potential, and reading it also.
Sending that same value over I2C seems a lot safer in my opinion.

And one volt represent 360 degrees compared to 9.82*2 m/s2 on the MPU.

Where is this delta-t value that was so important in your code? I see you use update interval (as I also suggested).
But your sensors are not in sync, the code discards 9 of 10 values the ADC takes.

And lastly your sensor is 10 times the price of a MPU.

Sorry but I just don’t get it.

Apologies if this makes no sense to you - my replies were to the questions posed by @jimmyeao.

In the end, this is just a pergola, not a rocket ship, so ridiculous precision is not really part of the brief. But we each have something that works well for us, which is great. And if it ain’t broke, don’t fix it!

1 Like

For info, I have the MPU6050 working with this code:

sensor:
  - platform: mpu6050
    address: 0x68
    accel_x:
      id: accel_x
      name: "accel X"
      filters:
         - sliding_window_moving_average:
            window_size: 5
            send_every: 1
         - delta: 0.5
    accel_y:
      id: accel_y    
      name: "accel Y"
      filters:
         - sliding_window_moving_average:
            window_size: 4
            send_every: 1   
         - delta: 0.5            
    accel_z:
      id: accel_z    
      name: "accel z"
      filters:
         - sliding_window_moving_average:
            window_size: 4
            send_every: 1   
         - delta: 0.5            
    gyro_x:
      name: "gyro X"
      filters:
         - sliding_window_moving_average:
            window_size: 4
            send_every: 1   
         - delta: 0.5            
    gyro_y:
      name: "gyro Y"
      filters:
         - sliding_window_moving_average:
            window_size: 4
            send_every: 1     
         - delta: 0.5            
    gyro_z:
      name: "gyro z"
      filters:
         - sliding_window_moving_average:
            window_size: 4
            send_every: 1      
         - delta: 0.5            
    temperature:
      name: "MPU6050 Temperature"
    update_interval: 1.5s
  - platform: template
    id: roll
    name: pan1roll
    accuracy_decimals: 2
    lambda: |-
      return  (atan( id(accel_y).state / sqrt( pow( id(accel_x).state , 2) + pow( id(accel_z).state , 2) ) ) * 180 / PI) ;
    update_interval: 250ms

  - platform: template
    id: pitch
    name: pan1pitch
    accuracy_decimals: 2
    lambda: |-
      return  (atan(-1 * id(accel_x).state / sqrt(pow(id(accel_y).state, 2) + pow(id(accel_z).state, 2))) * 180 / PI);
    update_interval: 250ms
4 Likes

how did you calibrate the angle_sensor_offset value? Curious to know what value you are using. Just got hold of some of these to try.

I have a simple calibration routine that I built into the code.

The first part creates a sensor that measures and filters the sensor voltage:

# Smooth sensor voltage for calibration
  - platform: template
    id: angle_voltage_smoothed
    lambda: return (id (angle_voltage).state);
    update_interval: 100ms
    accuracy_decimals: 3
    filters:
      - sliding_window_moving_average:
          window_size: 50
          send_every: 10

The second part assigns that value to my calibration offset when I push a button in HA:

# Surface button for sensor offset calibration
button:
  - platform: template
    name: "Calibrate Tilt Sensor"
    icon: "mdi:restart"
    on_press:
      - lambda: |-
          id (angle_offset_voltage) = id (angle_voltage_smoothed).state;

In my case, the calibration was done when my pergola blades were fully closed. I did this once a while back, and the value still seems fine.

To persist this across power cycles, I also store the calibration offset in an MQTT queue, but you could also write to the internal storage of the Wemos D1 Mini (in my case) or whatever hardware you are using and then use on_boot to read it on power up.

1 Like

Thats got me a lot closer…

How did you declare angle_sensor_offset ? If i set it up as a global variable it breaks the code, if I add it as:

  - platform: template
    id: angle_sensor_offset
    lambda: return (id (angle_sensor_offset).state);

I just get NaN returned in the logs

This is the main part I have:

globals:
  - id: angle_offset_voltage
    type: float
    restore_value: yes


captive_portal:

button:
  - platform: template
    name: "Calibrate Tilt Sensor"
    icon: "mdi:restart"
    on_press:
      - lambda: |-
          id (angle_offset_voltage) = id (angle_voltage_smoothed).state;
# Set up angle sensor
sensor:
  - platform: adc
    pin: A0
#    name: "Solar Angle Voltage"
    id: angle_voltage
    update_interval: 500ms
    accuracy_decimals: 3

# Smooth sensor voltage for calibration
  - platform: template
    id: angle_voltage_smoothed
    lambda: return (id (angle_voltage).state);
    update_interval: 100ms
    accuracy_decimals: 3
    filters:
      - sliding_window_moving_average:
          window_size: 50
          send_every: 10
  - platform: template
    id: angle_sensor_offset
#    lambda: return (id (angle_sensor_offset).state);
    
# Calculate angle from sensor      
  - platform: template
    id: pergola_angle
    name: "Panel 1 Angle"
    update_interval: 100ms
    accuracy_decimals: 0
# x-axis 0-360 deg is proportional to 0-1 volt, so this is an easy calculation    
    lambda: return ((id (angle_voltage).state - id (angle_sensor_offset).state) * 360);
    unit_of_measurement: "°"

I use this code:

globals:
  - id: angle_offset_voltage
    type: float
    restore_value: no
# Set to dummy value so we can figure out if we've rebooted later
    initial_value: '999.9'

It’s a bit clunky, but allows me to pick up reboots and restore the offset value from an MQTT queue if needed.

Yes, I’m using MQTT as a persistence layer, not the internal NV storage on the microcontroller.