Forum: Mikrocontroller und Digitale Elektronik Problem mit Kompass Achsen (Mega644p + HMC5883L)


von Philipp M. (lord-maricek)


Lesenswert?

Hallo,

für ein aktuelles Projekt haben wir uns Platinen ätzen lassen, auf dem 
Board sind ein HMC5883L und ein Mega644p, verbunden über I2C.
Zum testen habe ich es schematisch wie im Arduino Beispiel Code gemacht:
Hier http://www.sparkfun.com/products/10530 -> Documents -> Example Code
Benutzten tue ich außerdem die I2C Lib von Peter Fleury.

Wenn ich den Kompass drehe, zeigt er immer nur Werte von 280 Grad bis 
360 Grad an.
Hier ein paar Beispiele (immer um 90° weiter gedreht):
1.
Heading: 309
X: -9
Y: 11
Z: 6

2.
Heading: 351
X: -14
Y: 2
Z: 7

3.
Heading: 339
X: -27
Y: 10
Z: 7

4.
Heading: 325
X: -23
Y: 16
Z: 6

Das Heading berechne ich so:
angle = atan2((double)y,(double)x) * 180 / 3.14159265 + 180;

Hier der Code:
die Main.c (Hier werden die Funktionen nur ausgefürhrt, die I2c 
funktionen sind weiter unten)
1
#define F_CPU 20000000UL
2
#define BAUD 115200UL
3
#include <util/delay.h>
4
#include <avr/io.h>
5
#include <stdio.h>
6
#include <stdlib.h>
7
#include <math.h>
8
#include "compass.h"
9
10
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)
11
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))
12
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)
13
 
14
#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
15
  #warning Systematischer Fehler der Baudrate grösser 1% und damit zu hoch! 
16
#endif 
17
18
/*void delay_ms(double t)
19
{
20
  for(int i=0;i<t;i++)
21
  {
22
    _delay_ms(1);
23
  }
24
}*/
25
26
void uart_init(void)
27
{
28
  //UART0 
29
    UCSR0B |= (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);    
30
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
31
 
32
    UBRR0H = UBRR_VAL >> 8;
33
    UBRR0L = UBRR_VAL & 0xFF;
34
35
  //UART1 
36
    UCSR1B |= (1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1);                      
37
    UCSR1C |= (1<<UCSZ11)|(1<<UCSZ10); 
38
39
  UBRR1H = UBRR_VAL >> 8;
40
  UBRR1L = UBRR_VAL & 0xFF;
41
}
42
43
int uart_putc(unsigned char c)
44
{
45
    while (!(UCSR0A & (1<<UDRE0)))
46
    ;                             
47
    UDR0 = c;
48
  
49
  while (!(UCSR1A & (1<<UDRE1)))
50
    ;                             
51
    UDR1 = c;                    
52
    return 0;
53
}
54
55
void uart_puts (char *s)
56
{
57
    while (*s)
58
    {
59
        uart_putc(*s);
60
        s++;
61
    }
62
}
63
64
65
int main(void)
66
{
67
  DDRB = 0xff;
68
  uart_init();
69
  delay_ms(100);
70
  char tmp = compass_init();
71
  if(tmp==0)
72
  {
73
    uart_puts("\ncompass init failed\n");  
74
  }
75
  else
76
  {
77
    uart_puts("\ncompass init\n");  
78
  }
79
  delay_ms(100);
80
      
81
    while(1)
82
    {
83
    PORTB = 0xff;
84
        int x,y,z;
85
    tmp = compass_get_axis(&x,&y,&z);
86
    if(tmp==0)
87
    {
88
      uart_puts("\ncompass read failed\n");
89
    }
90
    else
91
    {
92
      int angle= atan2((double)y,(double)x) * 180 / 3.14159265 + 180;
93
      char send[255];
94
      int n = sprintf(send,"\nNew Data:\nHeading: %d\nX: %d\nY: %d\nZ: %d\n",angle,x,y,z);
95
      for(int i=0;i<n;i++)
96
      {
97
        uart_putc((unsigned char)send[i]);
98
      }
99
    }
100
    PORTB = 0x00;
101
    delay_ms(3000);
102
    }
103
}

compass.h
1
#ifndef COMPASS_H_
2
#define COMPASS_H_
3
4
#include "i2cmaster.h"
5
6
#define COMPASS_READ 0x3D
7
#define COMPASS_WRITE 0x3C
8
9
void delay_ms(int t);
10
extern unsigned char compass_init(void);
11
extern unsigned char compass_get_axis(int * x,int * y, int * z);
12
13
#endif /* COMPASS_H_ */

