PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Beschleunigung aus Winkel & Beschleunigung errechnen



Che Guevara
22.09.2013, 01:08
Hi,

auch wenn der Titel im ersten Moment etwas komisch wirkt, hab ich eine plausible Frage dazu:
Ich messe mit einem stillstehendem Accelerometer die Beschleunigung in 3 Achsen (X, Y & Z). Daraus kann ich auch die Winkel in X & Y Richtung errechnen, bis dahin kein Problem.
Da ich allerdings mehrere Boards habe, muss / möchte ich den Ausgabewert bei einer Beschleunigung von 1g errechnen. Hintergrund ist, dass die Sensoren alle ein bisschen streuen und ich die MÖGLICHST GENAUEN Ausgabewerte brauche, da die Werte anschließend integriert werden sollen (Integral wird natürlich gestützt, aber um so genauer es ist, umso genauer ist im Endeffekt das Resultat).
Mein erster Gedanke war eine Art Dreisatz, allerdings komme ich nicht auf einen grünen Zweig...
Ich kenne: Beschleunigung (X, Y, Z) , Winkel (X , Y , (Z kann berechnet werden).
Ich will wissen: Beschleunigung bei 1g

Die Beschleunigung bei einem gegebenem Winkel kann ich über sin(Winkel) errechnen, den Winkel bei gegebener Beschleunigung über arcsin(Beschleunigung).

Ich hoffe, ihr könnt mir bei meinem kleinen Problem helfen.
Vielen Dank & Gruß
Chris

EDIT:
Achja, eigentlich dachte ich, es reicht, wenn ich Beschleunigung1g = BeschleunigungX + BeschleunigungY + BeschleunigungZ rechne, aber sowohl mit, als auch ohne den Beträgen funktionierts nicht ... Je weiter der Sensor geneigt ist, desto größer werden die Werte, obwohl doch die Beschleunigung immer 1g beträgt (bei Stillstand)?!

schorsch_76
22.09.2013, 08:11
Du musst den Pythagoras auf die 3 Werte anwenden.

Wert bei 1g = sqrt(x^2+y^2+z^2)

Che Guevara
22.09.2013, 12:16
Hi,

das stimmt leider nicht ganz.. Wenn der Sensor um mehr als ca. 20° geneigt ist, weichen die Werte bereits um ca. 200 ab ... Bei mehr als 40° weichen sie dann schon um bis zu 2000 ab ... (1g = 16384)
Eigentlich dachte ich auch an eine etwas andere Lösung:
Ich errechne den Winkel, um den die Z-Achse schief steht, berechne die daraus resultierende Beschleunigung und berechne anschließend irgendwie (???) die Beschleunigung bei 1g...
Den Winkel der Z-Achse kann ich berechnen, indem ich den Pythagoras von WinkelX & WinkelY berechne und von 90° abziehe, oder?
Die Beschleunigung errechne ich dann anhand der sinus-Funktion.

Stimmt mein Ansatz soweit?

Gruß
Chris

EDIT:
So ungefähr stelle ich mir das vor:
1. WinkelZ = 90 - pythagoras(WinkelX,WinkelY);
2. BeschleunigungZ = sin(WinkelZ);
3. Beschleunigung1g = 90/WinkelZ * BeschleunigungZ;

Bei Formel 3 bin ich mir noch unschlüßig, da das Verhältnis Winkel / Beschleunigung nicht linear ist ... evtl. komme ich da mit dem arcsin weiter?

Klebwax
22.09.2013, 14:41
Die Beschleunigung ist ein dreidimensionaler Vektor. Dein Sensor liefert dir, da er in Wirklichkeit aus 3 orthogonal angeordneten Sensoren besteht, die 3 Komponenten des Beschleunigungsvektors in kartesischen Koordinaten.

Die Größe der (Erd)Beschleunigung ist der Betrag des Beschleunigungsvektors. Und der Betrag ist die Wurzel aus dem Skalarprodukt des Vektors mit sich selber. Bei rechtwinkligen Koordinaten also a = sqrt(ax^2 + ay^2 + az^2). Sind die Sensoren nicht rechtwinklig, braucht man den Kosinussatz.

Dieser Betrag ist die Beschleunigung. Das gilt für alle Winkel. Wenn man nur die Beschleunigung wissen will, braucht man den Winkel nicht extra auszurechnen.

Zwei Spezialfälle zu Prüfung: die Z-Achse steht senkrecht, ax = ay = 0, az ist die Beschleunigung, a = sqrt(0^2 + 0^2 + az^2) = az
Der Sensor liegt auf der Seite, die Y-Achse steht senkrecht, ax = az = 0, ay ist die Beschleunigung, a = sqrt(0^2 + ay^2 + 0^2) = ay

Wenn man den Winkel nicht wissen will, braucht man ihn auch nicht extra auszurechnen. Da sqrt() gern länglich wird, versucht man die Funktion zu vermeiden. Ist der Winkel (eigentlich zwei Winkel) bekannt, kann man den Betrag des Vektors auch anders rechnen. Muß man die Winkel erst berechnen, läufts auf das gleiche hinaus.

MfG Klebwax

Che Guevara
22.09.2013, 15:12
Hi,

also so ganz schlau werde ich nicht aus deinem Beitrag, ist mir wohl ein bisschen zu mathematisch ...
Wenn ich die Formel a = sqrt(ax^2+ay^2+az^2) verwende, kommt je nach Schrägstellung des Sensors (in Ruhelage) ein etwas anderer Wert heraus, wie oben geschrieben ...
Außerdem habe ich dann wieder das Problem, wenn die einzelnen Achsen versch. Offsets haben, dass dann der Wert wieder etwas daneben liegt.
Deswegen wollte ich den Dreisatz anwenden, allerdings scheitere ich daran, dass das Verhältnis von Winkel & Beschleunigung nicht linear ist.
Ich kenne die Beschleunigung der Z Achse und auch deren Winkel, daraus möchte ich die Beschleunigung bei einem Winkel von 90° errechnen.

Was meinst du mit "da sqrt() gern länglich wird"? Meinst du die Ausführungszeit? Das wäre egal, da der Wert nur einmal zum Start des Programms errechnet werden müsste.

Gruß
Chris

schorsch_76
22.09.2013, 17:56
sqrt benötigt recht viel Rechenzeit. Die Implementierung auf Prozessoren bassiert auf einer Taylorreihe. Genauso wie die trigonometrischen Funktionen.

http://de.wikipedia.org/wiki/Taylorreihe

Klebwax
22.09.2013, 18:57
Wenn ich die Formel a = sqrt(ax^2+ay^2+az^2) verwende, kommt je nach Schrägstellung des Sensors (in Ruhelage) ein etwas anderer Wert heraus, wie oben geschrieben ...
Die Physik sagt aber, der Wert muß konstant sein. Was du hier also siehst, sind die Messfehler deines Sensors.


Außerdem habe ich dann wieder das Problem, wenn die einzelnen Achsen versch. Offsets haben, dass dann der Wert wieder etwas daneben liegt.
Deswegen wollte ich den Dreisatz anwenden, allerdings scheitere ich daran, dass das Verhältnis von Winkel & Beschleunigung nicht linear ist.
Dein wirkliches Problem ist nicht die Formel, auch wenn sie kompliziert wäre, sondern daß du den Winkel nicht kennst. Erst wenn du deinen Sensor kalibriert hast, kannst du den Winkel berechnen. Ist dein Sensor kalibriert, brauchst du den Winkel zum kalibrieren nicht mehr.

Zum Kalibrieren deines Sensors brauchst du (solange er linear ist) 6 Werte: 3 Offsets und 3 Faktoren. Und das gilt auch nur, wenn der Sensor Zeit- und Temperaturstabil ist. Wenn du dann auch noch zwei Sensoren vergleichen willst, sind es 12 Werte, 6 für jeden Sensor. Um das zu vereinfachen, muß man sich den Sensor genau ansehen und entscheiden, für welchen der 6 Werte man z.B. konstante, feste Werte ansetzen kann. Wie du selbst sagst, ist der Offset ein Problem. Also muß er gegen einen anderen Sensor (z.B. Wasserwaage oder Lot) kalibriert werden. Alles andere ist der Versuch, sich an den eigenen Haaren aus dem Sumpf zu ziehen.

MfG Klebwax

schorsch_76
23.09.2013, 06:33
Eventuell könnte man einen Regressionsansatz wählen. was ja konstant ist, g. Gleichungssystem aufstellen und viele Messwerte reinballern. Mit der Methode der kleinsten Quadrate dann die optimalen Offsets berechnen.


Die Frage ist, wie muss das Gleichungssystem aussehen.

Gruß Georg

Che Guevara
23.09.2013, 11:06
Hi,

also wie man einen ACC kalibriert, darüber hab ich mir schonmal Gedanken gemacht ...
Ich brauche je Achse einen Offset-Wert und einen Bereichs-Wert, der den maximalen Ausgabewert enthält.
Der Sensor-Wert wird wird dann wie folgt berechnet:


if(accx<accx_min) accx_min = accx;
if(accx>accx_max) accx_max = accx;
accx_offset = (accx_min+accx_max)>>1;
accx_range = (labs(accx_min)+labs(accx_max));
accx_corrected = (accx-accx_offset)/accx_range*32768;

Allerdings dachte ich bis jetzt immer, ich komme auch ohne Kalibrierung aus ... Aber anscheinend muss ich das wohl wirklich mal in Angriff nehmen, damits gescheit funktioniert.

Gruß
Chris

schorsch_76
23.09.2013, 15:32
Hab mir auch grad Gedanken gemacht wie man so einen Sensor am besten mathematisch beschreibt. Kannst du ein paar Beispieldaten hier reinsetzten? Also die Werte der 3 Ausgänge in unterschiedlichen Lagen. Schätze so ca. 6-8 Werte wären hilfreich. Bitte alle bei Stillstand ;)

