<texit>\part{HOWTOs}</texit>
From Conception to Measurement
<texit>\index{From Conception to Measurement|(}</texit> Of course the main idea behind the MSR is that you're able to write new modules for it. This chapter will give an introduction with an actual example of a module as well as an implementation of a radio-transmission. After this you should be able to create your own modules and put them into use. An important introduction can be found in chapter <ref>cha:simple radio</ref>. You should also already have run the example in chapter <ref>cha:getting started</ref>. This part is a bit heavy on coding, but you won't be able to write modules without a good knowledge of C.
Defining New Modules
<texit>\index{From Conception to Measurement!Defining new modules}</texit>
In here you will learn the most important things about a module, how it works, how to use it, and how to extend it. This example is already present in the tree, but you can read this section to get a feel of it.
The goal of this module will be to measure the SNR of the signal. Even though this functionality is already implemented in a module, it is a nice idea to have a possibility to compare the results of the two approaches. The existing module compares the received training sequence with the original in order to calculate the SNR. As the training sequence is only part of a transmitted slot, it is a good idea to compare this SNR with the SNR computed in here.
![]() |
The example slot |
In order to calculate the SNR differently, we will transmit a random sequence and then compare it after the transmission. This is depicted in fig. <ref>cap:The-example-slot</ref>. In order to know the exact amplitude, it is important to know the random sequence in advance. This is done by setting the seed parameter of the random-module.
Once we have the received signal
and the transmitted signal
, we can calculate the amplitude:
and also the variance:
and the signal to noise ratio is then
The correctness of this assertion is left as an exercise to the reader.
The Files
Now that we know what it is about, let's have a look at the written files. For your information you will learn where the templates for the files come from in every section. The discussion then is only about the parts that have been added. In the directory Modules/Signals, there is a directory called SNR. in there you find the code for the SNR-module. The MSR knows about this directory because of the file Modules/Signals/Makefile that has an entry SNR in the list of DIRS. You can have a look at this Makefile to see it.
The template files come from the Conventions directory and are called:
Conventions/multi_*.c Conventions/Makefile.module
Most of the modules come in two parts: one sending part and one receiving part. But as they usually are used in a pair, they are put together into one module. So as not to be confused with different modules, they are renamed to:
multi_template.c -> snr.c multi_template_send.c -> snr_send.c multi_template_rcv.c -> snr_rcv.c Makefile.module -> Makefile
snr.c
Now look first at the file snr.c and look at the places that contain some documentation. It is really important to keep this documentation up-to-date, so that other people know what it's about:
SNR - measure the signal to noise ratio of a transmitted slot. In order to do this, we send a slot of known random data that is measured on the other side.
This is all that is needed. Not a big description, just enough to know what it's about.
What it does is the following: once the module snr is loaded into the memory, the function spc_module_init is called, which in turn calls rcv_module_init from snr_rcv.c and send_module_init from snr_send.c. These two functions are responsible to tell the CDB about their name, their input and output as well as their paramters.
snr_send.c
This is the part that prepares the slot.
documentation. At the top of the file, you see again a short description of the module
snr_send.c - sends a slot of random symbols
and a bit further down (after the copyright message) a bit more of description.
This module expects some random input that is then modulated using QPSK modulation so that the noise can be mesured. It can send the QPSK symbols either on the axis or in the corners.
config-structure <texit>\index{Module!Example!config}</texit> We want the user to be able to change the amplitude of what we send over the channel. So, edit the config-structure, and make it something like:
typedef struct { // The amplitude of the generated QPSK-signal int amplitude; // 32767 // The QPSK-type, 0->in the corner, 1->on the axes int type; // 0 } config_t;
It may seem strange that we take an integer for the amplitude, but
you have to know that the signals are all in 16-bit integers, so what
usually is between
and
is now between
and
.
private-structure <texit>\index{Module!Example!private}</texit> Once the user changed the configuration, we will store it in our private variable. This is more for convenience than anything else:
typedef struct { int amplitude; int type; } private_t;
The other structure may be left empty, we don't need it for this example. Just after the structures is a function called
send_init <texit>\index{Module!Example!init}</texit> Why another initialisation function, you might ask. Well, remember from chapter <ref>sec:framework</ref> that first the module is registered with the CDB, before it is possible to instantiate it. So, this function is what is called each time this module is instantiated. In our example, we just want to put a default-value in the amplitude-part of the configuration, so add this line:
config->amplitude = 32767; config->type = 0;
32767 is the maximum number that we can have in a 16-bit signed variable.
send_configure_input
<texit>\index{Module!Example!configure_input}</texit>
This function is called whenever the MSR wants to know what the size
of the input should be, given the size of the output. So, for each
2 bit of input, we create one symbol with the QPSK representation.
This means that two bits of input create one symbol of output, at
least for an even number of symbol-outputs. The only tricky part here
is that the input is not counted in bits, but rather in bytes. So
, which is written in this
function as
size_in(0) = ( size_out(0) + 7 ) * 2 / 8;
This assures that we always have enough bits to write to the output.
send_configure_output <texit>\index{Module!Example!configure_output}</texit> The same as before, but this time the opposite direction:
size_out(0) = size_in(0) * 8 / 2;
send_reconfig <texit>\index{Module!Example!reconfig}</texit> We use this function to copy the configuration-data to our private structure:
private->amplitude = config->amplitude; private->type = config->type;
send_pdata <texit>\index{Module!Example!send_pdata}</texit> Now comes finally the processing function. This is where the main action takes place. Let's first define some variables:
// Definition of variables - don't touch stats_t *stats; int i, amp; U8 *in; SYMBOL_COMPLEX *out;
To get to the input and output-buffers, we have to do the following:
in = buffer_in(0); out = buffer_out(0);
The first line deletes the data bit on the input-port, signaling the MSR that the input-port is free again to receive some data. Furthermore it returns a pointer to the input-buffer of this module. The second line gets the output-buffer of this module and sets the data bit on the output-port. Once this function returns, the MSR checks for the data-bit in the output-port and, if it is set, handles the processing to the next module.
OK, now we just have to process the data:
// Fill the slot with random QPSK symbols for ( i=0; i<size_out(0); i++ ){ switch( 2 ){ case 1: // The amplitude in this case is // Sqrt( Re^2 + Im^2 ) and thus the // desired amplitude has to be divided by // sqrt(2) amp = private->amplitude / sqrt( 2 ); out[i].real = ( 2 * ( *in & 1 ) - 1 ) * amp; *in = *in >> 1; out[i].imag = ( 2 * ( *in & 1 ) - 1 ) * amp; *in = *in >> 1; break; case 2: amp = private->amplitude; if ( *in & 1 ){ *in = *in >> 1; out[i].real = ( 2 * ( *in & 1 ) - 1 ) * amp; out[i].imag = 0; } else { *in = *in >> 1; out[i].imag = ( 2 * ( *in & 1 ) - 1 ) * amp; out[i].real = 0; } *in = *in >> 1; break; } // Get the next input-byte of random if ( i && !( i % 4 ) ){ in++; } }
You might not be completely fond of this example, but it works (yet to check).
send_custom_message This function is not needed and can be deleted
send_module_init <texit>\index{Module!Example!module_init}</texit> Registers this module with the CDB. The CDB first wants to be informed about the type of module to be attached 1). In our case, we have one input, one output, one config-parameter and 0 stats-parameter:
desc = swr_spc_get_new_desc( 1, 1, 2, 0 );
Then we have to tell about the config-parameter, the input-type and the output-type:
UM_CONFIG_INT( ''amplitude'' ); UM_CONFIG_INT( ''type'' ); UM_INPUT( SIG_U8, 0 ); UM_OUTPUT( SIG_SYMBOL_COMPLEX, 0 );
In order for the MSR to know what functions to call in what case, we have to define 'call-back functions'. As these are always the same, they are already pre-defined in the templates, and we only have to delete the send_custom_msg entry. Now the module-description is complete, save for the name:
send_id = swr_cdb_register_spc( &desc, "snr_send" );
snr_rcv.c
Let's start with the comment in the beginning of the file:
This module receives the stream from the matched filter and the stream of random-signals that are supposed to be the same that have been used by the snr_send. It then calculates the amplitude, the variance and the snr.
This means that this module has two inputs: one from the channel, and another one from the random-module.
config-structure <texit>\index{Module!Example!config}</texit> Again we have the possibility to change the type:
typedef struct { // The QPSK-type, 0->in the corner, 1->on the axes int type; } config_t;
stats-structure <texit>\index{Module!Example!stats}</texit> So we're able to retrieve the SNR from the outside, we have to write it in this structure:
typedef struct { double snr; } stats_t;
rcv_init <texit>\index{Module!Example!init}</texit> Let's just start with a SNR of -2.3:
stats->snr = -2.3; config->type = 0;
rcv_reconfigure <texit>\index{Module!Example!reconfigure}</texit>
private->type = config->type;
The functions rcv_configure_input and rcv_configure_output can be deleted, as this module is at the end of the chain.
rcv_pdata <texit>\index{Module!Example!pdata}</texit> Here goes the working function. It's just about implementing the above formula. Let's go through step by step. Definition of variables:
stats_t *stats; SYMBOL_COMPLEX *in, *buf_rnd; U8 *in_rnd; double signal = 0., noise = 0.; int i;
Then we have to make sure that we have both the signal from the channel and the random-data:
if ( !data_available(0) !data_available(1) ){ PR_DBG( 4, "Not all data available yet\n" ); return 0; }
Now we reconstruct
so that we can implement the formula given
above. Instead of calculating
and then taking the sign of it,
we directly calculate
with an amplitude of
. Again, once
for the QPSK-signals on the axes, and once for the signals tilted
by
in_rnd = buffer_in(1); buf_rnd = swr_malloc( size_in(0) * sizeof( SYMBOL_COMPLEX ) ); for ( i=0; i<size_in(0); i++ ){ switch( private->type ){ case 0: buf_rnd[i].real = ( 2 * ( *in_rnd & 1 ) - 1 ); *in_rnd = *in_rnd >> 1; buf_rnd[i].imag = ( 2 * ( *in_rnd & 1 ) - 1 ); *in_rnd = *in_rnd >> 1; break; case 1: if ( *in_rnd & 1 ){ *in_rnd = *in_rnd >> 1; buf_rnd[i].real = ( 2 * ( *in_rnd & 1 ) - 1 ); buf_rnd[i].imag = 0; } else { *in_rnd = *in_rnd >> 1; buf_rnd[i].imag = ( 2 * ( *in_rnd & 1 ) - 1 ); buf_rnd[i].real = 0; } *in_rnd = *in_rnd >> 1; break; } // Get the next input-byte of random if ( i && !( i % 4 ) ){ in_rnd++; } }
Now we can calculate the amplitude of the signal:
in = buffer_in(0); // Calculate signal energy for ( i=0; i<size_in(0); i++ ){ signal += (double)( in[i].real ) * buf_rnd[i].real + (double)( in[i].imag ) * buf_rnd[i].imag; } signal = signal / size_in(0);
And the noise-variance:
for ( i=0; i<size_in(0); i++ ){ noise += pow( (double)in[i].real - signal * buf_rnd[i].real, 2 ) + pow( (double)in[i].imag - signal * buf_rnd[i].imag, 2 ); } noise = noise / size_in(0) / 2;
Finally we can calculate the snr:
PR_DBG( 2, "signal_amp: %i, noise_amp: %g\n", (int)signal, noise ); // And write the snr swr_sdb_get_stats_struct( context->id, (void**)&stats ); if ( noise > 0 ){ stats->snr = 10 * ( log10( signal ) * 2 - log10( noise ) ); } swr_free( buf_rnd );
Again, the rcv_user_msg is not used and can be deleted. rcv_module_init We have only 1 input, no output, 1 config-variable and 1 stats-variable, and lots of functions are not used:
desc = swr_spc_get_new_desc( 1, 0, 1, 1 ); UM_STATS_DOUBLE( ''snr'' ); UM_INPUT( SIG_SYMBOL_COMPLEX, 0 ); desc->fn_init = rcv_init; desc->fn_reconfigure = rcv_reconfig; desc->fn_process_data = rcv_pdata; desc->fn_finalize = rcv_finalize; rcv_id = swr_cdb_register_spc( &desc, "snr_rcv" );
Makefile
<texit>\index{Module!Example!Makefile}</texit> In the makefile we have to tell the final name of the module, as well that we use the math-library:
MODULE_NAME = snr MATH = true
Compile it
Now you can try to compile it by typing make on the command-line. If there are any errors, try to fix them, the above lines should work, they have been tested. In order to include this module even better in the MSR, you can add the name of the directory to the file Modules/Signals/Makefile in the line DIRS = . Like this a top-level make will also update the SNR-module.
Testing
Up to now only the module has been written. It is not yet in a usable state, as it is only registered with the CDB, but not yet instantiated. Theoretically we could write everything in the module to make an instance, but this would turn upside-down the idea of modules. So what we need is an own program that implements the chain and runs it, just to look how good it runs.
Perhaps as a surprise, this program will again be a module, but this time a module that does actually something. Implementing a simple chain. So there is a function called um_module_init that will be called upon inserting the module. This function itself creates a new thread that will be used to create the chain. In order to be compatible for further RTLinux implementation, we have to do this two-step calling.
The Directory
In the MSR, there is a directory called Test which holds already different tests. The test for the SNR is of course in a directory called Test/SNR. The templates for the test-module are in
Conventions/test_template.c Conventions/Makefile.module
Again, for easier handling they are renamed:
test_template.c -> test_snr.c Makefile.module -> Makefile
Makefile
The makefile wants to know the name of the module, which is test_snr, as well as the modules to load in order for the MSR to function correctly. In our case, these are the modules random, snr, midamble, rrc and block:
MODULE_NAME = test_snr DEPENDS = random snr chest rrc block
test_snr.c
Let's have a look at the documentation:
Make a simple chain: random - snr_send - chest_send - rrc - block - rrc_rcv - chest_rcv - snr_rcv and additionally: random - snr_rcv(2)
Then we can create our main-function, which is called start_it for the test-program.
start_it
The first thing we have to do is to create a chain of modules. A chain is a logical suit of signal-processing modules, that take some input and produce some output that is handled further down the chain.
When using the swr_chain_create functionwe give a list of all modules, that will be automatically connected together, and finish the list with END_CHAIN. In this call to swr_chain_create, you see three different kind of macros, NEW_SPC_VAR, NEW_SPC and OLD_SPC_IN all of which are described in <ref>subsec:sdb</ref>. In short, while the former allows you to give a variable where a reference to the module will be stored, the latter just creates the module and connects it, without giving the reference of the created module. The third takes an already defined module for further connections.
swr_sdb_id rnd, mafi, snr_rcv; test_chain = swr_chain_create( NEW_SPC_VAR( ''random'', rnd ), NEW_SPC_VAR( ''snr_send'' ), NEW_SPC( ''chest_send'' ), NEW_SPC( ''rrc'' ), NEW_SPC_VAR( ''block'' ), NEW_SPC_VAR( ''rrc_rcv'', mafi ), NEW_SPC_VAR( ''chest_rcv'', mafi ), NEW_SPC_VAR( ''snr_rcv'', snr_rcv ), END_CHAIN ); swr_sdb_set_config_int( mafi, "cacl_taps", 8 ); test_chain_2 = swr_chain_create( NEW_SPC_VAR( ''random'', rnd2 ), OLD_SPC_IN( snr_rcv, 1 ), END_CHAIN );
So we have created a chain. The modules have the following function:
- random
- create random bytes
- snr_send
- our module created above, takes some random bytes as input, and creates a QPSK output
- chest_send
- inserts the training-sequence in the middle of the stream
- rrc
- Root Raised Cosine pulse-shape filtering
- block
- a very simple channel-simulation
- rrc_rcv
- Applies again the Root Raised Cosine filtering
- chest_rcv
- uses the training-sequence to make a channel-estimation and does a matched-filtering on the received samples
- snr_rcv
- our module to calculate the SNR
If we compile and test our module with make; make user, this chain will be created and the program will exit. What we forgot is to really use this chain. For this, the random module listens to user-messages, and begins creating a random-output whenever it receives such a user-message. But first we have to make sure that both random-modules create the same values:
swr_sdb_set_config_int( rnd, "seed", 0x1234 ); swr_sdb_set_config_int( rnd2, "seed", 0x1234 );
Then we adjust a bit the amplitudes, so that we don't run all the time on the edge:
swr_sdb_set_config_int( snr_send, "amplitude", 16384 / 4 ); swr_sdb_set_config_int( mid, "amplitude", 16384 / 4 );
To make it a bit more nice, we have a look at different values, using the sigma parameter of the block module:
for ( i=0; i<50; i+=5 ){ swr_sdb_set_config_double( block, "sigma", i ); swr_sdb_send_msg( rnd, SUBS_MSG_USER, NULL, -1 ); swr_sdb_send_msg( rnd2, SUBS_MSG_USER, NULL, -1 ); PR( "Amp: %2i:%2i, Noise: %3i:%3i SNR : %5.5g - %5.5g = %5.5g\n", swr_sdb_get_stats_int( mafi, "mid_amp" ), swr_sdb_get_stats_int( snr_rcv, "amp" ), swr_sdb_get_stats_int( mafi, "noise_var" ), swr_sdb_get_stats_int( snr_rcv, "var" ), swr_sdb_get_stats_double( mafi, "snr" ), swr_sdb_get_stats_double( snr_rcv, "snr" ), swr_sdb_get_stats_double( mafi, "snr" ) - swr_sdb_get_stats_double( snr_rcv, "snr" ) ); }
And after a make; make user you should see something like:
Amp: 63:63, Noise: 1:1 SNR : 34.40 - 34.24 = 0.15426 Amp: 62:62, Noise: 5:6 SNR : 28.52 - 28.10 = 0.42007 Amp: 62:62, Noise: 27:26 SNR : 21.50 - 21.58 = -0.077267 Amp: 62:62, Noise: 66:63 SNR : 17.61 - 17.83 = -0.21601 Amp: 62:61, Noise: 122:111 SNR : 14.96 - 15.34 = -0.37944 Amp: 61:61, Noise: 190:187 SNR : 12.89 - 13.07 = -0.18071 Amp: 58:59, Noise: 179:219 SNR : 12.72 - 12.01 = 0.70692 Amp: 65:64, Noise: 317:322 SNR : 11.23 - 11.06 = 0.16854 Amp: 66:63, Noise: 421:393 SNR : 10.14 - 10.15 = -0.016712 Amp: 65:65, Noise: 528:555 SNR : 9.026 - 8.83 = 0.18867
So you see that our method gives more or less the same results as the snr calculated in the matched_filter.
Going Over the Air
OK, now that the module is written, a simple test-case shows that our module works, we can go on and write a simple radio that transmits the SNR-slot and then receives it and shows the result. We will make a simple radio that has a master, the BaseStation, that transmits the synchronisation-signal, and a client, the MobileStation, that synchronises to it and sends a SNR-slot in return.
The Directories and Files
It will be a radio, so we find the code in the directory Radios/SNR. The base for this radio is the simple-radio that can be found at Radios/Simple and contains the following files:
simple_radio.h Makefile BS/Makefile BS/radio_bs.c MS/Makefile MS/radio_ms.c
To reflect the fact that it isn't the simple radio anymore, we have to adjust the names in the Makefiles. The first lines in BS/Makefile contains:
MODULE_NAME = radio_snr_bs DEPENDS = rrc synch energy mapper chest random \ rndstr spread sink cch macro_sch snr
and in MS/Makefile reads:
MODULE_NAME = radio_snr_ms DEPENDS = rrc synch energy mapper chest random \ rndstr spread sink macro_synch macro_sch snr
README
This file also reflects the changes and has a very small documentation in it.
MS/radio_ms.c
There is a lot of things to say about the basic system. You can find an introduction in <ref>cha:simple radio</ref>. Here we just try to focus on the things necessary to run our SNR-module over a real channel.
A very short simplification: when the mobile synchronises for the first time to the base-station, it creates the necessary chains. This happens in the function synchronised. Near the end of this function, you have to replace the declaration of the UP-chain with the following chain:
// And setup a simple UP-chain... ch_up = swr_chain_create( NEW_SPC_VAR( "random", rnd ), NEW_SPC( "snr_send" ), NEW_SPC( "chest_send" ), NEW_SPC( "rrc" ), OLD_SPC_IN( stfa, 1 ), CHAIN_END ); swr_stfa_notice_sdb( stfa, 1, rnd ); PR( "Ready to go" );
You see a new macro in the function swr_chain_create here, which is called OLD_SPC_IN and uses the stfa module. The macro tells the function that this module has already been created before. The module stfa is quite important in the MSR: it creates the link between the modules and the actual hardware. So an input to the stfa is like a connection to the antenna.
As we don't know exactly when the mobile will be synchronised with the base-station, we set the seed of the random module every frame to the same value. Like this we're sure that both the BS and the MS have the same random-values. To achieve this, the function do_send_up is handy. It is called once in a frame, and we can put the following line in there:
swr_sdb_set_config_int( rnd, "seed", 0x1234 );
That's it for the mobile-station part.
radio_bs.c
This part of the radio sets up the chains for reception anyway and then just waits on what happens. As it gives the synchronisation and doesn't need to wait for it, it is much more simple than radio_ms.c. So we can directly change the construction of the UP-chain in the function start_it to:
PR( "Setting uplink in slot 1\n" ); ch_rach = swr_chain_create( OLD_SPC_OUT( stfa, 1 ), NEW_SPC( "rrc_rcv" ), NEW_SPC_VAR( "chest_rcv", mafi ), NEW_SPC( "snr_rcv" ), CHAIN_END ); ch_rach_2 = swr_chain_create( NEW_SPC_VAR( "random", rnd ), OLD_SPC_IN( snr_rcv, 1 ), CHAIN_END ); swr_sdb_set_config_int( sch, "mafi0", mafi ); while ( looping++ ){ usleep( 1000000 ); PR( "mafi0: %g, mafi1: %g\n", swr_sdb_get_stats_double( mafi, "snr" ), swr_sdb_get_stats_double( snr_rcv, "snr" ) ); }
One last important thing we don't have to forget: once a frame we have to tell the random-module to generate some data. This is best done in the function called do_send_down, which is used once a slot. So we can insert there:
swr_sdb_set_config_int( rnd, "seed", 0x1234 ); swr_sdb_send_msg( rnd, SUBS_MSG_USER, NULL, -1 );
Running it with the channel-simulation
Again, to help track down errors, it is more adviseable to run it first in simulation-mode, like this you can track down errors much more simple. In order to do so, change to the directory Radios/SNR and type make to compile it, and make server; make show_bsms which should bring up two windows, one showing the mobilestation and another showing the basestation. If something goes wrong with the compilation, fix it and run make again. If something goes wrong with the display, type make kill; make cleanproc which should clean-up the directories, and then you can try make server; make show_bsms again.
Running the real thing
Now that you did all this work and the modules returned some decent values, you can be pretty sure that it shouldn't run havoc in real-time mode. So let's try it. First, you have to run the radio on the basestation, issuing a make rf_show from the Radios/SNR/BS directory. Then, on the other machine, you can run make rf_show from the Radios/SNR/MS directory. If everything is set up correctly, the hardware is OK and all things are nicely connected and plugged in (this will give another chapter or two, installing the hardware), you should again see two windows, one from the basestation and one from the mobilestation, and the values this time are REAL values. If you come this far, congratulations! <texit>\index{From Conception to Measurement|)}</texit>