At least in the first instance, we’re going to make use of the sensor in analog mode, and connect it to our Arduino board, which in turn will be connected to our iPhone via the Redpark cable.
Open Xcode, choose to create a new project, select a View-based application for the iPhone device family, and when prompted name it “MaxSonar” and save it to the Desktop.
Open your copy of the Redpark Serial SDK and grab the redparkSerial.h and rscMgr.h header files from the inc/ folder, and the libRscMgrUniv.a static libraries from lib/. Drag and drop them into your new MaxSonar project, remembering to tick the “Copy items into destination group’s folder (if needed)” checkbox when prompted.
We also need to add the External Accessories framework, so click on the project icon at the top of the Project pane in Xcode, and click on the MaxSonar Target and then on the Build Phases tab. Finally, click on the Link Binary with Libraries item to open up the list of linked frameworks, and click on the + symbol to add a new framework. Select the External Accessory framework from the drop-down list and click the Add button.
Finally, we need to declare support for the cable. Click on the
SerialConsole-Info.plist file to
open it in the editor. Right-click on the bottommost row of the list and
select Add Row from the menu. An extra row will be added to the table,
and you’ll be presented with a drop-down menu. Type
UISupportedExternalAccessoryProtocols
into the
box. This will change to the human-readable text “Supported external
accessory protocols.” Type the string com.redpark.hobdb9
into Item 0 to declare
our support for the cable.
Unsurprisingly perhaps when dealing with sensor data, the need to display a graph or a plot of some kind comes up fairly often when you’re developing applications. Unfortunately, there is no native support for graphs in the iOS SDK. The Core Plot library fills that hole (see Figure 4-3).
The Core Plot library is a third-party 2D plotting framework for iOS and Mac OS X, tightly integrated with Core Animation and under active development. At the time of this writing, the latest release was version 0.4. You can download it from http://code.google.com/p/core-plot/.
Note
If you want to live on the bleeding edge, you can check out the source code straight from Core Plot’s repository using Mercurial:
hg clone http://core-plot.googlecode.com/hg/ core-plot
Mercurial doesn’t ship with Xcode, so unless you’ve already installed it, your first step before doing that will probably be to install Mercurial itself. You can download the latest version of Mercurial from http://mercurial.selenic.com/wiki/.
Download the CorePlot_0.4.zip file from the Core Plot website, or check the latest version out of the project’s Mercurial repository. Open up the CorePlot/framework/ folder and click on the CorePlot-CocoaTouch.xcodeproj project file to open it in Xcode.
Select “Universal Library | iOSDevice” from the Scheme drop-down menu and then Build (⌘B) from the Product menu to build a universal (fat) static library that we can use on both the device and in the iPhone Simulator.
After building, move one folder back up and go into the newly created build directory. Inside the build/Release-universal/ directory you’ll find the libCorePlot-CocoaTouch.a static library; however, in addition to this, we need the associated header files. The custom build script, which created the universal library, doesn’t copy these header files from the individual build directories. However you can find them in the build/Release-iphoneos/usr/local/include/ or build/Release-iphonesimulator/usr/local/include/ directories. Which set of header files you use in your project isn’t really relevant; both sets are identical.
Drag and drop the libCorePlot-CocoaTouch.a static library from the build/Release-universal/ folder into your project, remembering to tick the “Copy items into destination group’s folder (if needed)” checkbox when prompted. Then copy one of the two sets of header files, all of them, into your project. Again remember to tick the “Copy items into destination group’s folder (if needed)” checkbox.
Click on the MaxSonar project file at the top of the Project
pane, select the MaxSonar Target, and click on the Build Settings tab.
Find the Other Linker Flags setting (under the Linking section) and
double-click to open the pop-up window and add the -ObjC -all_load
flags to the target (see
Figure 4-4).
Finally, since the Core Plot library makes use of the QuartzCore framework, we need to add this to our project. Click on the Build Phases tab and then on the Link Binary with Libraries item to open up the list of linked frameworks, and click on the + symbol to add a new framework. Select the QuartzCore framework from the drop-down list and click the Add button.
Now that we’ve added the third-party infrastructure we’ll need to
build the application, it’s time to build out the user interface. Click
on the MaxSonarViewController.xib
file to open it in Interface Builder and drag and drop a UINavigationBar
, a UISwitch
, a couple of UILabel
elements, and finally a generic
UIView
into your View. Arrange them
as shown in Figure 4-5. Change
the text in the navigation bar to read “LV-MaxSonar-EZ1” and the text in
the two labels to be “Distance:” and “0.00”.
Click on the generic UIView
in
your View and open up the Identity inspector in the Utilities pane on
the right. We need to change the Class of our View from UIView
to be a CPTGraphHostingView
. See Figure 4-6.
Warning
Depending on which version of the Core Plot library you are
working with, you may need to specify the custom view as a CPGraphHostingView
instead.
This custom view is the one we’re going to use to plot our real-time graph of the sensor readings coming from the EZ1 sensor via the Arduino and the serial link to our iPhone.
After changing the view’s class, close the Utilities panel and
open up the Assistant Editor. Make sure it’s in Automatic mode and
Ctrl-click and drag from the CPTGraphHostingView
to the MaxSonarViewController.h interface file to
create a graph
instance variable and
property. Similarly, Ctrl-click and drag from the right-hand UILabel
and UISwitch
to create distance
and toggle
instance variables and properties.
Finally, Ctrl-click and drag one more time from the UISwitch
to a toggled
method and IBAction
, as in Figure 4-7.
Save your changes, switch back to the Standard Editor, and click
on the MaxSonarViewController.h
interface file to open it in the editor. You should notice that we’ve
got an error next to the CPTGraphHostingView
instance variable; that’s
because Xcode doesn’t know what it is yet. Go ahead and add the
following import statement at the top of your code:
#import "CorePlot-CocoaTouch.h"
If you select Build (⌘B) from the Product menu to build your project at this point, everything should compile. If that’s not the case, you should probably make sure you’ve added the External Accessory and QuartzCore frameworks to your project before you begin looking elsewhere.
We’re done with the user interface. What we need to do now is connect our interface to the incoming measurements from our Arduino board. Obviously the first thing we need to do is integrate the Redpark library into our code. The code for this is going to look almost identical to the code we’ve seen in the last couple of chapters. However, we’re also going to have to establish some infrastructure for our plot. We’ll use a simple XY scatter plot for that task.
Open the MaxSonarViewController.h interface file and add the following code:
#import <UIKit/UIKit.h> #import "CorePlot-CocoaTouch.h" #import "RscMgr.h" #define BUFFER_LEN 1024 #define MAX_POINTS 200 @interface MaxSonarViewController : UIViewController <CPTPlotDataSource, RscMgrDelegate> { CPTGraphHostingView *graph; CPTXYGraph *plot; NSMutableArray *dataForPlot; UILabel *distance; UISwitch *toggle; RscMgr *manager; UInt8 rxBuffer[BUFFER_LEN]; UInt8 txBuffer[BUFFER_LEN]; } @property (nonatomic, retain) IBOutlet CPTGraphHostingView *graph; @property (nonatomic, retain) IBOutlet UILabel *distance; @property (nonatomic, retain) IBOutlet UISwitch *toggle; @property (nonatomic, retain) NSMutableArray *dataForPlot; - (IBAction)toggled:(id)sender; - (NSNumber *) yValueForIndex:(NSUInteger)index; @end
This makes our class both a Core Plot data source and a Redpark Serial Cable delegate object. Now open the corresponding MaxSonarViewController.m implementation file. We need to remember to synthesize our new property:
@synthesize dataForPlot;
and then release it in the dealloc
method:
[dataForPlot release];
and finally set it to nil in viewDidUnload
method:
[self setDataForPlot:nil];
We want to go ahead and add a viewDidLoad
method. Here we’re going to set up
the cable manager object as usual, but also our Core Plot plot object.
Doing that setup involves a lot of boilerplate code, and I’m not really
going to talk about it in detail here, as it’s all a bit tangential to
what we’re actually doing:
- (void)viewDidLoad { [super viewDidLoad]; // cable manager rscMgr = [[RscMgr alloc] init]; [rscMgr setDelegate:self]; // Create plot from theme plot = [[CPTXYGraph alloc] initWithFrame:CGRectZero]; CPTTheme *theme = [CPTTheme themeNamed:kCPTPlainBlackTheme]; [plot applyTheme:theme]; graph.collapsesLayers = NO; graph.hostedGraph = plot; plot.paddingLeft = 10.0; plot.paddingTop = 10.0; plot.paddingRight = 10.0; plot.paddingBottom = 10.0; // Setup plot space CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)plot.defaultPlotSpace; plotSpace.allowsUserInteraction = YES; plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(1.0) length:CPTDecimalFromFloat(MAX_POINTS)]; plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(1.0) length:CPTDecimalFromFloat(MAX_POINTS)]; // Axes CPTXYAxisSet *axisSet = (CPTXYAxisSet *)plot.axisSet; CPTXYAxis *x = axisSet.xAxis; x.majorIntervalLength = CPTDecimalFromString(@"100"); x.minorTicksPerInterval = 4; x.minorTickLength = 5.0f; x.majorTickLength = 7.0f; CPTXYAxis *y = axisSet.yAxis; y.majorIntervalLength = CPTDecimalFromString(@"50"); axisSet.yAxis.minorTicksPerInterval = 2; axisSet.yAxis.minorTickLength = 5.0f; axisSet.yAxis.majorTickLength = 7.0f; // Create a green plot area CPTScatterPlot *line = [[[CPTScatterPlot alloc] init] autorelease]; CPTMutableLineStyle *lineStyle = [CPTMutableLineStyle lineStyle]; lineStyle.miterLimit = 1.0f; lineStyle.lineWidth = 3.0f; lineStyle.lineColor = [CPTColor greenColor]; line.dataLineStyle = lineStyle; line.identifier = @"Green Plot"; line.dataSource = self; [plot addPlot:line]; // Do a green gradient CPTColor *areaColor = [CPTColor colorWithComponentRed:0.3 green:1.0 blue:0.3 alpha:0.8]; CPTGradient *areaGradient = [CPTGradient gradientWithBeginningColor:areaColor endingColor:[CPTColor clearColor]]; areaGradient.angle = −90.0f; CPTFill *areaGradientFill = [CPTFill fillWithGradient:areaGradient]; line.areaFill = areaGradientFill; line.areaBaseValue = [[NSDecimalNumber zero] decimalValue]; // Add plot symbols CPTMutableLineStyle *symbolLineStyle = [CPTMutableLineStyle lineStyle]; symbolLineStyle.lineColor = [CPTColor blackColor]; CPTPlotSymbol *plotSymbol = [CPTPlotSymbol ellipsePlotSymbol]; plotSymbol.fill = [CPTFill fillWithColor:[CPTColor greenColor]]; plotSymbol.lineStyle = symbolLineStyle; plotSymbol.size = CGSizeMake(10.0, 10.0); line.plotSymbol = plotSymbol; CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; fadeInAnimation.duration = 1.0f; fadeInAnimation.removedOnCompletion = NO; fadeInAnimation.fillMode = kCAFillModeForwards; fadeInAnimation.toValue = [NSNumber numberWithFloat:1.0]; // Add some initial data NSMutableArray *contentArray = [NSMutableArray arrayWithCapacity:200]; self.dataForPlot = contentArray; }
Now we need to add the CorePlot CPTPlotDataSource
data source methods:
#pragma mark - CPTPlotDataSource Methods -(void)pointReceived:(NSNumber *)point{ CPTPlot *thisPlot = [plot plotWithIdentifier:@"Green Plot"]; [self.dataForPlot addObject:point]; [thisPlot insertDataAtIndex:self.dataForPlot.count-1 numberOfRecords:1]; self.distance.text = [NSString stringWithFormat:@"%@", point]; } -(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot { return [dataForPlot count]; } -(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index { if (fieldEnum == CPTScatterPlotFieldX) { return [NSNumber numberWithInteger:index]; } else { return [self yValueForIndex:index]; } }
Along with our yValueForIndex:
convenience method:
- (NSNumber *) yValueForIndex:(NSUInteger)index { return [self.dataForPlot objectAtIndex:index]; }
Finally add the Redpark Serial delegate methods:
#pragma mark - RscMgrDelegate methods - (void) cableConnected:(NSString *)protocol { [rscMgr setBaud:9600]; [rscMgr open]; } - (void) cableDisconnected { } - (void) portStatusChanged { } - (void) readBytesAvailable:(UInt32)numBytes { int bytesRead = [rscMgr read:rxBuffer Length:numBytes]; NSString *string = nil; for(int i = 0;i < numBytes;++i) { if ( string ) { string = [NSString stringWithFormat:@"%@%c", string, rxBuffer[i]]; } else { string = [NSString stringWithFormat:@"%c", rxBuffer[i]]; } } [self pointReceived:[NSNumber numberWithInt:[string intValue]]]; } - (BOOL) rscMessageReceived:(UInt8 *)msg TotalLength:(int)len { return FALSE; } - (void) didReceivePortConfig { }
Check that everything builds and runs by running the app in the simulator. You should see something like Figure 4-8. If that looks good, switch to the device and build and deploy the application onto your iPhone.
If everything has gone well, at this point we need to put the iPhone application, along with Xcode itself, to one side for now. It’s time to look at the Arduino end of our build.
Open up the Arduino development environment and enter the following code and upload it to your Arduino board. We’ll be using analog pin 0, normally marked as A0 on most boards, for the analog input from the EZ1 sensor:
int statusLed = 13; int ez1Analog = 0; void setup() { pinMode(statusLed,OUTPUT); pinMode(ez1Analog,INPUT); Serial.begin(9600); } void loop() { int val = analogRead(ez1Analog); if (val > 0) { val = val / 2; float cm = float(val)*2.54; Serial.println( int(cm) ); } blinkLed( statusLed, 100 ); } void blinkLed(int pin, int ms) { digitalWrite(pin,LOW); digitalWrite(pin,HIGH); delay(ms); digitalWrite(pin,LOW); delay(ms); }
Nominally the EZ1 requires 5 V; however, according to the datasheet, we can operate it at 3.3 V, albeit with somewhat reduced performance. This simplifies connecting all of our components to the Arduino board. Plug the +VE wire from the EZ1 into the 3.3V pin on the Arduino board, and the GND wire into one of the available GND pins, then connect the AN wire to analog pin 0 (A0). Finally connect your RS-232 to TTL serial adaptor to the 5V, GND, RX, and TX pins as normal (see Figure 4-9).
If you do want to connect the EZ1 to the +5V pin rather than the 3.3V pin, probably the easiest thing to do would be to use a breadboard and some jumper wires.
Once you’re satisfied that everything is connected correctly, power on the Arduino board and start up the application. Point the EZ1 toward the ceiling to get a decent distance measurement, and then place your hand over the sensor. You should see the measured distance drop dramatically. Slowly raise your hand, holding it above the sensor to see the reading change. Hopefully you’ll see something that looks a lot like Figure 4-10 at this point.
You’ve probably noticed that we haven’t done anything with the toggle switch. Let’s go ahead and make that usable now. First of all, we’ll need to change our Arduino sketch:
int statusLed = 13; int powerLed = 12; int ez1Analog = 0; int aByte; int flag = 0; void setup() { pinMode(statusLed,OUTPUT); pinMode(ez1Analog,INPUT); Serial.begin(9600); pinMode(powerLed,OUTPUT); blinkLed( statusLed, 500 ); } void loop() { if (Serial.available() > 0) { aByte = Serial.read(); if ( flag == 0 ) { flag = 1; digitalWrite(powerLed, HIGH); } else { flag = 0; digitalWrite(powerLed, LOW); } } if ( flag == 1 ) { int val = analogRead(ez1Analog); if ( val > 0 ) { val = val / 2; float cm = float(val)*2.54; Serial.println( int(cm) ); // cm blinkLed( statusLed, 100); } } } void blinkLed(int pin, int ms) { digitalWrite(pin,LOW); digitalWrite(pin,HIGH); delay(ms); digitalWrite(pin,LOW); delay(ms); }
Here we’ve rewritten our loop(
)
so that the Arduino only sends data to the iPhone if it has
been toggled on, by receiving a byte from the phone, and then stops
transmitting data if it receives another byte. I’m also using a second
LED in pin 12 to indicate that data is being sent, but that’s purely
optional, and you can omit it if you can’t be bothered to wire another
LED up to your Arduino board.
You’ll notice that we’re assuming an initial state of “off,” but that currently our user interface has an initial state of “on.” We need to fix that, so open up the MaxSonarViewController.xib file in Interface Build, click on the switch in the view, and in the Attributes inspector in the Utilities panel, change the initial switch state to “off.”
Finally, to glue everything together, we add some code to the toggled callback:
- (IBAction)toggled:(id)sender { txBuffer[0] = 1; int bytesWritten = [rscMgr write:txBuffer Length:1]; }
Now if you rebuild and then redeploy the MaxSonar application to the iPhone, you should be able to stop and start the data coming from the phone using the switch.
Get iOS Sensor Apps with Arduino now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.