Programming Tutorial:Implementing a Source Module: Difference between revisions
No edit summary |
Atennissen (talk | contribs) No edit summary |
||
| Line 2: | Line 2: | ||
hardware, | hardware, | ||
and code required to access a specific hardware. | and code required to access a specific hardware. | ||
You provide a function that waits for and reads A/D | |||
data (line 3 in the EEG source pseudo code of [[Modules:Data Acquisition]]), | data (line 3 in the EEG source pseudo code of [[Modules:Data Acquisition]]), | ||
together with some helper functions that perform initialization and | together with some helper functions that perform initialization and | ||
| Line 30: | Line 30: | ||
that it will return a pointer to a buffer containing the data in its | that it will return a pointer to a buffer containing the data in its | ||
first argument. | first argument. | ||
Each of the functions will return zero if everything went well, | Each of the functions will return zero if everything went well, otherwise | ||
some error | some error value will be given. | ||
value | |||
Luckily, ''Tachyon Corporation'' gives you just | Luckily, ''Tachyon Corporation'' gives you just | ||
what you need for a BCI2000 source module, so implementing the ADC | what you need for a BCI2000 source module, so implementing the ADC | ||
| Line 130: | Line 129: | ||
but also the format of the <tt>dat</tt> file written by the source | but also the format of the <tt>dat</tt> file written by the source | ||
module. | module. | ||
For the <tt>Initialize</tt> function, | For the <tt>Initialize</tt> function, it will only be | ||
called if | called if | ||
<tt>Preflight</tt> did not report any errors. | <tt>Preflight</tt> did not report any errors. If everything works | ||
fine with | fine with | ||
the parameters, | the parameters, you may skip any checks, and write | ||
<pre> | <pre> | ||
void TachyonADC::Initialize() | void TachyonADC::Initialize() | ||
| Line 155: | Line 154: | ||
==Data Acquisition== | ==Data Acquisition== | ||
Note that the function may not return unless the output signal is filled with data, so it is | |||
function | crucial that <tt>TachyonWaitForData</tt> is a blocking function. | ||
may not return unless the output signal is filled with data, so it is | |||
crucial | |||
that <tt>TachyonWaitForData</tt> is a blocking function. | |||
(If your card does not provide such a function, and you need to poll | (If your card does not provide such a function, and you need to poll | ||
for data, | for data, don't forget to call <tt>Sleep( 0 )</tt> inside your polling loop to | ||
don't forget to call <tt>Sleep( 0 )</tt> inside your polling loop to | avoid hogging the CPU.) | ||
avoid | |||
hogging the CPU.) | |||
<pre> | <pre> | ||
void TachyonADC::Process( const GenericSignal*, GenericSignal* outputSignal ) | void TachyonADC::Process( const GenericSignal*, GenericSignal* outputSignal ) | ||
Revision as of 16:23, 9 May 2007
Data acquisition modules are factored into code required for any hardware, and code required to access a specific hardware. You provide a function that waits for and reads A/D data (line 3 in the EEG source pseudo code of Modules:Data Acquisition), together with some helper functions that perform initialization and cleanup tasks. Together these functions form a class derived from GenericADC.
Scenario
Your Tachyon Corporation A/D card comes with a C-style software interface declared in a header file "TachyonLib.h" that consists of three functions
#define TACHYON_NO_ERROR 0 int TachyonStart( int inSamplingRate, int inNumberOfChannels ); int TachyonStop( void ); int TachyonWaitForData( short** outBuffer, int inCount );
From the library help file, you learn that TachyonStart configures the card and starts acquisition to some internal buffer; that TachyonStop stops acquisition to the buffer, and that TachyonWaitForData will block execution until the specified amount of data has been acquired, and that it will return a pointer to a buffer containing the data in its first argument. Each of the functions will return zero if everything went well, otherwise some error value will be given. Luckily, Tachyon Corporation gives you just what you need for a BCI2000 source module, so implementing the ADC class is quite straightforward.
Writing the ADC Header File
In your class' header file, "TachyonADC.h", you write
#ifndef TACHYON_ADC_H
#define TACHYON_ADC_H
#include "GenericADC.h"
class TachyonADC : public GenericADC
{
public:
TachyonADC();
~TachyonADC();
void Preflight( const SignalProperties&, SignalProperties& ) const;
void Initialize();
void Process( const GenericSignal*, GenericSignal* );
void Halt();
private:
int mSourceCh,
mSampleBlockSize,
mSamplingRate;
};
#endif // TACHYON_ADC_H
ADC Implementation
In the .cpp file, you will need some #includes, and a filter registration:
#include "TachyonADC.h" #include "Tachyon/TachyonLib.h" #include "BCIError.h" using namespace std; RegisterFilter( TachyonADC, 1 );
From the constructor, you request parameters and states that your ADC needs; from the destructor, you call Halt to make sure that your board stops acquiring data whenever your class instance gets destructed:
TachyonADC::TachyonADC()
: mSourceCh( 0 ),
mSampleBlockSize( 0 ),
mSamplingRate( 0 )
{
BEGIN_PARAMETER_DEFINITIONS
"Source int SourceCh= 64 64 1 128 "
"// this is the number of digitized channels",
"Source int SampleBlockSize= 16 5 1 128 "
"// this is the number of samples transmitted at a time",
"Source int SamplingRate= 128 128 1 4000 "
"// this is the sample rate",
END_PARAMETER_DEFINITIONS
}
TachyonADC::~TachyonADC()
{
Halt();
}
ADC Initialization
Your Preflight function will check whether the board works with the parameters requested, and communicate the dimensions of its output signal:
void TachyonADC::Preflight( const SignalProperties&,
SignalProperties& outputProperties )
const
{
if( TACHYON_NO_ERROR !=
TachyonStart( Parameter( "SamplingRate" ), Parameter( "SourceCh" ) ) )
bcierr << "SamplingRate and/or SourceCh parameters are not compatible"
<< " with the A/D card"
<< endl;
TachyonStop();
outputProperties = SignalProperties( Parameter( "SourceCh" ),
Parameter( "SampleBlockSize" ),
SignalType::int16 );
}
Here, the last argument of the SignalProperties constructor determines not only the type of the signal propagated to the BCI2000 filters but also the format of the dat file written by the source module. For the Initialize function, it will only be called if Preflight did not report any errors. If everything works fine with the parameters, you may skip any checks, and write
void TachyonADC::Initialize()
{
mSourceCh = Parameter( "SourceCh" );
mSampleBlockSize = Parameter( "SampleBlockSize" );
mSamplingRate = Parameter( "SamplingRate" );
TachyonStart( mSamplingRate, mSourceCh );
}
Balancing the TachyonStart call in the Initialize function, your Halt function should stop all asynchronous activity that your ADC code initiates:
void TachyonADC::Halt()
{
TachyonStop();
}
Data Acquisition
Note that the function may not return unless the output signal is filled with data, so it is crucial that TachyonWaitForData is a blocking function. (If your card does not provide such a function, and you need to poll for data, don't forget to call Sleep( 0 ) inside your polling loop to avoid hogging the CPU.)
void TachyonADC::Process( const GenericSignal*, GenericSignal* outputSignal )
{
int valuesToRead = mSampleBlockSize * mSourceCh;
short* buffer;
if( TACHYON_NO_ERROR == TachyonWaitForData( &buffer, valuesToRead ) )
{
int i = 0;
for( int channel = 0; channel < mSourceCh; ++channel )
for( int sample = 0; sample < mSampleBlockSize; ++sample )
( *outputSignal )( channel, sample ) = buffer[ i++ ];
}
else
bcierr << "Error reading data" << endl;
}
Finished
You are done! Use your TachyonADC.cpp to replace the GenericADC descendant in an existing source module, add the TachyonADC.lib shipped with your card to the project, compile, link, and find the bugs...