Measuring Displacements Using Accelerometers: Part 3- Testing And Video

In the last post I showed the results from a test of a few millimeters but that was one of the last tests I conducted, first I tested the output of the gyroscope compared to the angle moved which I talked about before, then tested measuring a linear movement, then a movement made up of both linear and angular movements and then finally I tested the device for its initial purpose. As I already discussed testing the output of the gyroscope, I’m going to talk about the linear movement tests. I tested the device ability to measure a linear movement by mounting the sensor on a rack of a rack and pinion controlled by a servo and by controlling the angle of the servo I could repeatedly move the sensor a known distance. I don’t have a picture of the actual setup but for a visual of it, I created it on CAD.

linear_test1.JPG

So the sensor in black was laid flat on the rack and moved varying distance and the output of the accelerometer was compared to the calculated movement. The movement was calculated by using the equation for the length of an arc that the gear moved, this would be translated to linear movement to the rack.
L(\Theta)=2*\pi*r (\frac{\Theta }{360})

L being the length of the arc, r being the radius of the gear and θ being the angle the servo turned. One of the tests had the servo turn 165 degrees and the gear radius was 1.1cm so the distance traveled was 3.168cm and the results from one of them tests is shown below:

3.168cm(2)

In the test the servo moved to the set angle at a constant speed, paused and returned to the starting position and this graph gives a very good representation of that, I did 7 tests for this distance and the average displacement was 3.0703cm with a standard deviation of  +-0.13cm showing that this is very accurate for a displacement of this size, some of the error can be attributed to the accuracy of the servo which had an accuracy of about +-0.1919mm using the above equation. I conducted other tests for decreasing distances and the results were very good until I got to distances of 1cm or smaller, the acceleration from the servo stepping through the angles became too large compared to the acceleration of the movement so I couldn’t measure the accuracy below this distance. I also conducted other linear tests where the sensor wasn’t mounted flat as seen in the picture below:

linear_test2.JPG

I mounted the sensor on a parer which had an inclination of 15 degrees and conducted the same tests as before and found that I got different results than before because the incoming acceleration is at an angle to the recording axis, the output won’t be completely right, this can be explained in the diagram below:

sensor@15degrres.jpg

Because the acceleration is coming in at an angle this has to be account for and using trigonometry we can get this equation:

Acc(n)=\frac{Y(n)}{cos(\Theta (n))}

15 degrees won’t have much of an effect but it can be seen graph below:

linear_at_15_degree

The blue line is the displacement not accounting for the angle and the orange is accounting for the angle, the orange gets closer to real value. For larger angles, the difference would be more noticeable, you could also you the same approach using the acceleration recorded on the Z axis but as the acceleration came in at an angle of 75 to the Z axis, it might not pick up the incoming acceleration accurately. You could also use Pythagoras Theorem using the Y and Z axis accelerations but you’d lose the sign telling you the direction of the movement.

The above shows that device can measure linear displacement so now I needed to test its ability to measure displacement including linear and angular movements, for this I used a rig which would change the angle a few degrees and gave a displacement of a few cm. The rig can be seen below:

 

On the left is the rig which has the sensor mounted on some felt which has a wheel behind it, this wheel is controlled by a servo. When the wheel is moved the sensor is pushed or falls back depending on the position of the wheel. This rig was difficult to measure the actually distance the sensor moves along each axis but from using a ruler, I estimated the Z distance to be roughly 2cm and the Y distance of a few millimeters. The acceleration and displacement graph on the Z axis and trajectory graph from one of these tests are below:

Looking at the acceleration graph, the calculated gravity follows pretty well and the displacement graph shows a max displacement of roughly 2cm and has the shape expected of this movement. I plotted the displacements against each other to show the 2D movement of the sensor as if viewed from the side, it’s just another way of seeing if the results were correct and helps with visualising the movement. Now after this and the previous tests, I could conclude that the device was able to measure displacements but only under certain circumstances. Them being:

  • The sensor must return to its starting position
  • Must not rotate around the X axis during the movement
  • The movement must have a duration of less than 3s due to integration error
  • The minimum amount of samples are integrated to reduce integration error(user chooses start and end of movement)

So after about three and a half months, I was finished with the project and I’d say I’m happy with how it went, the problem with converting the acceleration held me back for weeks, every day I would try to try something new and it was just disheartening to not have it working for so long but it worked in the end. If someone wanted to make a device like this or if I was to make it again I would know the highest frequency you’ll be measuring then you could choose an appropriate sampling frequency to reduce integration error, make a test rig which is both very low noise and can accurately perform a movement of a few millimeters to test the accuracy of the device and finally improve the methods for removing gravity like designing a filter to do the job, even that I tried to design a filter, it’s not something I’d know too much about and maybe someone else would have better luck. Below is a video of me demonstrating the device and doing a linear test.