compass.c
1
#include "compass.h"
2
#ifndef F_CPU
3
# warning "F_CPU not defined for compass.h"
4
# define F_CPU 1000000UL
5
#endif
6
7
#include <util/delay.h>
8
void delay_ms(int t)
9
{
10
  for(int i=0;i<t;i++)
11
  {
12
    _delay_ms(1);
13
  }
14
}
15
16
unsigned char compass_init(void)
17
{
18
  unsigned char tmp;
19
  i2c_init();
20
  _delay_ms(10);
21
  tmp = i2c_start(COMPASS_WRITE);
22
  if(tmp!=0)
23
  {
24
    i2c_stop();
25
    return 0;
26
  }
27
  tmp = i2c_write(0x02);
28
  if(tmp!=0)
29
  {
30
    i2c_stop();
31
    return 0;
32
  }
33
  tmp = i2c_write(0x00);
34
  if(tmp!=0)
35
  {
36
    i2c_stop();
37
    return 0;
38
  }
39
  i2c_stop();
40
  return 1;
41
}
42
43
44
unsigned char compass_get_axis(int * x,int * y, int * z)
45
{
46
  unsigned char tmp;
47
  tmp = i2c_start(COMPASS_WRITE);
48
  if(tmp!=0)
49
  {
50
    i2c_stop();
51
    return 0;
52
  }
53
  tmp = i2c_write(0x03);
54
  if(tmp!=0)
55
  {
56
    i2c_stop();
57
    return 0;
58
  }
59
    
60
  tmp =i2c_rep_start(COMPASS_READ);
61
  if(tmp!=0)
62
  {
63
    i2c_stop();
64
    return 0;
65
  }
66
  
67
  *x = i2c_readAck()<<8;
68
  *x |= i2c_readAck();
69
  
70
  *z = i2c_readAck()<<8;
71
  *z |= i2c_readAck();
72
  
73
  *y = i2c_readAck()<<8;
74
  *y |= i2c_readNak();
75
    
76
  i2c_stop();  
77
  return 1;  
78
}

Das Programm ist nur so zum testen zusammen gefuscht.
In der compass_init(), wird lediglich das Register 0x02 auf 0x00 
gesetzt, für den Continuous-measurement mode mit 15Hz (default).
Die compass_get_axis(...) wird alle 3 Sekunden ausgeführt, also das 
passt locker mit 15Hz. In der Funktion wird erst der Register Indexer 
auf 0x03 gesetzt und ab da dann 8 Bytes ausgelesen und in Varibalen 
gespeichert.

Aber irgentwie passt das mit den Werten alles nicht.
habt ihr ne Idee was falsch sein könnte? Wie gesagt habe ich das alles 
nur aus nem Arduino Beispielcode und sollte eigentlich Funtzten.

Danke schon mal im voraus.

MfG
Philipp

von olf79 (Gast)


Lesenswert?

wie ist i2c_readAck deklariert?

wenn nur ein 8-Bit-Wert zurückgegeben wird (so sieht es aus), schieb man 
den beim shiften ohne Typecast ins Nirvana..
*x = i2c_readAck()<<8;

Ich würde mal
*x = ((int)i2c_readAck())<<8;
probieren..

von olf79 (Gast)


Lesenswert?

.. und die Endianess berücksichtigen .. also ggf. muss der Wert 
andersrum zusammengesetzt werden, erst das LowByte, dann das HighByte..

von olf79 (Gast)


Lesenswert?

oder vielleicht so:

unsigned char *pbyte;

pbyte = x;

*pbyte     = i2c_readAck();
*(pbyte+1) = i2c_readAck();

oder halt

*(pbyte+1) = i2c_readAck();
*pbyte     = i2c_readAck();


dann das gleiche mit z und y

von Philipp M. (lord-maricek)


Lesenswert?

Moin,

Hier die Funktionen:
1
unsigned char i2c_readAck(void)
2
{
3
  TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
4
  while(!(TWCR & (1<<TWINT)));    
5
6
    return TWDR;
7
8
}/* i2c_readAck */
9
10
unsigned char i2c_readNak(void)
11
{
12
  TWCR = (1<<TWINT) | (1<<TWEN);
13
  while(!(TWCR & (1<<TWINT)));
14
  
15
    return TWDR;
16
17
}/* i2c_readNak */
Der Typecast funktioniert auch nicht.

Hier ein Auszug ausm Datenblatt:
1
Address Location Name Access
2
00
3
Configuration Register A
4
Read/Write
5
01
6
Configuration Register B
7
Read/Write
8
02
9
Mode Register
10
Read/Write
11
03
12
Data Output X MSB Register
13
Read
14
04
15
Data Output X LSB Register
16
Read
17
05
18
Data Output Z MSB Register
19
Read
20
06
21
Data Output Z LSB Register
22
Read
23
07
24
Data Output Y MSB Register
25
Read
26
08
27
Data Output Y LSB Register
28
Read
29
09
30
Status Register
31
Read
32
10
33
Identification Register A
34
Read
35
11
36
Identification Register B
37
Read
38
12
39
Identification Register C
40
Read

Zuerst kommt MSB dann LSB.

MfG
Philipp

von Philipp M. (lord-maricek)


Lesenswert?

1
unsigned char *pByte;
2
  pByte = x;
3
  *(pByte+1) = i2c_readAck();
4
  *pByte = i2c_readAck();
5
  
6
  pByte = z;
