Hello acceleration enthusiasts,
I guess many of you had already a similar problem. I am now working on a mobile solution for a MPU 6050 to record tilting of objects. I do not get the right values from the MPU 6050 on the SD card. They are way off from the values I get in the serial monitor when I am not storing the data to the SD card. If I am only printing the data to the serial monitor I can easily level out the MPU to 0 degrees in both direction x and y. But when printing data to the SD they are 130 or even more off (getting wores the more I delay the serial print).
Might this be a problem with the frequency?
Cheers!
Here the sketch:
// This sketch seems to work, but not the right data is saved???
#include <SD.h>
#include<SPI.h>
#include<Wire.h>
//
const int MPU_addr=0x68;
double AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
uint32_t timer;
double compAngleX, compAngleY;
#define degconvert 57.2957786
String dataString ="";
//
//
void setup()
{
// Set up MPU 6050:
Wire.begin();
#if ARDUINO >= 157
Wire.setClock(400000UL);
#else
TWBR = ((F_CPU / 400000UL) - 16) / 2;
#endif
Wire.beginTransmission(MPU_addr);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
Serial.begin(115200);
Serial.print("Initializing SD card...");
pinMode(4, OUTPUT);
if (!SD.begin(4)) {
Serial.println("Card failed, or not present");
return;
}
Serial.println("card initialized.");
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr,14,true);
AcX=Wire.read()<<8|Wire.read();
AcY=Wire.read()<<8|Wire.read();
AcZ=Wire.read()<<8|Wire.read();
Tmp=Wire.read()<<8|Wire.read();
GyX=Wire.read()<<8|Wire.read();
GyY=Wire.read()<<8|Wire.read();
GyZ=Wire.read()<<8|Wire.read();
double roll = atan2(AcY, AcZ)*degconvert;
double pitch = atan2(-AcX, AcZ)*degconvert;
double gyroXangle = roll;
double gyroYangle = pitch;
double compAngleX = roll;
double compAngleY = pitch;
//start a timer
timer = micros();
}
void loop(){
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr,14,true);
AcX=Wire.read()<<8|Wire.read();
AcY=Wire.read()<<8|Wire.read();
AcZ=Wire.read()<<8|Wire.read();
Tmp=Wire.read()<<8|Wire.read();
GyX=Wire.read()<<8|Wire.read();
GyY=Wire.read()<<8|Wire.read();
GyZ=Wire.read()<<8|Wire.read();
double dt = (double)(micros() - timer) / 1000000;
timer = micros();
double roll = atan2(AcY, AcZ)*degconvert;
double pitch = atan2(-AcX, AcZ)*degconvert;
double gyroXrate = GyX/131.0;
double gyroYrate = GyY/131.0;
compAngleX = 0.99 * (compAngleX + gyroXrate * dt) + 0.01 * roll;
compAngleY = 0.99 * (compAngleY + gyroYrate * dt) + 0.01 * pitch;
dataString = String(compAngleX, 5) + "," + String(compAngleY, 5);
saveData();
delay(100);
}
void saveData(){
if(SD.exists("data.csv")){ // check the card is still there ?
Serial.println("Creating data.csv...");
File data = SD.open("data.csv", FILE_WRITE);
if (data) {
data.println(dataString);
data.close(); // close the file
}
else {
Serial.println("Error writing to file !");
}}
else {
Serial.println("data.csv does not exist !");
}
}
General tips:
1.) Don't use the "string" or "String" classes. Use arrays of chars if at all possible.
2.) Try avoiding using I2C and SPI (especially if using an SD card). In short, you might find yourself running dangerously short on SRAM memory due to the large demands of all the libraries you are using. BTW, what MCU are you using? An Uno, Mega, what?
3.) Don't use delay(). Look at this tutorial.
Also, could you post some sample data (both from serial monitor AND SD card results)? Maybe even a vid?
Power_Broker:
General tips:
1.) Don't use the "string" or "String" classes. Use arrays of chars if at all possible.
2.) Try avoiding using I2C and SPI (especially if using an SD card). In short, you might find yourself running dangerously short on SRAM memory due to the large demands of all the libraries you are using. BTW, what MCU are you using? An Uno, Mega, what?
3.) Don't use delay(). Look at this tutorial.
Also, could you post some sample data (both from serial monitor AND SD card results)? Maybe even a vid?
Thanx Power_Broker!
I like the example with the pizza in the tutorial.
Right now I am using the Arduino Uno with the Ethernet shield, but for the project I am actually planning to use the Nano with a SD shield and a real time clock (I need a timestamp and everything should fit in a small plastic circuit box).
I uploaded some data and a graph….sorry for the bad quality of the excel graph….it needed to be quick.
Would the SDfat.h be a better option with the I2C?
I mean I am recording too much data anyway….a reading every 30sec would be more than sufficient (I want to record the swaying of trees and how they get tilted by different wind speeds). If not with a delay function, what other options are there? Are there any examples for this?
Cheers,
Nobby
Serial monitor and SD output.txt (535 KB)
Ok, I got rid of the string and delay function. The values are still about 20 degrees off from what I expect. I used the long function with an interval of 1000, but still this is not reducing the amount of data.
Thus my resume for today:
1) I am still not getting the right readings when the MPU is level out (around 0± some degrees, I mean I can never level it out 100%).
2) I would like to reduce the data output to maybe one value in 30 seconds.
Any other ideas?
// Still 20 degrees off and producing too much data!
#include <SD.h>
#include<SPI.h>
#include<Wire.h>
//
const int MPU_addr=0x68;
double AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
uint32_t timer;
double compAngleX, compAngleY;
#define degconvert 57.2957786
unsigned long previousMillis = 0;
const long interval = 1000;
// String dataString ="";
//
//
void setup()
{
// Set up MPU 6050:
Wire.begin();
#if ARDUINO >= 157
Wire.setClock(400000UL);
#else
TWBR = ((F_CPU / 400000UL) - 16) / 2;
#endif
Wire.beginTransmission(MPU_addr);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
// Open serial communications
Serial.begin(9600);
Serial.print("Initializing SD card...");
pinMode(4, OUTPUT);
//
// see if the card is present and can be initialized:
if (!SD.begin(4)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");
//setup starting angle
//1) collect the data
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr,14,true);
AcX=Wire.read()<<8|Wire.read();
AcY=Wire.read()<<8|Wire.read();
AcZ=Wire.read()<<8|Wire.read();
Tmp=Wire.read()<<8|Wire.read();
GyX=Wire.read()<<8|Wire.read();
GyY=Wire.read()<<8|Wire.read();
GyZ=Wire.read()<<8|Wire.read();
double roll = atan2(AcY, AcZ)*degconvert;
double pitch = atan2(-AcX, AcZ)*degconvert;
double gyroXangle = roll;
double gyroYangle = pitch;
double compAngleX = roll;
double compAngleY = pitch;
//start a timer
timer = micros();
}
//
void loop(){
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr,14,true);
AcX=Wire.read()<<8|Wire.read();
AcY=Wire.read()<<8|Wire.read();
AcZ=Wire.read()<<8|Wire.read();
Tmp=Wire.read()<<8|Wire.read();
GyX=Wire.read()<<8|Wire.read();
GyY=Wire.read()<<8|Wire.read();
GyZ=Wire.read()<<8|Wire.read();
double dt = (double)(micros() - timer) / 1000000;
timer = micros();
double roll = atan2(AcY, AcZ)*degconvert;
double pitch = atan2(-AcX, AcZ)*degconvert;
double gyroXrate = GyX/131.0;
double gyroYrate = GyY/131.0;
compAngleX = 0.99 * (compAngleX + gyroXrate * dt) + 0.01 * roll;
compAngleY = 0.99 * (compAngleY + gyroYrate * dt) + 0.01 * pitch;
// dataString = String(compAngleX) + "," + String(compAngleY);
saveData();
//delay(100);
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;}
}
//
void saveData(){
if(SD.exists("data.csv")){ // check the card is still there ?
Serial.println("Creating data.csv...");
File data = SD.open("data.csv", FILE_WRITE);
if (data) {
//Serial.println(dataString);{
Serial.print(compAngleX); Serial.print(compAngleY);
//data.println(dataString);
data.print(compAngleX); data.println(compAngleY);
data.close(); // close the file
}
else {
Serial.println("Error writing to file !");
}}
else {
Serial.println("data.csv does not exist !");
}
}
1.) I have no idea why the SD card data is 20deg off. I would wipe the card, reformat it, and then use a simple test sketch (without the MPU 6050) like this one and see if it gives the correct results.
2.) Simply put the "saveData()" call in the timer's if-statement. I would also use millis() instead of micros() and have interval = 30000.
One more thing: In order to measure tree tilt, you might also be interested in measuring oscillations. This will require you to do a little research into the Nyquist Sampling Criterion and frequency response theory. This criterion basically says that if you want to detect an oscillation of a frequency no higher than 3Hz (for instance), then you must sample at least 6 times a second (or 2*3Hz = 6Hz).
Power_Broker:
1.) I have no idea why the SD card data is 20deg off. I would wipe the card, reformat it, and then use a simple test sketch (without the MPU 6050) like this one and see if it gives the correct results.
Done this with a simple sketch. Used a 10k linear slide potentiometer.
Result:
- works fine
- data range between 0 and 1023 (as expected)
Conclusion:
Will now get into Nyquist Sampling Criterion. And will change the micros(). I will keep you posted!
This is so much fun!
Thanx so much for helping.
By the way here the test-sketch for the potentiometer:
#include <SPI.h>
#include <SD.h>
#define POTENTIOMETER_PIN 0
const int chipSelect = 4;
void setup() {
Serial.begin(9600);
while (!Serial) {
;
}
Serial.print("Initializing SD card...");
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");
}
void loop() {
String dataString = "";
// read three sensors and append to the string:
int sensor = analogRead(POTENTIOMETER_PIN);
dataString += String(sensor);
File dataFile = SD.open("datalog.txt", FILE_WRITE);
if (dataFile) {
dataFile.println(dataString);
dataFile.close();
// print to the serial port too:
Serial.println(dataString);
}
else {
Serial.println("error opening datalog.txt");
}
}
PS: I was thinking a little bit....what I actually want is constant readings (whatever Hz this is) and an average value over e.g. 30 seconds.
Additional note regarding the 20 degree offset. I will test if it is a real offset. This would be easy to fix....I could sample and then just set the values to zero in the analysis. Will run a test and see if I am getting the same values.
treehugger:
PS: I was thinking a little bit....what I actually want is constant readings (whatever Hz this is) and an average value over e.g. 30 seconds.
Sounds like a better idea, but I think an even better idea would be to do a digital low pass filter (if you're up to the challenge). A low pass filter will smooth out your data and you can easily tweak your filter to smooth the data more or less. Discrete Low Pass Filter Reference
Either way, do what you need to do. Whatever you think is best.
If you actually wanted a constant, you would go into the forest with your protractor, measure the trees and walk out with all your data complete.
You said "30 seconds". That would resolve movements with a period of 60 seconds or more. Maybe your desired sample frequency is even slower than that, but it's not "constant".