As I mentioned in the video because this device was built fro a research group I can’t post the Processing sketch as it has a lot of code to do with the project and would take a while to edit as to include all of the functions but remove things referencing to the project, if there’s a lot of demand I’ll post the Processing sketch. I can post the Energia code and the circuit diagram though and this can be seen below:

Screenshot_1.jpg

//
// Accel4.ino
// Written by Ronan Byrne, adapted from other sources which are referanced 
// where approperiate
// Last updated 30/04/2016
/*
   This code controls a MSP432 which is connected to two IMUs which record both
   accelerometer and gyroscope signals and communicate through the I2C protocol. The data is 
   recorded when a record button is held, this data is saved to the RAM of the MSP.Once the send button is pressed this data is read from
   the SD and sent over the Serial port to a PC which will process the data.
*/
// Include application, user and local libraries
#include <Wire.h>
// Timer library and code adapted from https://github.com/ArduCAM/Energia/tree/master/examples/10.MultiTasking/TimerLibrary
#include "Timer.h"

#define BUFFER_SIZE 5500

Timer myTimer;

// Define variables and constants
boolean buttonState, buttonState2;
const uint8_t ON_BUTTON = 30;
const uint8_t RECORD_BUTTON = 8;
const uint8_t SEND_BUTTON = 6;
const uint8_t LedG = 26;
const uint8_t LedB = 27;
const uint8_t LedR = 28;
const uint8_t LedY = 29;
const int8_t MPU6050=0x68;  // I2C address of the MPU-6050
const int8_t MPU9150 = 0x69; // I2C address of the MPU-9150
int16_t AcX[BUFFER_SIZE],AcY[BUFFER_SIZE],Ac2Y[BUFFER_SIZE],
Ac2Z[BUFFER_SIZE], gX[BUFFER_SIZE];
volatile unsigned Index=0;
volatile unsigned n;
volatile int Tic=0;

// Add setup code
void setup()
{
  Serial.begin(115200); // Set baudrate at max speed(115200)
  pinMode(LedB, OUTPUT);
  pinMode(LedG,OUTPUT);
  pinMode(LedR,OUTPUT);
  pinMode(ON_BUTTON,INPUT);
  pinMode(RECORD_BUTTON,INPUT);
  pinMode(SEND_BUTTON, INPUT);

  Serial.print(Timer_getNumTimers(), DEC);
  Serial.println(" timers");

  Serial.print("myTimer.begin... ");
  myTimer.begin(timerFunction, 1, 1000); // Run timer at 1kHz.
  Serial.println("done");    

  Serial.print("myTimer.start... ");
  myTimer.start();
  Serial.println("done");
  Wire.begin();
  Wire.beginTransmission(MPU6050);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Wire.beginTransmission(MPU9150);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-9150)
  Wire.endTransmission(true);
}

// Add loop code
void loop() 
{
  if(digitalRead(ON_BUTTON)==1){
    digitalWrite(LedG, HIGH);
    unsigned count;
    // Check Button States
    buttonState = digitalRead(RECORD_BUTTON);
    buttonState2 = digitalRead(SEND_BUTTON);

    if (buttonState == 1){ // If recorded button is pressed recorded data
      if (Index < BUFFER_SIZE) 
      {
        while (Tic==0);// wait until the timer ticks
        readAccelerometer(); // read from the MPU-6050
        readAccelerometer2(); // read from the MPU_9150
        Index++;
        Tic=0;// reset timer
      }
      else {
        // If index is greater than the buffer size, turn on red LED
        digitalWrite(LedR,HIGH);
      }
    }
    else if (buttonState2 == 1){// if send button is pressed, send data over Serial
      n = Index;
      count = 0;
      digitalWrite(LedY,HIGH);
      Serial.println("B");
      establishContact();// Wait for a response from the PC
      while (count < n)
      {
        if(digitalRead(ON_BUTTON)==0) break;
        // Wait for communication over serial
        if (Serial.available() > 0){
          Serial.print(AcX[count]);
          Serial.print(","); 
          Serial.print(AcY[count]);
          Serial.print(","); 
          Serial.print(Ac2Y[count]);
          Serial.print(","); 
          Serial.print(Ac2Z[count]);
          Serial.print(",");
          Serial.println(gX[count]);
          count++;
          delay(1);
        }
      }
      delay(300);
      Serial.println("Done");// Send "Done" to PC
      delay(1000);
    }
    else{ 
      // Switch all outputs but Green led
      digitalWrite(LedY,LOW);
      digitalWrite(LedB,LOW);
      digitalWrite(LedR,LOW);
    }
  }
  else{
    // Switch out all outputs and reset index
    Index = 0;
    digitalWrite(LedG,LOW);
    digitalWrite(LedB,LOW);
    digitalWrite(LedR,LOW);
    digitalWrite(LedY,LOW);
  }
}

void establishContact() {
  // Send "B" until a response is heard
  while (Serial.available() <= 0) {
    if(digitalRead(ON_BUTTON)==0) break;
    Serial.println("B");
    delay(300);
  }
}

