Data Logging with a BrainStem GP 1.0
Last Modified: 2006-11-17
find:

basket

Acroname Robotics PDF webpage version Data Logging with a BrainStem GP 1.0 PDF

Related
Products

Product image for Brainstem GP 1.0 Module
Brainstem GP 1.0 Module
Product image for SensComp 6500 Ranging Module
SensComp 6500 Ranging Module
Product image for SensComp Instrument Package
SensComp Instrument Package
Product image for SensComp Inst Grade Transducer
SensComp Inst Grade Transducer

Contents

Introduction

This tutorial walks through the writing of a C program on the host computer that uses the aIO and aStem libraries to communicate with the BrainStem module.  This program loads a data retrieval TEA program into the module and then initiates the program at a user specified rate.  The data retrieved is sent back to the host computer and stored in a text file.  This file can then be read into a statistics or graphics package for viewing or imported into any program that can handle tab-delimited data. 

The Design

This task is a common request so it seems the best approach is to accomplish it with a general tool that can handle different settings and options.  Since the BrainStem is also supported on many computing platforms, it would be nice to make the solution as cross-platform as possible to minimize the effort to support it on multiple platforms.  Finally, since Acroname offers a host of sensors, it would be nice if the solution could handle various sensor types with little effort. 

The approach taken here is to break the problem into two pieces:

  • A C program on the host that manages the timing of the readings, the number of readings to take, and the storing of the readings into a file.  Here we assume that all readings will fit into a 2-byte value which offers a range of -32768 to 32767 for each reading.  The number of readings is limited only to the maximum file size on the host computer for the resulting data file. 
  • A small TEA program that runs on the module for each reading taken.  This allows the actual sensor interface to be completely separated from the initiation of readings and logging of data.  In this way, the same data logging program can be used with a host of sensors by just using different TEA programs. 

The interface between these two separate components is a routine definition that takes no parameters and returns an 2-byte value (an int in the TEA language).  Here is roughly what that routine will look like:

