mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-03-12 09:33:59 -03:00
Tools: add motor vibration test and support for post-test fft
perform FFT analysis on vibration output
This commit is contained in:
parent
449e9ee13d
commit
39e948a40a
@ -7,6 +7,7 @@ import math
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
import numpy
|
||||||
|
|
||||||
from pymavlink import mavutil
|
from pymavlink import mavutil
|
||||||
from pymavlink import mavextra
|
from pymavlink import mavextra
|
||||||
@ -1847,6 +1848,73 @@ class AutoTestCopter(AutoTest):
|
|||||||
|
|
||||||
self.do_RTL()
|
self.do_RTL()
|
||||||
|
|
||||||
|
def fly_motor_vibration(self):
|
||||||
|
"""Test flight with motor vibration"""
|
||||||
|
self.context_push()
|
||||||
|
|
||||||
|
ex = None
|
||||||
|
try:
|
||||||
|
self.set_rc_default()
|
||||||
|
self.set_parameter("INS_LOG_BAT_MASK", 3)
|
||||||
|
self.set_parameter("INS_LOG_BAT_OPT", 2)
|
||||||
|
self.set_parameter("LOG_BITMASK", 958)
|
||||||
|
self.set_parameter("LOG_DISARMED", 0)
|
||||||
|
self.set_parameter("SIM_VIB_MOT_MAX", 350)
|
||||||
|
self.set_parameter("SIM_GYR_RND", 100)
|
||||||
|
self.set_parameter("SIM_DRIFT_SPEED", 0)
|
||||||
|
self.set_parameter("SIM_DRIFT_TIME", 0)
|
||||||
|
self.reboot_sitl()
|
||||||
|
|
||||||
|
self.takeoff(10, mode="LOITER")
|
||||||
|
self.change_alt(alt_min=15)
|
||||||
|
|
||||||
|
hover_time = 5
|
||||||
|
tstart = self.get_sim_time()
|
||||||
|
self.progress("Hovering for %u seconds" % hover_time)
|
||||||
|
while self.get_sim_time_cached() < tstart + hover_time:
|
||||||
|
attitude = self.mav.recv_match(type='ATTITUDE', blocking=True)
|
||||||
|
tend = self.get_sim_time()
|
||||||
|
|
||||||
|
self.do_RTL()
|
||||||
|
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
|
||||||
|
# ignore the first 20Hz
|
||||||
|
ignore_bins = 20
|
||||||
|
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
|
||||||
|
if numpy.amax(psd["X"][ignore_bins:]) < -22 or freq < 200 or freq > 300:
|
||||||
|
raise NotAchievedException("Did not detect a motor peak, found %f at %f dB" % (freq, numpy.amax(psd["X"][ignore_bins:])))
|
||||||
|
else:
|
||||||
|
self.progress("Detected motor peak at %fHz" % freq)
|
||||||
|
|
||||||
|
# now add a notch and check that the peak is squashed
|
||||||
|
self.set_parameter("INS_NOTCH_ENABLE", 1)
|
||||||
|
self.set_parameter("INS_NOTCH_FREQ", freq)
|
||||||
|
self.set_parameter("INS_NOTCH_ATT", 50)
|
||||||
|
self.set_parameter("INS_NOTCH_BW", freq/2)
|
||||||
|
self.reboot_sitl()
|
||||||
|
|
||||||
|
self.takeoff(10, mode="LOITER")
|
||||||
|
self.change_alt(alt_min=15)
|
||||||
|
|
||||||
|
tstart = self.get_sim_time()
|
||||||
|
self.progress("Hovering for %u seconds" % hover_time)
|
||||||
|
while self.get_sim_time_cached() < tstart + hover_time:
|
||||||
|
attitude = self.mav.recv_match(type='ATTITUDE', blocking=True)
|
||||||
|
tend = self.get_sim_time()
|
||||||
|
self.do_RTL()
|
||||||
|
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
|
||||||
|
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
|
||||||
|
if numpy.amax(psd["X"][ignore_bins:]) < -22:
|
||||||
|
self.progress("Did not detect a motor peak, found %f at %f dB" % (freq, numpy.amax(psd["X"][ignore_bins:])))
|
||||||
|
else:
|
||||||
|
raise NotAchievedException("Detected motor peak at %f Hz" % (freq))
|
||||||
|
except Exception as e:
|
||||||
|
ex = e
|
||||||
|
|
||||||
|
self.context_pop()
|
||||||
|
|
||||||
|
if ex is not None:
|
||||||
|
raise ex
|
||||||
|
|
||||||
def fly_vision_position(self):
|
def fly_vision_position(self):
|
||||||
"""Disable GPS navigation, enable Vicon input."""
|
"""Disable GPS navigation, enable Vicon input."""
|
||||||
# scribble down a location we can set origin to:
|
# scribble down a location we can set origin to:
|
||||||
@ -4407,12 +4475,15 @@ class AutoTestCopter(AutoTest):
|
|||||||
"Test rangefinder drivers",
|
"Test rangefinder drivers",
|
||||||
self.fly_rangefinder_drivers),
|
self.fly_rangefinder_drivers),
|
||||||
|
|
||||||
|
("MotorVibration",
|
||||||
|
"Fly motor vibration test",
|
||||||
|
self.fly_motor_vibration),
|
||||||
|
|
||||||
("LogDownLoad",
|
("LogDownLoad",
|
||||||
"Log download",
|
"Log download",
|
||||||
lambda: self.log_download(
|
lambda: self.log_download(
|
||||||
self.buildlogs_path("ArduCopter-log.bin"),
|
self.buildlogs_path("ArduCopter-log.bin"),
|
||||||
upload_logs=len(self.fail_list) > 0))
|
upload_logs=len(self.fail_list) > 0))
|
||||||
|
|
||||||
])
|
])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import traceback
|
|||||||
import pexpect
|
import pexpect
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import operator
|
import operator
|
||||||
|
import numpy
|
||||||
|
|
||||||
from MAVProxy.modules.lib import mp_util
|
from MAVProxy.modules.lib import mp_util
|
||||||
|
|
||||||
@ -3713,3 +3714,98 @@ switch value'''
|
|||||||
ret = self.run_tests(tests)
|
ret = self.run_tests(tests)
|
||||||
self.post_tests_announcements()
|
self.post_tests_announcements()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def mavfft_fttd(self, sensor_type, sensor_instance, since, until):
|
||||||
|
'''display fft for raw ACC data in current logfile'''
|
||||||
|
|
||||||
|
'''object to store data about a single FFT plot'''
|
||||||
|
class MessageData(object):
|
||||||
|
def __init__(self, ffth):
|
||||||
|
self.seqno = -1
|
||||||
|
self.fftnum = ffth.N
|
||||||
|
self.sensor_type = ffth.type
|
||||||
|
self.instance = ffth.instance
|
||||||
|
self.sample_rate_hz = ffth.smp_rate
|
||||||
|
self.multiplier = ffth.mul
|
||||||
|
self.sample_us = ffth.SampleUS
|
||||||
|
self.data = {}
|
||||||
|
self.data["X"] = []
|
||||||
|
self.data["Y"] = []
|
||||||
|
self.data["Z"] = []
|
||||||
|
self.holes = False
|
||||||
|
self.freq = None
|
||||||
|
|
||||||
|
def add_fftd(self, fftd):
|
||||||
|
self.seqno += 1
|
||||||
|
self.data["X"].extend(fftd.x)
|
||||||
|
self.data["Y"].extend(fftd.y)
|
||||||
|
self.data["Z"].extend(fftd.z)
|
||||||
|
|
||||||
|
mlog = self.dfreader_for_current_onboard_log()
|
||||||
|
|
||||||
|
# see https://holometer.fnal.gov/GH_FFT.pdf for a description of the techniques used here
|
||||||
|
messages = []
|
||||||
|
messagedata = None
|
||||||
|
while True:
|
||||||
|
m = mlog.recv_match()
|
||||||
|
if m is None:
|
||||||
|
break
|
||||||
|
msg_type = m.get_type()
|
||||||
|
if msg_type == "ISBH":
|
||||||
|
if messagedata is not None:
|
||||||
|
if messagedata.sensor_type == sensor_type and messagedata.instance == sensor_instance and messagedata.sample_us > since and messagedata.sample_us < until:
|
||||||
|
messages.append(messagedata)
|
||||||
|
messagedata = MessageData(m)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if msg_type == "ISBD":
|
||||||
|
if messagedata is not None and messagedata.sensor_type == sensor_type and messagedata.instance == sensor_instance:
|
||||||
|
messagedata.add_fftd(m)
|
||||||
|
|
||||||
|
fft_len = len(messages[0].data["X"])
|
||||||
|
sum_fft = {
|
||||||
|
"X": numpy.zeros(fft_len/2+1),
|
||||||
|
"Y": numpy.zeros(fft_len/2+1),
|
||||||
|
"Z": numpy.zeros(fft_len/2+1),
|
||||||
|
}
|
||||||
|
sample_rate = 0
|
||||||
|
counts = 0
|
||||||
|
window = numpy.hanning(fft_len)
|
||||||
|
freqmap = numpy.fft.rfftfreq(fft_len, 1.0 / messages[0].sample_rate_hz)
|
||||||
|
|
||||||
|
# calculate NEBW constant
|
||||||
|
S2 = numpy.inner(window, window)
|
||||||
|
|
||||||
|
for message in messages:
|
||||||
|
for axis in [ "X","Y","Z" ]:
|
||||||
|
# normalize data and convert to dps in order to produce more meaningful magnitudes
|
||||||
|
if message.sensor_type == 1:
|
||||||
|
d = numpy.array(numpy.degrees(message.data[axis])) / float(message.multiplier)
|
||||||
|
else:
|
||||||
|
d = numpy.array(message.data[axis]) / float(message.multiplier)
|
||||||
|
|
||||||
|
# apply window to the input
|
||||||
|
d *= window
|
||||||
|
# perform RFFT
|
||||||
|
d_fft = numpy.fft.rfft(d)
|
||||||
|
# convert to squared complex magnitude
|
||||||
|
d_fft = numpy.square(abs(d_fft))
|
||||||
|
# remove DC component
|
||||||
|
d_fft[0] = 0
|
||||||
|
d_fft[-1] = 0
|
||||||
|
# accumulate the sums
|
||||||
|
sum_fft[axis] += d_fft
|
||||||
|
|
||||||
|
sample_rate = message.sample_rate_hz
|
||||||
|
counts += 1
|
||||||
|
|
||||||
|
numpy.seterr(divide = 'ignore')
|
||||||
|
psd = {}
|
||||||
|
for axis in [ "X","Y","Z" ]:
|
||||||
|
# normalize output to averaged PSD
|
||||||
|
psd[axis] = 2 * (sum_fft[axis] / counts) / (sample_rate * S2)
|
||||||
|
psd[axis] = 10 * numpy.log10 (psd[axis])
|
||||||
|
|
||||||
|
psd["F"] = freqmap
|
||||||
|
|
||||||
|
return psd
|
||||||
|
Loading…
Reference in New Issue
Block a user