Chapter 22
Accelerometer

Modified

Disclosure

First example created by Brandon Cannaday on 8/5/09. Copyright 2009 Paranoid Ferret Productions. http://www.switchonthecode.com/tutorials/iphone-tutorial-reading-the-accelerometer

Second example created by Brandon Cannaday on 8/5/09. Copyright 2009 Paranoid Ferret Productions and edited to focus on the accelerometer. See text examples.

Downloads

Acceleration Tutorial application.

Ball Balance application.

Overview

The accelerometer of the iPhone measures force in 6 directions using an LIS302DL accelerometer with dynamically user selectable full scales of ± 2g/± 8g and it is capable of measuring accelerations with an output data rate of 100 Hz or 400 Hz.

Apple states a data delivery rate of 10Hz to 100Hz.

The figure at right illustrates the orientation of the accelerometer.

With the device positioned as in the figure, the Y axis acceleration is -1.0 due to gravity.

Example 1

Basic accelerometer settings are of the sample rate and a delegate object.

Below, the sample rate is every 1/10 second or 10 times per second.

The delegate object is self, the defining class.

self.accelerometer = [UIAccelerometer sharedAccelerometer];
self.accelerometer.updateInterval = 0.1;
self.accelerometer.delegate = self;

 

The delegate method receives a callback after each time interval has elapsed. Below, we update the X/Y/Z directional labels and progress bars for each.

- (void)accelerometer:(UIAccelerometer *)accelerometer
               didAccelerate:(UIAcceleration *)acceleration
{
    labelX.text = [NSString stringWithFormat:@"%@%f", @"X: ", acceleration.x];
    labelY.text = [NSString stringWithFormat:@"%@%f", @"Y: ", acceleration.y];
    labelZ.text = [NSString stringWithFormat:@"%@%f", @"Z: ", acceleration.z];
 
    self.progressX.progress = ABS(acceleration.x);
    self.progressY.progress = ABS(acceleration.y);
    self.progressZ.progress = ABS(acceleration.z);
}

 

When the app is run on a real device, notice that the accelerometer values are not precisely zeroed due to accelerometer error or the device not exactly orthogonal to the earth's center of gravity; normally software would offset values by an initial value to compensate for the error. Also notice that at rest, the device acceleration measurements vary by a few hundredths.

#import <UIKit/UIKit.h>

@interface MainViewController : UIViewController  {
  IBOutlet UILabel *labelX;
  IBOutlet UILabel *labelY;
  IBOutlet UILabel *labelZ;
  
  IBOutlet UIProgressView *progressX;
  IBOutlet UIProgressView *progressY;
  IBOutlet UIProgressView *progressZ;
  
  UIAccelerometer *accelerometer;
}

@property (nonatomic, retain) IBOutlet UILabel *labelX;
@property (nonatomic, retain) IBOutlet UILabel *labelY;
@property (nonatomic, retain) IBOutlet UILabel *labelZ;

@property (nonatomic, retain) IBOutlet UIProgressView *progressX;
@property (nonatomic, retain) IBOutlet UIProgressView *progressY;
@property (nonatomic, retain) IBOutlet UIProgressView *progressZ;

@property (nonatomic, retain) UIAccelerometer *accelerometer;

@end
#import "MainViewController.h"
@implementation MainViewController

@synthesize labelX;
@synthesize labelY;
@synthesize labelZ;

@synthesize progressX;
@synthesize progressY;
@synthesize progressZ;

@synthesize accelerometer;

- (void)viewDidLoad {
    [super viewDidLoad];
  
    self.accelerometer = [UIAccelerometer sharedAccelerometer];
    self.accelerometer.updateInterval = 0.1;
    self.accelerometer.delegate = self;
}

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
    labelX.text = [NSString stringWithFormat:@"%@%f", @"X: ", acceleration.x];
    labelY.text = [NSString stringWithFormat:@"%@%f", @"Y: ", acceleration.y];
    labelZ.text = [NSString stringWithFormat:@"%@%f", @"Z: ", acceleration.z];
  
    self.progressX.progress = ABS(acceleration.x);
    self.progressY.progress = ABS(acceleration.y);
    self.progressZ.progress = ABS(acceleration.z);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {}
