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.
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.
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.
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.
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.
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!
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.
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.