7
  *(pByte+1) = i2c_readAck();
8
  *pByte = i2c_readAck();
9
  
10
  pByte = y;
11
  *(pByte+1) = i2c_readAck();
12
  *pByte = i2c_readAck();

So isses wieder wie vorher, andersherum, sehen sind die Achsen schon im 
Tausender Bereich, so wies sein soll, aber ein vernünftiges Heading 
bekommen wir trotzdem immer noch nicht raus.

MfG
Philipp

von Interpreter (Gast)


Lesenswert?

Philipp Maricek schrieb:
> Hier ein Auszug ausm Datenblatt:
> 00
> Configuration Register A
>...

Mit dem unformatierten Zeugs meinst du wahrscheinlich:
1
Address Location Name           Access
2
00  Configuration Register A    Read/Write
3
01  Configuration Register B    Read/Write
4
02  Mode Register               Read/Write
5
03  Data Output X MSB Register  Read
6
04  Data Output X LSB Register  Read
7
05  Data Output Z MSB Register  Read
8
06  Data Output Z LSB Register  Read
9
07  Data Output Y MSB Register  Read
10
08  Data Output Y LSB Register  Read
11
09  Status Register             Read
12
10  Identification Register A   Read
13
11  Identification Register B   Read
14
12  Identification Register C   Read

von Wolfgang H. (Firma: AknF) (wolfgang_horn)


Angehängte Dateien:

Lesenswert?

Hi, Philipp,

> Wenn ich den Kompass drehe, zeigt er immer nur Werte von 280 Grad bis
> 360 Grad an.

Das ist typisches Symptom für Fehlkalibrierung: Der Sensor misst nicht 
nur das geomagnetische Feld, sondern auch die magnetischen Felder, die 
von magnetischen Remanenzen in Stahl und Eisen erzeugt werden sowie von 
elektrischen Strömen.

Mancher Autofahrer kauft sich einen neckischen Kompass und wundert sich, 
dass das Einschalten der Heckscheibenheizung den Nordpol ferngesteuert 
springen, und der Nordwert mit dem Gas einstellen lässt.

Dagegen hilft:
1. Stationäre Kalibrierfehler: Neukalibrierung mitsamt der gesamten 
mobilen Plattform (für die häufigsten Fälle siehe Anhang).
2. Nicht-stationäre Fehler durch veränderliche Stromflüsse auf der 
Platine: Außerordentlich problematisch. Ansatz zur Minimierung: Sensor 
zur Messung eines solchen Stromflusses, dann dessen Beitrag aus den X-, 
Y- und Z-Komponente heraus rechnen.

Nicht ohne Grund werden Kompasse in Flugzeugen und Schiffen in extremen 
Positionen montiert, von tief im Kiel bis zum Ende der Tragfläche.

Ciao
Wolfgang Horn

von Wolfgang H. (Firma: AknF) (wolfgang_horn)


Lesenswert?

Hi, Philipp, zweiter Punkt.

> Das Heading berechne ich so:
> angle = atan2((double)y,(double)x) * 180 / 3.14159265 + 180;

Deine Formel ist schön einfach. Ich hatte für denselben Zweck eine mit 
Fallunterscheidungen, je nach Quadranten habe ich die Vorzeichen 
gewechselt.

Ciao
Wolfgang Horn

von Michael A. (Gast)


Lesenswert?

Philipp Maricek schrieb:
> Hier ein paar Beispiele (immer um 90° weiter gedreht):
> 1.
> Heading: 309
> X: -9
> Y: 11
> Z: 6
> ...

Bei deinen Y-Werten ist die Verstärkung im Vergleich zu X fast einen 
Faktor 2 zu niedrig und du hast Offsets von x0=-18 und y0=-10 auf deinen 
Werten. Das einfachste ist, wenn du den Sensor einmal langsam um 360° 
drehst und deine XY-Wertepaare in ein XY-Diagramm einträgst. Das 
Erdmagnetfeld beschreibt dann idealerweise einen Kreis und der 
Permanentmagnetvektor deines Aufbaus verschiebt den Mittelpunkt aus dem 
Ursprung.
Wenn deine Daten auf einem Ursprungskreis liegen, kannst du mit dem Atan 
anfangen zu rechnen.

von Philipp M. (lord-maricek)


Lesenswert?

Hallo,

hab nochmaln bisschen getestet, und festgestellt, dass die x,y,z 
Register wahrscheinluch vertauscht sind. Ich muss noch rausfinden, wie 
genau.

MfG
Philipp

von Florian G. (floppes)


Lesenswert?

Ich verwende einen sehr ähnlichen Code mit dem HMC5883L und habe das 
gleiche Problem, dass meine Berechnete Richtung sich nicht ändert, egal 
wie ich den Sensor drehe.

Hast du mittlerweile eine Lösung gefunden?

von Tbd (ids2001)


Lesenswert?

Hallo,

ich schließe mich hier den Thread auch an. Habe ein ähnliches Problem.
lg
Dieter

von Florian G. (floppes)


Lesenswert?

