Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Real-time Signal Analysis & Real-Time Linux: Part II


Aug03: Real-Time Signal Analysis & Real-Time Linux: Part II

Matt is a programmer at FSMLabs in New Mexico. He can be contacted at [email protected].


Hard real-time systems need to immediately capture, analyze, and act on incoming signals. These days, however, most systems have additional tasks to attend to, such as reporting on progress and allowing operators (or other software) to direct future activity. In the first installment of this article, I focused on capturing real-time signals and visualizing data using a simple Java GUI. This month, I simplify both the real-time and Java code and add a digital filter. To do this, I use the Controls Kit from FSMLabs (the company I work for; http://www.fsmlabs.com/) to filter the real-time signal noise and undersample without worrying about signal aliasing. The Controls Kit also lets you provide automatic-control interfaces for real-time variables. This allows userspace components (local or remote) to automatically control real-time systems without forcing you to write real-time to nonreal-time communication protocols. Although the XML interfaces allow for automatic integration capabilities, I use the control variable features and Controls Library, which implement common filtering algorithms.

One such filter is the rolloff filter that lets you undersample a noisy incoming signal down to a clean 1-Hz wave without massively oversampling in the sampling thread. While these Hz ranges are very low (to simplify visualization), RTLinux can sample at very high KHz ranges using modern processors with no external hardware support.

Adding the Filter

To make the code changes necessary to enable filtering, you need to include the Controls Kit headers, then add a couple more declarations up front; see Listing Seven (Listings One through Six were presented in Part 1). I've also added a filtering thread that runs between the signal generation and sampling threads introduced last month. To prepare for this, you include the Controls Kit headers. Next, add another thread handle for the filter, a value for the filtering thread to dump the clean signal samples into, a hertz rate for the thread, and finally, the rolloff filter itself. There are also a few other Controls Kit variables declared—I'm swapping the sigaction() and FIFO communication protocols out for simple Controls Kit variables. These can be directly controlled with userspace utilities, so you don't need extra FIFOs, sigaction() callbacks, shared structural protocol defining the data, and (above all) the endian reordering.

The signal generation thread has slightly changed in this example in that it still exists solely to generate a noisy waveform, but now pulls its execution period information from a Controls Kit variable:

isignal_hz = CK_scalar_get_int(&signal_hz);

inoise_hz = CK_scalar_get_int(&noise_hz);

These variables are used to generate the signal components. Next, declare the filtering thread, which takes in a noisy signal and outputs a clean wave; see Listing Eight. That's all you need to do in the real-time OS to filter the waveform. As with all of the other threads, this thread enters a loop sleeping with clock_nanosleep(), waking on a specific period (which is extracted from the CK variable). On every iteration, the thread wakes up, grabs the current signal value, and hands it to the filter for processing. The rolloff filter takes the period fraction as a parameter to direct the filtering process. What you get back is the clean signal value without the high-frequency noise. This value is saved in cur_signal_filtered for the old sampling thread to use. This thread is the next step in the pipeline. In last month's example, this thread directly sampled the noisy output of the high-frequency thread. Now, you want it to sample the filtered value, which requires a simple change such that it looks at the filtered value you just used; see Listing Nine.

In addition to the change from sample = cur_signal; to sample = cur_signal_filtered;, this thread gets its period value from a CK variable. Now the sampling thread watching the wave sees the filtered output rather than the noisy wave. As before, it writes the sampled output to a FIFO so you can visualize it with the Java tool presented last month.

Initialization and Destruction

The last step involves spinning off control threads, with the new filter thread joining in. You need to initialize the rolloff filter entity, name it, set a lower bound (0), upper bound (200), and initial value for the corner frequency (100). These bounds provide safety mechanisms for the real-time control variables, so userspace programs gone bad can't induce failure. If you have machinery that must not exceed 400 PSI, this maximum can be defined at startup, and a bad pointer can't tell the system to jump to 5986 PSI.

System shutdown is easy: The filter thread must be safely joined and canceled with the rest of the threads, and the filter entity must be destroyed, along with our "JavaControl" group. This is done by adding a few lines to the shutdown code; see Listing Eleven. Destroying the root of a CK group triggers recursive destruction through the tree, so all of our hertz-control variables are cleaned up simply by destroying the group. The other difference is that, since we don't need to create extra control FIFOs, there is of course no need to unlink them.

Controlling the Filter

At this point, you've added a filter and controlled the sampling rate, but still can't shift the filter's corner frequency to filter the noise frequencies in the signal. This requires adding a new text field and another step in the update callback to the Java program. The text field is straightforward—it's just like the rest of the Java code that adds graphical components in the ControllerGUI object. You declare it and set the label along with the other components (Listing Twelve). The remaining code instantiates the field, sets the initial value, and adds a line to the ActionListener so that when you send/receive values with the buttons, the SignalCommand object data is updated properly.

Last month, I built a SignalCommand object that encapsulated the interaction with the real-time FIFOs. But since there are now no FIFOs, SignalCommand controls the variables using the Controls Kit userspace utilities.

Viewing the Data With Controls Kit Tools

Using the ck_hrt_op command, you can look at what pieces are available for control by the Controls Kit:

# ck_hrt_op -Ln1

*:LowPassFilter

+—————>:Wn : 100.00

*:JavaControl

+—————>:FilterHz : 100

+—————>:SampleHz : 10

+—————>:NoiseHz : 8

+—————>:SignalHz : 1

There are two separate groups—one for the rolloff filter and its corner frequency, and another grouping our various hertz rates. With different switches, you can look at specific subtrees, change the detail level, or generate a raw XML tree of state information. You have an initial corner frequency of 100 Hz, sampling rate of 10 Hz, noise at 8 Hz, and core signal at 1 Hz. To ensure that you can safely clean out any noise, the filter thread always runs at 10 times the rate of the noise component.

Letting Java Control the System

Rather than piping variables over FIFOs, the SignalCommand object directly uses Controls Kit to control the variables. The sendCommand() routine now makes system calls in Listing Thirteen. For each command, it makes a system call to set a variable to the appropriate value and waits for the call to complete. The receiving mechanism works similarly, but with a different set of arguments.

In larger systems, it would be better to interact with the XML output from Controls Kit directly, which would result in fewer system calls. An added benefit is that the variables can be controlled independently—the SampleHz variable could be changed, followed by an update to FilterHz (or vice versa). With FIFO communication, this isn't possible because single-variable pushes conflict with the defined FIFO protocol. A single variable update requires every value to be sent over the FIFO to maintain a clean state.

Results

The benefits of these changes are easily apparent. Using the updated control GUI, I set the core frequency to 1 Hz and the noise to 64 Hz, with a sampling thread rate at 10 Hz and the filter's corner frequency at 100. In this case, the sampling thread is horribly undersampling, relative to the Nyquist rate. Figure 4 (Figures 1-3 appeared last month) shows the noisy pure signal and what happens when it is undersampled. The corner frequency of the rolloff filter is outside the range of the noise component, and the noise is untouched. Leaving the noise at 64 Hz with a sampling rate of 10 Hz and an updated corner frequency of 11 Hz (protecting everything above the sampling rate) provides the results in Figure 5.

With the filter enabled, the noise is handled, leaving a clean 1-Hz signal. The sampling thread can safely undersample the higher frequency noise component and not worry about aliasing. (Close inspection of the cleaned signal shows minor aliasing; this is a result of the smooth rolloff filter. If the noise had been farther from the corner frequency, the result would have been cleaner. Alternatively, a sharper filter from the Controls Kit could have been used.)

With this capability, you are free to do more work with the signal components that are most interesting, without worrying about other components. The result is a smaller piece of real-time code with the ability to do digital filtering, and more flexible real-time variable control from userspace.

MATLAB and Real-Time Components

In virtually every case, hard real-time systems are expected to capture, analyze, and immediately act on incoming data. Once data has been sampled, the real work begins—it needs to be analyzed. With this in mind, I'll now focus on integrating the signal data with MATLAB (a language and interactive environment for numerical computing from The MathWorks; http://www.mathworks.com/) to measure the effectiveness of the digital filter.

As you might expect, there are minor changes you need to make to the real-time code to talk to MATLAB. For instance, you must declare new FIFOs, then pipe data through them such that you can directly analyze the output with MATLAB code. In the real-time threads, you add Listing Fourteen, which declares more file descriptors and writes to them in addition to the normal /pure_signal and /sampled_signal FIFOs used by the Java visualization tool. These devices are created during startup and destroyed on exit (Listing Fifteen). They are simply additional FIFOs with another descriptor to write to. The Java code remains unchanged, and samples the data as in last month's example. The interesting code in this installment interacts with MATLAB.

In newer versions of MATLAB, the command environment supports direct input of Java code, and the environment directly interprets and runs the Java code. In this article, I pull some of the sampled data into MATLAB and analyze it, with the goal of numerically validating the effectiveness of the digital filter. Other analysis is possible. In fact, anything you could normally do with MATLAB can be done on the signal data. The difference here is that you are directly sampling and interacting with data, rather than capturing it and performing off-line analysis. The MATLAB code itself is straightforward; see Listing Sixteen.

First, you close everything down and reset the environment, then open the FIFOs as normal Java I/O objects. Since you could sample forever, you must ask for a sample count and prime the FIFOs in case the parameters have changed (there may be backlogged values representing a different filter/sample set). MATLAB uses a simple system call to interrogate (and control) the hertz rate of the sampling thread. Here, I only check the rate, which is needed for the periodogram() call. In another application, MATLAB could direct the real-time code to change behavior depending on the output it is seeing. The actual sampling is done in the two loops, which results in a mix of MATLAB and Java syntax. These loops read sample values out of the FIFO and into a normal vector. Once the requested number of samples have been pulled, the data is thrown into periodogram() to analyze the power spectral density of the signal. If the filter behaves correctly, you should see a noisy incoming signal, but a clean power distribution in the sampled data with a power spike only at the critical frequency.

Figure 6 shows the output of an unfiltered signal with a 1-Hz wave, noise at 38 Hz, sampled at 10 Hz. Running this through the MATLAB code with 500 samples yields Figures 7 and 8. Without the filter in place, you have a very unclean sampled signal.

In Figure 7, there is signal power at 1 Hz and a spike at the noise frequency. The undersampling real-time thread cannot analyze this properly without a filter, so aliasing creeps in and creates a false low-frequency signal below the 1-Hz component in Figure 8.

Running the same code with the same variables (1-Hz component, 38-Hz noise, sampling at 10 Hz), but setting the rolloff filter's corner at 11 Hz to filter out high-frequency noise will visually cleanse the sampled output. Figure 7 still represents the pure signal's power distribution, but Figure 9 shows clean output—there is significant power falloff with respect to the signal alias.

The power of the alias is lessened, but it's still there because I'm using a simple but not sharp rolloff filter—a sharper filter would reduce this alias. Moving the noise to 98 Hz while sampling at 6 Hz with a corner frequency at 7 Hz yields the output in Figure 10, where the noise is farther into the rolloff area and the output is much cleaner. A clean spike is visible at 1 Hz, but the false spike is gone. Another useful example can be seen by putting the noise directly in the rolloff so that it can easily corrupt the sampled data and create false spikes in the harmonics. Figure 11 shows this with a sampling rate of 22 Hz, noise at 24 Hz, and a corner frequency of 23 Hz. Clear but false power spikes can be seen popping up at 2 Hz, 4 Hz, 6 Hz, 8 Hz, 10 Hz, and so on.

Conclusion

From real-time sampling methodology to userspace control to MATLAB integration, I've covered a lot of ground. Still, the real-time component is less than 200 lines of code, including full initialization and destruction, dynamic POSIX FIFOs, three real-time threads, a hard real-time digital filter, and flexible, controlled integration with the nonreal-time code.

DDJ

Listing Seven

#include <rtl.h>
#include <time.h>
#include <unistd.h>
#include <rtl_fifo.h>
#include <pthread.h>
#include <math.h>
#include <sys/mman.h>
#include <ck_module.h>
#include <FSMCL_core.h> 

RTLINUX_MODULE(A_D_CKIT);
static pthread_t signal_gen_t;
static pthread_t sampler_t;
static pthread_t filter_t;

static int sampled_fd;
static int signal_fd;
static float cur_signal;
static float cur_signal_filtered;
#define PI 3.14159

static CK_entity_control_group;
static CK_entity_signal_hz;
static CK_entity_noise_hz;
static CK_entity_sample_hz;
static CK_entity_filter_hz;
static FSMCL_Rolloff_entity rolloff_filter; 

Back to Article

Listing Eight

void *filter(void *arg) { 
   int iperiod;
   int ifilter_hz;
   struct timespec next;
   float fsample;
   float period;
   clock_gettime(CLOCK_REALTIME, &next);
   while (1) { 
      ifilter_hz = CK_scalar_get_int(&filter_hz); 
      iperiod = (int)(1000000000/ifilter_hz);
      timespec_add_ns(&next, iperiod);
      clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME,&next, NULL);
      fsample = cur_signal;
      period = 1.0 / ifilter_hz;
      FSMCL_Rolloff(&fsample, &period, &rolloff_filter);
      cur_signal_filtered = fsample;
   }
   return 0;
}

