UserProc: Real-Time 'C' Code Entry Point
Userproc.c represents a general entry-point for user-defined 'C'
routines. It is called after each analog input/output buffer combination
is processed by the analog I/O drivers and has been presented to the
foreground processing layer in the DSP operating system. This type of
operation is known as "frame-based real-time processing", where background
interrupt service routines use automatic serial port DMA onchip circuitry
to assemble analog input samples into a series of frames, or buffers, which are
then processed to the foreground algorithm for processing on a "frame"
basis. Note that the "current analog I/O drivers" are drivers that have
been selected in the host PC software user-interface for the DSP/analog
hardware that is currently installed in the system. In Hypersignal software
this is done using the System Config menu selection; in DirectDSP software
this is done using the DSP/Analog Hardware Configuration dialog box.
Parameters passed to userproc.c include a pointer to the just-acquired input
buffer, a pointer to the next output buffer, length of each buffer in
elements, and number of traces (channels) contained in each element. Note
that each "element" could actually be more than one sample; for example a
three-channel acquisition would cause buffers to contain interleaved data
in the form ch0, ch1, ch2, ch0, ch1, ch2, etc.
Below is the default source code listing for the basic userproc.c routine, which is
configured for simple loopback operation (loop input back to output) before
user-defined modification:
Default UserProc Routine
void UserProc(void* ptrIn, void* ptrOut, long nLen, short int nNumTrace) {
#ifdef defined(TMS320C3x) || defined(TMS320C4x) || defined(DSP5600x) || defined(ADSP2106x)
#define x ((long*)ptrIn)
#define y ((long*)ptrOut)
#endif
#ifdef defined(DSP32C) || defined(DSP3210) || defined(TMS320C2x) || defined(TMS320C5x)
|| defined(ADSP21xx)
#define x ((short int*)ptrIn)
#define y ((short int*)ptrOut)
#endif
short int n;
for (n=0; n<nLen; n++) {
y[n] = x[n]; /* loop input back to output */
#ifdef defined(TMS320C3x) || defined(TMS320C4x)
y[n] <<= 16;
#endif
}
}
Here are some important notes about the above code structure:
1) Each input buffer ("x") contains 16-bit A/D values represented as right-justified,
signed, N-bit integer values, where N is the natural integer word-length of the DSP
device. The only exception to this are DSP32C and DSP3210 devices, in which case
input buffer values are packed as 16-bit signed integers, even though these devices
are 32-bit floating-point (they can make 8-bit, 16-bit, or 32-bit memory accesses).
2) Each output buffer ("y") contains 16-bit D/A values represented as right-justified,
signed, N-bit integer values, where N is the natural integer word-length of the DSP
device. Exceptions include DSP32C and DSP3210 devices, in which case output buffer
values are packed as 16-bit signed integers, and TMS320C3x and TMS3204x devices, in
which case output buffer values are left-justified 16-bit D/A values in a 32-bit integer.
The TMS320xx exception is due to Hypersignal operation which historically has used
"upper half" writes to obtain fast transfer of data for older 'C3x boards with 16-bit
host PC interfaces. For several of these boards, a single 16-bit write was the fastest
way to send D/A output data, but only the upper 16 bits could be accessed this way.
3) The nNumTrace parameter usually corresponds to the number of channels. However, it
is more accurate to say "traces" instead of "channels" because the Channel List specified
in Hypersignal or DirectDSP software may actually be an arbitrary ordering of channels,
in which case, for example, channel 0 may not correspond to trace 0.
If the nNumTrace parameter is > 1, then data is stored as interleaved values, in
repeating groups of nNumTrace values. For example,
tr0,tr1,tr2,tr0,tr1,tr2, ...
for 3-trace acquisition or signal output.
4) The xxxLINKC.CMD or (xxxLINKC.CTL) linker
command (or control) file should be used if run-time 'C' support is needed. Otherwise,
the xxxLINK.CMD or xxxLINK.CTL linker command/control file can be used. See
Source Code Modification Process for details.
Run-time 'C' support is made necessary by adding functions or routines which require
the run-time and support library "rtsPP.lib", where PP is a name assigned by the DSP
chip manufacturer (for example "30" for TMS320C3x, "56" for DSP5600x, etc.).
The xxxLINKC.CMD and xxxLINKC.CTL command/control files link in the appropriate rtsPP.lib
file.
5) Important: UserProc must complete its processing before the next
input/output buffer pair is ready! Otherwise, real-time operation is lost.
Application Examples
A few simple application examples are given below.
Signal Arithmetic Example
A very simple example of user-defined real-time 'C' code is basic signal arithmetic
requiring no recursive input (i.e. no input from previous output samples). In this
example, the DSP algorithm scales and adds a constant to input channel 0 data, and also
loops the data back to the output. The difference equation for this example could
look something like:
y[n] = 0.75*x[n] + 1000
First, userproc.c is modified to look like:
void UserProc(void* ptrIn, void* ptrOut, long nLen, short int nNumTrace) {
#ifdef defined(TMS320C3x) || defined(TMS320C4x) || defined(DSP5600x) || defined(ADSP2106x)
#define x ((long*)ptrIn)
#define y ((long*)ptrOut)
#endif
#ifdef defined(DSP32C) || defined(DSP3210) || defined(TMS320C2x) || defined(TMS320C5x)
|| defined(ADSP21xx)
#define x ((short int*)ptrIn)
#define y ((short int*)ptrOut)
#endif
short int n;
for (n=0; n<nLen; n++) {
x[n] = 0.75*x[n] + 1000;
y[n] = x[n];
#ifdef defined(TMS320C3x) || defined(TMS320C4x)
y[n] <<= 16;
#endif
}
}
Second, the following steps are performed:
1) Compile userproc.c. As an example of how to do this, the batch file "c.bat" is
included which invokes the manufacturer's 'C' compiler from a DOS command prompt.
2) Run the lnk.bat file if run-time support is not needed
(if the rtsPP.lib file is not needed).
Otherwise, run the lnkc.bat
file.
3) Copy the resulting .out file to the Hypersignal directory. The default .out
filename is as specified in the Hypersignal-Macro Series Hardware Reference Guide; other
filenames can be specified by providing the lnk.bat or lnkc.bat files a filename
parameter, for example:
lnkc test.out
or
lnk test
which will both produce an output file of "test.out".
4) Run host PC software to activate and test newly modified DSP code, using
Hypersignal software or DirectDSP DLL software with Hypersignal as a DSP engine.
To activate UserProc in Hypersignal operation, enter:
'a,rt,r'
or
'a,rtd,r'
in the ACQ/GEN field in the Analog Conversion function. The 'rt' entry causes real-time
simultaneous input/output operation (mode 6; see
Source Code Overview). The 'rtd' entry works the same way, except recording to
waveform file is enabled. Maximum sampling rates at which continuous operation (no gaps
or data discontinuity) can be achieved are lower when file recording is enabled. In all
cases, maximum sampling rates at which real-time operation can be maintained are heavily
system dependent. Important factors include the DSP chip speed, DSP onboard SRAM
access time, and efficiency of user-defined 'C' code. In the case of waveform file
recording, factors also include host PC speed, disk drive speed and controller type,
ISA or PCI bus speed, etc. For a detailed discussion of the factors influencing maximum
continuous sampling rate to file, please see the "Super Guide" online help file that
ships with DirectDSP software products.
Important Note: if you have changed the name of the DSP program file from
the default (by renaming the output of lnkc.bat, or by giving lnkc.bat a filename
parameter), you must tell Hypersignal about the change before running Analog Conversion.
To do this, suffix the filename to the ANALOG CONVERSION field in the System Config menu. For example, you might have
an entry like 'IIC31-B-50,test.out', or 'TBS56-B,AC3.OUT', or 'sig32c,dsp32cxx.out'.
To activate UserProc when using DirectDSP DLL software and a Hypersignal package
as a DSP engine, there are two methods:
DSAcquireWvfrmFile Approach. Use either "rt" or "rtd" as the stimulus
filename
szStimName structure member) in the
CONVERSIONINFO structure passed to the
DSAcquireWvfrmFile function. This instructs
Hypersignal to replace stimulus filename output with real-time processed data.
The "rt" value causes indefinite real-time operation (until the engine is idled; see the
dtape.cpp source code file for an example of how to use
the DSSendEngineCommand(hEngine, DS_SEC_IDLE,
DS_EEF_SYNC) function call). The "rtd" value causes real-time operation with
continuous recording to waveform file of the analog input data until the NumSamples
structure member is reached. The record-to-disk option is provided as a means to
post-analyze, in detail, data that has been processed in real-time by userproc.c.
Waveform file data can be analyzed using a Hypersignal or Real-Time Composer
waveform display function.
Also, the stimulus mode (szStimMode stucture
member) value should be set to "R", which instructs the DSP code to perform
repetitive frame output until the waveform file is complete or the operation is aborted.
DSP Code Control Approach. Modify the rtcode.cpp file as needed. The
rtcode.cpp file shows an example of initializing DSP code for UserProc operation with
DSPutHVarMem calls. The key action is to choose operating mode 6 (set
DSP_OPMODE variable to 6) and then initialize
the value of DSP_FRMSIZ to the length of each
analog input buffer (normally the value should be the same as
DSP_BUFLEN). Note that with this approach,
continuous recording of input or output buffer data to waveform file is not active;
to make this functionality active, it would have to be added to the rtcode.cpp file.
Running Sum Example
A slightly more complex example is to combine basic signal arithmetic with recursive
input (i.e. input from previous output samples). In this example, the DSP algorithm
performs a running sum on input channel 0 data, with no loopback to the output. The
difference equation for this example could look something like:
y[n] = y[n-1] + (x[n] - x[n-M])/M
where M is the length of the running sum, in samples. First, userproc.c is modified to
look like:
#define N 4096 /* define N as maximum buffer size */
#define M 32 /* define M as length of running sum */
short int ySum[N]; /* create storage of size of one buffer, assume it is initialized
to zero */
short int xPrev[M]; /* create storage of length of running sum, to hold samples at end
of each
previous buffer ("memory" between successive frames) */
void UserProc(void* ptrIn, void* ptrOut, long nLen, short int nNumTrace) {
#ifdef defined(TMS320C3x) || defined(TMS320C4x) || defined(DSP5600x) || defined(ADSP2106x)
#define x ((long*)ptrIn)
#define y ((long*)ptrOut)
#endif
#ifdef defined(DSP32C) || defined(DSP3210) || defined(TMS320C2x) || defined(TMS320C5x)
|| defined(ADSP21xx)
#define x ((short int*)ptrIn)
#define y ((short int*)ptrOut)
#endif
short int n,xn;
long static lSum = 0;
for (n=0; n<nLen; n++) {
if (n - M < 0) xn = xPrev[n]; /* use "memory" if index is before start of
current buffer */
else xn = x[n-M];
lSum += x[n] - xn; /* calculate x term sum */
ySum[n] += (short int)(lSum/M); /* calculate running sum */
}
for (n=0; n<M; n++) xPrev[n] = x[nLen-n]; /* store last M points for use by next
frame */
for (n=0; n<nLen; n++) x[n] = ySum[n]; /* store processed data in input buffer */
}
Note: the use of a long division intermediate calculation above (lSum/M)
is not suggested for real-time processing; it is used only to make the example general
and portable across different DSP devices. A better approach would be to make M a power
of 2, so the division could be achieved by shifting the lSum term right.
After modifying userproc.c, see the steps listed
above to re-compile, link, and test the modifications.