The GPMPU28 boards are highly customizable and contain all you need to make permanent or breadboard PIC projects using most Microchip 28 pin PICs (including the PIC16F873 shown above). The above board is "fully loaded" with the power supply option, an optional terminal block, and an optional reset switch -- you can customize these boards in lots of ways (just see the manual for some ideas).

Our first kit based around this board is a Morse code beacon and keyboard keyer. This is ideal for radio location beacons (foxes), beacons, or any of a number of other items. Here's how it works:

When you first power the board up (or reset it) you have 5 seconds to send a character down the serial port at 19,200 baud (8 bits, no parity, 1 stop bit). Usually you'll use Hyperterminal set for hardware handshaking and turn local echo ON so you can see what you are typing!

If the device has never been set up, or you hit any key except for a K within 5 seconds, you enter programming mode. This lets you set a message (up to 75 characters), a delay in seconds between messages, and a speed in words per minute (approximate). Sorry, but there is no backspace allowed! However, if you mess up you'll have an opportunity to reenter the data.

Once the data is entered, the beacon plays the message and asks for your approval. You can reenter the data, hear the message again, or approve it. If you approve, the chip writes the parameters into onboard EEPROM and you never have to set it again (unless you want to).

Once you are done (or if you didn't hit a key within 5 seconds) the chip enter beacon mode. It plays your message, pauses for the delay, and then repeats it. The delay is not atomic clock precise, but it isn't bad either. There are several outputs and inputs:

  1. The LED on the board blinks the code.

  2. One pin on the edge connector also goes high with the code (this could key a transmitter).

  3. One pin on the edge connector goes high 50mS before sending and goes low 50mS after sending is complete. This can control the transmitter's PTT line.

  4. There is a manual trigger input. If it goes low, the beacon sends immediately and restarts its delay count. If you don't want to use the trigger, just tie the pin high. If you want the chip to only send on trigger events, set the delay parameter to 0.

  5. There is provisions for a speed control pot (say, a 5K pot although it isn't critical). Just tie one side to +5V and the other side to ground. The wiper connects to the speed input. To use this to control the speed, set the WPM parameter to 0, otherwise the pot has no effect (and can be omitted).

When entering code you can use = for BT, ; for KN, ' for SK, and @ for AR. Upper or lower case letters are OK. The program also knows how to send periods, commas, slashes, and question marks. Any other character generates a space.

If you enter a K (or a k) at startup, the chip enters keyboard mode. Whatever you type on the terminal will be sent in the usual way. In this mode you have all the same characters, plus the backspace will generate 8 dots.

The + key increases your speed and the - key decreases it. You can also use the ~ key to switch between keyboard speed control and pot control (if you have a pot mounted).

When using the keyboard mode you also directly control the PTT line. Use ! to assert the PTT and * to return to receive mode. The chip will still send code even if PTT is off.

The Circuit

There are no modifications required to the board. A PIC16F873A forms the brains. You can follow the directions and the schematic in the manual. If you are breadboarding, you can just stick the board into your breadboard and then wire the following optional components:

  1. A piezo speaker on PortC.2 (pin 2 of JP1). The PWM that drives the speaker sounds buzzy, but it is acceptable. If you wanted to filter and amplify it, you could, but it is typically not necessary. If you omit the speaker, you'll still be able to see the code flashing on the onboard LED.

  2. If you want to drive a transmitter, you can take the output from pin 3 (PortC.1) which matches the onboard LED's Morse code output. Usually, you'd have this output drive a 2N2222 which would simulate a key to the radio. Of course, if you don't connect to a radio, you can omit this.

  3. Pin 4 (PortC.0) goes high 50mS before transmitting in beacon mode and stays high for 50mS after the beacon completes a message. This will also usually drive a 2N2222. Some radios won't need this output and if you aren't driving a radio, you can certainly ignore this output.

  4. Pin 6 (Port A.4) is the manual trigger. If you don't want to use this, connect it to 5V. If you want a switch, place a pull up resistor (say, 10K) to +5V and the switch to ground. The chip assumes that the trigger will be shorter than the message. If it isn't the message will repeat almost immediately until the trigger returns high. This is optional.

  5. Pin 10 (Port A.0) is the speed pot input. This allows a 0 to 5V voltage to set the speed to about 5WPM to 35WPM. This is read by the PIC's analog to digital converter and is optional since both beacon and keyboard mode can set their own speed numerically.

Of course, if you are building this as a permanent fixture, you can solder a general purpose PC board to the breadboard holes or use the holes to accommodate a cable to another board. In this case, I designed the software around the hole layout, but the board has a unique feature that lets you easily reroute other signals to the edge if you need them.

The Software

The software using Microengineering Lab's PIC Basic Pro compiler. You could probably convert this to run on a Basic Stamp pretty easy. You'd need to change all the HSERIN/HSEROUT commands to use SERIN and SEROUT instead. BRANCHL becomes BRANCH. You'd have to modify the pot reading commands to use RCTime (and add a capacitor to the circuit). Or you could just ditch the pot, which would be easier!

The code is relatively straightforward. The only problem is the limited EEPROM space on the PIC. I wanted to make most of it available for message storage. But I also needed a table that tells me how to convert letters to dots and dashes. I've done this in the past using a pure data table and interpreting it (for example, in our PAK-VI application note) but this time I tried something different.

There are two routines that generate dots and dashes called... drum roll please.... dots and dashes. Each routine takes a count of how many to make and makes them. There is also a piece of code known as lastdot. This subroutine plays a certain number of dots, pauses between characters, and goes back for more. The lastdash routine does the same for letters that end in dashes.

So to send an "E" (one dot) you set n=1 and go to lastdot. An "I" (two dots) takes n=2. When you have a mix of dots and dashes, I try to save space by jumping to a tail end letter. For example, an "R" (dot dash dot) is a single dot (n=1, call dots) and then a jump to the letter "N" (dash dot). In many cases, the letters just fall through into the tail end letter instead of executing a jump.

In retrospect, I think I like the other method better. However, this works, and there is plenty of code space so I've traded code space to get more data space. Another reason I had thought about this approach is that a ham friend of mine (hi Tom) was talking about doing this with landline Morse code. Landline Morse has things like long dashes and gaps in it, so using a code-based method like this give you a lot of flexibility. It would be very simple to change this code to generate letters that had odd spacing or elements.

Of course, any changes you want to make in the dot and dash generation is just a matter of changing dots and dashes. So the code isn't as pretty as the CW keyboard code in the PAK-VI application note, but it is very flexible.

The Basic Code (uses PICBasic Pro)

' Morse code beacon (c) 2003 by Al Williams. All Rights Reserved
' This version supports a keyboard input mode

' Hardware: GPMPU28 (unmodified)
' Extra hardware:
'  Piezo speaker (optional) on PortC.2
'  Transmitter key circuit (optional) on PortC.1
'  Transmitter keying circuit (optional) on PortC.0
'  Pot (optional) on PortA.0
'  Trigger on PortA.4 (low to manually trigger beacon)
'
' Operation: On power up, hit a key on the terminal within 5 seconds.
' A k or K will enter keyboard mode. Any other key will enter program 
' mode. If the device is empty, it will start program mode in 5 seconds
' of no action. The device expects a 19,200 baud/8/n/1
' serial terminal for programming. Hardware handshaking is used.
'
' You can insert a string (upper or lower case is OK), a delay in seconds
' and the words per minute you wish to send. The delay is the time
' between beacon messages. If the WPM is 0, the speed is read
' from a 0-5 voltage on PortA.0 (probably from a pot). 0V is 5WPM and
' 5V is 35 WPM.
'
' When you finish entering the data, the unit plays the data back
' (without keying the transmitter). You can approve it, listen again,
' or reenter the message.
'
' Once approved -- or if 5 seconds elapses with no input after reset --
' the beacon sends its message. There is a 50mS delay before and after the
' message that the transmitter is keyed. Then the delay time must expire
' before the message is transmitted again.
'
' The parameters are stored in EEPROM so once programmed, no connection
' to a PC or terminal is required. For best results, enable local echo
' on the serial terminal.

' If on power up, the mode pin is high, then the device operates as an
' RS232 to Morse code terminal. 

' Characters A-Z and a-z 0-9 , . / ? 

' Prosigns: 
' @ - AR   = - BT  ; - KN  ' - SK

' In keyboard mode, + and - adjust internal speed up and down 
' ~ switches to pot control of WPM
' ! turns on PTT and * turns it off,
' backspace sends 8 dots

' Set serial port 
DEFINE OSC 20
DEFINE HSER_RCSTA 90h
DEFINE HSER_TXSTA 20h
DEFINE HSER_BAUD 19200

' harmless to run under ICD or not
INCLUDE "icddefs.inc"

' IO definitions
LED var PortC.3
SPEAKER var PortC.2
PTT var PortC.1  ' PTT output to transmitter
KEY var PortC.0  ' key output to transmitter
SPDCTL con 0     ' A/D channel 0 - speed control
CTS var PortB.2  ' RS232 CTS
TRIGGER var PORTA.4  ' trigger input
MSGLEN con 75    ' Maximum message length
KEYDELAY con 50  ' delay (ms) between PTT and first or last element
TONE con 1000    ' sidetone in Hz
STARTDLY con 5000 ' timeout for start command

c var byte       ' character
ch var byte      ' character
dly var word     ' delay between messages (if $FFFF we haven't been programmed)
cdly var word    ' current delay time
rmsg var byte[MSGLEN]  ' the message
wpm var byte     ' words per minute
n var byte       ' general byte
dot var word     ' length of a dot in ms
dash var word    ' length of a dash in ms (3*dot)
chspace var word ' character space (1.5*dot)
msgoffset con 3  ' offset of message in EEPROM

HIGH CTS  ' disable serial in
LOW PTT
LOW KEY

' Set unused pins low
LOW PortC.4
LOW PortC.5
Low PortB.0
' Port B 1 is input from serial port
Low PortB.3
Low PortB.4
Low PortB.5
Low PortB.6
Low PortB.7

' On start up we listen for some sort of serial input for 5 seconds. If we don't get
' any we just go do our thing
HSEROUT ["5 seconds to beacon mode; press K for keyboard, other key to program",13,10]
LOW CTS
HSERIN STARTDLY,start, [c]
if c="k" or c="K" then keyboard
HIGH CTS
' ok we are in program mode
reprogram:
' Clear RAM message
for c=0 to msglen-1
  rmsg[c]=0
next
HSEROUT ["BEACON MESSAGE> "]
LOW CTS
HSERIN [str rmsg\msglen\13]
HIGH CTS
HSEROUT ["Message: ",str rmsg\msglen,13,10]
HSEROUT ["DELAY (seconds)> "]
LOW CTS
HSERIN [dec dly]
HIGH CTS
HSEROUT ["Delay: ", dec dly,13,10]
HSEROUT ["WPM> "]
LOW CTS
HSERIN [dec wpm]
HIGH CTS
HSEROUT ["WPM: ", dec wpm, 13,10]
replay:
HSEROUT ["Playing...",13,10]
gosub play
HSEROUT ["Return to accept, ? to replay, Esc to reenter",13,10]
wloop:
LOW CTS
HSERIN [c]
HIGH CTS
if c=="?" then replay
if c==$1b then reprogram
if c<>13 then wloop

' save into EEPROM
write 0,dly.highbyte
write 1,dly.lowbyte
write 2,wpm
for c=0 to msglen-1
  write c+msgoffset,rmsg[c]
next
HSEROUT ["Accepted",13,10]


' Here is where we go into beacon mode
start:
HIGH CTS   ' shut up serial port
' Load delay parameter
read 0,dly.highbyte
read 1,dly.lowbyte
read 2,wpm   ' Get WPM 
if dly=$FFFF then reprogram  ' no point in playing garbage!
if dly=0 then trigscan
' Load message from EEPROM once
for c=0 to MSGLEN-1
  read c+msgoffset,rmsg[c]
next
' This is the main beacon code
runloop:
sendit:
HIGH PTT     ' turn on transmitter
pause keydelay
gosub play
pause keydelay
LOW PTT
cdly=dly*1000
trigscan:
if trigger=0 then runloop
if dly=0 then trigscan
PAUSE 250
cdly=cdly-250  ' since *1000 this will always reach zero
if cdly<>0 then trigscan
goto runloop


' Play the message
play:
for c=0 to MSGLEN-1
  gosub setwpm      ' set WPM each time in case pot has moved
  ch=rmsg[c]
  if ch>="a" then
    ch=ch & $DF  ' fold lower case
  endif
  if ch=0 or ch=13 then ret   ' 0 or 13 marks end of string
  gosub send_char
next
return



' The code starting here generates the characters


' Any character <32 or >"z" is a space
' Any character "a" or above is folded to its upper case equivalent
' including the few special characters we don't support above "z"
' The branches vector to routines that compose each character
' This is not ideal, but allows us to trade flash space
' for data storage :-)
' For these to be branches instead of branch L's the targets need to be on the same page
' otherwise make them branchl
send_char:
  BRANCHL ch-33,[none,none,none,none,none,none,prosk]  ' 33-39
  BRANCHL ch-40,[none,none,none,none,comma,none,period,slash,zero,one]  ' 40-49
  BRANCHL ch-50,[two,three,four,five,six,seven,eight,nine,none,prokn]  ' 50-59
  BRANCHL ch-60,[none,probt,none,quest,proar,leta,letb,letc,letd,lete]  ' 60-69
  BRANCHL ch-70,[letf,letg,leth,leti,letj,letk,letl,letm,letn,leto]  ' 70-79
  BRANCHL ch-80,[letp,letq,letr,lets,lett,letu,letv,letw,letx,lety]  ' 80-89
  BRANCHL ch-90,[letz]
none:
space:
  PAUSE 2*dash
ret:
  return

' Set the wpm parameters
setwpm:
n=wpm
if wpm<>0 then setwms  ' not set by pot
' Read pot
ADCON1.7=0
ADCIN SPDCTL,n
n = (30*n)/255 + 5   ' convert to WPM ( 5 to 35 wpm)
' Either way we now have WPM in n so we compute dot/dash/chspace
setwms:
dot = 60000/(50 * n)
dash=dot<<1+dot   '(dash = dot * 3 )
chspace = dot + (dot >>1)  ' 1.5* dot
return

'Each character generator is either a digit/punctuation name (one, two) or prefixed with let 
' for letter or pro for prosign. So leta is A, letz is Z and probt is BT.
' The generators make as much as they have to and then jump to another letter
' to finish. So U (.--) might be dot + M. 

' This could be further optimized as I've done for U/V/A
' and others but since we have lots of room I didn't do every possible one

zero:
  n=5
lastdash:         ' This is where characters that end with a dash wind up
  gosub dashes
endchar:          ' This is the end of all characters
  pause chspace
  return

one:    ' dot + 4h
   n=1
   gosub dots
   n=4
   goto lastdash

two:    ' dot dot O
   n=2
letjopt:
   gosub dots
leto:
   n=3
   goto lastdash

three:   ' dot dot dot M
   n=3
dotletm:
   gosub dots
letm:
   n=2
   goto lastdash

four:    ' dot dot dot dot T
   n=4
letuv:
   gosub dots
lett:
   n=1
   goto lastdash

five:
   n=5
lastdot:       ' all characters that end in dots wind up here
   gosub dots
   goto endchar


six:     ' dash H
  n=1
  gosub dashes
leth:
  n=4
  goto lastdot

seven:   ' dash dash S
  n=2
letbopt:
  gosub dashes
lets:
  n=3
  goto lastdot

eight:    ' dash dash dash i
  n=3
dashi:
  gosub dashes
leti:
  n=2
  goto lastdot

nine:  ' dash dash dash dash E
  n=4
  goto dashe

leta:  ' dot T
  n=1
  goto letuv

letb:  ' dash S
  n=1
  goto letbopt

letc:  ' dash R
  n=1
  gosub dashes
letr:  ' dot N
  n=1
  gosub dots
letn:  ' dash E
  n=1
dashe:
  gosub dashes
lete:
  n=1
  goto lastdot

letd:  ' dash i
  n=1
  gosub dashes
  goto leti

letf:  ' dot N
  n=2
  gosub dots
  goto letn

letg:  ' dash dash E
  n=2
  goto dashe

letj:  ' dot O
  n=1
  gosub dots
  goto leto

letk: ' dash A
  n=1
  gosub dashes
  goto leta

letl: ' dot D
  n=1
  gosub dots
  goto letd

letp:
  n=1
  gosub dots
  goto letg
 

letq: ' dash dash A
  n=2
  gosub dashes
  goto leta

letu:  ' dot dot T
  n=2
  goto letuv

letv:  ' dot dot dot T
  n=3
  goto letuv


letx:  ' dash U
  n=1
  gosub dashes
  goto letu

lety:   ' dash W
  n=1
  gosub dashes
letw:   ' dot M
  n=1
  goto dotletm

letz:   ' dash dash i
  n=2
  gosub dashes
  goto leti


probt:   ' dash + V
  n=1
  gosub dashes
  goto letv

proar:   ' dot + C
  n=1
  gosub dots
  goto letc

prokn:   ' dash + P
  n=1
  gosub dashes
  goto letp

prosk:  ' dot dot dot + K
  n=3
  gosub dots
  goto letk

comma:  ' dash dash dot dot M
  n=2
  gosub dashes
  n=2
  goto dotletm

period:  ' dot dash dot dash A
  n=1
  gosub dots
  n=1
  gosub dashes
  n=1
  gosub dots
  n=1
  gosub dashes
  goto leta

slash:  'dash F
  n=1
  gosub dashes
  goto letf

quest:  ' dot dot Z
  n=2
  gosub dots
  goto letz

dashes:
  if n=0 then ret
  HIGH LED
  HIGH KEY
  FreqOut SPEAKER,dash,tone
  LOW LED
  LOW KEY
  pause dot
  n=n-1
  goto dashes

dots:
  if n=0 then ret
  HIGH LED
  HIGH KEY
  FreqOut SPEAKER,dot,tone
  LOW LED
  LOW KEY
  pause dot
  n=n-1
  goto dots


' Keyboard mode
keyboard:
wpm=15
HSEROUT ["Entering keyboard mode",13,10]
inloop:
' Read a character
LOW CTS
HSERIN [ch]
HIGH CTS
gosub setwpm
' fold to upper case
if ch<"a" then uppercase
ch=ch & $DF  ' make upper case!
uppercase:
' check for local commands
if ch="+" then speedup
if ch="-" then speeddown
if ch="~" then
  if wpm=0 then 
     wpm=15 
     goto showspeed
  else
     wpm=0
     hserout ["Manual speed control",13,10]
  endif
  goto inloop
endif
if ch="!" then 
  high ptt
  goto inloop
endif
if ch="*" then
  low ptt
  goto inloop
endif
if ch=8 then 
  ' send error
  gosub proerr
  goto inloop
endif
' ok send character
gosub send_char
goto inloop


proerr:
  n=8
  goto lastdot

speedup:
  if wpm<35 then 
    wpm=wpm+1
  endif
showspeed:
  hserout ["WPM=",dec wpm,13,10]
  goto inloop

speeddown:
  if wpm>1 then 
    wpm=wpm-1
  endif
  goto showspeed

Site contents © 1997-2018 by AWC, Houston TX    (281) 334-4341