Ich habe das Problem bei mir gefunden: mein Magnetometer saß zu nah an 
einem Lautsprecher mit Magnet. 5 cm weiter weg liefert es jetzt gute 
Werte.

von Randy S. (randy_s)


Lesenswert?

Hallo,

hat mittlerweile jemand eine Lösung des Problems? Bei mir kommen leider 
auch nur Werte oberhalb 280...

... habt ihr MSB und LSB als signed int oder unsigned int laufen?

Vielen Dank schon mal für Eure Hilfe!!! :-)

von Frank M. (frank_m35)


Lesenswert?

Euer Code?
Und bevor ihr versucht den Winkel auszurechnen, wie sehen die RAW Daten 
denn aus? Ergeben die Sinn? Was passiert wenn ihr eure Platine um 360 
Grad um eine Achse dreht? Lasst euch die Werte per RS232 oder LCD 
ständig ausgeben.
Was passiert wenn ihr einen schwachen Magnet von weit außen annähert?
Habt ihr magnetisierbare Objekte in dichter nähe? (Eisen, Schrauben, 
Lautsprecher, ...)
Habt ihr den IC selber gelötet oder fertig auf einer Platine bestellt? 
Falls selbst gelötet, habt ihr beim Schaltungsentwurf die im Datenblatt 
stehenden Dinge beachtet?

Hier mein Code der auf einem PIC24 funktioniert. Ich habe ihn nicht 
exakt getestet, da ich den Magnetsensor bisher nicht gebraucht habe, 
jedoch hat er bei den Tests sinnvolle Werte geliefert und auch der 
Winkel wurde sinnvoll berechnet.
Die Formel für den Winkel ist die selbe die der Threadstarter benutzt 
und entstammt aus einer Homepage die ich bei der Formel angegeben habe. 
Vielleicht solltet ihr die Homepage mal genauer durcharbeiten, denn dort 
stehen auch weitere nütliche Links.
1
int MAG_init();
2
int MAG_set_conf(char subaddr, char config);
3
unsigned char MAG_get_conf(char subaddr);
4
void MAG_get_heading(int* Xptr, int* Yptr, int* Zptr);
5
int MAG_get_angle();
6
7
#define MAG_ADDRESS_WRITE      0x3C
8
#define MAG_ADDRESS_READ      0x3D
9
10
#define MAG_CONF_REG_A        0
11
#define MAG_CONF_REG_B        1
12
#define MAG_MODE_REG        2
13
#define MAG_DATA_X_H        3
14
#define MAG_DATA_X_L        4
15
#define MAG_DATA_Y_H        5
16
#define MAG_DATA_Y_L        6
17
#define MAG_DATA_Z_H        7
18
#define MAG_DATA_Z_L        8
19
#define MAG_STAT_REG        9
20
#define MAG_ID_REG_A        10
21
#define MAG_ID_REG_B        11
22
#define MAG_ID_REG_C        12
23
24
25
/*****************************************************************************
26
 * Function:   MAG_init
27
 * Overview:   function initiates Magnetic sensor
28
 * Input:     None
29
 * Output:     0 successful, 1 error occured
30
 *****************************************************************************/
31
int MAG_init()
32
{
33
  int error=0;
34
35
  // 8-average, 15 Hz default, normal measurement
36
  error |= MAG_set_conf(MAG_CONF_REG_A, 0x70);
37
38
  // Gain=5
39
  error |= MAG_set_conf(MAG_CONF_REG_B, 0xA0);
40
41
  // Continuous measurement mode
42
  error |= MAG_set_conf(MAG_MODE_REG, 0x00);
43
44
  return error;
45
}
46
47
48
/*****************************************************************************
49
 * Function:   MAG_set_conf
50
 * Overview:   set value of register
51
 * Input:     None
52
 * Output:     0 successful, 1 error occured
53
 *****************************************************************************/
54
int MAG_set_conf(char subaddr, char config)
55
{
56
  int error = 0; 
57
58
  I2C_start();
59
  error |= I2C_send_byte(MAG_ADDRESS_WRITE);
60
  error |= I2C_send_byte(subaddr);
61
  error |= I2C_send_byte(config);
62
  I2C_stop();
63
64
  return error;
65
}
66
67
68
/*****************************************************************************
69
 * Function:   MAG_get_conf
70
 * Overview:   get value of register
71
 * Input:     address of register to read
72
 * Output:     value of read register
73
 *****************************************************************************/
74
unsigned char MAG_get_conf(char subaddr)
75
{
76
  unsigned char temp;
77
  
78
  // send address  
79
  I2C_start();
80
  I2C_send_byte(MAG_ADDRESS_WRITE);
81
  I2C_send_byte(subaddr);
82
  
83
  // receive config
84
  I2C_restart();
85
  I2C_send_byte(MAG_ADDRESS_READ);
86
  temp = I2C_receive_byte(I2C_NACK);
87
  
88
  //I2C_Ack();  
89
  I2C_stop();
90
  
91
  return temp;
92
}
93
94
95
/*****************************************************************************
96
 * Function:   MAG_get_heading
97
 * Overview:   retrieve all 6 bytes, containing x, y, and z values
98
 * Input:     pointers for data to store
99
 * Output:     assembled 16-bit X, Y and Z value
100
 *****************************************************************************/
