PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] MPU-6000 per SPI ansteuern



crabtack
24.11.2014, 13:45
Hallo!

Ich versuche einen MPU-6000 per SPI mit einem Atxmega128A1 anzusteuern.
Soll für einen Quadrocopter verwendet werden, aber ist ja auch egal.
Das Problem ist, dass er überhaupt keine Daten ausgibt.
Zuerst dachte ich, dass das IC vielleicht beim Reflowlöten zerstört wurde, daher habe ich es gegen ein fertig verlötetes IC auf einem Breakoutboard ausgetauscht, das kann ja praktisch nicht kaputt sein.
Dann habe ich die Signale gemessen, die am MPU-6000 ankommen mit dem Oszilloskop gemessen, Clock, Chipselect und MOSI kommen durch und es wird auch der korrekte Wert per MISO gesendet (194).
Damit möchte ich ihm sagen, dass ich das Register 66 Auslesen (Untereres Byte des Temperaturmesswerts).
Die 194 ergibt sich durch Hinzufügen des MSBs, welches Signalisiert, dass das Register ausgelesen werden soll.

Das Problem ist, dass SDO einfach garnichts macht, bleibt durchgehen low.
Also es kommen nur Nullen an.
Hat jemand eine Idee, woran das liegen kann?

Hier der Code:


#include <avr/io.h>
#include "Init.h"




int main(void)
{
Init::Clock_Init();
Init::USART_Init();
PORTH.DIRSET = 0xFF;
PORTH.OUTSET = 0x00;

PORTC.DIRSET = 0b10110000;
PORTC.OUTSET = 0b00010000;
SPIC.CTRL = 0b01011111; // spi master, spi mode 0
SPIC.INTCTRL = 0x3; // assign high priority to SPIF interrupts

PMIC.CTRL = 0x04; // enable high priority interrupts
sei(); // enable global interrupts

while(SPIC.STATUS & 0x80) {
char rx_byte = SPIC.DATA; // flush spi receive buffer
}
while(1)
{
PORTC.OUT &= ~(1 <<PIN4);
_delay_us(10);
SPIC.DATA = 194;
_delay_us(50);
SPIC.DATA = 194;
_delay_us(100);
PORTC.OUT |= (1 << PIN4);
_delay_us(10);
//_delay_ms(200);

}
}


ISR(SPIC_INT_vect) {
while (!(USARTD1.STATUS & USART_DREIF_bm));
USARTD1.DATA = SPIC.DATA;
PORTH.OUTTGL = 0x01;
}




mfg
Olaf

Che Guevara
24.11.2014, 14:05
Hi,

ich benutze auch den MPU6000 in einem Kopter, auch über SPI ;)
Mir fällt auf, dass in der ISR SPIF geschrieben ist, das ist wohl schonmal ein Fehler.
Wenn ich es richtig sehe, stellst du Mode 2 ein, der MPU will aber Mode 0 (3 geht glaube ich auch).
Außerdem solltest du den MPU initialisieren (Register Power etc..), das kannst du mit max. 1MHz SPI Takt machen.
Die Sensor Register (ACC, Temp, Gyro) kannst du mit max. 20MHz auslesen, alle anderen wie gesagt nur mit 1MHz.
Zum Testen würde ich erstmal anfangen, den SPI ohne ISR zu betreiben und beim MPU das WhoIAm Register auszulesen (nähere Infos im DB).

Hier mal meine SPI Routine:


uint8_t SpiTransfer(uint8_t data)
{
uint8_t dump;
SPID.DATA = data;
while(!(SPID.STATUS & SPI_IF_bm))
dump = SPID.STATUS;
return SPID.DATA;
}


Gruß
Chris

crabtack
24.11.2014, 14:17
Hi!

Danke, für die schnelle Antwort.
Ich habe schon viele Beiträge von dir durchgelesen, um an Informationen zu kommen, das war schonmal hilfreich :)
Die verschiedenen Modi habe ich alle durchprobiert, ist immer das gleiche Ergebnis.
Aber so brauche ich ab jetzt nur noch Modus 0 versuchen.
Wenn ich das im Datenblatt richtig verstanden habe ist das Initialisieren nicht zwangsläufig notwendig, um Register auszulesen, aber das werde ich definitiv noch machen, wenn die Kommunikation an sich läuft.
Fehlen in deiner Funktion nicht noch Wartezeiten und die Steuerung der CS Leitung?
Oder kümmert sich der Xmega da automatisch drum?

mfg
Olaf

Che Guevara
24.11.2014, 14:31
Jein, lt. DB sollte man gleich die Initialisierung machen, damit der MPU bei CS = High nicht in den I2C Modus fällt. Das ist aber eher eine Sicherheitsmaßnahme, die nicht unbedingt sein muss, das kann also hier nicht der Fehler sein.
Die CS Leitung setze ich außerhalb der Funktion, da ich ja sonst immer nur ein Byte senden / empfangen könnnte.
Hier mal meine Lese-Routine:


