Implementation of a UART
We've now looked at what it is that the UART actually does, but before we consider the detail of what we're going to implement, we really have to think about the sort of technology we're going to implement it in.
I've already talked about the relative merits of C and VHDL, or concurrent and non-concurrent languages. If we take the simplistic view that we'll choose one of these languages, in this case the languages are specific to solving a problem in a particular way i.e. software or hardware. In this case the choice of language, is not about a direct comparison between the merits of the languages, but more about the actual approach to solving the problem. We've also seen that even this distinction can be challenged. We could produce hardware from C or software from VHDL. From my own perspective, it seems the best thing is to simply, go with the flow.
It has already been stated that the UART is a fairly old technology, and we will also see on the next page that it is not actually a very demanding technology. In the light of this information one might be caused to ask, "Why don't we absorb this function into software". Many embedded systems will implicitly require a microprocessor, and the performance of such technology has improved considerably since the humble UART was invented. It is a reasonable motive to try and leverage an expensive microprocessor and get the best value for money. In a cost driven embedded system, this kind of motive is very strong.
The critical problem with a software implementation for something like a UART is that one will probably expect to use the implicit processor capability for something other than just the serial port function. In fact the "other" function is probably going to have greater importance in the context of the design. Notwithstanding, it is quite possible to do this. Modern computers can do multi-tasking, right?
Let's look at this in some detail. If we were to implement a UART in software, the receiver would be the most complex component. The basic principle of operation for the software receiver would be to monitor a single pin of the microprocessor for incoming data. We could, then, implement a software scheme to sample the pin in one of four ways;
- Polling - Where the software periodically checks the voltage level on the serial port pin
- Polling in a single application wide loop
- Polling by a dedicated thread
- Interrupts - Where an event signals the software and causes execution of a dedicated port monitor function
- Interrupts from a timer
- Interrupts direct from the port pin
By one of these methods the port pin must be monitored on the receiver such that we can capture and interpret the individual signalled bits. These bits are to be present on the line for only a short time, and if possible each must be captured in the middle of the bit period. We've already seen how the length of the RS232 line will affect the signal quality, and that the signal quality will vary throughout the lifetime of a single bit, leading to a window of opportunity for sampling. Here, I'm only going to consider the problem of trying to get a single sample during the period of the bit, but bear in mind, what we really need to do is sample at the "sweet spot" exactly in the middle.
At a Baud rate of 19K2, the actual bit period is only 52uS. That's only a very short time, even if the actual processor can handle as many as 100K instructions in that time. Looking at our four methods we have to consider how we might cause the software to actually sample the pin, with this sort of regularity and precision, whilst performing some "other" function in the background.
The use of polling in a single application loop, is almost completely hopeless. For example, there are many other things that the software must do. Whilst it is possible to operate in this way, the difficulty is ensuring that no single top level thing that the software does, will take any longer than some small part of the bit period. As the software complexity grows, it is less easy to do this. In the extreme, it is not desirable to limit anybody writing software to such a constraint, just for a little serial port. This is especially true when you think that these "other" functions may actually have conflicting requirements among themselves.
Polling in a dedicated thread is a better approach since the computer can task switch. In this case it doesn't matter how long any given software operation takes. The task switching hardware of the microprocessor can simply stop what is currently happening at arbitrary intervals, and switch to the thread which only checks the port pin. Although this sounds good, there are still problems. The actual task switch takes some time in the processor. It has to save what it was doing, and load the port pin checking routine. A typical context switch for a modern PC might be in the order 16uS. By comparison with the 52uS data period, this is doable but attempting to hit the sweet spot, means most of the processor time is spent merely task switching.
Extending the previous idea, activating a port monitoring function from a hardware timer seems O.K. Interrupts are similar to task switches, except that they are driven from an explicit event. In this case the event is a hardware timer in the microprocessor. The main difference is that one can achieve far finer control over the actual timing of the task switch. Rather than needlessly task switching to find there is no work to do, one can organise the timing such that switches (or interrupts now) only occur when they are known to be necessary. The problem here is that the timer cannot necessarily be connected to the start bit of the port data, in such a way as to synchronise the timings with the current byte. Because the interface is asynchronous, there is no guarantee about the relationship between different bytes of data on the port pin. The timings might be good for one byte, but not for another.
The final suggestion follows from the previous interrupt idea. Perhaps it is possible to generate an interrupt directly from the start bit, which then synchronises the timer which causes individual bits to be sampled. This is the closest that software can be to implementing a practicable UART. This scheme in fair condition could be made to work. The problem with it, is that it detects the edge of the start bit, and in difficult situations, and with long lines it will not work well. We saw previously how the edges of the bits must be avoided for accurate sampling, particularly when long communication links are used. Typically for a software solution this will result in false triggering, and wasted time in the microprocessor.
Overall, it becomes apparent that software is just not as good at some tasks as others, even when they are old fashioned. In particular, a hardware solution can devote 100% attention to the port pin, and not have any impact on the performance of any other capability. Even though the UART is quite a simple thing, sometimes hardware really is the only solution.