Spectrum analyzer app code

For a learning experience, I wrote a spectrum analyzer and since the code is pretty short, I’ve just posted it below.

It scans all 256 channels, with the receiver set up in the same way as the other Pololu apps except that sync word detection is disabled. Using the companion app test_radio_signal_tx running on another wixel, one can clearly see the strong overlap between two adjacent channels; close to 100%.

I have not been inspired to come up with a fancy display of the output, so at the moment, the code scans through all channels 10 times (listening about 1.6 ms per scan per channel) and reports those channels with maximum signal strength greater than -90 dBm over the usb serial connection (e.g. Hyperterm). Both average and maximum signal strength values are reported.

All comments, modifications and improvements are welcome.

PS: I want to thank everyone at Pololu who contributed to the thorough wixel user guide. The instructions on using Eclipse were VERY helpful – it is lovely software with some very nice features. I would add only that I found it easier to load apps using the Wixel Configuration Utility, rather than using the command line window, mainly because no typing is required.

Thanks, Jim

/* spectrum analyzer:
 *
 * The receivers(s) (the Wixel(s) loaded with spectrum_analyzer) will
 * report the signal strength on all 256 channels.
 *
 * Average and maximum signal strength is reported in dBm on the USB serial connection.
 * This will be a number typically between -100 and -30, indicating the signal strength.
 * Heavily modified from test_radio_signal_rx app by S. James Remington
 *
 */

#include <wixel.h>
#include <radio_registers.h>
#include <stdio.h>
#include <usb.h>
#include <usb_com.h>

static volatile int16 DATA i,channel;
static volatile int32 DATA rssiSum;
static volatile uint8 DATA reportLength;
static volatile uint8 XDATA report[20];
static volatile int16 XDATA rssiAvg[256],rssiMax[256],rssiVal=0;
static volatile uint8 XDATA rfdata;

void updateLeds()
{
    usbShowStatusWithGreenLed();
    LED_YELLOW(0) ;
    LED_RED(0);
}

void perTestRxInit()
{
    radioRegistersInit();


    MCSM0 = 0x14;    // Auto-calibrate  when going from idle to RX or TX.
    MCSM1 = 0x00;    // Disable CCA.  After RX, go to IDLE.  After TX, go to IDLE.
    // We leave MCSM2 at its default value = 0x07
	MDMCFG2 = 0x70;	//disable sync word detection
	RFST = 4; //idle radio
}

void CheckRadioChannels()
{
	int8 j;

	LED_YELLOW(1);
	for(channel=0; channel<256; channel++)
		{
		rssiAvg[channel] = 0;
		rssiMax[channel] = -999;
		}

	for(j=0; j<10; j++) //ten times through all channels
	{

	usbComService(); //keep usb connection alive

	for(channel=0; channel<256; channel++)
		 {
		 while(MARCSTATE != 1);  //radio should already be idle, but check anyway
		 CHANNR = channel;
		 RFST = 2;  // radio in RX mode and autocal
		 while(MARCSTATE != 13);  //wait for RX mode
		 rssiSum = 0;
		 for (i=0; i<100; i++)
		 	 {
			 if (TCON & 2) //radio byte available?
			 	 {
				 rfdata=RFD; //read byte
				 TCON &= ~2; //clear ready flag
			 	 }
			 rssiSum += radioRssi();
		 	 }
		 RFST = 4; //idle radio

		 rssiVal = (int16) (rssiSum/100);
		 rssiAvg[channel] += rssiVal;
		 if (rssiMax[channel] < rssiVal) rssiMax[channel] = rssiVal; // save maximum

		 }  // the above loop takes about 414 ms on average, so about 1.6 ms/channel
	}

	for (channel=0; channel<256; channel++) rssiAvg[channel] /= 10;

	LED_YELLOW(0);
}

void reportResults()
{
		for (i=0; i<256; i++)
    	 {
			if (rssiMax[i] > -90) //report activity on channel if maximum is above -90 dBm
				{
				while (usbComTxAvailable() < 20) usbComService() ;    //wait for usb TX buffer space
      			   reportLength = sprintf(report, "%4d, %4d, %4d\r\n", i, rssiAvg[i], rssiMax[i]);
     			   usbComTxSend(report, reportLength);
				}
     	 }
}

void main()
{
    systemInit();
    usbInit();
    perTestRxInit();
    usbInit();
    while(1)
    {
    	boardService();
        usbComService();
        updateLeds();
        CheckRadioChannels();
        reportResults();
     }
}

Hello, Jim.

I’m glad that you enjoyed using Eclipse! We use Eclipse here to develop all the Wixel apps and like it too.

At a command prompt it only takes two keystrokes to run the previous command again: Up Arrow, Enter. For me, that amount of typing takes a lot less time than the clicking that would be required to load apps using the Wixel Configuration Utility.

Thanks for sharing your spectrum analyzer code with us! What kind of license are you releasing the app under?

I made a branch for developing your app in the wixel-sdk repository on github and started making modifications and improvements. You can see the final result here:
github.com/pololu/wixel-sdk/blo … analyzer.c

You can see the list of commits (changes) I made and the messages I wrote to explain them:
github.com/pololu/wixel-sdk/com … 2Fanalyzer

If you click on a commit in that list you can see exactly what part of the code changed, for example:
github.com/pololu/wixel-sdk/com … 57057d7164

The main thing I did to the app was to make sure that updateLeds() and boardService() get called frequently. Without doing that, I noticed that the USB connection was sometimes very slow to start up and the green LED would sometimes remain off for long periods of time.

I haven’t changed the basic algorithm for checking radio channels. You’re only spending 1.6ms on each channel before switching, and a good chunk of that is spent just calibrating the radio’s frequency synthesizer. For some perspective, it takes about 1 ms for a Wixel to transmit a typical data packet. Your timing would work pretty reliably for detecting a Wixel that is frequently transmitting as fast as it can, but it would not work reliably to detect other types of Wixels (e.g. one running the wireless serial app, where there is no guarantee for how often a packet will be transmitted). You could, of course, spend a second on every channel but then it would take four minutes to generate a complete report. I’m not sure what the best solution is but maybe you can come up with something.

–David

Hi, David:

Thanks for your comments, additions and for creating a Github version. As far as I’m concerned, this is open source code, but I haven’t taken the time to learn how Github works.

I realized the need to more frequently call usbComService and updateLeds – that was on my TODO list. As for time spent scanning each channel, that could perhaps be a parameter as there are obvious tradeoffs.

Would it work to have a timer interrupt routine run every 20 ms or so, with calls to usbComService and updateLeds?

I’ve been experimenting and found it useful to calculate and output the variance of the RSSI as well as the average and max. I will add that feature to the Github version.

Cheers, Jim

That’s cool that you plan to work on improving the app. You might also enjoy learning how to use git, and using it to distribute your changes. You can make your own fork of the wixel-sdk repository on github.com and work on developing the app. Git makes it easy to merge your changes with the changes of other developers (e.g. me) and you would get more credit for your work because your name and email address would appear in the log. But if that sounds like a pain to you, it’s not a big deal; we’re only talking about a few hundred lines of code anyway.

–David

It would probably be OK to call updateLeds in an interrupt routine, but I would not call usbComService because it may choose to send a packet to the computer. If that happens during putchar, after usbComTxAvailable() has been called but before usbComTxSendByte() is called, then bad things will happen.

In general I try to keep the interrupt service routines as small and simple as possible; they only have code that needs to be run immediately. It is difficult to safely share memory between main loop code and ISR code, so it’s easiest to run code in the main loop when possible.

–David