void readAccelerometer() // Can not be called from within an interrupt.
{
  if (Index < BUFFER_SIZE)
  {
    digitalWrite(LedB,HIGH);
    Wire.beginTransmission(MPU6050);
    Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050,4,true);  // request a total of 6 registers
    AcX[Index]=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
    AcY[Index]=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
    digitalWrite(LedB,LOW);
  }
}
void readAccelerometer2() // Can not be called from within an interrupt.
{
  if (Index < BUFFER_SIZE)
  {
    digitalWrite(LedB,HIGH);
    Wire.beginTransmission(MPU9150);
    Wire.write(0x3D);  // starting with register 0x3D (ACCEL_YOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU9150,4,true);  // request a total of 6 registers
    Ac2Y[Index]=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)     
    Ac2Z[Index]=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)

    Wire.write(0x43);  // starting with register 0x3D (ACCEL_YOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU9150,2,true);  // request a total of 6 registers
    gX[Index]=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)     
    //gZ[Index]=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
    digitalWrite(LedB,LOW);
  }
}

// Timer Function
void timerFunction()
{
  Tic=1;
}

//
// Timer.cpp 
// Library C++ code
// ----------------------------------
// Developed with embedXcode+ 
// http://embedXcode.weebly.com
//
// Project 		EMT-TimerLibrary
//
// Created by 	Rei Vilo, Jun 17, 2015 09:29
// 				http://embeddedcomputing.weebly.com
//
// Copyright 	(c) Rei Vilo, 2015
// Licence		CC = BY SA NC
//
// See 			Timer.h and ReadMe.txt for references
//


// Library header
#include "Timer.h"

// Code
Timer::Timer()
{
    ;
}

void Timer::begin(void (*timerFunction)(void), uint32_t timerPeriod_unit, uint32_t unit)
{
    Error_Block eb;
    Error_init(&eb);

    // xdc_UInt TimerId = 3; // OK=3, NOK=2,1,0 MSP432=4 timers, only timer 3 available
    // Timer_ANY to take any available timer
    xdc_UInt TimerId = Timer_ANY;
    
    Timer_Params params;
    Timer_Params_init(&params);
    params.periodType = Timer_PeriodType_MICROSECS;
    params.period = timerPeriod_unit * unit; // 1 ms = 1000 us
    params.startMode = Timer_StartMode_USER; // Timer_start

    TimerHandle = Timer_create(TimerId, (Timer_FuncPtr)timerFunction, &params, &eb);

    if (TimerHandle == NULL)
    {
        // Serial.println("*** Timer create failed");
        System_abort("Timer create failed");
    }
}

void Timer::start()
{
    Timer_start(TimerHandle);
}

void Timer::stop()
{
    Timer_stop(TimerHandle);
}

///
/// @file		Timer.h
/// @brief		RTOS Timer, part of the Galaxia Library Suite
/// @details	RTOS Timer as C++ object for Energia MT
/// @n
/// @n @b		Project EMT-TimerLibrary
/// @n @a		Developed with [embedXcode+](http://embedXcode.weebly.com)
/// 
/// @author		Rei Vilo
/// @author		http://embeddedcomputing.weebly.com
///
/// @date		Rei Vilo, Jun 17, 2015 09:29
/// @version	102
/// 
/// @copyright	(c) Rei Vilo, 2015
/// @copyright	CC = BY SA NC
///
/// @see		ReadMe.txt for references
///


// Core library for code-sense - IDE-based
// Include application, user and local libraries
#include <Energia.h>
#include <xdc/runtime/Error.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/hal/Timer.h>

#ifndef Timer_h
#define Timer_h

///
/// @brief      RTOS Timer as an object
/// @details    The RTOS Timer is encapsulated as a C++ object for easier use
/// @note       Only one single timer available on the MSP432.
///
class Timer
{
private:
    Timer_Handle TimerHandle;
    
public:
    ///
    /// @brief      Define the timer
    /// @warning    Only one single timer available on the MSP432.
    ///
    Timer();
    
    ///
    /// @brief      Create the timer
    /// @param      timerFunction function to be called
    /// @param      timerPeriod_unit period in unit
    /// @param      unit in us, us = 1, ms = 1000, s = 1000000
    /// @note       The function must be void timerPeriod_ms()
    /// @code   void timerPeriod_ms()
    ///         {
    ///             digitalWrite(RED_LED, HIGH);
    ///         }
    /// @endcode
    /// @bug        Some fucntions like Serial.print(); don't work :(
    ///
    void begin(void (*timerFunction)(void), uint32_t timerPeriod_unit, uint32_t unit = 1000);

    ///
    /// @brief      Start the timer
    ///
    void start();
    
    ///
    /// @brief      Stop the timer
    ///
    void stop();
};

#endif

<< Part 1Part 2 >>

Advertisements

2 thoughts on “Measuring Displacements Using Accelerometers: Part 3- Testing And Video

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s