Gruß
Georg

- - - Aktualisiert - - -

Das hier scheint mir exakt dies zu bewirken.
http://de.wikipedia.org/wiki/Basiswechsel_(Vektorraum)

Dazu eben die Funktion
B1: Basisvektor 1
B2: Basisvektor 2
B3: Basisvektor 3

s1: Array mit Werten aus dem Sensor 1
s2: Array mit Werten aus dem Sensor 2
s3: Array mit Werten aus dem Sensor 3

S:sum((((len(s1[i]*B1)^2 + len(s2[i]*B2)^2 + len(s3[i]*B3)^2))-g^2)^2,i,1,length(s1));

das ganze dann nach den 9 Kooeffizienten der Matrix ableiten und 0 setzen (kleinster Abstand).

Jetzt müssten nur noch genügend Daten vorhanden sein, um richtige Kalibrierwerte zu bekommen.

Gruß
Georg

Klebwax
24.09.2013, 01:24
Allerdings dachte ich bis jetzt immer, ich komme auch ohne Kalibrierung aus ... Aber anscheinend muss ich das wohl wirklich mal in Angriff nehmen, damits gescheit funktioniert.

Woran erkennst du, ob es gescheit funktioniert? Was funktioniert denn nicht?

Jeder Messwert hat Tolleranzen. Ein Zimmermann kennt keine Millimeter, trotzdem hält das Dach. Wo liegt diese Grenze bei deinem System? Wenn du diese Grenze kennst und deine Messwerte entsprechend rundest, brauchst du möglicherweise nicht zu kalibrieren.