int main() { // take reading here return reading; } // main

This TEA program can be independently tested to make sure it is returning valid sensor data by just running it directly from the BrainStem Console application.  Once it is tested out and working, the C program can be used to automatically load and run the program and also record its output. 

Circuit Schematic

First, we will need to connect the Polaroid Ranger to the proper pins on the GP 1.0 module.  This diagram shows a Series 7000 Transducer but the Instrument Grade or Series 600, Stainless Steel transducers would work equally well.  Here is a diagram of how to connect it:

Wiring diagram between a Polaroid ranger and a BrainStem.

Source Code - TEA Program

This program will be pretty simple in most cases.  Typically it will just take a single reading and return it to the host.  Below is a program that takes readings from a Polaroid Instrument Grade Sonar Package.  Programs for other rangers or devices, including custom-made sensors, could also be added. 

// filename: 6500_log.tea // set which digital I/O pins control the 6500 // note: this must be in place ahead of the // include below of the a6500 program #define a6500_INIT 1 #define a6500_ECHO 2 // now include the 6500 driving routines #include <a6500.tea> int main() { int reading; // initialize the 6500 ranger pins a6500_Setup(); reading = a6500_ReadInt(); return reading; } /* main */

Compiling, Loading and Testing the TEA Program

Once the sensor is hooked up as shown above, we can compile the TEA program and load it onto the module.  To do this, first save the file to the "aUser" directory in your BrainStem distribution folder with the name "6500_log.tea".  Make sure that your operating system doesn't add a ".txt" extension to this file when you save it.  It should have a .tea extension. 

Tip

If you are not familiar with the console application you may want to take a break here and go through the Console Introduction before proceeding. 

Once the file is in place, launch the console application and compile the program using the steep command.  Do this by typing:

steep "6500_log"

Notice you don't have to specify the .tea extension here as it will automatically be added to a filename without a specified extension. 

This will create the output file 6500_log.cup in the "aObject" directory of your brainstem distribution.  You can load this onto the module with the load command.  Do this by typing:

load "6500_log" 2 0

In this case, 2 is the module number and 0 is the slot number.  We will stick with these for this example but you could substitute others depending upon your needs.  Your GP 1.0 board must be powered up and connected with the interface cable for this load to work.  Checking for a live heartbeat on both the console and GP 1.0 module will ensure you have a good link before loading the program. 

Finally, you can launch the program using the load command (again while the module is powered up with a live heartbeat).  Do this by typing:

launch 2 0

You should see the vm launch mesage in the console output, hear a click from the transducer, and then see the vm exit message in the console.  Here is a sample session of running the program:

Screenshot of the Console application while loading the TEA program.

The last number in the VM exit message is the return value from the transducer.  You can try running this several times (hit the up arrow key to recall the previously typed console command) with your hand at various distances from the sensor to see changes in the return value. 

If you have this much working, the easy half of the solution is now in place.  Next, we will work on a C program on the host side that automatically triggers these sensor readings on the module and records them to a file. 

Source Code - C Program

The C program is going to automatically handle the triggering of and recording from the TEA program we have already written.  This data is to be recorded to a file on the host computer.  Here is the rough idea of what is going to happen when the C program runs:

initialize the aIO and aStem libraries get the configuration from a settings file establish the serial link to the brainstem module open the data file for each measurement initiate the measurement get the result wait for the remaining period time close the data file release the aIO and aStem libraries

To keep things very simple, we will not put any user interface on this program at all to start with.  We will also use a setting file to avoid the differences for each OS platform when handling input parameters.  For example, the PalmOS doesn't have a text shell built in so we can't specify command line parameters for PalmOS.  We will specify all the parameters for the program in a setting file that is just a simple text file.  The aIO library offers most of what we need to manage the files and basic I/O.  The aStem library handles the BrainStem module interaction. 

We will for now skip the creation of the project on your specific platform as there are many options.  We will focus on the code itself and how it works.  Later we will get into the specifics for each platform. 

Note

The code shown below is all available in the download section as a complete project for various development platforms.  You may want to build from this code once you have read through the following section and understand the code operation. 

To start with, lets create a C structure that holds all the variables that will be shared by all the various routines.  These could be global variables but that is widely understood to be a bad programming practice.  The program state structure below is a much cleaner approach:

typedef struct aDataLogger { aIOLib ioRef; aStemLib stemRef; aSettingFileRef settingFile; unsigned char module; unsigned char slot; int baudRate; char portName[MAXSETTING]; char hbModeSave; aStreamRef linkStream; aStreamRef dataStream; aStreamRef outputStream; int nMeasurements; int nFrequency; char dataFileName[MAXSETTING]; } aDataLogger;

This structure is the complete state of the program and can then be passed to various routines to accomplish the necessary program steps. 

Now, lets write the routines starting with a subroutine that initializes the aIO and aStem libraries.  This routine is passed an aDataLogger structure pointer and it fills it in as it initializes.  The aErr return type is a BrainStem error value.  We generally want to see this have the value with the pre-processor definition "aErrNone" to indicate success. 

aErr initialize(aDataLogger* pDL, aStreamRef output) { aErr err = aErrNone; /* initialize the structure's memory */ aBZero(pDL, sizeof(aDataLogger)); /* get the reference to the aIO library */ if (err == aErrNone) aIO_GetLibRef(&pDL->ioRef, &err); /* get the reference to the aStem library */ if (err == aErrNone) aStem_GetLibRef(&pDL->stemRef, &err); /* retain the output stream */ if (err == aErrNone) pDL->outputStream = output; return err; } /* initialize */

Next, we create a settings file object.  This object is referred to by an opaque reference that you use for setting file functions with the file you have created.  Actually, the file is not created, it is read in (if present) and then you can request various settings from it.  If they are found in the file, they are returned.  If not, defaults are returned. 

aErr get_settings(aDataLogger* pDL) { aErr err = aErrNone; if (err == aErrNone) aSettingFile_Create(pDL->ioRef, MAXSETTING, CONFIGFILENAME, &pDL->settingFile, &err); /* get the BrainStem module number */ if (err == aErrNone) { int module; aSettingFile_GetInt(pDL->ioRef, pDL->settingFile, MODULEKEY, &module, DEFAULTMODULE, &err); pDL->module = (unsigned char)module; } /* get the BrainStem slot number */ if (err == aErrNone) { int slot; aSettingFile_GetInt(pDL->ioRef, pDL->settingFile, SLOTKEY, &slot, DEFAULTSLOT, &err); pDL->slot = (unsigned char)slot; } /* get the number of measurements from the setting file */ if (err == aErrNone) aSettingFile_GetInt(pDL->ioRef, pDL->settingFile, MEASUREMENTSKEY, &pDL->nMeasurements, DEFAULTNUMMEASURMENTS, &err); /* get the frequency of measurement from the setting file */ if (err == aErrNone) aSettingFile_GetInt(pDL->ioRef, pDL->settingFile, FREQUENCYKEY, &pDL->nFrequency, DEFAULTFREQUENCY, &err); /* get the baud rate from the setting file */ if (err == aErrNone) aSettingFile_GetInt(pDL->ioRef, pDL->settingFile, BAUDRATEKEY, &pDL->baudRate, DEFAULTBAUDRATE, &err); return err; } /* get_settings */

The next step is to open a serial port stream for communicating with the BrainStem module and set it as the stream for the aStem library to use when sending and receiving packets.  We get the name of the serial port from the setting file so that it can easily be changed without re-compiling the program. 

aErr build_link(aDataLogger* pDL) { aErr err = aErrNone; char* pPortName; /* get the port name string and copy it into our state block */ if (err == aErrNone) { aSettingFile_GetString(pDL->ioRef, pDL->settingFile, PORTNAMEKEY, &pPortName, DEFAULTPORTNAME, &err); aStringCopy(pDL->portName, pPortName); } /* create the stream to the serial port */ if (err == aErrNone) { aStream_CreateSerial(pDL->ioRef, pDL->portName, (unsigned int)pDL->baudRate, &pDL->linkStream, &err); if (err != aErrNone) { aStream_WriteLine(pDL->ioRef, pDL->outputStream, "unable to open serial port", NULL); cleanup(pDL); } } /* set this as the link port for the aStem library * this link gets automatically destroyed when the aStem * library is released */ if (err == aErrNone) aStem_SetStream(pDL->stemRef, pDL->linkStream, kStemModuleStream, &err); /* now, make sure the module is in place and bail if not */ if ((err == aErrNone) && !aModuleUtil_EnsureModule(pDL->stemRef, pDL->module)) { aStream_WriteLine(pDL->ioRef, pDL->outputStream, "active module not found", NULL); cleanup(pDL); err = aErrIO; } /* finally, store the old heartbeat mode and set auto * heartbeat */ if (err == aErrNone) { err = aModuleVal_Get(pDL->stemRef, pDL->module, mvHBMode, &pDL->hbModeSave); if ((err == aErrNone) && (pDL->hbModeSave != 1)) err = aModuleVal_Set(pDL->stemRef, pDL->module, mvHBMode, 1); } return err; } /* build_link */

We set auto heartbeat mode because, depending on our data acquisition rate, we may want to stall the program for long periods of time with a sleep function.  If we do this, the heartbeat will be starved and will no longer send data back to us until the heartbeat is re-established.  Auto heartbeat mode doesn't do this but we loose the diagnostic information provided by the heartbeat. 

The next step is to open a data file.  This involves getting the filename from the settings file and then opening the file as an output stream.  By using the stream we can redirect the output to other places (such as an IP port) and these routines also work on all platforms supported by the BrainStem. 

aErr open_datafile(aDataLogger* pDL) { aErr err = aErrNone; char* pFileName; if (err == aErrNone) aSettingFile_GetString(pDL->ioRef, pDL->settingFile, DATAFILEKEY, &pFileName, DEFAULTDATAFILE, &err); if (err == aErrNone) { aStringCopy(pDL->dataFileName, pFileName); aStream_CreateFileOutput(pDL->ioRef, pDL->dataFileName, aFileAreaUser, &pDL->dataStream, &err); } return err; } /* open_datafile */

Now we are ready to actually take the measurements.  This routine loops for each measurement and computes the stall to get the proper timing.  This example is not extremely precise but will be fairly consistent and not drift much. 

aErr measure(aDataLogger* pDL) { aErr err = aErrNone; int i, val; unsigned long then, now, ticks; char num[10]; /* compute the number of ticks that will give us the * correct frequency */ ticks = (unsigned long)1000 / pDL->nFrequency; /* find out the time right now */ aIO_GetMSTicks(pDL->ioRef, &then, &err); /* do the loop to take the measurements */ for (i = 0; (i < pDL->nMeasurements) && (err == aErrNone); i++) { err = aModuleVM_Execute(pDL->stemRef, pDL->module, 0, 0, NULL, &val); /* convert the number and store it */ if (err == aErrNone) { aStringFromInt(num, val); aStream_WriteLine(pDL->ioRef, pDL->dataStream, num, &err); } /* stall for the remaining time before the next * measurement */ if (err == aErrNone) aIO_GetMSTicks(pDL->ioRef, &now, &err); /* if the time is too quick, we may oversleep so bail */ if ((ticks - (now - then)) < 6) { aStream_WriteLine(pDL->ioRef, pDL->outputStream, "Error, frequency too fast, try a faster baudrate.", NULL); cleanup(pDL); err = aErrTimeout; } if (err == aErrNone) aIO_MSSleep(pDL->ioRef, ticks - (now - then), &err); if (err == aErrNone) aIO_GetMSTicks(pDL->ioRef, &then, &err); } return err; } /* measure */

Here we clean up when we are done (or there has been and error).  Not doing this can introduce memory leaks and lock out the resources for use by other programs. 

aErr cleanup(aDataLogger* pDL) { aErr err = aErrNone; /* restore the previous heartbeat mode */ if (pDL->linkStream) aModuleVal_Set(pDL->stemRef, pDL->module, mvHBMode, pDL->hbModeSave); if (pDL->dataStream) aStream_Destroy(pDL->ioRef, pDL->dataStream, &err); if (pDL->settingFile) aSettingFile_Destroy(pDL->ioRef, pDL->settingFile, &err); if (pDL->ioRef) aIO_ReleaseLibRef(pDL->ioRef, &err); if (pDL->stemRef) aStem_ReleaseLibRef(pDL->stemRef, &err); return err; } /* cleanup */

This is the wrapper that will work with any platform and it takes only an output stream for displaying the status of the application's progress. 

int aDataLogger_Execute(aStreamRef output) { aErr err = aErrNone; aDataLogger dl; /* output a console header */ aStream_WriteLine(aStreamLibRef(output), output, "", &err); aStream_WriteLine(aStreamLibRef(output), output, "aDataLogger Application", &err); aStream_WriteLine(aStreamLibRef(output), output, "", &err); if (err == aErrNone) err = initialize(&dl, output); if (err == aErrNone) err = get_settings(&dl); if (err == aErrNone) err = open_datafile(&dl); if (err == aErrNone) err = build_link(&dl); /* report what we are up to the output stream */ if (err == aErrNone) { show_int_param(output, "module number", dl.module); show_int_param(output, "TEA file slot number", dl.slot); show_int_param(output, "baud rate", dl.baudRate); show_string_param(output, "port name", dl.portName); show_int_param(output, "number of measurements", dl.nMeasurements); show_int_param(output, "measurement frequency (Hz)", dl.nFrequency); show_string_param(output, "data file name (in aUser directory)", dl.dataFileName); } if (err == aErrNone) err = measure(&dl); if (err == aErrNone) err = cleanup(&dl); /* report the completion */ aStream_WriteLine(aStreamLibRef(output), output, "", NULL); if (err == aErrNone) aStream_WriteLine(aStreamLibRef(output), output, "Measurements Complete", NULL); else aStream_WriteLine(aStreamLibRef(output), output, "Measurement Failed", NULL); return(err != aErrNone); } /* aDataLogger_Execute */

Here is the wrapper main routine for STDIO machines.  Others will need to provide an output stream for status and then call aDataLogger_Execute. 

int main(int argc, char* argv[]) { aErr err; aIOLib ioRef; aStreamRef output; int retVal; /* get the aIO library reference */ aIO_GetLibRef(&ioRef, &err); /* create a stream on standard output */ if (err == aErrNone) err = aStream_Create_STDIO_Console_Output(ioRef, &output); /* run the program */ if (err == aErrNone) retVal = aDataLogger_Execute(output); /* release the aIO library */ if (err == aErrNone) aIO_ReleaseLibRef(ioRef, &err); return retVal; } /* main */

Windows Metroworks Building Specifics

This program can easily be built using the Metrowerks development tools for Windows.  A project is included in the examples directory of the "cdev" and "sdk" downloads for Windows in the Acroname Download Center that you can just open and compile.  This example is for Codewarrior 7.0.  Other versions may also work with this project file but it was created using 7.0. 

The project file is called "win32_aDataLogger.mcp" and the project builds the stdio command line version, placing the executable in the aBinary directory when done with the name "aDataLogger.exe". 

Microsoft Visual Studio .NET Development Specifics

This program can easily be built using the Microsoft Visual Studio .NET development tools for Windows.  A project is included in the examples directory of the "cdev" and "sdk" downloads for Windows in the Acroname Download Center that you can just open and compile in Visual Studio .NET.  The project is named "win32_aDataLogger.vcproj".  You can double click this to open it or build your own as follows. 

  • Create an empty custom C++ application and turn off UI in the wizard. 
  • Set the output directory to "..\..\aBinary" under the project's General Settings Property panel. 
  • Set intermediate directories to "win32_aDataLogger" under the project's General Settings property panel. 
  • Add the ".; .\aWin; ..\..\aInclude; ..\..\aSource; ..\..\aSystem" directories to the include search path under the project's C/C++ General Settings property panel. 
  • Add the forced include of win32_aDataLogger_pfx.h file under the project's C/C++ Advanced Settings property panel. 
  • Set the output file to be "..\..\aBinary\aDataLogger.exe" under the project's Linker General Settings property panel. 
  • Add in the aDataLogger.c, win32_aDataLogger.c, and aStream_STDIO_Console.c, aModuleVM.c, aModuleUtil.c, aModuleVal.c, and aUtil.c files.  You can just drag them over the project in the Solution Explorer Window. 
  • Add the aIO.lib and aStem.lib library files. 

Once you have the project settings, you can build the project and save the solution as "win32_aDataLogger.sln" when prompted as part of the build.  This will place the aDataLogger.exe result in the aBinary folder of your brainstem distribution. 

Using the Finished Project

Now, with the TEA and C programs complete, you can begin using the data logger! To do this, first make sure your TEA program is compiled, tested, and loaded onto the BrainStem module in slot zero as shown above. 

Next, you can either stick with the defaults or create your own setting file to change the parameters used by the application.  To create a setting file, build a file called "aDataLogger.config" in the aBinary directory (the same location you have your aDataLogger executable program).  Here are the settings for this file:

element type not assigned

Program Configuration File

Here is a sample "aDataLogger.config" file:

# # aDataLogger.config # # parameter definitions for the aDataLogger application # frequency = 4 measurements = 30 datafile = mydata.dat
Note

Be aware of your operating system trying to "help" you by hiding the extension of the file you created.  You may not see the ".config" ending or, worse yet, your OS may have added an additional extension of ".txt".  We strongly recommend you turning this hand-holding in your OS so you can actually see what is on your machine. 

Once you have the configuration details set up properly for your application, you can just power up the BrainStem, make sure the cable is plugged in properly (perhaps by running the console to ensure a valid heartbeat), and then run the application from a command shell by just typing "aDataLogger" in the aBinary directory.  The program should start up and take the requested number of measurements while logging them to a file. 

Revision History:

  • 2002-07-26: Tutorial Created

BrainStem
Resources

 

Related Links:

Senscomp (Polaroid) Sonar Ranging Primer

voice: 720-564-0373, email: sales@acroname.com, address: 4822 Sterling Dr., Boulder CO, 80301-2350, privacy
© Copyright 1994-2008 Acroname, Inc., Boulder, Colorado. All rights reserved.