101
void MAG_get_heading(int* Xptr, int* Yptr, int* Zptr)
102
{
103
  // send address  
104
  I2C_start();
105
  I2C_send_byte(MAG_ADDRESS_WRITE);
106
  I2C_send_byte(MAG_DATA_X_H);
107
108
  // receive all 6 bytes
109
  I2C_restart();
110
  I2C_send_byte(MAG_ADDRESS_READ);
111
  *Xptr =  I2C_receive_byte(I2C_ACK)<<8;
112
  *Xptr |= I2C_receive_byte(I2C_ACK);
113
  *Zptr =  I2C_receive_byte(I2C_ACK)<<8;
114
  *Zptr |= I2C_receive_byte(I2C_ACK);
115
  *Yptr =  I2C_receive_byte(I2C_ACK)<<8;
116
  *Yptr |= I2C_receive_byte(I2C_NACK);
117
  I2C_stop();
118
}
119
120
121
/*****************************************************************************
122
 * Function:   MAG_get_angle
123
 * Overview:   calculates the angle based on the received values
124
 * Input:     - 
125
 * Output:     calculated 16-bit angle
126
 *****************************************************************************/
127
int MAG_get_angle()
128
{
129
  int X, Y, Z;
130
  MAG_get_heading(&X, &Y, &Z);
131
  
132
  // calibrate YOUR compass, so compass raw x,y (,z) data is centered around 0,0,0 axis!
133
  // To do this read do an XY scatter plot for 2D xy data, and just "read"
134
  //   work out or guess the offset to center the data (you need to rotate the compass to get lots of data pairs)
135
  //   for my compass the values where x=-100, y=-100 (did not work out z axis!)
136
  // http://usabledevices.com/wiki/index.php?title=Compass_Honeywell_HMC5883L
137
  const int x_offset = 0;
138
  const int y_offset = 0;
139
  //const int z_offset = 0;
140
  
141
  return atan2((double)Y - y_offset,(double)X - x_offset)* (180 / 3.14159265) +180; // angle in degrees
142
143
}

von Moses (Gast)


Lesenswert?

"Vielleicht solltet ihr die Homepage mal genauer durcharbeiten, denn 
dort
stehen auch weitere nütliche Links."

404 - Page not found...

Gibts das noch irgendwo anders?

Habe das gleiche Problem.

von Frank M. (frank_m35)


Lesenswert?

Definiere gleiches Problem.
Wie sieht dein physikalischer Aufbau aus?
Meinen Source code mal getestet, selbes Problem?

von Michael (Gast)


Lesenswert?

Moses schrieb:
> Habe das gleiche Problem.

Dann zeige doch mal einen Scatterplot von deinen Rohdaten, so wie sie 
aus dem Sensor kommen.

von Moses (Gast)


Lesenswert?

Ok, das erste Problem, dass da irgendwelche sinnlosen, wilden Kurswerte 
berechnet wurden, alle zwischen 240 und 310 Grad, hat sich erledigt. Ich 
sollte einfach lesen lernen und darauf achten, in welcher Reihenfolge 
die x-, y- und z-Werte aus dem Sensor kommen...

Was jetzt noch bleibt ist, dass die Kurswerte noch heftig rumeiern.
Also wenn ich den Kompass um 90 Grad drehe erwarte ich eigentlich auch 
eine Änderung der Gradzahl um diesen Betrag.
Wenn ich das aber mache (Orientierung an der Tischkante, immer 90 Grad 
weitergedreht, ebene Fläche) kommen diese Werte raus (auf den nächsten 
5er gerundet):
   x      y      z  kurs
 130    125   -435    40
-230     35   -430   170
-155   -335   -435   245
 195   -275   -450   305

Statt immer ca 90 Grad sind die Differenzen zwischen 60 und 130 Grad.
Im Moment bin ich ein bisschen überfragt, wie ich diese großen 
Abweichungen kompensieren kann.

Der relevante Teil des Codes ist:

Init des HMC:

  i2c_start_wait(COMPASS_ADDR_WRITE);
  i2c_write(0x02);
  i2c_write(0x00);
  i2c_stop();

  i2c_start_wait(COMPASS_ADDR_WRITE);
  i2c_write(0x01);
  i2c_write(0x20);
  i2c_stop();

  i2c_start_wait(COMPASS_ADDR_WRITE);
  i2c_write(0x00);
  i2c_write(0x10);
  i2c_stop();

  _delay_ms(6);

