Showing posts with label WSPR encoder. Show all posts
Showing posts with label WSPR encoder. Show all posts

Tuesday, February 7, 2012

WSPR Encoder - Version 1

Updated this posting 8 Feb 2012

I have decided not to release my WSPR encoder for Propeller on the Object Exchange at the Parallax web site and instead I created a Github project repository for it.  I am releasing it free of charge under the same MIT License that is used at the Object Exchange however.

You can obtain the files at https://github.com/ko7m/WSPR.  Looking at this web page, you will see the following:


By clicking on any of the files you can examine them or you can get it all in one go by clicking on "ZIP" near the top and it will bundle up a zip archive  and push it down to you.

As always, I welcome feedback or questions.  I hope you find it useful.

Monday, February 6, 2012

WSPR Encoder for Propeller - Update

I have decided to implement the main interface to the WSPR encoder such that you just pass in a single string containing the WSPR message you desire to encode.  The object will return a pointer to a byte array containing the symbol set for that message.  So the calling sequence will look like this:

OBJ
  WSPR : "ko7mWSPREncode"

VAR
  LONG Symbols

Symbols := WSPR.encodeWSPR(string("KO7M CN87 27"))

I think this will be the most intuitive and least error prone.  The returned value will point to an array of 162 bytes containing the symbol set.

VAR
  BYTE ich

repeat ich from 0 to 161
  sendSymbol(BYTE[Symbols][ich])

You can reference each byte with byte[Symbols][<index>] or you can treat the entire thing as a single string.

  sendSymbols(Symbols)

Things may change again, but I think this is the direction I am going.  I plan to publish the final encoder in the Propeller Object Exchange at the Parallax site.  I am investigating this opportunity now.

Saturday, February 4, 2012

Learned something new about Spin

I did a little study related to my previous post where I was befuddled about parameter passing in Spin for the Propeller.  To review...

Passing parameters to a method looks like this

VAR
  BYTE a
  WORD b
  LONG c

foo(a, b, c)

On the receiving end, we declare foo like this

PUB foo(x, y, z)

This is confusing to me when compared to other languages.  On the calling end, I can declare the size of the data.  I am passing a byte, word and long to foo.  On the receiving end, x, y and z are all longs.  Furthermore, if you declare any local variables, they are longs too.  For example:

PUB FOO(x, y, z) | i, j, k

So, when you pass a parameter, regardless of the size of the data, it is first converted to a long and the called method will receive it as a long.

However, you can also pass an address as a parameter.  Remember, my example was passing my callsign, locator, power and the symbol table to my encodeWSPR method.  This example is working:

VAR
  BYTE sym[162]

 
OBJ
  WSPR : "ko7mWSPREncode"


WSPR.encodeWSPR(string("KO7M "), string("CN87"), 27, @sym)

While this version yielded different results:

DAT
  mycall BYTE "KO7M ", 0
  myloc BYTE "CN87", 0
  mypwr BYTE 27

WSPR.encodeWSPR(@mycall, @myloc, @mypwr, @sym)


The issue here is that I am trying to make this same method handle two different format parameters.  Namely, the third parameter is the WSPR power setting.  In the first example, I am passing the value 27 in the parameter list.  This gets converted to a long and assigned to the third parameter in the call to encodeWSPR.  It then needs to be treated as a long value in the method.  In the second example, I am passing the address of a byte containing the power value.  The address gets converted to a long and assigned to the third parameter.  In the method, I have to then treat it as the address of a byte rather than as a long value.  For example:

encodeWSPR(call, loc, power, sym) | pwr

pwr := BYTE[power]

So, the reason my encodeWSPR was returning different results is that I was using the power parameter in different ways while the method was expecting to receive a long containing the value, not the address of it.  Bonehead...

So, I have to decide which way it is going to be as it cannot be both in the same method.  I can provide other methods that allow either kind of parameter to be passed or separate methods to set each parameter even.  My inclination is to pass the power by value rather than by reference.  Any preferences?

WSPR Encoder for Propeller Beacon Update

Well, after a bit of hair tearing, I have managed to get my WSPR encoder running on the propeller platform.  I am doing some cleanup of the code and will post it as soon as I have completed the changes.  I implemented it as a separate object, so to use it you would do something like this:

VAR
   BYTE sym[162]

OBJ
   WSPR : "ko7mWSPREncode"