Back to Article

Listing Nine

void *sampler(void *arg) { 
   struct timespec next;
   float sample;
   int isample;
   int iperiod, isample_hz;
   clock_gettime(CLOCK_REALTIME, &next);
   while (1) { 
      isample_hz = CK_scalar_get_int(&sample_hz);
      iperiod = (int)(1000000000/isample_hz); 
      timespec_add_ns(&next, iperiod);
      clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
      sample = cur_signal_filtered;
      isample = (int)(sample * 10000);
      isample = htonl(isample);
      write(sampled_fd,&isample,sizeof(int));
   }
   return 0;
}

Back to Article

Listing Ten

pthread_attr_t_attr;

mkfifo("/pure_signal",0777);
signal_fd = open("/pure_signal", O_RDWR | O_NONBLOCK);
ftruncate(signal_fd, sizeof(int)*10);
mkfifo("/sampled_signal",0777);
sampled_fd = open("/sampled_signal", O_RDWR | O_NONBLOCK);
ftruncate(sampled_fd, sizeof(int)*10);

CK_entity_init(&control_group, CK_GROUP, "JavaControl", 
                          "Control Group for Java interaction", NULL);
CK_scalar_int_init(&signal_hz, "SignalHz", 
                          "Core Signal Frequency",&control_group, 0, 100, 1);