Berechnung in der Hauptschleife, wird mit 15 Hz ausgeführt:

  i2c_start_wait(COMPASS_ADDR_WRITE);
  i2c_write(0x03);
  i2c_stop();

  i2c_start_wait(COMPASS_ADDR_READ);
  x_raw = i2c_read(1); x_raw <<= 8;
  x_raw |= i2c_read(1);
  z_raw = i2c_read(1); z_raw <<= 8;
  z_raw |= i2c_read(1);
  y_raw = i2c_read(1); y_raw <<= 8;
  y_raw |= i2c_read(0);
  i2c_stop();

  float heading = 0;
  heading = atan2((double)y_raw,(double)x_raw);
  heading = heading * 180 / M_PI;
  if (heading < 0) heading = heading + 360;
  if (heading >= 360) heading = heading - 360;

Ich hoffe, ihr habt dazu eine Idee.

LG

von Randy Stiegler (Gast)


Lesenswert?

Da ich das gleiche Problem kürzlich auch hatte ...

Dein Problem ist, dass dein Sensor nicht kalibriert ist.
D.h. in der Umgebung in der er eingebaut ist bzw momentan getestet wird
musst du offset Werte ermitteln und hinterlegen.

Schreib ein Kalibrier-Programm, welches ständig den größten und
kleinsten Messwert jeder Achse speichert
if (messwert-x < min-x ) min-x = messwert-x...

Nachdem du deinen Sensor in jede erdenkliche Lage gebracht hast, weißt
du nun in welche Richtung dein Offset geht.
-420 bis 420 wäre ein offset=0
-440 bis 400 ergibt offset=-20

Nach deiner Kalibrierung kannst du nun bei jedem Messwert in deiner
Anwendung -20 abziehen ... Also jeden Messwert dieser Achse +20 rechnen.
Dann stimmt dein Winkel hoffentlich.

Evtl. noch die Magnetfeldkorrektur für deine geografische Lage
hinterlegen um vom mag Norden auf geo Norden zu kommen - fertig.

Hoffe das hilft - gib bitte feedback

von Moses (Gast)


Lesenswert?

wahhh ich könnt dich jetzt sowas von abknutschen... wenn du ne Frau 
wärst ;)

Der Tipp war goldrichtig, jetzt zeigt er sauber die richtigen Werte an.
Danke!

Hab immer nicht verstanden, was die in den Quellcodes vom Multiwiikopter 
oder bei MicroKopter da mit der Konstante nach dem Abgleich machen bzw 
wie die die ermitteln, aber jetzt hats klick gemacht :)

Nochmal vielen vielen Dank!

von Randy Stiegler (Gast)


Lesenswert?

Haha :-)
Kein Problem!

Falls du rausfindest wie man die Neigung mit in die Rechnung einbezieht 
... Ich habe noch einen ACC und Gyro mit kalman-filter und PID fertig 
... Aber mit dem Magnetfeld bin ich noch nicht so sehr zufrieden.
Zwar bekomme ich nun ordentliche Werte zwischen 0 und 360, aber ich bin 
der Meinung, dass Norden doch noch 20° daneben ist...?!

Kalibrierung allerdings auf Tisch mit PC(WLAN, Bluetooth...), Handy und 
vielen elektr. Geräten.
Wo/wie hast du kalibriert?

von Jonathan (Gast)


Lesenswert?

Moses schrieb:
> Im Moment bin ich ein bisschen überfragt, wie ich diese großen
> Abweichungen kompensieren kann.

Erstmal solltest du das ein bisschen genauer messen. Zeichne doch 
einfach mal die (x,y,z) Datenpunkte auf, während du den Kompaß langsam 
in allen Raumrichtungen drehst. Jeweils zwei der Parameter gegeneinander 
geplottet müssen einen Kreis ergeben. Erst wenn da saubere Kreise 
rauskommen, macht es Sinn daraus Winkel zu berechnen.

von Moses (Gast)


Lesenswert?

Auf dem Schreibtisch, mindestens 30 cm von allen elektrischen Geräten 
entfernt.
Für die Praxis nachher muss ich dann eh eine Routine einbauen, die die 
Minimal- und Maximalwerte ermittelt und daraus das Offset bestimmt.
Mir gings jetzt erstmal um die generelle Lösung des Problems.

Die Neigungskompensation ist dann der nächste Schritt. Gedacht ist das 
Ganze für einen Quadrokopter. Von der Steuerplatine bekomme ich die 
aktuellen Neigungswerte in x- und y-Richtung (Gyro und ACC bereits 
fusioniert). Die muss ich mir dann noch normieren und dann werde ich das 
mal mit diesen Formeln hier versuchen:
Quelle: 
http://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial

von Randy Stiegler (Gast)


Lesenswert?

Ich glaube wir sollten Kontakt bleiben - baue auch einen quadcopter.
Momentan hängt das aber etwas mit der tiltcompensation bei mir.

Falls du Hilfe brauchst bei Gyro, Acc, Kalmanfilter, PID ... Habe ich 
soweit alles laufen.
Die gleichen Formeln habe ich auch im Controller aber irgendwie stimmt 
da was nicht. Kann auch daran liegen, dass die falschen Werte verrechnet 
werden. Ist ein komplettes board (gy80) - vielleicht sind die Achsen 
nicht so wirklich kompatibel - z.B. Winkelx+ entspricht magx- oder so.