uint8_t dump;
MpuCsLow;
dump = SpiTransfer(0xBB);
ax.bytes.byte2 = SpiTransfer(0x00);
ax.bytes.byte1 = SpiTransfer(0x00);
ay.bytes.byte2 = SpiTransfer(0x00);
ay.bytes.byte1 = SpiTransfer(0x00);
az.bytes.byte2 = SpiTransfer(0x00);
az.bytes.byte1 = SpiTransfer(0x00);
t.bytes.byte2 = SpiTransfer(0x00);
t.bytes.byte1 = SpiTransfer(0x00);
gx.bytes.byte2 = SpiTransfer(0x00);
gx.bytes.byte1 = SpiTransfer(0x00);
gy.bytes.byte2 = SpiTransfer(0x00);
gy.bytes.byte1 = SpiTransfer(0x00);
gz.bytes.byte2 = SpiTransfer(0x00);
gz.bytes.byte1 = SpiTransfer(0x00);
MpuCsHigh;

Wo sollten den da Wartezeiten sein? bzw. für was? Ich hab da keine.

Gruß
Chris

EDIT:
Hab hier mal schnell was zusammengezimmert, sollte funktionieren, musst nur noch ne Ausgabe o.ä. einbauen.


/*
MPU6000 SPI Test
*/

#define F_CPU 32000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include <util/twi.h>
#include <math.h>
#include <avr/eeprom.h>
#include <stdlib.h>



#define MpuCsLow (PORTC.OUTCLR = PIN4_bm)
#define MpuCsHigh (PORTC.OUTSET = PIN4_bm)


typedef union{
int16_t integer;
struct{
uint8_t byte1;
uint8_t byte2;
}bytes;
}int16byte;


int main(void);

void InitClock(void);
void InitSpi(void);

uint8_t SpiTransfer(uint8_t data);

void InitMpu(void);
void ReadMpu(void);



int16byte ax;
int16byte ay;
int16byte az;
int16byte t;
int16byte gx;
int16byte gy;
int16byte gz;

int16_t GyroX;
int16_t GyroY;
int16_t GyroZ;
int16_t Temperature;
int16_t AccX;
int16_t AccY;
int16_t AccZ;


int main(void)
{
InitClock();
InitSpi();

_delay_ms(50);
InitMpu();
_delay_ms(50);

//16MHz SPI
SPIC.CTRL = SPI_CLK2X_bm | SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESCALER_DIV4_gc;

while(1)
{
ReadMpu();
//Print Data

_delay_ms(10);
}
}


void InitClock(void)
{
OSC.CTRL |= OSC_RC32MEN_bm;
while(!(OSC.STATUS & OSC_RC32MRDY_bm));
CCP = CCP_IOREG_gc;
CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
}

void InitSpi(void)
{
PORTC.DIRSET = PIN7_bm | PIN5_bm | PIN4_bm;

//1MHz SPI
SPIC.CTRL = SPI_CLK2X_bm | SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESCALER_DIV64_gc;

MpuCsHigh;
}

uint8_t SpiTransfer(uint8_t data)
{
uint8_t dump;
SPIC.DATA = data;
while(!(SPIC.STATUS & SPI_IF_bm))
dump = SPIC.STATUS;
return SPIC.DATA;
}

void InitMpu(void)
{
uint8_t dump;

MpuCsLow;
dump = SpiTransfer(106);
dump = SpiTransfer(0x10);
MpuCsHigh;
_delay_ms(1);
MpuCsLow;
dump = SpiTransfer(25);
dump = SpiTransfer(0x00);
MpuCsHigh;
_delay_ms(1);
MpuCsLow;
dump = SpiTransfer(26);
dump = SpiTransfer(0x03);
MpuCsHigh;
_delay_ms(1);
MpuCsLow;
dump = SpiTransfer(27);
dump = SpiTransfer(0x18);
MpuCsHigh;
_delay_ms(1);
MpuCsLow;
dump = SpiTransfer(28);
dump = SpiTransfer(0x00);
MpuCsHigh;
_delay_ms(1);
MpuCsLow;
dump = SpiTransfer(107);
dump = SpiTransfer(0x03);
MpuCsHigh;
}

