| Data Logging with a BrainStem GP 1.0 Last Modified: 2006-11-17 | | |
| Acroname Robotics | PDF webpage version | ||
| 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:
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: ![]() 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.
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: ![]() 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.
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.
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 assignedProgram Configuration File Here is a sample "aDataLogger.config" file: #
# aDataLogger.config
#
# parameter definitions for the aDataLogger application
#
frequency = 4
measurements = 30
datafile = mydata.dat
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:
|
| |||||||||||||
Related Links: | |||||||||||||||
| 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. |