MfG Klebwax

Che Guevara
24.09.2013, 10:09
Hi,

also Messwerte könnte ich später nachliefern, allerdings kann ich persönlich mit diesem mathematischem Gedöns zur Zeit nicht viel anfangen, ist schon zu lange her ...
Also die Grenzen liegen darin, dass z.b. wenn die Platine waagrecht steht, kein Wert von 0 ausgegeben wird sondern von 1-3.
Ich werd jetzt mal eine Kalibrierung einbauen und dann mal sehen, obs einen merkbaren Fortschritt bringt oder nicht.

Gruß
Chris

schorsch_76
24.09.2013, 12:37
Bin immer noch am grübeln. Irgendwie lässt mich dieses Thema nicht los ;)

Ich hab einen BIAS (Offset) in alle Richtungen, eine Skalierung und nicht orthogonale Basisvektoren.

Muss doch zu lösen sein ....

schorsch_76
24.09.2013, 18:23
Jetzt hab ich mal das Pferd von der anderen Seite aufgezäumt.

Ich habe 3 Sensoren Welche einen Bias haben, AD gewandelt werden und in meinem Beispiel 85° bzw. 95° zueinander versetzt sind.

Also ist die Welt zu Sensor Matrix
WCS := BIAS . AD . BASISVEKTOREN

