Shortly after getting an Arduino running, I wrote a Morse code library for its debug light. I thought this would save considerable time but it didn't for the very simple reason that I've been avoiding C and Arduino due to the bloat. After getting re-acquainted with 16 bit address-space systems via the AVR processor architecture, I found the 3KB Arduino base run-time overhead to be far too excessive. This is in addition to any system libraries, such as required for sprintf(), or my software which included 3.5KB for the Morse code debug library and 6.5KB for serial networking. I find this unworkable. Regardless, there is one part which can be salvaged.
The principle of a Morse code output library is good and can easily be implemented on 6502 using one or two pages of ROM. Indeed, it is of suitable scope for student exercise.
Trivial use of one debug light is grossly inefficient. Getting one bit of state from a development system, maybe, after remembering to deploy the correct version of test firmware is a very slow, tedious and error-prone method of diagnosing problems. It is possible but it will require many iterations of flashing firmware. At best, you will divide the problem by two. At worse, you will learn nothing or obtain false data.
There are more advanced techniques for extracting more bits of state from a system.
I've suggested using odd values of blink lights to extract a diagnostic code. If you counted an even value then you counted wrong. Unfortunately, this trivial back-check becomes increasingly strained when conveying more than three bits of data.
GARTHWILSON suggests using a piezo buzzer on pin PB7 of a 6522. This is a more advanced technique because it is possible to convey three or more bits of state via tone and a further two or more bits of state via duration. It also has the benefit that tone can be produced autonomously by a 6522 without halting the system. (Anyone who has used an ZX Spectrum will be *particularly* aware of the limitations of software controlled piezo tone generation.)
By using Morse code, it is possible to convey dozens or possibly hundreds of bits of state, with or without sprintf() or similar formatting. Furthermore, many of you are amateur radio enthusiasts or otherwise know Morse code and some of you, such as BigDumbDinosaur and drogon, already have an
LED wired directly to the EMU pin of a 65816. You may find yourself in the lucky situation where you already know Morse code and have already implemented the necessary hardware. I wouldn't recommend a Morse code LED as a replacement for a 16*2 character LCD. However, it is not a desperate substitute. Well, unless you have the usual problems with a 16*2 LCD module.
One of the limitations of my Morse code library was to store the data in a one bit per byte format. Specifically, literal strings, such as "..---". This is obviously inefficient. A six symbol, null terminated string requires seven bytes and an array of 256 strings requires 1.75KB. The case statement compiled by avr-gcc was also quite inefficient; in part due to multiplication by seven to access array elements.
Many of these deficiencies can be solved in 6502 assembly and especially so if the lookup table can be compacted. In particular, I believe the table can be reduced to a one byte encoding such that the top three bits define one of eight cases. Furthermore, the encoding is very amenable to branching and serialization on 6502.
Code:
LSB MSB
x P P x x 0 0 0 - Where PP is for different length pauses for space, comma, full stop or other punctuation.
S x x x x 1 0 0 - Where S is for Morse encodings with one symbol. ("E" or "T".)
S S x x x 0 1 0 - Where SS is for Morse encodings with two symbols.
S S S x x 1 1 0 - Where SSS is for Morse encodings with three symbols.
S S S S x 0 0 1 - Where SSSS is for Morse encodings with four symbols.
S S S S S 1 0 1 - Where SSSSS is for Morse encodings with five symbols.
S S S S S 0 1 1 - Where SSSSS is for Morse encodings with six symbols ending with dot.
S S S S S 1 1 1 - Where SSSSS is for Morse encodings with six symbols ending with dash.
On 6502 or 65816, it is possible to pass a string pointer to a Morse code output routine via two consecutive memory locations in zero page. Such a calling convention may also define a string length or require a null terminated string.
The Morse code conversion may be implemented with LDA (TABLE,X). A copy of the lookup table entry can be pushed on stack with PHA. After bit shifts and a bit mask, TAX // PLA // JMP (CASE,X) applies the top three bits of the lookup table entry to an 8 address (16 byte) jump table while keeping the original lookup table entry in RegA. Jump case zero performs another bit mask and a further JMP (PAUSE,X). The remaining six distinct cases may fall through while performing 1-6 iterations of dot or dash display using ROR // BCS. (Case six and case seven have the same implementation.)
A minor source of difficulty is the lack of portable timer or output port. This applies equally to 6502/65816 and AVR.
For timing, it is possible to reduce everything to multiples of one pause unit and then implement this pause with idle loops. For example, it is possible to define WAITDASH as JSR WAITDOT // JSR WAITDOT // JMP WAITDOT (or fall through to WAITDOT). It is then possible to implement WAITDOT using nested loops which use RegX and RegY. The intention of the idle loop is to wilfully consume a fixed number of processor cycles. You may want to find suitable loop boundary values empirically. You may also wish to preserve registers if you are indexing through a string.
On 65816, the output port may be the EMU pin. In this case, output of a dot would be SEC // XCE // JSR WAITDOT // CLC // XCE. This would raise and lower the EMU pin with the desired duration. This sequence or a similar sequence for dash can be selected using ROR // BCS. In other cases, it may be possible to flip one bit of an I/O port. Unfortunately, the address of the port and the bit to be flipped is arbitrary. Fortunately, this information can be supplied as parameters.
If you wish to halt a system and continuously display a message, it is possible to call the Morse code output routine within an endless loop. Parameters should be arranged such that the string is not consumed. Specifically, the pointer to the start of the message must be retained in some manner.
You may wish to implement a full 256 byte lookup table. This allows trivial encoding of Latin1 symbols (or Greek or Cyrillic). Alternatively, it is trivial to cap the table at 128 entries. With further manipulation, it is possible to fold upper case and lower case such that 96 or 102 bytes are required. However, this doesn't save much space. Likewise, I don't recommend elimination of the 32 ASCII control codes for the very simple reason that CHR(10) (line feed) and CHR(13) (carriage return) may be usefully mapped to pauses. Anyhow, depending upon preferences, a large implementation may not exceed 400 bytes and the smallest implementation will be less than 256 bytes.
If you want to get fancy, it is possible for a completely different implementation to work via periodic interrupt. In this case, a string would be loaded into a dedicated buffer and a small amount of state would be required to maintain progress through each dot, dash or gap. This would allow a message to be output indefinitely or for a finite number of viewings while other activity occurs. Unfortunately, a trivial LED connected to 65816 EMU pin and updated via interrupt is mutually exclusive with 65816 native mode. For this reason, I recommend connecting trivial LEDs to an
I/O port with read-back, such as the LE74HC574K245N and primarily using
digital LEDs with 65816 EMU pin.