Visions danced in my head! Visions of a completely controlled Amateur Radio station.
Imagine! A microprocessor-controlled system that would translate a Morse Code message and display it on the monitor, translate a message into Morse Code and transmit it at a pre-determined code speed, control a rotor to allow a beam to follow the Oscar satellites (satellites developed by an international Amateur Radio group for its exclusive use), and automatically log all the stations that I had communication with. The possibilities are endless.
I explained to my wife that a personal computer could do more than play games. (How else was I to persuade her that a computer was a necessary purchase?) I listed all the useful functions. At first she listened to my pipedreams in disbelief, but as my plans grew more detailed and I gave rational explanations of how my ideas could be accomplished, she became interested, then impressed.
My immediate interest in a computer was to develop a Morse Code transmit-receive converter. Then, when more memory became available, it would grow into the self-contained control system I had always envisioned.
My MicroAce 2K kit arrived, and it took less than a week to assemble. Finally I was ready to program a task. But wait, a good functional check of the system was in order. Why not program a few games? Bombardment is fun, and Depth Charge adds even more challenge. The new issue of SYNC contains an enjoyable behind the Castle Doors game, and I have got to try the “Draw a Picture” program. Two months later enough games had been played on the system to functionally check an IBM 370.
“Okay,” My wife said. “You were right. Personal computers are useful. I mean, if you’re ever stuck in a dungeon, at least you’d know which door to choose.”
I chuckled at this but realized she had a point. Computer games can be a trap. My initial project was being ignored. It was time I got busy.
I felt that the Morse Code transmitter portion of my project would be the easiest since I would need additional circuitry if I wanted to receive Morse Code and translate it. The computer, as it stands, is not equipped to convert the output from the receiver into a digital waveform. Due to obvious memory constraints, the program has to be done in machine code. My plan was to read the keyboard input, find the input by consulting a look-up table, then convert it to a Morse Code type digital output which would clock a relay (on the transmitter). A software keyboard buffer is needed to allow the operation to ‘type ahead’ of the transmitted output, and a driver subroutine is needed to clock the relay at the desired code speed. The typed input should also be displayed on the monitor. This can be accomplished within the MicroAce 2K memory if the code is written properly.
The first stumbling block came when I tried to read the keyboard input, using machine language code.
The Problem
To read the input in Basic, an input statement is used, but the INPUT command looks for either a number or a letter and cannot be used to accept both randomly. I could not use it for my application, and other programs may have a similar problem. To read a key on the keyboard using one Basic routine would use up too much memory, and I doubt that it is even possible. Therefore, it is machine code all the way!
Even a person with no interest in Morse Code could find the keyboard input routine interesting since it has many other applications. If nothing else, it provides a better understanding of the Sinclair/MicroAce hardware.
The Solution
I began by studying the schematic to understand how the keyboard is read by the software. As it turns out, both the Sinclair and MicroAce use the same technique. The keys are wired in a matrix configuration, and the rows of the matrix are connected to the Z-80 address lines A8-A15, while the columns are connected to the Z-80 through a latch that is energized by the KEYBD signal (active whenever an I/O instruction is executed). When a key is depressed, the address line for the row of the key and the data line for the column of the key are connected. If the address line is low at that instant, the data line will be pulled low. Therefore, the Z-80 will analyze the data lines after a known address is issued with an I/O instruction to determine if a specific key was depressed.
If all the address lines A8-A15 were low, the Z-80 could not tell which row caused the data lines to change. So to scan the keyboard, each I/O instruction must have only one address line low at a time to determine the exact key that was depressed.
The Machine Code Monitor
Understanding the technique, I proceeded to write the machine code to decode the keyboard. Although I tried to keep the code as efficient as possible, it is still almost 100 bytes of instructions. Typing in this many POKEs did not seem much of a challenge so I wrote the following program in Basic:
5 LET MARK=0
10 PRINT "ENTER STARTING
ADDRESS"
20 INPUT A
30 PRINT A; " (1 sp.) "; PEEK (A)
40 INPUT B
60 IF B [illegible] 255 THEN GO TO 130
70 POKE A,B
80 PRINT A; " (1 sp.) "; PEEK (A)
90 LET MARK=MARK +1
100 IF MARK [illegible] 10 THEN GO TO 110
105 CLS
107 LET MARK = 0
109 PRINT A; " (1 sp.) "; PEEK (A)
110 LET A = A+1
120 GO TO 30
130 STOP
[Figure: Figure 1.]
This program allows easy loading of sequential memory locations. When it begins, it asks for the starting address, entered in decimal. The program then displays the address and its current memory contents.
Enter the new contents in decimal, followed by a return. The program displays the new contents and then automatically increments to the next location, displaying the address and current contents. Continue entering your machine code program. When that is completed, enter a number larger than 225 to stop the monitor program.
I am sure you will find this method much easier to use than entering a POKE instruction for every machine code instruction to be loaded. Since so much machine code is written in hexidecimal notations in the ‘real world,’ a good modification to this monitor would be to allow the memory contents to be loaded by entering the number in hex notation. Since this would require entering the numbers (0-9) and letters (A-F), a keyboard input program such as the one to be described would be required. Now that we have an easy way to enter machine code on our Sinclair/MicroAce the rest is a piece of cake.
I have not yet devised a clean way of saving long machine code programs on a cassette tape, but I did find a technique that works. If you set up a few DIM statements in the beginning of the monitor program to dimension a few arrays with variables not used in the program, the system will ‘reserve’ memory locations for these arrays. The machine code can now be loaded, and, when the SAVE command is executed, the Basic program, including the arrays, will be saved. With some luck, the machine code program will reside in the ‘reserved’ space and be saved. When the program is downloaded from tape and re-run, be sure to use the GO TO and not the RUN instruction to begin the program, or the array space will be erased.
Keyboard Input Program
The program, as written, resides in memory locations 17401-17497. If this is not convenient on your system, the program can be re-located easily enough by changing a few of the instructions that reference memory.
When the program is run, the code of the depressed key is placed in memory location 17400. Therefore, a PRINT CHR$ (PEEK(17400)) command will display the key depressed on the keyboard. Other uses of this code can be devised.
As the program is being executed, it will loop between addresses 17405-17412 until a key is depressed. The INA,(C) instruction will place the contents of the B Register onto the address lines A8-A15 and the contents of the C Register onto address lines A0-A7. Therefore, by rotating a zero through the B Register and keeping the C Register set to zero, the keyboard can be scanned. When the data lines change, we know a key was depressed. Now we must decode the findings.
When a key is depressed, the accumulator and the B Register are analyzed to determine which key was depressed. Figure 2 shows the A and B Register contents for each key.
Accumulator Contents
30 29 27 23 15
Register B 254 | shift Z X C V
Contents 253 | A S D F G
251 | Q W E R T
247 | 1 2 3 4 5
239 | 0 9 8 7 6
223 | P O I U Y
191 | new L K J H
| line
127 | space • M N B
[Figure: Figure 2.]
The program now checks each bit of the accumulator to determine which one is low. Register C is incremented once for each bit tested. Then each bit of Register B is checked to determine the one that is low. Register C is incremented by five for each bit tested.
The contents of Register C are then used as an offset for the look-up table found at addresses 17458-17497. The look-up table value is placed in location 17400 and the subroutine returns to the program that called it.
It should be noted that this program reads the total keyboard but only ‘lower-case.’ Code could be added to look for the SHIFT key code. If detected, the program could then add an offset to Register C and jump back to look for another key to be depressed. The look-up table must then be expanded to include all the SHIFT characters.
Conclusion
Although the code may be difficult to follow at first, the program is really doing a lot in 97 bytes of memory.
By the way, this program is fast! If you use it as a subroutine in a Basic program, be sure enough Basic instructions precede the USR instructions, or the keyboard input program will decode the NEWLINE key that you depress after the RUN instruction—unless, of course, you release it in a matter of milliseconds. A good trick is to include a FOR loop of about 10 just before the USR call instruction to give you enough time to release the NEWLINE key.
Now that I can read the keyboard, it is just a small matter of time before hitting the ham bands. But first, maybe I had better functionally check the system by running a few quick games of Acey Ducey.
Keyboard Scanning Program
Address Decimal Octal Comments
17401 14 16 LD C,0
17402 0 0
17403 6 6 LDB, 254
17404 254 376
17405 203 313 RLC B <--+
17406 0 0 |
17407 237 355 INA, (c) |
17408 120 170 |
17409 254 376 CP A, 31 |
17410 31 |
17411 40 50 JR Z, ---+
17412 248 370 (-8) 2's compliment
17413 221 335 LD IX, Table addr -6(17452)
17414 33 41
17415 44 54
17416 68 104
17417 95 137 LD E,A
17418 0 0 NOP ] For future
17419 0 0 NOP ] reference
17420 14 16 LDC,1
17421 1 1
17422 22 26 LD D,0
17423 0 0
17424 62 76 LDA, 00111011 <-----+
17425 59 73 |
17426 198 306 ADD A,000010000 <--+ |
17427 8 10 | |
17428 50 62 LD(17437),A | |
17429 29 35 low | |
17430 68 104 high | |
17431 103 147 LD H,A H is holder | |
17432 122 172 LDA,D | |
17433 129 201 ADD A, C | |
17434 87 127 LD D, A | |
17435 124 174 LDA,H | |
17436 203 313 BIT E | |
17437 X X Don't care | |
17438 32 040 JR NZ, cont. ---+ |
17439 242 362 -14 |
17440 88 130 LD E,B |
17441 203 313 BIT C, 2 |
17442 81 121 Test for a five |
17443 14 016 LDC,5 |
17444 5 5 |
17445 40 050 JRZ, back ------+
17446 233 351 -23
17447 122 172 LDA,D
17448 50 062 LD(17453),A
17449 45 55 low
17450 68 104 high
17451 221 335 LD A,(IX+d)
17452 126 176 d
17453 x x don't care
17454 50 062 LD 17400, A
17455 248 370 low order
17457 201 311 high order
17456 67 103 high order
17457 201 311 RET
17458 1 table starts
17459 63
17460 61
17461 40
17462 59
17463 38
17464 56
17465 41
17466 43
17467 44
17468 54
17469 60
17470 42
17471 55
17472 57
17473 29
17474 30
17475 31
17476 32
17476 32
17477 33
17478 28
17479 37
17480 36
17481 35
17482 34
17483 53
17484 52
17485 46
17486 58
17487 62
17488 231
17489 49
17490 48
17491 47
17492 45
17493 0
17494 155
17495 50
17496 51
17497 39
End of Program.
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.