CK_scalar_int_init(&noise_hz, "NoiseHz", "Noise Signal 
                           Frequency",  &control_group, 0, 100, 8);
CK_scalar_int_init(&sample_hz, "SampleHz", "Rate of Sampling 
                           Thread", &control_group, 0, 100, 10);
CK_scalar_int_init(&filter_hz, "FilterHz", "Rate of Filter 
                           Thread", &control_group, 0, 1000, 100); 
FSMCL_Rolloff_init(&rolloff_filter,"LowPassFilter", "Low Pass Filter", NULL);
CK_scalar_init_float_val(&rolloff_filter.Wn, 0, 200, 100);
CK_entity_set_suggested_representation(&rolloff_filter.Wn,"%.2f");
pthread_attr_init(&attr);
pthread_attr_setfp_np(&attr,1);
pthread_create(&filter_t, &attr, filter, 0);
pthread_create(&signal_gen_t, &attr, signal_gen, 0); 
pthread_create(&sampler_t, &attr, sampler, 0);

Back to Article

Listing Eleven

pthread_cancel(signal_gen_t);
pthread_join(signal_gen_t, NULL);
pthread_cancel(filter_t);
pthread_join(filter_t, NULL);
pthread_cancel(sampler_t);
pthread_join(sampler_t, NULL);
close(signal_fd); 
unlink("/pure_signal");
close(sampled_fd);
unlink("/sampled_signal");
FSMCL_Rolloff_destroy(&rolloff_filter);
CK_entity_destroy(&control_group);

Back to Article

Listing Twelve

Label_corner_freq_label;
JTextField corner_freq_text;
   private void setLabels() { 
   signal_hz_label.setText("Signal Hz: " + sc.getSignalHz());
   noise_hz_label.setText("Noise Hz: " + sc.getNoiseHz());
   sample_hz_label.setText("Sample Hz: " + sc.getSampleHz());
   corner_freq_text.setText(""+sc.getCornerFreq());
   signal_hz_slider.setValue(sc.getSignalHz());
   noise_hz_slider.setValue(sc.getNoiseHz()); 
   sample_hz_slider.setValue(sc.getSampleHz());
   corner_freq_text.setText(""+sc.getCornerFreq());
}

Back to Article

Listing Thirteen

public void sendCommand() { 
   Process proc;
   try { 
      proc = r.exec("ck_hrt_op -s " + sample_hz + 
                                     " -vp JavaControl/SampleHz");
      proc.waitFor();
      proc = r.exec("ck_hrt_op -s " + noise_hz + " -vp JavaControl/NoiseHz");
      proc.waitFor();
      proc = r.exec("ck_hrt_op -s " + signal_hz + " -vp JavaControl/SignalHz");
      proc.waitFor();
      proc = r.exec("ck_hrt_op -s " + filter_hz + " -vp JavaControl/FilterHz");
      proc.waitFor();
      proc = r.exec("ck_hrt_op -s " + corner_freq + " -vp LowPassFilter/Wn");
      proc.waitFor();
   } catch (Exception e) { 
            System.out.println("Error setting variables: " + e); 
   } 
}

Back to Article

Listing Fourteen

static int sampled_fd, matlab_sampled_fd;
static int signal_fd, matlab_signal_fd;
void *signal gen(void *arg) { 
   /* ... */
   write(signal_fd,&icur,sizeof(int));
   write(matlab_signal_fd,&icur,sizeof(int));
}
void *sampler(void *arg) {
   /* ... */
   write(sampled_fd,&isample,sizeof(int));
   write(matlab_sampled_fd,&isample,sizeof(int));
}

Back to Article

Listing Fifteen

int init_module(void) { 
   /* . . . */
   mkfifo("/pure_matlab_signal",0777);
   matlab_signal_fd = open("/pure_matlab_signal", O_RDWR | O_NONBLOCK);
   ftruncate(matlab_signal_fd, sizeof(int)*10);
   
   mkfifo("/sampled_matlab_signal",0777);
   matlab_sampled_fd = open("/sampled_matlab_signal", O_RDWR | O_NONBLOCK);
   ftruncate(matlab_sampled_fd, sizeof(int)*10);
   /* ... */ 
}
void cleanup_module(void) { 
   /* ... */
   close(matlab_signal_fd);
   unlink("/pure_matlab_signal");
   close(matlab_sampled_fd);
   unlink("/sampled_matlab_signal");
   /* ... */ 
}

Back to Article

Listing Sixteen

clear all; clf; close all;
r = java.lang.Runtime.getRuntime;
fp = java.io.FileInputStream('/pure_matlab_signal');
dis_pure = java.io.DataInputStream(fp);
fs = java.io.FileInputStream('/sampled_matlab_signal');
dis_sample = java.io.DataInputStream(fs);
samp_size = input('Number of samples: ');
% prime the fifos
for ii=1:10
   g=dis_sample.readInt;
   g=dis_pure.readInt;
end
p = r.exec('ck_hrt_op -gvp JavaControl/SampleHz');
pause(1); 
is = p.getInputStream;
samp_read = [ ];
num = is.available;

for ii=1:num
   samp_read = [samp_read read(is)];
end
close(is);
samp_freq = str2double(char(samp_read));

for ii=1:samp_size
   samp(ii) = dis_sample.readInt;
end

for ii=1:samp_size
   pure(ii) = dis_pure.readInt;
end
figure(1);
periodogram(samp, [ ], 512, samp_freq); 
xlabel('Sampled signal, Frequency (Hz)');
figure(2);
periodogram(pure, [ ], 512, 200);
xlabel('Pure signal, Frequency (Hz)');
dis_sample.close;
dis_pure.close;

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.