This declares a byte array to hold the encoded WSPR symbol set and includes the WSPR encoder in your code.  You would then call this WSPR object to encode your call, locator and power setting and provide the address of the sym BYTE array so it can fill it up with your message.  Typically this would be done in your initialization code.

WSPR.encodeWSPR(string("KO7M  "), string("CN87"), 27, @sym)

Then in you would in your main loop send each symbol returned.

PUB Main | iSym
  doInitialize
  repeat
    repeat iSym from 0 to 161
      sendSymbol(sym[iSym])
    noTone
    Sync := nextSync
    repeat while SecondCnt // Sync
      delay(100)


Pretty simple and it works quite well.  I have a couple of caveats that are important in the current implementation.

  1. Only standard WSPR messages are supported currently.  No six digit locators or calls with prefixes or suffixes such as KO7M/5 or JA2/KO7M.  I have more work to do to support these special message formats.
  2. Call signs must be exactly 6 characters and the third character must be a numeric.  This is according to Joe Taylor's specifications.  The point of this is that my routine does not do any error checking to be sure you passed in the call sign with the numeric aligned in the third character and that it is padded with spaces to make it exactly 6 characters.  You will need to attend to this in the parameter you pass to encodeWSPR or the results will not be predictable.  For example, my call is passed as KO7M<space><space>.  If you had a call like one of the special even calls of "K7S", it would need to be passed in as <space>K7S<space><space>.  I may attend to this in future versions if I get inspired to do the work.
I also ran into a caveat that I cannot yet explain.  I am looking for insights into why this is happening.

If I pass the parameters like so:

DAT
  mycall BYTE "KO7M  ", 0
  myloc  BYTE "CN87", 0
  mypwr  BYTE 27

WSPR.encodeWSPR(@mycall, @myloc, @mypwr, @sym)

The results are not encoded correctly.  Changing to use the "string("...") format rather than BYTE data block constants with exactly the same data shown above works correctly.  So, you will have to stick to this format until I figure out why there is any difference.  Any thoughts on this from any of you propeller hackers out there appreciated.

Friday, February 3, 2012

WSPR Encoder for Propeller Beacon

In my previous post, I was thinking out loud about my WSPR encoder for the propeller-based beacon project.  Dave, G4FRE posted a couple of comments, one of which was quite useful entitled WSPR Coding Process and was instrumental in enabling me to fix my WSPR encoder implementation.  I was in fact encoding the locator incorrectly.  This document gives the following information for locators:



Locator
Designating the four locator characters as [Loc 1] to [Loc 4], the first two can each take on the 18 values ‘A’ to ‘R’ only so are allocated numbers from 0 – 17. The second pair can take only the values 0 – 9.

Another integer M is formed from:

M1 = (179 - 10 * [Loc 1] - [Loc 3] ) * 180 + 10 * [Loc 2] + [Loc 4]

Which gives a range of values from ‘AA00’ = 32220 to ‘RR99’ = 179, which comfortably fit into a 15 bit representation (2^15 = 32768), leaving spare codes for further enhancements.

The useful bit here was the information that AA00 = 32220 and RR99 = 179.  The algorithm above is not quite correct however.  The corrected form should be:
 
 
M1 = (179 - 10 * ([Loc 1]-10) - [Loc 3] ) * 180 + 10 * ([Loc 2]-10) + [Loc 4]

This appears to give me the correct results for these end cases, so assuming that useful bit is accurate, I should be good to go.  I will be testing against the WSPR application and posting the final code bits for those interested very soon.

Thursday, February 2, 2012

WSPR Encoder - "Thinking Out Loud"

I have been pondering why my WSPR encoder routine could be giving me incorrect results.  When I encode my message "KO7M CN87 27" and let the WSPR 2.11 application decode it, I get the following:

<...> KO7M 27

In reading the specification, I see the following:

Special Message Formats
Normal WSPR messages consist of a callsign, 4-digit grid locator, and power level in dBm. These messages are always preferred when appropriate. However, compound callsigns (i.e., callsigns with add-on prefix or suffix) cannot fit into the 28 bits allocated in a standard message. Similarly, 6-digit locators cannot fit into 15 bits. Messages using these components are therefore sent using a two-transmission sequence. For example, if the callsign is PJ4/K1ABC, the 6-digit grid locator is FK52UD, and the power level 37 dBm, the following messages will be sent in alternating transmissions:
PJ4/K1ABC 37
<PJ4/K1ABC> FK52UD 37