Werde mir das heute anschauen - Wenn du das hinbekommst melde dich mal 
bitte ;-)

von Michael (Gast)


Lesenswert?

Moses schrieb:
> Für die Praxis nachher muss ich dann eh eine Routine einbauen, die die
> Minimal- und Maximalwerte ermittelt und daraus das Offset bestimmt.

Offset reicht nicht, die Skalierungsfaktoren der Achsen müssen auch 
stimmen.

von Randy Stiegler (Gast)


Lesenswert?

... D.h. ich muss selftest bit setzen - dann zeigt er mir feste Werte 
an. Und dann?

Da wusste ich nicht auf welchen Wert ich skalieren soll

von Randy Stiegler (Gast)


Lesenswert?

Achso und ganz wichtig - in welcher Reihenfolge?
Ich nehme mal an erst nach der Kombination msb&lsb skalieren und danach 
offset finden...richtig?

von Randy S. (Gast)


Angehängte Dateien:

Lesenswert?

Ok ... Habe im Datenblatt die Antwort gefunden (siehe Anhang)
Nachdem msb&lsb kombiniert wurden wird der scale_factor multipliziert.
Bei +-1.3 Ga z.B. 0.92

Mit diesen Werten dann Offset bestimmen und bei jeder Messung abziehen.
Wenn ich mich irre korrigiert mich bitte.

von Moses (Gast)


Lesenswert?

Hallo Michael,
hast du dafür mal ein konkretes Beispiel? Mit der Skalierung stehe ich 
noch etwas auf dem Schlauch.
Der Self-Test zeigt doch meines Erachtens nur, ob die ausgegebenen Werte 
bei der gewählten Verstärkung (Gain) innerhalb akzeptabler Grenzen 
liegen. Wenn nicht, muss man Gain so viele Stufen runterdrehen, bis es 
passt. Und wenn die Werte viel zu klein sind, dann eben Gain etwas nach 
oben.
Oder haben ich das falsch verstanden?
Dieses Beispiel im Datenblatt
1
If Gain = 6, self test limits are:
2
Low Limit = 243 * 330/390 = 206
3
High Limit = 575 * 330/390 = 487
zeigt doch nur, wie man die in den Spezifikationen für Gain=5 (+- 4.7 
Ga) angegebenen Grenzwerte für andere Verstärkungen berechnet, also
Grenzwert für Gain=5 mal neuer Verstärkungsfaktor geteilt durch Faktor 
für Gain=5 gibt den Grenzwert für die aktuelle Verstärkung.
Damit muss man doch im praktischen Betrieb nachher nicht mehr arbeiten, 
oder?

Was später vielleicht noch interessant wäre ist die 
Temperaturkompensation. Wenn ich das richtig verstanden habe muss mann 
dazu einfach immer wieder mal für ein paar Zyklen (bei 15 Hz ist das ja 
grad mal ein Wimpernschlag und das auch nur alle paar Sekunden) in den 
Selftestmodus schalten und den Maximalwert mit dem Referenzwert für eine 
bestimmte Temperatur vergleichen. Aktueller Maximalwert / Referenzwert = 
Faktor, mit dem dann alle Messwerte im Normalmodus multipliziert werden. 
Danach dann wieder in den Normalmodus schalten.
Klingt einfach und ist es hoffentlich auch...

LG

von Randy S. (mcstieg)


Lesenswert?

Moses schrieb:
> Hallo Michael,
> hast du dafür mal ein konkretes Beispiel? Mit der Skalierung stehe ich
> noch etwas auf dem Schlauch.

Das habe ich einen Post weiter oben beschrieben. Schau dir das Bild dazu 
an. Du erhälst vom Sensor msb und lsb, machst eine 16bit draus und dann 
kommt der Faktor 0.92 (für +-1.3Ga, andere siehe Bild) dazu.
Damit ist alles getan - jetzt kann der Winkel berechnet werden.

Der Self test zeigt dir (nehme ich an):
...ob du msb & lsb korrekt kombiniert hast.
...ob du, wie du selbst beschrieben hast, mehr oder weniger Gain 
benötigst
...ob die Temp anders ist.

Ich würde mal behaupten, dass man den Selftest während des Fluges nicht 
benötigt - ich lasse mich aber gern eines besseren belehren.
Bisher habe ich die genaue Anwendung des Selftests nicht verstanden.

von Moses (Gast)


Lesenswert?

Nun, jede Achse mit 0.92 zu multiplizieren macht doch eigentlich nur 
zusätzlichen Aufwand für den µC (Kommazahlen, igitt...).
Und am Ende ist es doch egal, ob man atn2(100/100) oder atn2(92/92) 
rechnet, der Faktor in der Klammer ist der gleiche.
Auch bei den Kompensationsformeln für die Neigung sollte das doch kein 
Problem sein. Mit dem Faktor kommt Xh und Yh eben um den Faktor 0.92 
kleiner raus und in der Winkelberechnung fliegt der Faktor doch eh raus.
Oder bin ich da jetzt auf dem falschen Dampfer?