26455

Die untere Matrix stellt meine 5 "Messwerte" dar, welche je exakt 1g haben. 3 mal je parallel zu einer Achse und 2x exakt 45° geneigt. 3.132^2=9.81
Es zeigt exakt das Verhalten das der OP beschrieben hat.

Jetzt habe ich ein Model, das ich (hoffentlich) über die Samplewerte wiederherstellen kann. Ich benötige dann "nur" die 9 Parameter der S2W Matrix.

Gruß
Georg

schorsch_76
27.09.2013, 10:00
Leider kann ich das System momentan nicht umkehen, da in der "klassischen" Optimierungsvariante (Methode der kleinsten Quadrate), Polynome 4. Grades entstehen. Jetzt werde ich versuchen andere optimierungsvarianten zu testen.

Gruß
Georg

schorsch_76
01.10.2013, 14:47
Habe jetzt ein Programm erstellt, das anhand beliebiger Sampledaten die Matrix ermittelt. Es arbeitet mittels "simulierten Abkühlens" [1]

Kannst du mir ca. 20 Sampledaten zu Verfügung stellen? Sprich den Sensor beliebig im Raum in alle Richtungen drehen und die Ausgabe der 3 Sensoren (die Rohwerte) mir hier einstellen?

Gruß
Georg

[1] http://en.wikipedia.org/wiki/Simulated_annealing

schorsch_76
02.10.2013, 21:37
Da der OP wohl nicht mehr interessiert ist, möchte ich hier meine Daten doch noch vorstellen.

Erstmal ein Bild :)

raw = rohe unkalibrierte Daten. habe hier extra ein sehr verzogenes System erstellen lassen.
Winkel X-Achse/Y-Achse: 70°
Winkel Z-Achse/Y-Achse: 110°
Also Extrem verstellt.

Grafische Darstellung:
26500

Standardabweichung und Mittelwert der Länge der "g" Vektoren:
26501

Wie man an dem Mittelwert und der Standardabweichung der transformierten (kalibrierten) Werten sieht, ist die Länge "g" nach der Kalibrierung wirklich gut.

0: Was ist das Ziel?
Es soll ein Weg gefunden werden um einen Beschleunigungssensor exakt zu kalibrieren.

1: Wie wurden die Samplewerte erstellt?
Das Model ist eine Verkettung von Matrixen:
BIAS: Verstellung am Ursprung, ein Fehlerhafter Offset
AD: Analog Digitalwandlung
BV: Verstellung der Basisvektoren, das schief stellen der Achsen(Sensoren) zueinander.

Welt zu Sampledaten
W2S: AD . BV . BIAS



S2W: [ s1x s2x s3x ]
[ 0 s2y s3y ]
[ 0 0 s3z ]

Diese Matrix wird also gesucht.

Damit habe ich per Zufallsgenerator und Rotation eines "g" Vektors um die 3 Achsen meine Samplewerte erstellt.

Das Ziel des gesamten Algorithmus ist also die inverse Matrix S2W.

2: Wie gehts von den Samplewerten zu der Umkehrmatrix S2W?
Leider konnte ich nicht per Ableitung nach den 7 Parametern eine Lösung finden, deshalb habe ich ein numerisches Lösungsverfahren angewandt. Simulierte Abkühlung wie ohen geschrieben.

Hier ist mein Code:


