In a series of previous posts (search this blog for "fox hunt"), I have described a fox hunt transmitter for simultaneous HF and VHF fox hunting.
I have made some slight changes to the code to select a VHF frequency based on serial number and made different recordings for the voice announcement on VHF that will identify the fox beacon code. I have reduced the audio sample rate to 8 kHz and set the deviation to +/- 12.75 kHz.
I decided that given I don't have any experience yet with the VHF fox that I would put each of them on a different VHF FM simplex frequency based on it's serial number.
SN 0 - 147.420
SN 1 - 147.435
SN 2 - 147.450
SN 3 - 147.465
Once I have some experience with locating a VHF fox, I may experiment with putting multiple foxes on the same frequency and staggering their announcements.
Here you can see my four little foxes ready for their fox den. The eye bolts on the end will be attached to lengths of wire to provide both the HF and VHF antenna connections and a convenient way to hang the fox up in a tree or other hiding place. The end caps are not glued in place, so they can be removed to facilitate repairs, firmware changes or battery replacement. They will keep dust and rain at bay however quite nicely.
A standard 9V battery powers these devices for several days with only the HF beacon running on CW. It remains to be seen how much the VHF FM mode reduces that battery life figure.
Sunday, June 19, 2016
Monday, June 6, 2016
Summertime! Let's get out there and fox hunt! (Part 3)
In this final installment describing my fox hunt transmitter, I provide the final bit of "glue" code that ties the other two pieces together.
As in previous modules, the constants section defines the frequency of the crystal used to clock the Propeller chip and indicates the PLL multiplier in use. In this case, the physical crystal is 5 MHz and a 16x multiplier is provided to clock the Propeller chip at 80 MHz.
'Salmoncon Fox Hunt Transmitter
'
' ko7m - Jeff Whitlatch
'
' The Salmoncon fox hunt transmitter is a simultaneous HF and VHF beacon transmitter
' typically used for RDF (Radio Direction Finding) events. As implemented, one HF CW and
' one VHF FM voice beacon are provided simultaneously.
'
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
Next we define the other modules that are included in the system from Part 1 describing the HF CW beacon and Part 2 describing the 2 metre FM beacon. The clock module is described below. In my case I gave these modules the quoted names you see here with the .spin suffix added to the file name.
OBJ
HFFox : "ko7mHFCWFox" ' HF CW beacon on 30 metres
FMFox : "ko7m2MFMFox" ' 2m FM voice beacon on 146.52 MHz
Clock : "ko7mClock" ' 1 second clock
The initialization code sets up the FM beacon to run every 10 seconds and starts the clock running.
PUB doInitialize
Sync := 10 ' FM beacon runs every 10 seconds
Clock.Start ' Start up the clock module
The main function calls the initization function above and then starts the HF CW transmitter running in its own core. The FM transmitter runs on this core, so we go into a loop and call it's Main function every 10 seconds. Pretty simple, eh?
PUB Main
doInitialize ' Initialize the Fox Hunt beacon
HFFox.Start ' Start the HF beacon in its own cog
repeat
FMFox.Main ' Run the 2M beacon
repeat while Clock.getSeconds // Sync
delay(100)
The clock module I will not describe line-by-line. Suffice it to say that its function is to increment a 32 bit integer every 1 second and to provide a way to retrieve the current counter value. The full source code can be seen at the end of this posting.
So, that is it! I hope you find my little fox hunt transmitter of some value. If there is interest in having a pre-compiled hex file available, I will put it up on a web page or FTP link somewhere and update this post to contain the link.
As always, I am happy to help if you get stuck. The Propeller chip is a bit different but quite functional and capable and provided me with the ability to create a rather unique solution in a very tiny package. Drop me a note here or at ko7m at arrl dot org and I will be happy to help. So, get out there and host some fox hunts this summer.
73's de Jeff - ko7m
Below I provide the entire source code for the top level controller and the clock module for your convenience. These should be put into separate .spin files for compilation.
'Salmoncon Fox Hunt Transmitter
'
' ko7m - Jeff Whitlatch
'
' The Salmoncon fox hunt transmitter is a simultaneous HF and VHF beacon transmitter
' typically used for RDF (Radio Direction Finding) events. As implemented, one HF CW and
' one VHF FM voice beacon are provided simultaneously.
'
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
OBJ
HFFox : "ko7mHFCWFox" ' HF CW beacon on 30 metres
FMFox : "ko7m2MFMFox" ' 2m FM voice beacon on 146.52 MHz
Clock : "ko7mClock" ' 1 second clock
VAR
LONG Sync
PUB Main
doInitialize ' Initialize the Fox Hunt beacon
HFFox.Start ' Start the HF beacon in its own cog
repeat
FMFox.Main ' Run the 2M beacon
repeat while Clock.getSeconds // Sync
delay(100)
PUB doInitialize
Sync := 10 ' FM beacon runs every 10 seconds
Clock.Start ' Start up the clock module
PUB delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
The clock module code follows:
' Clock module
'
' Simple module to provide a second counter for synchronizing events.
'
' ko7m - Jeff Whitlatch
'
'
CON
_clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz
_xinfreq = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
delayMS = 1000 ' Clock updates every second
VAR
LONG SecondCounter
LONG Stack[16]
BYTE Cog
PUB Start : fSuccess
SecondCounter := 0
fSuccess := (Cog := cognew(clock, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
PUB getSeconds : sec
sec := SecondCounter
PUB delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
PUB Clock ' Runs In its own COG
repeat
delay(delayMS) ' Update second counter every 1000 ms
SecondCounter++ ' Should be good for 2^32 seconds or about 136 years
As in previous modules, the constants section defines the frequency of the crystal used to clock the Propeller chip and indicates the PLL multiplier in use. In this case, the physical crystal is 5 MHz and a 16x multiplier is provided to clock the Propeller chip at 80 MHz.
'Salmoncon Fox Hunt Transmitter
'
' ko7m - Jeff Whitlatch
'
' The Salmoncon fox hunt transmitter is a simultaneous HF and VHF beacon transmitter
' typically used for RDF (Radio Direction Finding) events. As implemented, one HF CW and
' one VHF FM voice beacon are provided simultaneously.
'
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
Next we define the other modules that are included in the system from Part 1 describing the HF CW beacon and Part 2 describing the 2 metre FM beacon. The clock module is described below. In my case I gave these modules the quoted names you see here with the .spin suffix added to the file name.
OBJ
HFFox : "ko7mHFCWFox" ' HF CW beacon on 30 metres
FMFox : "ko7m2MFMFox" ' 2m FM voice beacon on 146.52 MHz
Clock : "ko7mClock" ' 1 second clock
The initialization code sets up the FM beacon to run every 10 seconds and starts the clock running.
PUB doInitialize
Sync := 10 ' FM beacon runs every 10 seconds
Clock.Start ' Start up the clock module
The main function calls the initization function above and then starts the HF CW transmitter running in its own core. The FM transmitter runs on this core, so we go into a loop and call it's Main function every 10 seconds. Pretty simple, eh?
PUB Main
doInitialize ' Initialize the Fox Hunt beacon
HFFox.Start ' Start the HF beacon in its own cog
repeat
FMFox.Main ' Run the 2M beacon
repeat while Clock.getSeconds // Sync
delay(100)
The clock module I will not describe line-by-line. Suffice it to say that its function is to increment a 32 bit integer every 1 second and to provide a way to retrieve the current counter value. The full source code can be seen at the end of this posting.
So, that is it! I hope you find my little fox hunt transmitter of some value. If there is interest in having a pre-compiled hex file available, I will put it up on a web page or FTP link somewhere and update this post to contain the link.
As always, I am happy to help if you get stuck. The Propeller chip is a bit different but quite functional and capable and provided me with the ability to create a rather unique solution in a very tiny package. Drop me a note here or at ko7m at arrl dot org and I will be happy to help. So, get out there and host some fox hunts this summer.
73's de Jeff - ko7m
Below I provide the entire source code for the top level controller and the clock module for your convenience. These should be put into separate .spin files for compilation.
'Salmoncon Fox Hunt Transmitter
'
' ko7m - Jeff Whitlatch
'
' The Salmoncon fox hunt transmitter is a simultaneous HF and VHF beacon transmitter
' typically used for RDF (Radio Direction Finding) events. As implemented, one HF CW and
' one VHF FM voice beacon are provided simultaneously.
'
CON
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
OBJ
HFFox : "ko7mHFCWFox" ' HF CW beacon on 30 metres
FMFox : "ko7m2MFMFox" ' 2m FM voice beacon on 146.52 MHz
Clock : "ko7mClock" ' 1 second clock
VAR
LONG Sync
PUB Main
doInitialize ' Initialize the Fox Hunt beacon
HFFox.Start ' Start the HF beacon in its own cog
repeat
FMFox.Main ' Run the 2M beacon
repeat while Clock.getSeconds // Sync
delay(100)
PUB doInitialize
Sync := 10 ' FM beacon runs every 10 seconds
Clock.Start ' Start up the clock module
PUB delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
The clock module code follows:
' Clock module
'
' Simple module to provide a second counter for synchronizing events.
'
' ko7m - Jeff Whitlatch
'
'
CON
_clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz
_xinfreq = 5_000_000
WMin = 381 'WAITCNT-expression-overhead Minimum
delayMS = 1000 ' Clock updates every second
VAR
LONG SecondCounter
LONG Stack[16]
BYTE Cog
PUB Start : fSuccess
SecondCounter := 0
fSuccess := (Cog := cognew(clock, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
PUB getSeconds : sec
sec := SecondCounter
PUB delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
PUB Clock ' Runs In its own COG
repeat
delay(delayMS) ' Update second counter every 1000 ms
SecondCounter++ ' Should be good for 2^32 seconds or about 136 years
Sunday, June 5, 2016
Summertime! Let's get out there and fox hunt! (Part 2)
In my previous post, I described the first half of a hidden transmitter (fox) implemented using a Propeller Mini board for an HF CW beacon and some simple firmware. In this installment, I will describe the 2 meter FM portion of the firmware.
My first thought was to implement the VHF beacon transmitter using the same design as used on the HF CW beacon except to use MCW rather than CW using FM modulation of the 2 meter carrier.
This approach is simple enough, but rather than generate the audio CW signal to FM modulate the carrier, why not just modulate it with any recorded audio? In this way a vocal announcement could be use to identify the beacon.
In my case I decided to use a free Windows application called "Audacity" to make an audio recording of a 2 second announcement "Salmoncon Fox Hunt" and save it as a .wav file. The file is exported from Audacity as a 11.025 kHz sample rate, 8 bit, monaural recording which is then loaded into the program flash of the Propeller chip and used to FM modulate the 146 MHz carrier. At 11.025 kHz, two seconds of audio will be 11025 * 2 bytes plus 44 bytes of wav file header.
Using 8 bit samples, the sample byte is multiplied by 32 and added to the frequency being generated. SInce this deviation of the carrier frequency is happening at an audio rate, we have achieved FM modulation of the transmitter. This yields a maximum of 256 * 32 = 8.192 kHz of frequency deviation. A multiplier of 58 will get you to just shy of 15 kHz. For my purposes, 8 kHz is just fine.
Let's take a quick walk through the code as it is pretty trivial. Up top we see the same structure defining the necessary constants used in the code. In the comments we can see the formula for calculating the value used to set the RF frequency. For example:
146520000 / (16 * 80000000) * 2^32 = 491639537.664. Truncating to an integer is sufficient for this purpose. This value is loaded into the counter FRQA register to generate the desired 146.52 MHz signal.
' 2 meter FM fox hunt beacon
'
' ko7m - Jeff Whitlatch
'
' FRQx = frequency / (16 * CLKFREQ) * 2^32
'
' Propeller manual recommends limiting upper frequency to 128 MHz for best stabillity
' We of course ignore such advice and utilize it at 146.52 MHz
CON
_clkmode = XTAL1 + PLL16X
_xinfreq = 5_000_000
WMin = 381
RFPin = 8
FRQAvalue = 491_639_537 ' For 146.52 MHz (146520000 / (16 * 80000000) * 4294967296)
sampleRate = 11025 ' 11.025 kHz audio
waitKeyDn = 1000 ' Delay before audio starts with key down
waitKeyUp = 2000 ' Delay after audio ends before key up
wavHdrSize = 44 ' Size of wave file header
devScale = 5 ' deviation scaler for audio samples (256 * 32 = 8.192 kHz)
The only local variables used are when we are running this code on its own core. In this case we define the Stack space required and keep track of which core (cog) was assigned.
VAR
LONG Stack[16]
BYTE Cog
The Main function initializes the VHF beacon and runs the main function. To enable this code to be run on separate cores, Start and Stop functions are provided.
PUB Main
doInit
doBeacon
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
The doInit function sets up the RF pin as an output and configures the counter to enable RF generation on 2 metres
pri doInit
DIRA[RFPin]~~ ' Set RF pin as an output
CTRA := constant(010_111 << 23) + RFPin ' Configure the counter
This is the main function of the beacon. The CPU clocks per sample of audio at the audio sample rate is calculated. The index of the first byte of audio is calculated and RF generation of a carrier is initiated. After a brief pause, each byte of the audio data is used to FM modulate the carrier. Once the entire message is sent, the carrier is kept on for a couple seconds to help fox hunters zero in on the transmitter. The RF is then shut down and after a short delay, the process repeats.
PRI doBeacon | p, time, clocksPerSample
clocksPerSample := clkfreq / sampleRate ' System clocks per audio sample
p := constant(@audioStart + wavHdrSize) ' Pointer into our audio data just past the wav file header
FRQA := FRQAvalue ' Go key down
delay(waitKeyDn) ' Wait before modulation starts
time := cnt
' Play the audio file once through
repeat while p++ < @audioEnd
FRQA := FRQAvalue + byte[p] << devScale ' Scale amplitude byte by 32 (2^8 * 32 = 8.192 kHz deviation max)
waitcnt(time += clocksPerSample) ' Wait until time for the next sample
delay(waitKeyUp) ' Delay before dropping carrier
FRQA := 0 ' Turn off NCO
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
The audio data is stored in the data secion of the code. Since there is only 64 kb of ROM on the device, the amount of recorded data is limited to available ROM space. If desired, the audio sample rate could be reduced to 8 kB to enable increased message length. The actual audio data is stored in the "foxhunt.wav" file specified in the same directory as this file.
DAT
audioStart byte
File "foxhunt.wav" ' Audio data to be transmitted
audioEnd byte 0
Ok, so now we have two beacons that can be used independently. In my next installment, I shall tie these two modules together so they can be used simultaneously. I will also provide a hex file of the compiled code should you not wish to mess with installing the Propeller Tools for code development.
Improvements of course could be made. Any sample rate of audio could be used and the encoded rate read from the .wav file header for example rather than needing to change a constant for a different sample rate file. Let your imagination be your guide. Have fun and as always if you get stuck, drop me a note at ko7m at arrl dot net and I will try my best to help you out.
See you in the next installment. For your convenience, the entire source code is reproduced below.
' 2 meter FM fox hunt beacon
'
' ko7m - Jeff Whitlatch
'
' FRQx = frequency / (16 * CLKFREQ) * 2^32
'
' Propeller manual recommends limiting upper frequency to 128 MHz for best stabillity
' We of course ignore such advice and utilize it at 146.52 MHz
CON
_clkmode = XTAL1 + PLL16X
_xinfreq = 5_000_000
WMin = 381
RFPin = 8
FRQAvalue = 491_639_537 ' For 146.52 MHz (146520000 / (16 * 80000000) * 4294967296)
sampleRate = 11025 ' 11.025 kHz audio
waitKeyDn = 1000 ' Delay before audio starts with key down
waitKeyUp = 2000 ' Delay after audio ends before key up
wavHdrSize = 44 ' Size of wave file header
devScale = 5 ' deviation scaler for audio samples (256 * 32 = 8.192 kHz)
VAR
LONG Stack[16]
BYTE Cog
PUB Main
doInit
doBeacon
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
pri doInit
DIRA[RFPin]~~ ' Set RF pin as an output
CTRA := constant(010_111 << 23) + RFPin ' Configure the counter
PRI doBeacon | p, time, clocksPerSample
clocksPerSample := clkfreq / sampleRate ' System clocks per audio sample
p := constant(@audioStart + wavHdrSize) ' Pointer into our audio data just past the wav file header
FRQA := FRQAvalue ' Go key down
delay(waitKeyDn) ' Wait before modulation starts
time := cnt
' Play the audio file once through
repeat while p++ < @audioEnd
FRQA := FRQAvalue + byte[p] << devScale ' Scale amplitude byte by 32 (2^8 * 32 = 8.192 kHz deviation max)
waitcnt(time += clocksPerSample) ' Wait until time for the next sample
delay(waitKeyUp) ' Delay before dropping carrier
FRQA := 0 ' Turn off NCO
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
DAT
audioStart byte
File "foxhunt.wav" ' Audio data to be transmitted
audioEnd byte 0
My first thought was to implement the VHF beacon transmitter using the same design as used on the HF CW beacon except to use MCW rather than CW using FM modulation of the 2 meter carrier.
This approach is simple enough, but rather than generate the audio CW signal to FM modulate the carrier, why not just modulate it with any recorded audio? In this way a vocal announcement could be use to identify the beacon.
In my case I decided to use a free Windows application called "Audacity" to make an audio recording of a 2 second announcement "Salmoncon Fox Hunt" and save it as a .wav file. The file is exported from Audacity as a 11.025 kHz sample rate, 8 bit, monaural recording which is then loaded into the program flash of the Propeller chip and used to FM modulate the 146 MHz carrier. At 11.025 kHz, two seconds of audio will be 11025 * 2 bytes plus 44 bytes of wav file header.
Using 8 bit samples, the sample byte is multiplied by 32 and added to the frequency being generated. SInce this deviation of the carrier frequency is happening at an audio rate, we have achieved FM modulation of the transmitter. This yields a maximum of 256 * 32 = 8.192 kHz of frequency deviation. A multiplier of 58 will get you to just shy of 15 kHz. For my purposes, 8 kHz is just fine.
Let's take a quick walk through the code as it is pretty trivial. Up top we see the same structure defining the necessary constants used in the code. In the comments we can see the formula for calculating the value used to set the RF frequency. For example:
146520000 / (16 * 80000000) * 2^32 = 491639537.664. Truncating to an integer is sufficient for this purpose. This value is loaded into the counter FRQA register to generate the desired 146.52 MHz signal.
' 2 meter FM fox hunt beacon
'
' ko7m - Jeff Whitlatch
'
' FRQx = frequency / (16 * CLKFREQ) * 2^32
'
' Propeller manual recommends limiting upper frequency to 128 MHz for best stabillity
' We of course ignore such advice and utilize it at 146.52 MHz
CON
_clkmode = XTAL1 + PLL16X
_xinfreq = 5_000_000
WMin = 381
RFPin = 8
FRQAvalue = 491_639_537 ' For 146.52 MHz (146520000 / (16 * 80000000) * 4294967296)
sampleRate = 11025 ' 11.025 kHz audio
waitKeyDn = 1000 ' Delay before audio starts with key down
waitKeyUp = 2000 ' Delay after audio ends before key up
wavHdrSize = 44 ' Size of wave file header
devScale = 5 ' deviation scaler for audio samples (256 * 32 = 8.192 kHz)
The only local variables used are when we are running this code on its own core. In this case we define the Stack space required and keep track of which core (cog) was assigned.
VAR
LONG Stack[16]
BYTE Cog
The Main function initializes the VHF beacon and runs the main function. To enable this code to be run on separate cores, Start and Stop functions are provided.
PUB Main
doInit
doBeacon
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
The doInit function sets up the RF pin as an output and configures the counter to enable RF generation on 2 metres
pri doInit
DIRA[RFPin]~~ ' Set RF pin as an output
CTRA := constant(010_111 << 23) + RFPin ' Configure the counter
This is the main function of the beacon. The CPU clocks per sample of audio at the audio sample rate is calculated. The index of the first byte of audio is calculated and RF generation of a carrier is initiated. After a brief pause, each byte of the audio data is used to FM modulate the carrier. Once the entire message is sent, the carrier is kept on for a couple seconds to help fox hunters zero in on the transmitter. The RF is then shut down and after a short delay, the process repeats.
PRI doBeacon | p, time, clocksPerSample
clocksPerSample := clkfreq / sampleRate ' System clocks per audio sample
p := constant(@audioStart + wavHdrSize) ' Pointer into our audio data just past the wav file header
FRQA := FRQAvalue ' Go key down
delay(waitKeyDn) ' Wait before modulation starts
time := cnt
' Play the audio file once through
repeat while p++ < @audioEnd
FRQA := FRQAvalue + byte[p] << devScale ' Scale amplitude byte by 32 (2^8 * 32 = 8.192 kHz deviation max)
waitcnt(time += clocksPerSample) ' Wait until time for the next sample
delay(waitKeyUp) ' Delay before dropping carrier
FRQA := 0 ' Turn off NCO
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
The audio data is stored in the data secion of the code. Since there is only 64 kb of ROM on the device, the amount of recorded data is limited to available ROM space. If desired, the audio sample rate could be reduced to 8 kB to enable increased message length. The actual audio data is stored in the "foxhunt.wav" file specified in the same directory as this file.
DAT
audioStart byte
File "foxhunt.wav" ' Audio data to be transmitted
audioEnd byte 0
Ok, so now we have two beacons that can be used independently. In my next installment, I shall tie these two modules together so they can be used simultaneously. I will also provide a hex file of the compiled code should you not wish to mess with installing the Propeller Tools for code development.
Improvements of course could be made. Any sample rate of audio could be used and the encoded rate read from the .wav file header for example rather than needing to change a constant for a different sample rate file. Let your imagination be your guide. Have fun and as always if you get stuck, drop me a note at ko7m at arrl dot net and I will try my best to help you out.
See you in the next installment. For your convenience, the entire source code is reproduced below.
' 2 meter FM fox hunt beacon
'
' ko7m - Jeff Whitlatch
'
' FRQx = frequency / (16 * CLKFREQ) * 2^32
'
' Propeller manual recommends limiting upper frequency to 128 MHz for best stabillity
' We of course ignore such advice and utilize it at 146.52 MHz
CON
_clkmode = XTAL1 + PLL16X
_xinfreq = 5_000_000
WMin = 381
RFPin = 8
FRQAvalue = 491_639_537 ' For 146.52 MHz (146520000 / (16 * 80000000) * 4294967296)
sampleRate = 11025 ' 11.025 kHz audio
waitKeyDn = 1000 ' Delay before audio starts with key down
waitKeyUp = 2000 ' Delay after audio ends before key up
wavHdrSize = 44 ' Size of wave file header
devScale = 5 ' deviation scaler for audio samples (256 * 32 = 8.192 kHz)
VAR
LONG Stack[16]
BYTE Cog
PUB Main
doInit
doBeacon
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
pri doInit
DIRA[RFPin]~~ ' Set RF pin as an output
CTRA := constant(010_111 << 23) + RFPin ' Configure the counter
PRI doBeacon | p, time, clocksPerSample
clocksPerSample := clkfreq / sampleRate ' System clocks per audio sample
p := constant(@audioStart + wavHdrSize) ' Pointer into our audio data just past the wav file header
FRQA := FRQAvalue ' Go key down
delay(waitKeyDn) ' Wait before modulation starts
time := cnt
' Play the audio file once through
repeat while p++ < @audioEnd
FRQA := FRQAvalue + byte[p] << devScale ' Scale amplitude byte by 32 (2^8 * 32 = 8.192 kHz deviation max)
waitcnt(time += clocksPerSample) ' Wait until time for the next sample
delay(waitKeyUp) ' Delay before dropping carrier
FRQA := 0 ' Turn off NCO
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
DAT
audioStart byte
File "foxhunt.wav" ' Audio data to be transmitted
audioEnd byte 0
Summertime! Let's get out there and fox hunt! (Part 1)
With the recent 32 degree C (90 F) weather we have been having lately, I began thinking of summertime ham radio activities and I think it is time to build a hidden transmitter (fox) for our summer Salmoncon group activities. My buddy Eldon (WA0UWH) and I have provided fox hunt activities for the group since about 2012. This year I decided to make some changes.
Historically, we have provided 1 to 4 beacons on 30 metres and coached folks on the use of simple loop antennas in radio direction finding (RDF). This year I have decided to add a VHF component to the hunt as most of the group owns VHF FM equipment.
Our summer camp site sits on about 50 acres up against the western slopes of the Cascade Mountains near North Bend, WA. It is a truly idyllic location and a wonderful venue for our summer camp-out called Salmoncon. If you are going to be in the area, we hope you will plan to drop in and visit.
Below and in a previous post I included a picture of a couple of our transmitter hunt participants from years past. We have done the event every year and I have threatened to not do the event a couple of times but the gathering lynch mob convinced us otherwise. So, it is a simple thing, but apparently it is quite popular.
What I want to do here is to provide the details of this year's hidden transmitter hardware and firmware so that others who might be interested in hosting such an event might be able to leverage the effort to best benefit.
Here is my little fox hunt transmitter that is able to transmit CW on any HF frequency you like while simultaneously sending an FM signal on 2 metres.
As you can see it fits nicely on the side of a standard 9V battery. I have attached it with a little blob of putty that is used to adhere things to the wall in your home but is still removable without damaging the wall. I find that it holds really well, but not as strongly as for example double sided foam tape. Not shown are the wires that attach to HF and VHF antennas. These connect to two of the pins along the side of the board. In practice this gets wrapped in bubble wrap and stuffed into a short length of PVC pipe. Pipe end caps provide water and dust protection. Small eye bolts are used to connect the electronics to external antennas and to provide a convenient way to hang the transmitter up in a tree for example using the antenna wire as a support.
The board is a Parallax Propeller Mini board. I have written previously on various projects that I have based on this processor. This is a slick little board packing a 32 bit, eight core 160 MIPS 80 MHz processor which is complete overkill for this project, but a key feature of the Propeller chip makes it ideal for this application.
Each core has two counter modules. Each counter module can control or monitor up to two I/O pins and perform conditional 32-bit accumulation of its FRQ register into its PHS register on every clock cycle. Each counter module also has its own phase-locked loop (PLL) which can be used to synthesize frequencies up to 128 MHz. In reality it can synthesize frequencies up to about 220 MHz, but Parallax recommends "for best stability" to limit frequency synthesis to 128 MHz maximum. I am using it at 146 Mhz without issue.
Let me first discuss the CW HF beacon. Typically we use four of these transmitters hidden around the venue. Each is assigned a serial number 0-3 which is used to determine the content of the transmitted beacon. We picked morse code characters that had the exact same transmission length for each of the beacons based on serial number. V, F, L, and B. The beacon will send this letter 5 times on a schedule discussed below. By turning on all four transmitters simultaneously, the fox hunters will hear zero or more of them identifying at the same time. The goal is to locate them all and we typically keep the game running for several days to give everyone a chance. If there is interest and the foxes have been found, we move them. (Don't forget to check the collar of any of the dogs on site for a transmitter...)
The Propeller chip is programmed in one of two languages. SPIN or PASM. Spin is a C-like (sort of) language while PASM is the Propeller ASseMbly language. All of my code presented in this posting will be in the SPIN language. I am not going to provide a tutorial on the language, for that you can hit the reference manual for the gory details. If you get stuck, drop me a line at ko7m at arrl dot org and I will try to help you out.
The top part of the code declares the clock speed, PLL settings and crystal frequency. This is followed by any constants used by the program.
' HF CW Fox Hunt Beacon
'
' ko7m - Jeff Whitlatch
'
con
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
The following constants are used in generating random numbers and to fine tune the timing accuracy of a delay function.
Seed = 31414
WMin = 381
Each transmitter gets a 6 second slot for transmitting, chosen at random and the transmitters re-sync random number generation every two minutes.
'Sync Each Ten Minutes So that Others can be be started Later, by watching the wall clock
TimeSync = 60 * 2 ' Seconds between time syncs (2 min)
SlotTime = 6 ' Seconds
TimeSlots = TimeSync / SlotTime ' Number of time slots (in this case 20)
We transmit at 15 words per minute on a frequency of 10.1395 MHz with a tone offset of 600 hz. Here we also define which pin is used to generate the RF at this frequency. Any available pin may be chosen for this task.
wpmInit = 15 ' Initial code speed in words per minute
defaultFreq = 10_139_500 ' Default transmit frequency
defaultTone = 600 ' Offset from transmit frequency
defaultRFPin = 16 ' RF output default pin
This data section defines an array of bytes used to identify each transmitter. These are the letters sent (repeated 5 times) during each time slot. For example the transmitter with the serial number 2 will send VVVVV in morse code in randomly chosen time slots.
Here we also declare an object instance of the frequency synthesizer object used to generate RF energy. This module is provided as part of the standard modules available with the development tools for the Propeller processor. The source code is provided and nothing is done here that you cannot do yourself in your own code.
dat
Ids BYTE "BFVL" 'Equal Length Single Character IDs, one for each SN
obj
Freq : "Synth"
Local variables are now defined.
var
long TimeSlot
long Rand
long WPM
long Frequency ' Current frequency
long toneFreq ' Offset from frequency in Hz
byte RFPin
byte ditTime ' Time in milliseconds for a dit (dot)
byte SN ' Serial number (set by switches)
long Stack[128]
BYTE Cog
Public functions now follow. Main performs initialization and then runs the CW beacon routines.
pub Main
Init
doHFFox
The Start and Stop functions allow starting this code up on a different core and stopping it. This allows me to implement both the HF and VHF transmitters in a way that they can function simultaneously.
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
This is the main function that does all the magic. It schedules the random slot selections and at the appropriate time sends the identifier character in morse code based on the serial number.
During slots 1 and 5, the fox with serial number 0 transmits. Slots 2 and 6, serial number 1 transmits and so forth. During slot 9 all foxes transmit and during slot 0, nobody transmits. The effect is that you may hear zero to four foxes all talking at once during each time slot.
pri doHFFox | Task, C
C := cnt
repeat
waitcnt(C += (clkfreq * SlotTime) #> WMin) ' Wait for our slot time '
C := cnt ' Get the current clock value
SN := 2 ' Serial number is hard coded - should be on switch input
if TimeSlot == 0 ' Reset Sequence
Rand := Seed
Task := ||?Rand // 10 ' Random task number (only use last digit)
case (Task)
1,5:
if SN == 0
sendId(SN) ' Send my ID at different times based on serial number and task
2,6:
if SN == 1
sendId(SN)
3,7:
if SN == 2
sendId(SN)
4,8:
if SN == 3
sendId(SN)
9:
sendId(SN) ' Everybody sends
0:
sendNothing ' Noboday sends (silence is heard for time required to identify)
TimeSlot := (TimeSlot + 1) // TimeSlots
Now, we have the private functions. Init handles initialization setting the frequency, morse code speed, RF pin to use, etc.
pri Init
TimeSlot := 0
Rand := Seed
WPM := wpmInit
ditTime := 1200 / WPM ' Calculate dit time based on 50 dit duration standard
Frequency := defaultFreq
toneFreq := defaultTone
RFPin := defaultRFPin
Now we have the various "send" functions that handle the actual morse code transmissions. sendId send the single identifier character based on serial number 5 times. sendNothing does exactly that. sendCode works with sendSymbol to allow morse code transmission of strings of characters.
pri sendId(_SN)
repeat 5
sendSymbol(Ids[_SN]) ' Send the associated ID for the serial number
pri sendNothing
pri sendCode(stringptr)
repeat strsize(stringptr)
sendSymbol(byte[stringptr++]) ' Send each symbol of the stream
sendSymbol is the main function that provides the conversion of an ascii character into a series of morse code symbols that are used to key the RF energy on and off forming morse code characters. The character itself is used as the index into a lookup table of morse code symbols for that character. The function then forms properly constructed morse code and keys the transmitter.
pri sendSymbol(char) | cwBits, dotsToWait, iCodes
if char == " " ' Handle space character as 7 dot times
dotsToWait := 5
delay(dotsToWait * ditTime)
else
if char => "a" and char =< "z" ' Convert lower case to upper case
char := char - "a" + "A"
iCodes := lookdown(char: "!", $22, "$&'()+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_")
' If unsupported character, ignore it
if iCodes == 0
return
cwBits := cwCodes[iCodes - 1] ' Grab the CW bit codes for this symbol
repeat while cwBits > 1
if cwBits & 1
dotsToWait := 3 ' dah
else
dotsToWait := 1 ' dit
keyDown
delay(dotsToWait * ditTime)
keyUp ' stop sending
delay(ditTime) ' one dot time between symbols
cwBits >>= 1 ' get the next symbol (dit or dah)
delay(ditTime * 2) ' two more dot times to give 3 dot character spacing
The following functions are use to control the frequency synthesizer to actually generate RF energy. sendTone turns on an RF carrier at the requested frequency plus a tone offset value. noTone turns off that RF carrier. These functions are used by keyDown and keyUp to generate morse code keying transitions. delay is a general purpose function to delay for the specified number of milliseconds.
pri sendTone(tone)
Freq.Synth("A", RFPin, Frequency + tone) '
pri noTone
Freq.Synth("A", RFPin, 0)
pri keyDown
sendTone(toneFreq) ' Sidetone or transmit
pri keyUp
noTone ' Stop sidetone or transmit
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
This data block is used to define the morse code characters as described in the comments below. Each character is stored in a byte where each one bit represents a dash (or dah) and a zero bit indicates a dot (or dit) in morse code.
dat
' Morse code tables below are encoded as "1" being a dah and "0" being a dit. The end of the character
' is signified by the most significant "1" bit. Thus, while shifting the byte value right, we can easily
' test for the end of character condition by checking to see if the current value is > 1.
'
' ! " $ & ' ( ) + , - . /
cwCodes byte %1110101, %1010010, %11001000,%100010, %1011110, %101101, %1101101, %101010, %1110011, %1100001, %1101010, %101001
' 0 1 2 3 4 5 6 7 8 9
byte %111111, %111110, %111100, %111000, %110000, %100000, %100001, %100011, %100111, %101111
' : ; = ? @
byte %1000111, %1010101, %110001, %1001100, %1010110
' A B C D E F G H
byte %110, %10001, %10101, %1001, %10, %10100, %1011, %10000
' I J K L M N O P Q R
byte %100, %11110, %1101, %10010, %111, %101, %1111, %10110, %11011, %1010
' S T U V W X Y Z _
byte %1000, %11, %1100, %11000, %1110, %11001, %11101, %10011, %1101100
In the next installment, I will describe the 2 metre FM fox. Stay tuned! For your reference I have pasted the entirety of the code above for convenience. If you use this code, paste it into its own file and save it in a directory where the rest of the fox hunt transmitter files will be stored. Name the file something like HFFox.spin. The .spin suffix is used for SPIN program files.
' HF CW Fox Hunt Beacon
'
' ko7m - Jeff Whitlatch
'
con
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
Seed = 31414
WMin = 381
'Sync Each Ten Minutes So that Others can be be started Later, by watching the wall clock
TimeSync = 60 * 2 ' Seconds between time syncs (2 min)
SlotTime = 6 ' Seconds
TimeSlots = TimeSync / SlotTime ' Number of time slots (in this case 20)
wpmInit = 15 ' Initial code speed in words per minute
defaultFreq = 10_139_500 ' Default transmit frequency
defaultTone = 600 ' Offset from transmit frequency
defaultRFPin = 16 ' RF output default pin
dat
Ids BYTE "BFVL" 'Equal Length Single Character IDs, one for each SN
obj
Freq : "Synth"
var
long TimeSlot
long Rand
long WPM
long Frequency ' Current frequency
long toneFreq ' Offset from frequency in Hz
byte RFPin
byte ditTime ' Time in milliseconds for a dit (dot)
byte SN ' Serial number (set by switches)
long Stack[128]
BYTE Cog
pub Main
Init
doHFFox
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
pri doHFFox | Task, C
C := cnt
repeat
waitcnt(C += (clkfreq * SlotTime) #> WMin) ' Wait for our slot time '
C := cnt ' Get the current clock value
SN := 2 ' Serial number is hard coded - should be on switch input
if TimeSlot == 0 ' Reset Sequence
Rand := Seed
Task := ||?Rand // 10 ' Random task number (only use last digit)
case (Task)
1,5:
if SN == 0
sendId(SN) ' Send my ID at different times based on serial number and task
2,6:
if SN == 1
sendId(SN)
3,7:
if SN == 2
sendId(SN)
4,8:
if SN == 3
sendId(SN)
9:
sendId(SN) ' Everybody sends
0:
sendNothing ' Noboday sends (silence is heard for time required to identify)
TimeSlot := (TimeSlot + 1) // TimeSlots
pri Init
TimeSlot := 0
Rand := Seed
WPM := wpmInit
ditTime := 1200 / WPM ' Calculate dit time based on 50 dit duration standard
Frequency := defaultFreq
toneFreq := defaultTone
RFPin := defaultRFPin
pri sendId(_SN)
repeat 5
sendSymbol(Ids[_SN]) ' Send the associated ID for the serial number
pri sendNothing
pri sendCode(stringptr)
repeat strsize(stringptr)
sendSymbol(byte[stringptr++]) ' Send each symbol of the stream
pri sendSymbol(char) | cwBits, dotsToWait, iCodes
if char == " " ' Handle space character as 7 dot times
dotsToWait := 5
delay(dotsToWait * ditTime)
else
if char => "a" and char =< "z" ' Convert lower case to upper case
char := char - "a" + "A"
iCodes := lookdown(char: "!", $22, "$&'()+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_")
' If unsupported character, ignore it
if iCodes == 0
return
cwBits := cwCodes[iCodes - 1] ' Grab the CW bit codes for this symbol
repeat while cwBits > 1
if cwBits & 1
dotsToWait := 3 ' dah
else
dotsToWait := 1 ' dit
keyDown
delay(dotsToWait * ditTime)
keyUp ' stop sending
delay(ditTime) ' one dot time between symbols
cwBits >>= 1 ' get the next symbol (dit or dah)
delay(ditTime * 2) ' two more dot times to give 3 dot character spacing
pri sendTone(tone)
Freq.Synth("A", RFPin, Frequency + tone) '
pri noTone
Freq.Synth("A", RFPin, 0)
pri keyDown
sendTone(toneFreq) ' Sidetone or transmit
pri keyUp
noTone ' Stop sidetone or transmit
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
dat
' Morse code tables below are encoded as "1" being a dah and "0" being a dit. The end of the character
' is signified by the most significant "1" bit. Thus, while shifting the byte value right, we can easily
' test for the end of character condition by checking to see if the current value is > 1.
'
' ! " $ & ' ( ) + , - . /
cwCodes byte %1110101, %1010010, %11001000,%100010, %1011110, %101101, %1101101, %101010, %1110011, %1100001, %1101010, %101001
' 0 1 2 3 4 5 6 7 8 9
byte %111111, %111110, %111100, %111000, %110000, %100000, %100001, %100011, %100111, %101111
' : ; = ? @
byte %1000111, %1010101, %110001, %1001100, %1010110
' A B C D E F G H
byte %110, %10001, %10101, %1001, %10, %10100, %1011, %10000
' I J K L M N O P Q R
byte %100, %11110, %1101, %10010, %111, %101, %1111, %10110, %11011, %1010
' S T U V W X Y Z _
byte %1000, %11, %1100, %11000, %1110, %11001, %11101, %10011, %1101100
Historically, we have provided 1 to 4 beacons on 30 metres and coached folks on the use of simple loop antennas in radio direction finding (RDF). This year I have decided to add a VHF component to the hunt as most of the group owns VHF FM equipment.
Our summer camp site sits on about 50 acres up against the western slopes of the Cascade Mountains near North Bend, WA. It is a truly idyllic location and a wonderful venue for our summer camp-out called Salmoncon. If you are going to be in the area, we hope you will plan to drop in and visit.
Below and in a previous post I included a picture of a couple of our transmitter hunt participants from years past. We have done the event every year and I have threatened to not do the event a couple of times but the gathering lynch mob convinced us otherwise. So, it is a simple thing, but apparently it is quite popular.
What I want to do here is to provide the details of this year's hidden transmitter hardware and firmware so that others who might be interested in hosting such an event might be able to leverage the effort to best benefit.
Here is my little fox hunt transmitter that is able to transmit CW on any HF frequency you like while simultaneously sending an FM signal on 2 metres.
As you can see it fits nicely on the side of a standard 9V battery. I have attached it with a little blob of putty that is used to adhere things to the wall in your home but is still removable without damaging the wall. I find that it holds really well, but not as strongly as for example double sided foam tape. Not shown are the wires that attach to HF and VHF antennas. These connect to two of the pins along the side of the board. In practice this gets wrapped in bubble wrap and stuffed into a short length of PVC pipe. Pipe end caps provide water and dust protection. Small eye bolts are used to connect the electronics to external antennas and to provide a convenient way to hang the transmitter up in a tree for example using the antenna wire as a support.
The board is a Parallax Propeller Mini board. I have written previously on various projects that I have based on this processor. This is a slick little board packing a 32 bit, eight core 160 MIPS 80 MHz processor which is complete overkill for this project, but a key feature of the Propeller chip makes it ideal for this application.
Each core has two counter modules. Each counter module can control or monitor up to two I/O pins and perform conditional 32-bit accumulation of its FRQ register into its PHS register on every clock cycle. Each counter module also has its own phase-locked loop (PLL) which can be used to synthesize frequencies up to 128 MHz. In reality it can synthesize frequencies up to about 220 MHz, but Parallax recommends "for best stability" to limit frequency synthesis to 128 MHz maximum. I am using it at 146 Mhz without issue.
Let me first discuss the CW HF beacon. Typically we use four of these transmitters hidden around the venue. Each is assigned a serial number 0-3 which is used to determine the content of the transmitted beacon. We picked morse code characters that had the exact same transmission length for each of the beacons based on serial number. V, F, L, and B. The beacon will send this letter 5 times on a schedule discussed below. By turning on all four transmitters simultaneously, the fox hunters will hear zero or more of them identifying at the same time. The goal is to locate them all and we typically keep the game running for several days to give everyone a chance. If there is interest and the foxes have been found, we move them. (Don't forget to check the collar of any of the dogs on site for a transmitter...)
The Propeller chip is programmed in one of two languages. SPIN or PASM. Spin is a C-like (sort of) language while PASM is the Propeller ASseMbly language. All of my code presented in this posting will be in the SPIN language. I am not going to provide a tutorial on the language, for that you can hit the reference manual for the gory details. If you get stuck, drop me a line at ko7m at arrl dot org and I will try to help you out.
The top part of the code declares the clock speed, PLL settings and crystal frequency. This is followed by any constants used by the program.
' HF CW Fox Hunt Beacon
'
' ko7m - Jeff Whitlatch
'
con
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
The following constants are used in generating random numbers and to fine tune the timing accuracy of a delay function.
Seed = 31414
WMin = 381
Each transmitter gets a 6 second slot for transmitting, chosen at random and the transmitters re-sync random number generation every two minutes.
'Sync Each Ten Minutes So that Others can be be started Later, by watching the wall clock
TimeSync = 60 * 2 ' Seconds between time syncs (2 min)
SlotTime = 6 ' Seconds
TimeSlots = TimeSync / SlotTime ' Number of time slots (in this case 20)
We transmit at 15 words per minute on a frequency of 10.1395 MHz with a tone offset of 600 hz. Here we also define which pin is used to generate the RF at this frequency. Any available pin may be chosen for this task.
wpmInit = 15 ' Initial code speed in words per minute
defaultFreq = 10_139_500 ' Default transmit frequency
defaultTone = 600 ' Offset from transmit frequency
defaultRFPin = 16 ' RF output default pin
This data section defines an array of bytes used to identify each transmitter. These are the letters sent (repeated 5 times) during each time slot. For example the transmitter with the serial number 2 will send VVVVV in morse code in randomly chosen time slots.
Here we also declare an object instance of the frequency synthesizer object used to generate RF energy. This module is provided as part of the standard modules available with the development tools for the Propeller processor. The source code is provided and nothing is done here that you cannot do yourself in your own code.
dat
Ids BYTE "BFVL" 'Equal Length Single Character IDs, one for each SN
obj
Freq : "Synth"
Local variables are now defined.
var
long TimeSlot
long Rand
long WPM
long Frequency ' Current frequency
long toneFreq ' Offset from frequency in Hz
byte RFPin
byte ditTime ' Time in milliseconds for a dit (dot)
byte SN ' Serial number (set by switches)
long Stack[128]
BYTE Cog
Public functions now follow. Main performs initialization and then runs the CW beacon routines.
pub Main
Init
doHFFox
The Start and Stop functions allow starting this code up on a different core and stopping it. This allows me to implement both the HF and VHF transmitters in a way that they can function simultaneously.
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
This is the main function that does all the magic. It schedules the random slot selections and at the appropriate time sends the identifier character in morse code based on the serial number.
During slots 1 and 5, the fox with serial number 0 transmits. Slots 2 and 6, serial number 1 transmits and so forth. During slot 9 all foxes transmit and during slot 0, nobody transmits. The effect is that you may hear zero to four foxes all talking at once during each time slot.
pri doHFFox | Task, C
C := cnt
repeat
waitcnt(C += (clkfreq * SlotTime) #> WMin) ' Wait for our slot time '
C := cnt ' Get the current clock value
SN := 2 ' Serial number is hard coded - should be on switch input
if TimeSlot == 0 ' Reset Sequence
Rand := Seed
Task := ||?Rand // 10 ' Random task number (only use last digit)
case (Task)
1,5:
if SN == 0
sendId(SN) ' Send my ID at different times based on serial number and task
2,6:
if SN == 1
sendId(SN)
3,7:
if SN == 2
sendId(SN)
4,8:
if SN == 3
sendId(SN)
9:
sendId(SN) ' Everybody sends
0:
sendNothing ' Noboday sends (silence is heard for time required to identify)
TimeSlot := (TimeSlot + 1) // TimeSlots
Now, we have the private functions. Init handles initialization setting the frequency, morse code speed, RF pin to use, etc.
pri Init
TimeSlot := 0
Rand := Seed
WPM := wpmInit
ditTime := 1200 / WPM ' Calculate dit time based on 50 dit duration standard
Frequency := defaultFreq
toneFreq := defaultTone
RFPin := defaultRFPin
Now we have the various "send" functions that handle the actual morse code transmissions. sendId send the single identifier character based on serial number 5 times. sendNothing does exactly that. sendCode works with sendSymbol to allow morse code transmission of strings of characters.
pri sendId(_SN)
repeat 5
sendSymbol(Ids[_SN]) ' Send the associated ID for the serial number
pri sendNothing
pri sendCode(stringptr)
repeat strsize(stringptr)
sendSymbol(byte[stringptr++]) ' Send each symbol of the stream
sendSymbol is the main function that provides the conversion of an ascii character into a series of morse code symbols that are used to key the RF energy on and off forming morse code characters. The character itself is used as the index into a lookup table of morse code symbols for that character. The function then forms properly constructed morse code and keys the transmitter.
pri sendSymbol(char) | cwBits, dotsToWait, iCodes
if char == " " ' Handle space character as 7 dot times
dotsToWait := 5
delay(dotsToWait * ditTime)
else
if char => "a" and char =< "z" ' Convert lower case to upper case
char := char - "a" + "A"
iCodes := lookdown(char: "!", $22, "$&'()+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_")
' If unsupported character, ignore it
if iCodes == 0
return
cwBits := cwCodes[iCodes - 1] ' Grab the CW bit codes for this symbol
repeat while cwBits > 1
if cwBits & 1
dotsToWait := 3 ' dah
else
dotsToWait := 1 ' dit
keyDown
delay(dotsToWait * ditTime)
keyUp ' stop sending
delay(ditTime) ' one dot time between symbols
cwBits >>= 1 ' get the next symbol (dit or dah)
delay(ditTime * 2) ' two more dot times to give 3 dot character spacing
The following functions are use to control the frequency synthesizer to actually generate RF energy. sendTone turns on an RF carrier at the requested frequency plus a tone offset value. noTone turns off that RF carrier. These functions are used by keyDown and keyUp to generate morse code keying transitions. delay is a general purpose function to delay for the specified number of milliseconds.
pri sendTone(tone)
Freq.Synth("A", RFPin, Frequency + tone) '
pri noTone
Freq.Synth("A", RFPin, 0)
pri keyDown
sendTone(toneFreq) ' Sidetone or transmit
pri keyUp
noTone ' Stop sidetone or transmit
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
This data block is used to define the morse code characters as described in the comments below. Each character is stored in a byte where each one bit represents a dash (or dah) and a zero bit indicates a dot (or dit) in morse code.
dat
' Morse code tables below are encoded as "1" being a dah and "0" being a dit. The end of the character
' is signified by the most significant "1" bit. Thus, while shifting the byte value right, we can easily
' test for the end of character condition by checking to see if the current value is > 1.
'
' ! " $ & ' ( ) + , - . /
cwCodes byte %1110101, %1010010, %11001000,%100010, %1011110, %101101, %1101101, %101010, %1110011, %1100001, %1101010, %101001
' 0 1 2 3 4 5 6 7 8 9
byte %111111, %111110, %111100, %111000, %110000, %100000, %100001, %100011, %100111, %101111
' : ; = ? @
byte %1000111, %1010101, %110001, %1001100, %1010110
' A B C D E F G H
byte %110, %10001, %10101, %1001, %10, %10100, %1011, %10000
' I J K L M N O P Q R
byte %100, %11110, %1101, %10010, %111, %101, %1111, %10110, %11011, %1010
' S T U V W X Y Z _
byte %1000, %11, %1100, %11000, %1110, %11001, %11101, %10011, %1101100
In the next installment, I will describe the 2 metre FM fox. Stay tuned! For your reference I have pasted the entirety of the code above for convenience. If you use this code, paste it into its own file and save it in a directory where the rest of the fox hunt transmitter files will be stored. Name the file something like HFFox.spin. The .spin suffix is used for SPIN program files.
' HF CW Fox Hunt Beacon
'
' ko7m - Jeff Whitlatch
'
con
_CLKMODE = XTAL1 + PLL16X
_XINFREQ = 5_000_000
Seed = 31414
WMin = 381
'Sync Each Ten Minutes So that Others can be be started Later, by watching the wall clock
TimeSync = 60 * 2 ' Seconds between time syncs (2 min)
SlotTime = 6 ' Seconds
TimeSlots = TimeSync / SlotTime ' Number of time slots (in this case 20)
wpmInit = 15 ' Initial code speed in words per minute
defaultFreq = 10_139_500 ' Default transmit frequency
defaultTone = 600 ' Offset from transmit frequency
defaultRFPin = 16 ' RF output default pin
dat
Ids BYTE "BFVL" 'Equal Length Single Character IDs, one for each SN
obj
Freq : "Synth"
var
long TimeSlot
long Rand
long WPM
long Frequency ' Current frequency
long toneFreq ' Offset from frequency in Hz
byte RFPin
byte ditTime ' Time in milliseconds for a dit (dot)
byte SN ' Serial number (set by switches)
long Stack[128]
BYTE Cog
pub Main
Init
doHFFox
PUB Start : fSuccess
fSuccess := (Cog := cognew(Main, @Stack) + 1) > 0
PUB Stop
if Cog
cogstop(Cog~ - 1)
pri doHFFox | Task, C
C := cnt
repeat
waitcnt(C += (clkfreq * SlotTime) #> WMin) ' Wait for our slot time '
C := cnt ' Get the current clock value
SN := 2 ' Serial number is hard coded - should be on switch input
if TimeSlot == 0 ' Reset Sequence
Rand := Seed
Task := ||?Rand // 10 ' Random task number (only use last digit)
case (Task)
1,5:
if SN == 0
sendId(SN) ' Send my ID at different times based on serial number and task
2,6:
if SN == 1
sendId(SN)
3,7:
if SN == 2
sendId(SN)
4,8:
if SN == 3
sendId(SN)
9:
sendId(SN) ' Everybody sends
0:
sendNothing ' Noboday sends (silence is heard for time required to identify)
TimeSlot := (TimeSlot + 1) // TimeSlots
pri Init
TimeSlot := 0
Rand := Seed
WPM := wpmInit
ditTime := 1200 / WPM ' Calculate dit time based on 50 dit duration standard
Frequency := defaultFreq
toneFreq := defaultTone
RFPin := defaultRFPin
pri sendId(_SN)
repeat 5
sendSymbol(Ids[_SN]) ' Send the associated ID for the serial number
pri sendNothing
pri sendCode(stringptr)
repeat strsize(stringptr)
sendSymbol(byte[stringptr++]) ' Send each symbol of the stream
pri sendSymbol(char) | cwBits, dotsToWait, iCodes
if char == " " ' Handle space character as 7 dot times
dotsToWait := 5
delay(dotsToWait * ditTime)
else
if char => "a" and char =< "z" ' Convert lower case to upper case
char := char - "a" + "A"
iCodes := lookdown(char: "!", $22, "$&'()+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_")
' If unsupported character, ignore it
if iCodes == 0
return
cwBits := cwCodes[iCodes - 1] ' Grab the CW bit codes for this symbol
repeat while cwBits > 1
if cwBits & 1
dotsToWait := 3 ' dah
else
dotsToWait := 1 ' dit
keyDown
delay(dotsToWait * ditTime)
keyUp ' stop sending
delay(ditTime) ' one dot time between symbols
cwBits >>= 1 ' get the next symbol (dit or dah)
delay(ditTime * 2) ' two more dot times to give 3 dot character spacing
pri sendTone(tone)
Freq.Synth("A", RFPin, Frequency + tone) '
pri noTone
Freq.Synth("A", RFPin, 0)
pri keyDown
sendTone(toneFreq) ' Sidetone or transmit
pri keyUp
noTone ' Stop sidetone or transmit
pri delay(Duration)
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> WMin) + cnt)
dat
' Morse code tables below are encoded as "1" being a dah and "0" being a dit. The end of the character
' is signified by the most significant "1" bit. Thus, while shifting the byte value right, we can easily
' test for the end of character condition by checking to see if the current value is > 1.
'
' ! " $ & ' ( ) + , - . /
cwCodes byte %1110101, %1010010, %11001000,%100010, %1011110, %101101, %1101101, %101010, %1110011, %1100001, %1101010, %101001
' 0 1 2 3 4 5 6 7 8 9
byte %111111, %111110, %111100, %111000, %110000, %100000, %100001, %100011, %100111, %101111
' : ; = ? @
byte %1000111, %1010101, %110001, %1001100, %1010110
' A B C D E F G H
byte %110, %10001, %10101, %1001, %10, %10100, %1011, %10000
' I J K L M N O P Q R
byte %100, %11110, %1101, %10010, %111, %101, %1111, %10110, %11011, %1010
' S T U V W X Y Z _
byte %1000, %11, %1100, %11000, %1110, %11001, %11101, %10011, %1101100
Subscribe to:
Posts (Atom)