Zur Temperaturkompensierung: Das ist wohl dann sinnvoll, wenn man im 
Schatten bei ungefähr Zimmertemperatur startet und dann in die pralle 
Sonne geht. In dem Beispiel im Datenblatt ist ja eine recht große 
Differenz (mal eben um 100 runter mit dem Wert). Aber die absolute 
Differenz ist ja auch unerheblich. Interessanter ist, wie groß die 
Unterschiede zwischen den Achsen bei Zu- bzw Abnahme werden. Also ob bei 
dem gleichen Temperaturunterschied die eine Achse um 100 abnimmt während 
die andere vielleicht nur 80 weniger zeigt. Dann passt die Gradzahl am 
Ende nicht mehr. Ich denke mal, das muss man einfach ausprobieren und 
wenn die Differenzen zu groß werden eben auch im Flug gelegentlich für 
ein paar Takte in den Selftest schalten, um die Werte des Selftests mit 
den Referenzwerten bei Zimmertemperatur zu vergleichen und danach 
entsprechend zu korrigieren.

LG

von Moses (Gast)


Lesenswert?

Auch wenn hier nichts mehr passiert ist wollte ich doch nochmal 
Rückmeldung zum aktuellen Stand geben.
Nach längerer Pause (zu viel um die Ohren...) habe ich mich jetzt 
nochmal an den Kompass gesetzt und siehe da, es funktioniert.
Als Formeln für die Neigungskompensation habe ich jetzt folgende 
verwendet:
Pitch und roll bekomme ich von der Steuerung des Copters, der Winkel 
ändert sich bei Neigung jetzt praktisch nicht mehr.

Als nächstes werde ich die Formeln nochmal testen, die ich weiter oben 
genannt hatte. Die hatte ich erst verworfen weil der Winkel mit denen 
total rumgesponnen hat, dann aber bemerkt, dass ich mich bei einer 
vertippt hatte und yh statt xh genommen hatte. Damit konnte es ja nicht 
funktionieren.
Wie gesagt, Test steht noch aus.

Als nächstes muss jetzt die Regelung her und dann ab damit in die Luft.

LG

von Moses (Gast)


Lesenswert?

winkel = atn2(yh, xh) muss das natürlich heißen...
LG

von Attila C. (attila)


Lesenswert?

Hallo!

Ich spiele auch grade mit einem HCM5883L rum.

1) Vielen Dank für die Beiträge hier, sie haben mir sehr geholfen.
2) Faszinierend was der Sensor für so wenig Geld leistet!
3) Ich glaube bei dieser Formel:

angle = atan2((double)y,(double)x) * 180 / 3.14159265 + 180;

muss es am Ende +179 heißen da man sonst einen Winkel von 360 und 0 
erhält.

Ich hoffe das die Beteiligten dieses Threads selbigen noch beobachten da 
ich mich gerne weiter austauschen würde!

Gruß

von Attila C. (attila)


Lesenswert?

Hallo nochmal!

Auf die Gefahr hin mich hier zu weit aus dem Fenster zu lehnen:

Eine Neigungs-Kompensation? Wozu denn? Die Verhältnisse der Vektoren X 
und Y bleiben doch immer gleich?

Gruß

von Randy (Gast)


Lesenswert?

Ja ich würde sagen das war zu weit ;)

Probier es mal ohne Kompensation aus und neige den Sensor ...
Dann ist Norden überall - aber nicht im Norden.

Hintergrund ist, dass der gemessene Anteil X & Y dann real mit der 
Z-Komponente überlagert ist. Diesen Anteil musst du natürlich mit 
beachten um X & Y im richtigen Maß zu korrigieren.

von Attila C. (attila)


Lesenswert?

Randy!

Ich stimme Dir 100% zu! :-)

Jetzt fehlt nur noch eine Berechnung der Lage im Raum nach dem Prinzip: 
Der Einfluss von Z auf X und Y ist ja vorhersehbar!

Wer hat die Formel? :-) :-) :-)

Gruß

von Jan H. (janiiix3)


Lesenswert?

Randy Stiegler schrieb:
> Schreib ein Kalibrier-Programm, welches ständig den größten und
> kleinsten Messwert jeder Achse speichert
> if (messwert-x < min-x ) min-x = messwert-x...

Hallo,

sorry wenn ich noch mal Nachfrage.
Ein "Offset" von ~500 ist bisschen zu viel des guten oder :D?

Den Offset ermittle ich folgendermaßen:
1
    compass_get_axis(&x,&y,&z);
2
      
3
    if (x < min_x )
4
    {
5
      min_x = x;
6
    }     
7
      
8
    if (x > max_x )
9
    {
10
      max_x = x;
11
    }  
12
    
13
    char Buffer[20];
14
15
    sprintf(Buffer,"Max_X : %d   Min_X : %d",max_x,min_x);
16
    uart_puts(Buffer);
17
    uart_puts("\n\r");

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.