/* Copyright (C) 2013 Georg Gast
schorsch_76@gmx.de

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/

#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>

#include <random>
#include <array>
#include <vector>

#include <string>
#include <limits>

#include <thread>
#include <functional>

// ------------------------------------------------
// base class
// ------------------------------------------------
template <class param_type, size_t number_parameter>
class SimualtedAnneal
{
public:
typedef std::array<param_type,number_parameter> ArrayT;

SimualtedAnneal()
: m_distribution_01(0,1)
{
}

virtual ~SimualtedAnneal() {}

ArrayT Anneal()
{
m_min_e = std::numeric_limits<param_type>::max();

size_t k;
param_type temperature, e, enew, ebest, p, r;
ArrayT snew;

// initial state
ArrayT s = s0();
ArrayT sbest = s;
e = ebest = E(s);

k = 0;
while (k < GetMaxCycles() && e > MaxAllowedE())
{
// current temperature
temperature = StartTemperature() * (param_type(1) - param_type(k) / GetMaxCycles());
snew = Neighbor(s);
enew = E(snew);

// Should we move on?
p = P(e,enew,temperature);
r = m_distribution_01(m_rnd);
if (p > r)
{
// yes, change state
e = enew;
s = snew;
}

// we found a better solution?
if (enew < ebest)
{
// this is our new best solution
ebest = enew;
sbest = snew;
m_min_e = ebest;
}

// next iteration
++k;
}

return sbest;
}

virtual param_type Check(ArrayT params)
{
return E(params);
}

protected:
// temperature
virtual param_type StartTemperature() = 0;

// iteration
virtual param_type MaxAllowedE() = 0;
virtual size_t GetMaxCycles() = 0;

// Neighbor
virtual ArrayT s0() = 0;
virtual ArrayT Neighbor(ArrayT s) = 0;

// propability to change the state
// higher temperature --> more propable
// higher difference --> more propable
// can be overridden, if needed
virtual param_type P(param_type e, param_type enew, param_type temperature)
{
param_type delta_e = enew-e;
return std::exp(-delta_e / temperature);
}

// get the current energy
virtual param_type E(const ArrayT& params) = 0;
private:
// random generators
std::random_device m_rnd;
std::uniform_real_distribution<param_type> m_distribution_01;
param_type m_min_e;
};

// ------------------------------------------------
// GyroSA
// ------------------------------------------------

template <class param_type = double>
class GyroSA : public SimualtedAnneal<param_type,6>
{
public:
typedef SimualtedAnneal<param_type,6> BaseT;

// parameter
GyroSA(int t=7)
: m_distribution(param_type(-1.0/std::pow(10,t)),param_type(1.0/std::pow(10,t)))
{
// read in sample data
std::ifstream ifs("/home/georg/samples.txt");
std::string line;
if (ifs.is_open())
{
std::array<param_type,3> sample;
while (std::getline(ifs,line))
{
std::istringstream iss(line);
iss >> sample[0] >> sample[1] >> sample[2];
m_samples.push_back(sample);
//std::cout << "sample: " << line << std::endl;
}
}
}

virtual ~GyroSA()
{
}

void SaveTransformedSamples(const typename BaseT::ArrayT& params)
{
param_type x,y,z,px,py,pz,c;

param_type s1x = params[0];

param_type s2x = params[1];
param_type s2y = params[2];

param_type s3x = params[3];
param_type s3y = params[4];
param_type s3z = params[5];

std::ofstream ofs("/home/georg/transformed.txt");
for (size_t i = 0; i < m_samples.size(); ++i)
{
x = m_samples[i][0];
y = m_samples[i][1];
z = m_samples[i][2];

px = s3x * z + s2x * y + s1x * x;
py = s3y * z + s2y * y;
pz = s3z * z;

ofs << px << "\t"
<< py << "\t"
<< pz << std::endl;
}
}

protected:
// temperature
virtual param_type StartTemperature() override
{
return 1;
}

// iteration
virtual param_type MaxAllowedE() override
{
return 1e-6;
}
size_t GetMaxCycles() override
{
return 5000000;
}

// randomize data
virtual typename BaseT::ArrayT s0() override
{
typename BaseT::ArrayT params;
// we know the solution is somewhere here
// 0: s1x 1.0/1000
// 1: s2x 0
// 2: s2y 1.0/1000
// 3: s3x 0
// 4: s3y 0
// 5: s3z 1.0/1000
params[0] = 1.0/10000;

params[1] = 0;
params[2] = 1.0/10000;

params[3] = 0;
params[4] = 0;
params[5] = 1.0/10000;

return params;
}
virtual typename BaseT::ArrayT Neighbor(typename BaseT::ArrayT s) override
{
typename BaseT::ArrayT params = s;
for (size_t i = 0; i < params.size(); ++i)
{
params[i]+= m_distribution(m_rnd);
}
return params;
}

// get the current energy
virtual param_type E(const typename BaseT::ArrayT& params) override
{
// the sum of the square distance to g from all samples to this model

param_type ret = 0;
param_type x,y,z,px,py,pz,c;

param_type s1x = params[0];

param_type s2x = params[1];
param_type s2y = params[2];

param_type s3x = params[3];
param_type s3y = params[4];
param_type s3z = params[5];

for (size_t i = 0; i < m_samples.size(); ++i)
{
x = m_samples[i][0];
y = m_samples[i][1];
z = m_samples[i][2];

px = s3x * z + s2x * y + s1x * x;
py = s3y * z + s2y * y;
pz = s3z * z;

c = std::sqrt(px*px+py*py+pz*pz);
ret += std::pow(c-9.81,2);
}
return ret;
}

private:
// random generator
std::default_random_engine m_rnd;
std::uniform_real_distribution<param_type> m_distribution;

// sample data
std::vector< std::array<param_type,3> > m_samples;
};

// ------------------------------------------------
// Thread execute function
// ------------------------------------------------
template<class T>
void Execute(T& sa, typename T::ArrayT& r)
{
r = sa.Anneal();
}

template <class T>
struct ThreadData
{
thread_t th;
T sa;
typename T::ArrayT r;
};

// ------------------------------------------------
// main
// ------------------------------------------------
int main(int argc, const char** argv)
{
typedef long double calc_t;
typedef GyroSA<calc_t> sa_t;

using namespace std::placeholders;

std::cout << std::setprecision(12) << std::fixed ;

// create 8 thread
static const int nr_threads = 8;

std::array< ThreadData<sa_t>, nr_threads> td;
for (int i = 0; i < nr_threads; ++i)
{
td[i].th = thread_t(Execute<sa_t>, std::ref(td[i].sa), std::ref(td[i].r));
}
for (int i = 0; i < nr_threads; ++i)
{
td[i].th.join();
}

// get the lowest e
int res = -1;
calc_t lowest_e = std::numeric_limits<calc_t>::max();
for (int i = 0; i < nr_threads; ++i)
{
calc_t e = td[i].sa.Check(td[i].r);
if (e < lowest_e)
{
res = i;
lowest_e = e;
}
}

std::cout << "MinE: " << lowest_e << std::endl;

for (size_t i = 0; i < td[res].r.size(); ++i)
{
std::cout << "Param[" << i << "]="
<< td[res].r[i]
<< std::endl;
}
std::cout << std::endl;

// transform all samples to the results file
td[res].sa.SaveTransformedSamples(td[res].r);
}


Das ganze wurde mit C++11 umgesetzt. Kann aber auch mit MSVC10 und boost genutzt werden.
Je kleiner MinE (minimale Energie) ist, umso besser ist die Näherung.

3: Wie nutze ich das ganze dann auf dem µC?
Ganz einfach: Die 7 Parameter sind die oben genanten s1x etx.

x: x Wert aus dem Sensor
y: y Wert aus dem Sensor
z: z Wert aus dem Sensor

px: x Wert Kalibriert
py: y Wert Kalibriert
pz: z Wert Kalibriert

px = s3x * z + s2x * y + s1x * x;
py = s3y * z + s2y * y;
pz = s3z * z;

Die 7 Werte müssen also einmal für den Sensor ermittelt werden und können dann ins Programm auf den µC genutzt werden.

Bei Fragen .... fragen ;)

Gruß
Georg