![]() |
When dealing with personal computers, it is necessary for a person to be familiar with his machine from both the hardware and software point of view. It has been my observation that hardware people often tend to stay in hardware and software people in software, each learning just enough about the other side to get along. I am guilty of this myself - I never do in hardware what could feasibly be done in software. Hopefully, this situation will change as my knowledge of microcomputing increases, but for the time being I am assuming that the computer runs because of the little elves pushing electrons around in the processor and putting most of my effort into programming.
This article is for people with similar bent. There are several electronic Morse code keyers on the market, but rather than spend precious cash, why not use that little digital demon of a small computer that you paid all of that money for. You've probably been trying to find a valid use for it anyway. Think of the glory as you transmit a message in Morse code several hundred characters long at a speed of 1000 WPM. (While there is a legal speed limit for radio Teletype, to my knowledge Morse Code bandwidth is not explicitly mentioned in FCC regulations.) Then comes a real challenge: Find someone whose computer can receive at that speed. Finally, the ultimate goal: Read the response sent back to you at the same speed. [See articles by Robert Grappel and Jack Hememway, by Bruce Filgate, and by Lawrence Krakauer in this issue.]
This article describes a software Morse code generator. The program listed is for a 6800 system (specifically the one sold by Southwest Technical Products); but a complete description of the generator is included to facilitate conversion to other systems.
Data Format
The data format used in my Morse code generator makes it possible to pack any of the "dot" and "dash" combinations associated with a Morse code character into a single byte. The rightmost 5 or 6 bits (throughout this article, "rightmost" and "least significant" are synonymous) contain the pattern of dots and dashes, and the upper 2 or 3 bits contain a count which informs the program how many of the data bits are actually associated with the character. The remaining bits are set to zero arbitrarily. Out of all of the characters in international Morse code, there are only two which will not work in this system: space and the error code. It is not surprising that the space is a maverick in any data representation, since it is not really data at all, but a lack of data. It is simply a lengthening of the normal interval between characters to form an interval between words, and must be handled as a special case. Similarly, the error code is not really a character either.
Table2: Special Codes and ASCII Graphics. | |||
To send: | Keyboard Entry | ||
---|---|---|---|
SK | End of work | '#' | ASCII hexadecimal 23 |
BT | Break | '&' | ASCII hexadecimal 26 |
AR | End of Message | '$' | ASCII hexadecimal 24 |
KN | Invitation to transmit specified station only |
'+' | ASCII hexadecimal 2B |
AS | Wait | '=' | ASCII hexadecimal 3D |
ERROR CODE (8 dots) | '!' | ASCII hexadecimal 21 | |
DEL | ASCII hexadecimal 7F |
When an error is made during a Morse transmission, due to a spasmodic twitch of the operator's hand or similar cause, the errant operator sends a string of 8 dots, meaning: "Oh I'm sorry I made and error please disregard the last character I send please will you huh." Both the space and the error code, since they don't fall in the normal scheme of things, are treated as special cases: Immediately upon detection, the program intercepts them, modifies them to a compatable format, and inserts them back into the main logic Flow.
For all characters five elements long or less, the format is absurdly simple, as shown in figure 1a. The high order 3 bits contain a binary number from 1 to 5 corresponding to the number of dots and/or dashes in the character. The right most 5 bits contain the data elements themselves, where 0 is a dot and 1 is a dash. They are stored from right to left so that when the byte is shifted to the right, they are transmitted serially in the proper sequence. As an example, the Morse code character Q is transmitted as "_ _ . _". The element count is four, so a binary 100 string is stored in the leftmost 3 bits of the byte. Using the coding scheme above, the equivalent Morse bit pattern for Q (the binary string 1011) is stored in the rightmost 4 bits. In this case, bit 4 is not needed so it is a "don't care" bit which is set to zero arbitrarily. Therfore, the code for Q stored in the table is the binary string 100001011 or hexadecimal 8B.
If all Morse code characters contained five elements or less, the coding system would be much simpler. However, many of the special characters, uncooperative cusses that they are, contain six elements. At first glance, this doesn't seem to be an obstacle, since the 3 bit count field can contain a binary number up to 7, inclusive. However, there are only 5 bits left over within the byte, which means that although the count can keep track of more than five elements, there is no place to store them. The one fact that keeps the entire system from collapsing into a pile of random logic is that there are no special codes with six elements, but I have been unable to come up with on containing seven. Since the difference between a count of six, binary string 110, and a count of seven (111) is one bit; and since there will never be a count of seven, then bit 5 becomes a "don't-care" and can be used as a data bit if bits 6 and 7 are both 1. We simply consider ny count greater than five (leftmost 2 bits on) to be equal to six, shift our wild card bit right with the others, and the problem is solved, as seen in figure 1b. When the count is extracted in this case, bit 5 is forced to a zero, making the count equal to six.
When Morse transmission actually starts, the data bits are shifted right one at a time. If the rightmost bit is a zero, the weight factor for a dot is loaded; if the rightmost bit is equal to a 1, the dash weight factor is loaded, normally exactly three times that of a dot. The key is turned on, and the timing subroutine, controlled by the value stored in ELESPEED, generates the proper delay for that element. The key is then turned off and the timing subroutine is reentered, this time to generate the space between elements, normally equal to a single dot time. This loop is repeated for every element in the character, shifting right each time. The space between elements is added to the last element in the character, also.
If a string of characters is being transmitted, rather than a single character, the space between them is generated according to the value stored at location CHRSPEED. If element and character speeds are synchronized, the delay caused by CHRSPEED is exactly twice the space between elements, which has already been generated after the last element. Combining the two causes the total space between characters to be exactly the length of a dash, the established spacing between Morse code characters (see figure 2).
If a space (in ASCII, hexidecimal 20) is detected, it is treated as a phantom character consisting of one dot length with the key off. When added to the 3 dot interval following the previous character, and the equal interval following itself, a total interval of 7 dot times is generated, the standard spacing between words. (ASCII codes with no Morse code equivalents are also treated as spaces.)
Speed Control
Table1: Speed Control Values. | ||
Morse Code Rate (WPM) |
Value Loaded into ELESPEED (hexadecimal) |
Value Loaded into CHARSPEED (hexadecimal) |
---|---|---|
5 | 00D6 | 01AC |
10 | 006B | 00D6 |
15 | 0047 | 008E |
20 | 0035 | 006A |
25 | 002b | 0056 |
30 | 0024 | 004B |
35 | 001F | 003E |
40 | 001B | 0036 |
50 | 0015 | 002A |
60 | 0012 | 0024 |
70 | 000F | 001E |
80 | 000D | 001A |
90 | 000C | 0018 |
100 | 000B | 0016 |
200 | 0005 | 000A |
500 | 0002 | 0004 |
1000 | 0001 | 0002 |
There are two separate controls for Morse code spacing: element speed and character speed. The element speed is the time duration of the smallest element of the Morse pattern. The time duration of dashes, dots, and the breaks in between are based upon it. If normal weighting is used, a dot is equal to one of these intervals, a dash is equal to three of them, and the space between dots and dashes within a character is equal to a single dot interval. The 2 byte field labelled ELESPEED contains a 16 bit binary number specifying the element delay as an integer number of milliseconds. With normal weighting, the duration of a dot will be exactly the number of milliseconds contained in ELESPEED, and the duration of a dash will be exactly three times that.
If you wish the element and character speeds synchronized (20 WPM character sent at 20 WPM intervals, etc), the binary value stored in CHRSPEED should be exactly double the binary value stored in ELESPEED. In this configuration the program will generate perfectly synchronized Morse code according to the extablished standard (dash duration three time of dot, durstion of space between elements equal to one dot, duration of space between characters equal to that of one dash, and duration of space between words seven times that of one dot). When you change speeds, change the values of ELESPEED and CHRSPEED (once again, the value of the latter should be twice the value of the former.)
If you wish to lengthen the interval between characters, without changing their internal speed, simply increase the value of CHRSPEED. For instance, if you with to practice copying code, you can set the ELESPEED field at the value for 20 WPM and the CHRSPEED field to the 5 WPM value. This will cause 20 WPM characters to be sent at 5 WPM intervals. You can tweak either speed to any 16 bit value you want (except zero), but the value of CHRSPEED must never be equal to less than two times that of ELESPEED or the spacing will be demolished!
And It Comes Out Here
The Morse code generator program is designed to output Morse signals through a parallel IO port. Two different types of output are available simultaneously: logical and oscillating. The logical output corresponds to the Morse signal as it is broadcast - the output is high during a dot or dash andlow in the times between. This corresponds to the telegraph key itself and may be fed to the transmitter directly or via a relay or other driver. The oscillating output changes state every millisecond while the logical output is high andis held low during the times the logical output is low. This output, when connected to a speaker, produces a 500 Hz tone and can be used as a sidetone or a code practice oscillator. Both types of output can be produced simultaneously, as seen in figure 3.
Which of the lines in your parallel port are to be used at logical outputs and which are oscillators is determined by the control byte labeled INITMASK. For every bit in INITMASK which is equal to zero, the corresponding bit in the parallel port is a logical output. Every bit in INITMASK which is in an on state causes the corresponding line in the output port to oscillate.
I am not a hardware type person; therefore, I am not going to attempt describing the interface necessary to take the Morse code output of the nonoscillating line and transfer it to your ham rig, especially since every transmitter has its own keying system. In a classic copout I say, "It is up to the user to take the logical output of the PIA (0 = key off, +5 V = key on) and get it onto the air without blasting the PIA output driver".
Viva Southwest Tech
This Morse data generator was written for a Southwest Technical Products 6800 system, which uses the Motorola MC6830L7 ROM (MIKBUG Revision 9). A complete cross assembly of CWBUFFER is printed in listing 1. The program was written to be configuration independent, however, and will work for any 6800 system having programmable memory at locations hexadecimal 00 to 49, at least 190 bytes of programmable memory elsewhere, and one PIA. The ASCII to Morse conversion subroutine is completely relocatable and reentrant, althouth the main generator routines (CWBUFFER and TRANSMIT) are not.
All of the timing loops are calibrated for the Southwest Technical Produdcts System, which has a 1.797 MHz master oscillator crystal. If your clock runs at a different speed, you may want to tweak the loop constant so that the output of one of the oscillating lines is exactly 500 Hz (each outer loop of the MILDELAY subroutine is supposed to last exactly one millisecond). The loop constant is at hexadecimal location 160; incrementing or decrementing it by one will increase or decrease the interval which should correspond to a millisecond by six machine cycles. If you aren't concerned with perfect timing, you can compensate by loading different values into ELESPEED and CHRSPEED.
The parallel port address used in the program corresponds to the serial control interface used by MIKBUG,which is really a parallel interface that only simulates a serial interface via software. Connect a small 8 ohm speaker between connections RO and GND on the serial control interface. (Your computer will probably be unable to talk to the TVT or Teletype while the speaker is attached, due to loading problems.) In the program turn on the rightmost bit in INITMASK to produce the oscillating output.
There is only one output line normally available from the PIA on the Southwest Technical Products serial control interface, although it separates into two output systems, RS-232 and 20 mA current loop. Another line on the PIA's A side is used for MIKBUG input, which leaves six lines conpletely unused. These lines can be used for Morse output if you bring them out via jumpers from the backside of the PIA. Of course, MIKBUG has designated them as inputs in the A side data direction register; but if you OR a hexadecimal 7E into the DDR, you will reset them as outputs and leave the normal MIKBUG lines alone. You can use any combination of those lines for Morse output.
Sundry Drivers
The Morse Code Generator Program (< Link to original artile PDF) is designed to be used as a subroutine. It simply takes a character or string of characters in memory and outputs the Morse code equivalents. It is completely up to the user as to how this data is input, whether through the keyboard, read from tape, generated by a random number genertor, or conjured up by evil spirits. All that is required is that the Morse code routine's controlls (ELESPEED, CHRSPEED, and INITMASK) be set before the subroutine is entered, and, if the CWBUFFER entry point is used, that the index register contains the address of the first byte of the string and stop byte, hexadecimal 03, follows immediately after the last byte of the string.
I have included three simple drivers which could be used for the 6800: one that generates one Morse character at a time from the keyboard; a second that buffers characters until a delimiter character (line feed) is encountered, after which it sends out the characters it has stored in one brilliant blast of precision keying; and a third driver which fills up a buffer with random characters, then sends them out (for code practice). These drivers are shown as in the original article link.
The single character driver (SINGLECH) lies dormant until a character is entered from the keyboard. When the character is received, it is passed immediately to TRANSMIT and the Morse code is sent out the PIA. The driver then goes back to sleep until a new character comes in.
The buffered driver loads incomming characters into a buffer in memory until either a line feed or ETX (Control C, hexadecimal 03) character is encountered. When either is received the starting address is loaded into the index register, a delimiter (ETX) is stored in the buffer after the last data byte; and the buffer is passed to CWBUFFER, which outputs the CW codes in a continuous stream until the end of the buffer is reached. Control that returns to the driver, which starts loading the buffer all over again. When a backspace command (Control H, hexadecimal 08) is received, the character immediately preceeding the command is deleted from the buffer.
CODEPRAC, the code practice program, fills a buffer in memory with random characters, then passes them to CWBUFFER, which sends them out the PIA into your speaker (hopefully you have set INITMASK so that the line is an oscillating output). The speed and spacing are controlled by whatever you have loaded into ELESPEED and CHRSPEED.
The code practice application is where the dual speed controls really come into play. When a person is first learning code, he obviously has to start at an extremely low character rate. At this speed, the dots and dashes are extremely dragged out and sound completely different than they do at higher speeds. The Morse code neophyte should really learn to recognize the characters by listening to the total pattern, not by counting dots and dashes. However, speeding up the characters also speeds up the character rate if normal element to space ratios are maintained and an entire message has gone by while the beginner is still trying to recognize the first character. Therefore, the ideal situation is to retain the sound of the high speed characters and yet increase the interval between them. This is accomplished by leaving the element speed (ELESPEED) the same and increasing the duration of time between characters (CHRSPEED) to whatever length is desired.
Anyway, to get back to the code practice driver, it will continue to send characters at the CHRSPEED rate until the end of the buffer is reached, at which the end of the buffer is reached, at which time it will generate another set of practice characters. Unfortunately, the random number generation routine itself is missing. The code practice driver was a last minute addition, and there was not time to develope one. (The program was tested using a kluge substitution.) However, there are different versions floating around. The one you use should generate a one byte random number and return it in accumulator A. It should not destroy accumulator B or the index register. Load the address of the routine in the dummy jump to subroutine at hexadecimal location 01D5. The address at location 01E7 is the end of buffer address or your maximum memory location. Don't forget to leave a place for the random number generator subroutine.
The drivers in this article are very primitive, and are designed simply to get you running. If your system is a Southwest Tech one, you should be able to load all of the programs and the table into your system exactly as coded (CODEPRAC too, with the addition of a random number routine), connect a speaker between RO and ground on the control interface, load ELESPEED and CHRSPEED according to table 1, turn on the right most bit of INITMASK, branch to the starting address of the driver of your choice, and start typing. As you hear the speaker sing a 500 Hz area with perfect 1 to 3 to 7 spacing, you may reflect that maybe programmers aren't such bad guys after all. Seriously, the sidetone output is a perfect way to ensure that the program is working with each of the various drivers before you tie it into your rig.
Other 6800 Systems
For non Southwest Tech 6800 systems, the installation of the Morse generator program is not much more difficult. The address of the PIA used for the Morse code output and that of the input routine are the primary concerns. If your system uses MIKBUG, which has the single character input routine at hexadecimal location E1AC and the serial control interface at 8004, so much the better - you shouldn't have to modify a single byte. If your configuration is different, substitute your own PIA address at hexadecimal locations 015D, 0195, 01C0, and 01CE, and your ASCII input routine address at location 01C3 and 019B.
The programs in this article were not assembled on Motorola's assembler. They were run on SPUCLA (Sewell's Psychedelic Universal Cross Assembler), a homemade cross assembler which runs on the IBM 379 and generates code for the 6800 and four other microprocessors. Listing 1 and 2, and the tables 3 and 4 were generated by SPUCA.
The formats are almost identical. but there are some minor differences that should be pointed out for the sake of clarity. The major difference is in instructions using the indexed mode of addressing. Motorola places the symbol for the index register (X) after the operand with a comma in between, where SPUCA looks for an I before the operand, again with a comma in between. In other words, a Motorola indexed instruction looks like this: LDA A OPERAND,X and one read in by SPUCA is in this format: LDAA I,OPERAND.
Most of the rest of the instructions are identical to Motorola's except that my assembler has no FCC (Form Constant Characters) directive and no separate column for the accumulator ID.
![]() |
Differences and Adjustments - K7MEM
Differences - You might notice that there are some differences between the original listing (above) and the listing from my assembler (below).
![]() |