This note describes how to use the serial interface on the HC11. This is probably the single most complex device on the chip, and requires quite a bit more background to understand than the other devices we've programed.
I'll be breaking this up into three sections: a brief introduction to the concepts and history of the serial port, the theory of its operation, and the practical application of this theory to programming the serial port on the HC11.
How do we communicate data between computers, or between peripherals and the CPU? We've already talked about one way to communicate data: using a bus. The thing is, a bus is expensive (it needs a wire for every bit, plus wires for control information). It turns out that we can trade some money for speed, and cram all the bits down a single wire. This is the idea behind serial communication.
When we talk about "the" serial interface, we're really talking about a particular standard, called RS-232 (Recommended Standard 232). It's actually gotten several different names in the decades it's been used, and it's "official" name now is TIA-232-F, but everybody still calls it RS-232. This standard has been in use basically since the dawn of computers, and is only now finally being replaced.
It's easy to be pretty snide when talking about the standard, and to look down our noses at it from many different perspectives. The thing to remember, though, is that it's being used for applications it was never intended for: its original purpose was to communicate between devices like teletypes (Data Terminal Equipment) and phone-line modems (Data Communication Equipment) with completely-hardware implementations. To see it used between devices like two computers (a PC and an HC11, for instance), is to step far outside its intended purpose. That it can be shoehorned into this role is a tribute to its flexibility and robustness. All the same, there are much better choices now, for which we can all be grateful.
In the PC world, the serial interface is nearly obsolete. It's regarded as a "legacy" port, and many new motherboards don't have them any more. This is actually a very good thing; programming a serial port has always been something approaching a black art requiring a lot of fiddling by the user. It has been almost completely replaced by USB, which is capable of higher transmission speeds, is more reliable, and allows devices to identify themselves to the computer system. On the other hand, it's much more difficult to implement, and effectively requires a computer to implement the protocol (where serial only takes a little bit of hardware).
In the embedded world, serial is actually still very common. A number of vendors sell USB-serial adapter chips (like the FTDI FT232BM) which have a pre-programmed CPU on board that is used to perform a translation between protocols. Quite a few products that are (or were) available in both serial and USB form only have serial internally; the USB version just connects this serial port to a USB adapter chip. Without having opened one up to check, I'm told that this is true of the SmartHome 2412S and 2412U powerline modems are like this.
This section will discuss the basic notions of conversion between serial and parallel, and synchronizing between a serial transmitter and serial receiver. We won't be talking about a lot of addition information that's part of the standard: voltage levels, flow control, signal lines specific to communication with modems, and a lot of other capabilities of the standard that I've never actually seen implemented.
We're going to be discussing this in terms of logic levels (1 and 0), not actual voltages. The RS-232 standard predates TTL logic and its standardization on 0 and 5V, so its voltages are (from our modern perspective) really screwy.
Let's suppose we have some data we want to pass down the line. To do this, we need a "shift register" at each end. A shift register is a register that we can shift data through, one bit at a time, bringing data in one end and putting it out the other. Normally, you can also read or write all of the bits of a shift register at the same time (parallel input and output). So the idea is that we can put a whole byte into the shift register all at once, and then ship it down the wire one bit at a time.
To use shift registers in serial communication, we need two of them. At the transmitter end, we need to be able to load up all the data bits in parallel, and then shift them out one bit at a time. At the receiver end, we need to be able to shift the data in one bit at a time, and then read the whole eight bits at once.
Here's a figure showing the basic idea.
The figure shows a PC and an HC11, connected with their serial ports. Each has a transmitter (a parallel-in, serial-out shift register) and a receiver (a serial-in, parallel-out shift register). There is also a wire connecting the grounds on the two ends; this is called the "signal ground". The whole interface is a called a "three-wire" interface, since only those three wires are connected — there's no hardware flow control.
Now, we start to get into the details. First, how long (in time) is a bit? If the transmitter and the receiver don't agree, the receiver will get the wrong data. There are a bunch of standard bit-per-second speeds that are used; these are all multiples of 300 bps for historical reasons. Also for historical reasons, we frequently say baud (named after Baudot) when we mean bps. It turns out that these don't mean quite the same thing -- baud means the actual number of voltage transitions possible; modems use some pretty fancy signal processing techniques to encode more bits/second than the actual baud rate. Next question: how many bits are in a character? The original baudot code was a five bit code with explicit escapes to go back and forth between letters and numbers/punctuation; for a very long time we normally saw a seven bit character; today we normally see an eight bit character.
The next problem is making sure the data is sent correctly. There are two subproblems here: getting it to work at all, and getting it to work in the presence of noise.
The transmitter is not going to send data continuously. You've seen
this in doing downloads, and in running hexmon: there's normally more
time spent between sending characters, than actually sending them. When
you're not sending data, the line is held at a steady value of "1."
So, how do you tell when a data character is coming? Suppose we see a
binary sequence 11111111000011111. Here's a figure
showing this. Notice that when think in terms of bits on the wire,
there's nothing to show us where one bit ends the next begins!
If we're using a
seven-bit ASCII character, this could be 1110000
('p'), 1100001 ('a'),
1000011 ('C'), or 0000111
(BEL). It could even be two characters, with some of the
0's in each one! These possibilities are 1111110 0001111
('~/', 1111100 0011111 ('|US')
or 1111000 0111111 ('x?'). How can we tell
the difference?
What we'll do is put a 0 bit in front of every character,
to tell it that a character is coming (the 0 isn't be
part of the data, it's in front of the data). We call this a "start
bit;" it marks the start of a data character.
Here's the previous example, modified to include a start bit:
Now, suppose we get some noise on the line. We can end up thinking a data bit is the start bit, and read a character of nonsense. What's more, in this case if we get several characters in a row they can all be interpreted incorrectly. We'll never get back "on track" until we get a full character's time with no data. There are a couple of things we can do about this. The first one is to use a stop bit: we follow each data character with some fixed number of 1's. This means the receiver does the following to try to read a character:
Notice that this will miss a lot of errors, especially if there's a lot of time between characters. If the noise ended halfway through a character, we'll see everything after the next 0 in the character as data, and then see the 1's that go between characters as more data. We can do a little better than this by adding another bit, called a parity bit. On the transmitter end, we'll take a look at a character to be sent and ask, "are there an even or an odd number of 1's in this character?" Then, we'll inject an extra bit after the character (but before the stop bit) to force the number of 1's to be either even or odd (our choice). So if we're going to send out "odd parity," we make sure that the character+parity bit always contains an odd number of 1's. If we're going to send out even parity, we'll make sure it's even. There are five parity functions:
Programming RS-232 ports would be hard enough if you actually had to get the configuration right for it to work. Amazingly, there are a bunch of ways you can come up with configurations that don't agree, but which work - or even worse, that almost work.
For instance, you might have one end set to 8 bits of data, 1 parity, and 1 stop bit, while the other end is set to 8 bits of data, no parity, and two stop bits. It'll work!
Or, you could have one end set to 7 bits of data, 0 parity, and 1 stop bit, with the other end set to 8 bits of data and 1 stop bit. This will work as long as you only send ASCII characters; as soon as the end supporting 8 bits of data sends a character with the most significant bit set the other end will report a parity error. This will manifest itself as something like successfully running a text editor over the wire (for hours at a time!), but failing very quickly when you try to download a file.
At one time, the capabilities described for serial ports were all needed to make sure data was not lost. People tended to work at dumb computer terminals, which communicated over relatively long distances to central computers. Where I went to school, they even economized by using only two wires to connect the terminals to the department's computer: one wire for data in each direction. They relied on the building ground to provide a signal ground! This worked surprisingly well for a long time; we just saw noise on the line whenever the air conditioner started up or something. As time went on we kept adding more terminals and computers; then one day the ground plane got noisy enough that the whole scheme basically quit working completely, which required an emergency rewiring of the whole building (the moral of that story, of course, it "do it right or do it over" — something it seems like I need to relearn every six months or so!).
This idea of putting extra "stuff" around the data you're actually interested in is called putting the data in a frame, and is a very standard technique in networking.
For most networking, the frame is quite a bit more complicated than this, because additional routing information is also required. A data packet is typically sent out with information that encodes things like what computer the packet started at, what computer it is supposed to go to, what process on the destination computer it is intended for, and so forth. We already know all this stuff in our application, so we can just send a bunch of bytes.
An example of a more complicated data frame is how data is sent on an ethernet. An Ethernet packet contains:
A very nice feature of the SCI is that it provides some limited buffering, which makes it easier to keep the transmitter line full at all times, and gives some latitude in receiving. Here's a conceptual picture of the SCI port:
The "Data In" and "Data Out" lines are the actual serial IO wires going in to and out of the chip.
On the input side, bits are read into a "shift register," one bit at a time. When all the bits for a character have come in, it's moved into the input buffer and is ready to be read.
On the output side, the programmer writes a character to the output buffer. The character is transferred to another shift register, and sent out one bit at a time.
An odd thing about this diagram is the way
I've represented the SCDR (serial communication data register). The
idea here is that it's really two separate registers; when you read
it (by reading address $102f), you read the input
buffer. When you write to it (by writing to the same address, you
write to the output buffer. This seems really weird, but isn't at all
uncommon. The idea is, there's really never a reason to either read
the output buffer or write the input buffer, so they might as well use
the same address for both and save an address. As weird as this use
of one address for two purposes seems when you read about it, it ends
up feeling perfectly reasonable when you actually do it.
Configuring the port requires setting the speed, defining the character format, and enabling interrupts on desired conditions. Looking at these in turn:
$102b) determines the speed the SCI
is running. The SCP1-0 and SCR2-0 bits select the rate (in
conjunction with the system clock speed). The other bits in the baud
register are not used. The reference guide gives tables showing the
various baud rates possible depending on the setting of these
BAUD register bits; all we really care about in this class is that
setting SCP to 11 and setting SCR2-SCR0 to 000 will give us 9600 bps.
$102c) is the M bit (bit 4),
which selects 8 or 9 bit mode (M=0 for 8 bits). If we were to use the
SCI in 9-bit mode, we'd use the most significant two bits to read and
write the ninth bit. The WAKE bit (bit 3) is used in conjunction with
a standby mode the SCI can be put in (the Reference Guide showing bit
3 as unimplemented is an error).
SCCR2 ($102d) is used to enable and disable virtually the
whole subsystem... transmitter, receiver, interrupts, etc. etc. The
bits are:
We don't really care about bits 1 and 0. Bit 1 is a "receiver wakeup bit"; it's possible to set the receiver in a sleeping state and have it wake up automatically. Bit 0 sends a "break" character; that's implementing by sending enough 0's in a row on the line to guarantee that the other side will get a framing error (what that means will be described in a minute).
$102e) is used to report the
current state of the serial interface. This includes both data
presence/absence and error conditions. This register contains:
One important thing to mention is that to clear these flags, you need to read the SCSR, and then either read or write the SCDR as appropriate (read it to clear the receiver-related flags, write it to clear the transmitter-related flags).
The SCI Data Register ($102f) is used to send/receive
data. It is actually two registers, which share a single address.
When you write to the register, you write to the transmiter UART.
When you read from it, you read from the receiver UART.