Establishing Top of Second
The WSPR protocol requires that sender and receiver's times are relatively in sync with UTC. Transmissions happen on the "0 second" boundary on even numbered minutes. 14:02:00 and 14:06:00 are examples of valid times to start a WSPR transmission.
On the tracker, we have a GPS chip. Even before a full GPS coordinate lock is established, the chip first gets a time lock with the satellites. The time is uttered in the GPS NMEA strings and can be used to synchronize the beginning of the transmission.
The GPS chip establishes what time it is, and then sends the NMEA string down the UART at, say, 9600 baud. It takes some time for that NMEA string to be uttered, and for the microprocessor to decode it, and decide what second it is. While not a horribly long time, it could cause us to get off by a bit.
Say we had a NMEA string like this:
That's 66 characters long. With 8 bits, 1 start, 1 stop, that's 10 bits per character - 660 bits of data. At 9600 baud, that's 68.8ms. Assuming some delay before the characters are uttered, and some processing time after they're received, and we could be upward of a few hundred milliseconds off, and that assumes that the GPS utters the NMEA string immediately, beginning at the top of the second.
An alternative - using the TIMEPULSEThe U-Blox MAX7 GPS chip can generate a time pulse, and it can be configured in a number of different ways. For the Wisp1, I've opted to configure it as follows. The Rising Edge of the signal on the TIMEPULSE line will be at Top of Second. The TIMEPULSE line will generate an EXTI interrupt on leading edge. So, I'll get an interrupt at the top of each second. In the ISR for that interrupt, I will zero my "milliseconds" counter that I get from HAL_Systick(). After that, I will await the next NMEA string to come in, and parse the time. From that string, I can retroactively determine which second the ToS interrupt was for. My milliseconds counter will then be counting right from ToS.
Sneaky Tricks - Time Pulse "Locked"The U-Blox allows for configuration of the frequency and duty cycle of the time pulse. I'm configuring the pulse at 1 HZ, with a 10% duty cycle, meaning it's held high for 100ms, and down for 900ms.
The U-Blox also has another feature that I intend to use to simplify life a little. It has settings for both "Time Pulse" and "Time Pulse Locked". The former is how often to generate a pulse, regardless of lock status, and the second is the rate at which to generate time pulses once the GPS has locked into the time.
At GPS initialization time, I will set the GPS up so that the "Duty Cycle" for the TIMEPULSE in non-locked state is 0%, and 10% in locked state. Basically, this means that the GPS will not generate any interrupts until it has a time lock. This avoids the situation of messing with the timers before the GPS actually knows what time it is.
Further, given that I intend to turn off the GPS between uses, I will set up the ISR for the TIMEPULSE to only receive one interrupt. Basically, after turning on the GPS, I will receive one timepulse interrupt, at the top of a second. I can zero my millisecond timer at that point, and await the NMEA string to determine exactly which second I'm on. I have no further use for time pulses, as the CPU clock will keep me on track after that.
After going into STOP mode (for power conservation) and waking up again, it's likely my clock will be off. Since I'll be powering on the GPS again each time I wake up, I'll simply add this timepulse logic to the subroutine I call whenever I power up the GPS. The GPS will give me a good time synchronization long enough to do my transmission, and go back to sleep for the next go-around.
Sources of errorHAL_Systick()
So, when the interrupt comes in, I intend to zero my "milliseconds" counter. However, I do not intend to mess with the counter used internally by the HAL_Systick() function. I'm not sure if there are other dependencies there which I might confuse. Rather than confuse the matter, I'll be keeping my own "milliseconds" counter, which is incremented by Systick. I'll zero it when the TIMEPULSE interrupt comes in. However, the attentive student may note that the timepulse interrupt may happen at any time as the Systick counter counts down between it's 1 millisecond pops. A potential worst case scenario is that I zero my systick counter, and one 32 MHZ clock tick later, Hal_Systick() pops again, leaving me nearly 1ms off. On average, though, I'm more likely to be 0.5ms off. Given that by simply parsing the GPS strings, I'd likely be more than 100 milliseconds off, I'll live.
The GPS provides for configuring compensation for "antenna cable delay" and "RF group delay". These are adjustments in nanoseconds. I haven't bothered to configure them. I'm sure they should be something but god only knows how I'd figure that out. Being a very small source of error (in the nanoseconds), I'm ignoring it.
GuestimateJust off the hip, given the average of 0.5 ms error with systick, and other error, I'm going to assume that my tracker will be within 1.0 millisecond of correct. Close 'nuff, I think.
Taking a few deep breaths about timingIt has to be remembered that WSPR is specified to operate if the sending and receiving stations are +/- 1 second of correct. That means that the stations may be almost 2000 ms apart on time, and still work. Also, the receiving station might be several thousand km away. I've seen a pico balloon received from 3000 km away. In air (299,700 km/s) that signal takes 10 ms to travel from sender to receiver. So, my tracker being off by 1 millisecond is still awesome. Also, it pays to note that some other WSPR implementations I've seen are simply parsing the NMEA strings to figure out the time, and they're operating beautifully with much higher time error than I anticipate.
Timepulse configurationHere's the commentary from the Wisp1 source code I'm using for timepulse configuration.
/* CFG_TP5 message: No pulse until we have time lock. Then pulse with rising edge at top of second.
* 0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, - CFG-TP5 message, 32 bytes
* 0x00, 0x00, 0x00, 0x00 - tpIdx=TIMEPULSE, reserved0, reserved1
* 0x00, 0x00, 0x00, 0x00 - Antenna and RF Group Delay. 0ns each
* 0x01, 0x00, 0x00, 0x00 - freqPeriod 1hz
* 0x01, 0x00, 0x00, 0x00 - freqPeriodLocked 1hz
* 0x00, 0x00, 0x00, 0x00 - pulseLenRatio (no timepulse)
* 0x0a, 0x00, 0x00, 0x00 - pulseLenRatioLocked (10 %)
* 0x00, 0x00, 0x00, 0x00 - userConfigDelay
* 0x6f, 0x00, 0x00, 0x00 - flags: Active,LogGPSFreq, lockedOtherSet, isFreq, alignToTow, Polarity
* 0xd2, 0x2b - checksum