Sunday, September 25, 2016

Hojo and the case of the garbled WSPR packet

Hojo and the case of the garbled WSPR packet

Problem

After implementing my third telemetry packet, I found that data was getting garbled in my WSPR packets.  Gathering some data, It appeared that the garbling was occurring as follows:

Expected Data Received Data 
QE6IAB
QE6IAB (correct)
Q86IAB
Q8AIA (garbled)
Q26IAB
Q2AIA (garbled)


Diagnostics

False start

I spent a lot of time assuming that this problem was in my data, and that somehow my unsigned integer data got converted to signed integer, and the arithmetic to convert the integers to characters was garbling the string.  After a bunch of debugging, I did make one minor code change that would have dealt with that situation, but I wasn't confident I had nailed it.

Conclusive data


While testing the fix above, I was confident that my code should now generate clean telemetry strings, even if I didn't exactly understand the failure scenario that caused the problem in the first place..  I had run through every scenario I could think of with both good and bad data, and the strings were being created correctly.  I had moved on to other testing in preparation for flight, when I happened to notice that the data, as received by WSPR on my radio, was NOT what my program intended to send.  In fact, the data in the tracker was correct, but it was incorrect when received and decoded by the radio.  Ah hah!!!  The problem was not in the encoding of my ASCII string.  It was lower down, in the WSPR encoding routines!

I ran some diagnostics, and confirmed the problem.  I generate a "tone array" based on the characters to send.  Here's a sample of the tone array, as generated by my code, and what the tone array should have been, when running the "wsprcode.exe" program, shipped for Windows.

From my code:
Building Tone array for Q36FAA  FN12  60
3 1 2 0 2 2 2 2 1 0 0 0 3 1 3 2 2 0 3 0 2 1 0 3 3 1 3 0 2 0 
0 2 0 0 3 0 2 3 2 1 0 0 2 0 2 2 1 0 3 3 2 0 1 1 2 1 2 2 0 3 
3 2 3 0 2 0 2 3 1 2 3 2 1 2 1 2 3 0 0 1 0 0 1 2 1 3 0 2 2 3 
1 2 1 0 3 0 2 2 3 0 2 0 2 2 3 2 0 1 2 0 3 3 3 2 3 3 2 0 1 3 
0 1 0 0 0 1 1 1 2 0 0 2 2 1 2 1 0 0 3 1 2 0 0 2 2 0 0 3 1 2 
1 0 3 3 0 0 2 3 1 0 2 0
From wsprcode.exe:
Channel symbols:
      3 1 0 0 2 0 2 2 3 0 2 0 3 1 1 2 2 0 3 0 2 3 0 3 1 3 3 0 0 2
      2 2 2 2 1 0 0 1 0 1 2 2 2 0 0 0 3 0 1 1 0 0 1 1 0 1 0 0 2 3
      3 2 1 0 0 0 0 3 3 2 3 2 3 0 3 2 1 2 0 1 2 0 3 2 3 1 2 2 2 1
      3 2 1 0 3 0 0 0 1 0 2 2 2 2 1 0 2 1 2 2 1 3 1 2 1 3 0 2 1 3
      0 3 2 0 2 1 1 1 2 2 0 2 0 1 0 1 0 0 1 1 2 0 2 2 0 0 0 3 3 0
      1 0 3 3 0 0 0 3 1 0 2 0
They don't match!

 I had gotten a wspr encoding subroutine from a blog post by Mark VandeWettering (K6HX).  I love the code because it's really lean.  There are other routines out there, but they're using malloc() and free() calls and doing all kinds of whacky stuff.  Mark's routine was short, elegant, and brutally efficient.

The comments at the code made it very clear what the problem might be, once I realized it was an encoding issue:

/* Callsigns must be 2x3, 1x3, 2x1, or 1x2 for the purposes of this code */

Therein lies the rub.   "2x3" means "Two characters before the digit, and three after."  So, a telemetry callsign like "QE6IAB" fits that beautifully.  However, a telemetry callsign like "Q86IAB" is ambiguous.  I needed to read his code.

The Fix


Looking at the code, he was basically right-adjusting the string into an array, forcing the digit to be in column 3 (array index 2).


if (isdigit(callsign[1])) {
 /* 1x callsigns... */
 for (i=0; i<strlen(callsign); i++)
    call[1+i] = callsign[i] ;
    } else if (isdigit(callsign[2])) {
 /* 2x callsigns... */
 for (i=0; i<strlen(callsign); i++)
    call[i] = callsign[i] ;
    } else {
 return 0 ;
    }

This code was misinterpreting the callsign, in the event of two consecutive digits.   A quick and dirty fix was this:

if ( (isdigit(callsign[1])) && (!isdigit(callsign[2])) ) {
 /* 1x callsigns... */
 for (i=0; i<strlen(callsign); i++)
    call[1+i] = callsign[i] ;
    } else if (isdigit(callsign[2])) {
 /* 2x callsigns... */
 for (i=0; i<strlen(callsign); i++)
    call[i] = callsign[i] ;
    } else {
 return 0 ;
    }

Conclusion

That one-liner fixed my problem, and my telemetry callsigns are now coming in beautifully in my current flight.

| 2016-09-25 14:04 | KD2EAT   | 14.097061 | IN29 | +33 | DC5AL-R   | 20m  |
| 2016-09-25 14:06 | QQ6LFT   | 14.097055 | IN29 | +53 | DG7BBP    | 20m  |
| 2016-09-25 14:08 | Q46LAB   | 14.097060 | IN29 | +60 | DC5AL-R   | 20m  |

I'm grateful to Mark for sharing his code so generously on github for the benefit of the ham community!

No comments:

Post a Comment