If you have special need to use a 6-digit locator with a normal callsign, check the box
Force transmission of 6-digit locator. If the callsign is K1ABC, the 6-digit grid locator FN42AX, and the power level 37 dBm, the following messages will then be sent in alternating transmissions:

K1ABC FN42 37
<K1ABC> FN42AX 37

Callsigns enclosed in angle brackets are actually sent as 15-bit hash codes. If such a code is received by another station before the full callsign has been received, it will be displayed as

<...>

on the decoded text line. Once the full callsign has been received, the decoder will thereafter recognize the hash code and fill in the blanks. Two very different callsigns might have the same hash code, but the 15-bit hash-code length ensures that in practice such collisions will be rare.

Please note that messages with compound callsigns or 6-digit locators will not be properly decoded by WSPR versions earlier than 2.0. Further details on message formats can be found in Appendix B, and in the WSPR source code.

Hmmm...  So, if I am reading this correctly, in a normal format message, I am supposed to encode the message as follows:

  1. 28 bits encodes a normal 6 character callsign
  2. 15 bits encodes a 4 character locator
  3. 7 bits encodes the power in dBm.
The special message format from the description above encodes in two messages, the content depends on the format of the data being encoded and whether or not the "Force transmissions of 6-digit locator" checkbox is  set to "checked".

Case 1:
Callsign with slash character in it
In this case, the normal (up to) 6 character callsign is augmented with a slash character (front or back) and up to 3 additional characters.  For example WA0FOO/7, JA2/KJ7ABC, etc.  The 28 bit callsign field and the 15 bit locator are large enough to contain this data while the power is sent in its own normal 7 bit field.  Here is my hypothesis:

  • When encoding WSPR messages, a the callsign field is required to have no more than 6 characters and is required to have a numeric in the third character position.  If the callsign does not match these specifications, spaces are added as neeed.  For example, "K7XX" is encoded as "<space>K7XX<space>".  This I have previously proven before the special formats where encorporated into WSPR.
  • When encoding a special format callsign, I believe that the "/" character and up to three additional characters must be encoded in the 15 bit locator field while the normal callsign is encoded in the callsign field padded with spaces as necessary.
  • The power level goes in the normal 7 bit field.
  • The first message contains the special callsign and power level only.
  • The second message packs up to 6 digits of a locator, space padded as needed into the normal 28 bit callsign field.  A 15 bit hash of the callsign is computed and sent in the 15 bit locator field.  The power is sent normally in its 7 bit field.
This case should allow for adorned callsigns as well as six character locators to be sent in two messages max.

Unanswered Questions
  1. Can the application can detect when decoding whether the callsign adornment belongs on the left or right side of the call?  For example: JA2/WA0AAA vs. WA0AAA/JA2.  I think this could be stored in the encoding of the "/" character.
  2. What is the precise encoding of the "/" character?
  3. Precisely how to calculate the 15 bit hash of the callsign?
Case 2:
Normal unadorned callsign (no slash) and 6 character locator
In this case the first message is a normal message with the extra 2 characters of the locator truncated as indicated in the documentation reference above.  My hypothesis about the remainder of the message is:
  1. The six digit locator is encoded into the 28 bit callsign field.
  2. The 15 bit hash is computed of the callsign and sent in the locator 15 bit field.
  3. The 7 bit power field contains the power level in dBm.


My code to encode the WSPR message doesn't know anything about special formats (yet) but is generating a symbol set that is decoding as if it was the second message a two message set, very much as would be found in case 2 above.  Joe Taylor's spec says that any callsign enclosed in <...> characters is sent as a 15 bit hash.  This points to the locator being incorrectly encoded.  The message I am sending is being decoded as:

<...> KO7M 27

This tells me that my call sign and power are both being encoded correctly because WSPR can decode them.  Since the encoding between the callsign and the locator fields is a different algorithm, the callsign must be encoded correctly and placed in the correct 28 bit callsign field of the WSPR message.  What appears to be wrong is the locator encoding.

The results would match case 1 above, if WSPR thought the locator field was a 15 bit hash, not a locator and that it has not yet received the first message of the two message set.  WSPR would then display precisely what I am seeing.

So, this little exercise has served to narrow my focus to the encoding of the locator field.  I must have some sort of logic error in this part of the code.

More to follow.