A Monthly Column
Here's an expert's explanation of how to telecommunicate with the help of machine language.
So you want to communicate? If you want to reach another device or computer over a distance of 20 feet or so, you can string eight wires or more and spit out all eight bits of a byte at one time. But to go across town or around the world, you'll have to send one bit at a time, one behind the other. That's serial transmission.
We'll take a small part of the communications interface – the part that changes the computer's bytes into serial and back – and discuss the machine language approach.
What Is It?
Most small computers run their communications at a modest speed, say 300 bits per second. (Don't say "baud": it has a special meaning in telecommunications and will just get things muddled.) At this low speed, we use a type of transmission called "asynchronous" (a-SINK-ron-uss). In non-technical terms, this means send a character when you feel like it, and send nothing if you choose. This makes it easy for the sender: if there's nothing to send, don't worry about it. It's a little tougher for the receiver, which must keep an eye on the line and decide if there's a character coming in or not.
The usual code used for communications characters is ASCII. Some books tell you that it's a seven-bit code. Don't believe it. For communications purposes, we send eight bits of data. Wait, there's more. We have to send a little extra because of this asynchronous thing.
Before we send the eight bits, one behind the other, we must send a "start bit." This tells the other end, "Look out, there's a character coming!" After we have sent the start bit plus the eight data bits, we enforce a quiet time of one-bit length. This helps the receiving end catch up in case we're getting a bit ahead. This quiet time is called the "stop bit," and it's a minimum wait time only. If we don't have another character to send, the quiet time continues far beyond a single-bit time.
The receive end must spot the start bit, and then start clocking in the eight bits of data that follow it. It can ignore the stop bit pause at the end, but might choose to check that it's there so as to spot possible errors.
Setting The Quiet Line
The outgoing line is often one of the output ports. We'll need to separate it from the other ports. To set the line to the quiet state (called "marking"), we'll probably want to set the port to a binary one. Let's assume that we're using bit six of an I/O register at E84F. We'd code: LDA $E84F:ORA #$40:STA $E84F and the line would now be set to the idle, marking state.
Starting The Character
Suddenly, we have a character to send. We'll store it into location CHAR, and set a value of 8 into location COUNT to count the bits as they go out. Now our job is to send the start bit. That's a zero, or spacing, signal. But wait! We must decide about the timing.
Three hundred bits per second works out to a timing of 3,333 microseconds per bit. That's the value we must place in our timer if it counts in microseconds: 3333 or hexadecimal 0D05. The value might need to be slightly adjusted, but it's close. We might code a subroutine:
SPACE LAD $E84F AND #$BF (clear out bit) STA $E34F TIMER LDA #$05 STA $E848 (timer low) LDA #$0D STA $E849 (timer high) WAIT BIT $E849 (watch timer) BPL WAIT RTS
This routine will hang in a stall loop until the time is up. It seems inefficient – interrupts could do the job more efficiently – but maybe we have nothing better to do anyway until the time has gone by.
Sending The Bits
Our start bit has gone. Now for the eight bits of our byte. Let's count down with DEC COUNT, and, if all the bits have gone, we can exit with BEQ EXIT. Otherwise, we get the bit (low order first) with LSR CHAR. The bit we want pops into the Carry flag. If the bit is zero (carry clear), we want to call subroutine SPACE again. If the bit is one (carry set), we must call a new subroutine, MARK:
MARK LDA $E84F AND #$40 STA $E84F BNE TIMER
The last branch is unconditional; the AND guarantees that the Z flag is clear. This way, both MARK and SPACE will time out by one-bit time.
The calling sequence, then, looks like:
BITZ DEC COUNT BEQ EXIT LSR CHAR BCS DOMARK JSR SPACE JMP BITZ DOMARK JSR MARK JMP BITZ
What do we do when we have sent all eight bits and go to EXIT? We call JSR MARK one last time. That clocks out the enforced pause and sets the line back to idle for us. After this we can look for another character to send, or do other jobs if we want.
One odd protocol note: many programs choose to send the enforced pause – the stop bit – as the first part of a character. This is OK. So long as the pause is definitely sandwiched in between characters, it doesn't matter how you arrange it.
Receiving: An Outline
To receive, we have a slightly more complex task to do. When the line is idle, we must watch it constantly, since a character might begin at any time. Interrupts are sometimes used to good effect here.
When the start bit is spotted, we have a special job to do. At the moment that we detect the start, we're on the edge of the timing. If we delayed one-bit time, we might be at the beginning of the first data bit, or we might be a shade early and be at the end of the stop bit. This isn't satisfactory: we could read the wrong signal.
So instead of waiting one bit time, 3333 micro-seconds, we wait for one and one-half times: 5000 microseconds. That should place us right in the middle of the first bit timing, allowing us to take a solid reading. For the remaining bits, we'll return to a time delay of 3333, allowing us to check each bit smack in the middle of its time.
We'll pack the bits together by placing each bit into the Carry flag and then doing a ROR to the memory byte. And we'll remember to count, of course. When we receive the last bit, we will time out one more bit time; that should place us in the middle of the stop bit. We can now put our assembled character away and start watching the line for a new character.
It's educational and economical to do your own communications interface. We've looked at only one facet of the job: changing the computer's parallel data to a serial signal.
Of course, you could buy a UART (Universal Asynchronous Receive Transmit) chip to do the whole job for you, but that's the easy way out. Or is it? Last time I put a board together, my sports jacket became a smoking jacket.