- (void)dealloc {
    [super dealloc];
}
@end

Example 2 - Balancing Game

Here, the accelerometer is used to control a ball balanced on the screen. The example is based on one from the text but has been simplified to better see the role of the accelerometer.

BallView

 A UIView that manages the display of ball; moving the ball requires displaying the ball in background color at the old location then displaying again in foreground color at the new location.

The white ball (actually a rectangular area with an ellipse filled) of diameter 20 with frame origin at <40, 100> is defined by:

ballRect.size.width = 20;
ballRect.size.height = 20;
ballRect.origin.x = 40;
ballRect.origin.y = 100;

and drawn by:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillEllipseInRect(context, ballRect);

UIView methods:

-setNeedsDisplayInRect: rectangle causes the -drawRect: method to be eventually called in the UI run loop.

-drawRect: rectangle is an over-ridden UIView method called when the rectangular screen area needs to be drawn.

CGRectUnion(rectangle1, rectangle2) returns the smallest rectangle that contains the two provided rectangles, in this example, allowing the smallest screen area to be updated.

In IB, open the AccelerometerBallBalanceViewController.xib file and the Inspector | Identity and set the class to BallView. This sets the graphic context to the UIView of BallView rather than the AccelerometerBallBalanceViewController.

BallView
#import <UIKit/UIKit.h>

@interface BallView : UIView {
	CGRect ballRect;
	CGRect oldBallRect;
}
-(void) setBallX: (CGFloat) newX Y: (CGFloat) newY;
-(void) setBallWidth: (CGFloat) newW height: (CGFloat) newH;

@end

#import "BallView.h"

@implementation BallView

- (void) setBallWidth: (CGFloat) newW height: (CGFloat) newH {
	ballRect.size.width = newW;
	ballRect.size.height = newH;
	oldBallRect.size.width = newW;
	oldBallRect.size.height = newH;
}

-(void) setBallX: (CGFloat) newX Y: (CGFloat) newY {
	ballRect.origin.x = newX;
	ballRect.origin.y = newY;
	
	// update view
	CGRect clipRect = CGRectUnion (oldBallRect, ballRect);
	[self setNeedsDisplayInRect: clipRect];
	
	// update oldBallRect
	oldBallRect.origin.x = ballRect.origin.x;
	oldBallRect.origin.y = ballRect.origin.y;
}

- (void)drawRect:(CGRect) rect {		// Drawing code
	// In IB, set the class of AccelerometerBallBalanceViewController.xib to BallView.
	CGContextRef context = UIGraphicsGetCurrentContext();
	
	// undraw ball at old location
	CGColorRef undrawColor = self.backgroundColor.CGColor;
	CGContextSetFillColorWithColor (context, undrawColor);
	CGContextFillRect (context, oldBallRect);
	
	// draw ball at new location
	CGContextSetGrayFillColor(context, 1.0, 1.0);
	CGContextFillEllipseInRect(context, ballRect);
}

- (void)dealloc {
    [super dealloc];
}
@end

 

AccelerometerBallBalanceViewController

 A UIViewController that manages the accelerometer and corresponding movement of the ball.

Note that a UIViewController has a UIView, self.view, that is the view displayed by the view controller; which will be used to display the ball.

BallView, a UIView, is imported into the UIViewController, its methods are called by casting, as in:

[(BallView*) self.view setBallX:ballX Y:ballY];

The accelerometer delegate callback is used to determine the velocity of the ball by computing the time between accelerometer readings and the current reading:

NSTimeInterval elapsedTime = acceleration.timestamp - lastAccelTimestamp;

ballVelocityX = ballVelocityX + (acceleration.x * MAX_ACCEL_PER_SEC * elapsedTime);
ballVelocityY = ballVelocityY - (acceleration.y * MAX_ACCEL_PER_SEC * elapsedTime);

Note:

Finally, set delegate to nil to stop accelerometer readings, prevent a crash and save energy.