void ReadMpu(void)
{
uint8_t dump;
MpuCsLow;
dump = SpiTransfer(0xBB);
ax.bytes.byte2 = SpiTransfer(0x00);
ax.bytes.byte1 = SpiTransfer(0x00);
ay.bytes.byte2 = SpiTransfer(0x00);
ay.bytes.byte1 = SpiTransfer(0x00);
az.bytes.byte2 = SpiTransfer(0x00);
az.bytes.byte1 = SpiTransfer(0x00);
t.bytes.byte2 = SpiTransfer(0x00);
t.bytes.byte1 = SpiTransfer(0x00);
gx.bytes.byte2 = SpiTransfer(0x00);
gx.bytes.byte1 = SpiTransfer(0x00);
gy.bytes.byte2 = SpiTransfer(0x00);
gy.bytes.byte1 = SpiTransfer(0x00);
gz.bytes.byte2 = SpiTransfer(0x00);
gz.bytes.byte1 = SpiTransfer(0x00);
MpuCsHigh;

GyroX = gx.integer;
GyroY = gy.integer;
GyroZ = gz.integer;
Temperature = t.integer;
AccX = ax.integer;
AccY = ay.integer;
AccZ = az.integer;
}

crabtack
24.11.2014, 14:56
Danke schonmal, das WhoAmI Register auslesen funktioniert Problemlos, ich bekomme die 0x68.
Die Register von Beschleunigungssensor, Temperatursensor und Gyroskop enthalten momentan nur Nullen.
Morgen werde ich mal versuchen den MPU erst zu Initialisieren.

Dann lag ich ja schonmal richtig mit CS.
Also laut Datenblatt ist nachdem CS auf low gezogen wird eine kurze Wartezeit nötig, bevor Daten übertragen werden können, bei mir funktioniert es aber auch ohne.
Jetzt bin ich erstmal beruhigt, dass der MPU noch funktioniert :D

mfg
Olaf

crabtack
25.11.2014, 13:45
Hi!
Danke, dein Programm hat mir sehr geholfen, danke dafür.
Ich habe es leider etwas zu spät gesehen und zu dem Zeitpunkt schon ein eigenes Programm fertig gehabt, dass nicht ganz funktioniert hat.
Dann habe ich deine Initialisierung für den MPU6000 übernommen und noch ein Software Reset mit Wartezeit vor der Initialisierung eingefügt und jetzt funktioniert die Auswertung fehlerfrei.

Jetzt habe ich nur ein kleines Problem.
Ich Schreibe alle Werte des Sensors in uint8_t variablen, so wie du.
Über den UART lasse ich mir z.B. die beiden Bytes, vom der Z-Achse des Beschleunigungssensors ausgeben.
Problem ist, dass ich einen recht kleinen Wert herausbekomme, wenn ich den Sensor gerade auf den Tisch lege, sollte ich ja eigentlich 1G Erdbeschleunigung messen.
Ich erhalte jedoch den Wert 2060 (High Byte: 0x08 Low Byte: 0x0C), was 0.126g entspräche.
Ich erziele ein sehr ähnliches Resultat auf der X und Y Achse mit minimaler Abweichung.
Und Umgekehrt erhalte ich den Wert - 2036 (High Byte 0xF8 Low Byte: 0x0C).

Also prinzipiell stimmt alles schätze ich, nur wird die Skalierung irgendwie falsch sein.
Im Regsiter 28 stelle ich mit 0x00 ein, dass die Full Range +/- 2g beträgt.
Ist meine Rechnung zur Berechnung der Beschleunigung in g richtig?


(2060/32768) * 2g= 0.126g

Liegt da der Fehler oder denke ich gerade vollkommen falsch?

Edit:
Zur Kontrolle habe ich auch den Temperatursensor ausgewertet.
Dabei erhalte ich einen Wert von 26,5°C.
Ich denke, der ist realistisch, da ich in der Werkstatt 25°C Raumtemperatur habe und mir vorstellen kann, dass sich das IC beim Betrieb erwärmt.

Edit2:
Wenn ich die zu messende Achse Parallel zur Erde ausrichte erhalte ich 0g so, wie es sein soll.


mfg
Olaf

Che Guevara
25.11.2014, 15:05
Hi,

also der Wert ist definitiv falsch! Bei +1G sollte der Wert bei ca. 16384 (0x4000) liegen. Hast du mein Programm übernommen oder ein eigenes? Wenns nicht meins ist, stell mal deins rein.

Gruß
Chris

crabtack
26.11.2014, 06:26
Hi,

Ich habe mein Programm verwendet.
Heute habe ich dein Programm probiert und es um einen Software Reset erweitert, das funktionierte fehlerfrei, ich habe die 0x4000 erhalten.
Anschließend habe ich es mit meinem Programm verglichen und in meinem Programm nach jedem schreiben in ein Register 1mS Wartezeit eingefügt, jetzt funktioniert es und ich kann von -1G bis +1G messen, wenn ich den Sensor drehe.
Vielen Dank schonmal dafür.
Jetzt wirds erst richtig Witzig, mit der Sensorfusion :D

Edit: Die Idee mit dem Union ist genial, werde ich auch so machen.


mfg
Olaf