[[UIAccelerometer sharedAccelerometer] setDelegate: nil];
AccelerometerBallBalanceViewController
#import <UIKit/UIKit.h>

@interface AccelerometerBallBalanceViewController : UIViewController <UIAccelerometerDelegate> {
	CGFloat ballX;
	CGFloat ballY;
	CGFloat ballVelocityX;
	CGFloat ballVelocityY;
	NSTimeInterval lastAccelTimestamp;
	
	UIAccelerationValue filteredAccelX;
	UIAccelerationValue filteredAccelY;
}
@end

#import "AccelerometerBallBalanceViewController.h"
#import "BallView.h"

@implementation AccelerometerBallBalanceViewController
// ball size
CGFloat BALL_WIDTH = 100.0;
CGFloat BALL_HEIGHT = 100.0;

// how many pixels/sec/sec an accel of 1.0 represents
CGFloat MAX_ACCEL_PER_SEC = 200;

- (void) resetBall {
	ballVelocityX = 0.0;
	ballVelocityY = 0.0;
	ballX = self.view.frame.size.width / 2;
	ballY = self.view.frame.size.height / 2;
}

- (void) checkBallInPlay {
	if ((ballX + BALL_WIDTH < 0) ||
		 (ballY + BALL_HEIGHT < 0) ||
		 (ballX > self.view.frame.size.width) ||
		 (ballY > self.view.frame.size.height)) {
		NSLog (@"reset ball out of bounds");
		[self resetBall];
	}
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {	
	// apply acceleration to velocity
	NSTimeInterval elapsedTime = acceleration.timestamp - lastAccelTimestamp;
	ballVelocityX = ballVelocityX + (acceleration.x * MAX_ACCEL_PER_SEC * elapsedTime);
	ballVelocityY = ballVelocityY - (acceleration.y * MAX_ACCEL_PER_SEC * elapsedTime);
	
	if (lastAccelTimestamp > 0) {
		// recalc ball position
		ballX += (ballVelocityX * elapsedTime);
		ballY += (ballVelocityY * elapsedTime);
		// update view
		[(BallView*) self.view setBallX:ballX Y:ballY]; 
		// check for ball out of bounds
		[self checkBallInPlay];
	}
	lastAccelTimestamp = acceleration.timestamp;
}
- (void)viewDidLoad {
	[super viewDidLoad];
	[(BallView*) self.view setBallWidth: BALL_WIDTH height:BALL_HEIGHT];
	[self resetBall];
	[(BallView*) self.view setBallX:ballX Y:ballY];
	[[UIAccelerometer sharedAccelerometer] setUpdateInterval: 0.03]; // 30 fps
	[[UIAccelerometer sharedAccelerometer] setDelegate: self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {}
- (void)dealloc {
	[[UIAccelerometer sharedAccelerometer] setDelegate: nil];
	[super dealloc];
}
@end

Filtering

The accelerometer readings varies even when the device is not accelerating. One way to reduce these random variations is to filter the readings.

By combining previous readings with the current, low or high pass filters can be constructed.

Consider the graph at right.

Low pass filter:

A low pass filter has the effect of changing slowly based on the current raw data reading, accomplished by weighting the previous readings more than the current.

FILTERFACTOR = 0.1

weights the current reading only 10% and previous readings 90%, hence only low frequency changes have an major effect on the filtered result.

low = acceleration.x * FILTERFACTOR + (previousLow * (1.0 - FILTERFACTOR));

High pass filter:

A high pass filter has the effect of changing rapidly based on the current raw data reading, accomplished by weighting the current readings more than the previous.

FILTERFACTOR = 0.1

weights the current reading 90% and previous readings 90%, hence high frequency changes to have an major effect on the filtered result. The 180% results in the high pass values being shifted higher.

high = acceleration.x - ( acceleration.x * FILTERFACTOR) +(previousHigh * (1.0 - FILTERFACTOR));

The appropriate filtering depends upon the application, low pass filtering when acceleration changes slowly, high pass filtering for rapid changes.