Embedded Systems/IO Programming
An embedded system is useless if it cannot communicate with the outside world. To this effect, embedded systems need to employ I/O mechanisms to both receive outside data, and transmit commands back to the outside world. Few Computer Science courses will even mention I/O programming, although it is a central feature of embedded systems programming. This chapter then will serve as a crash course on I/O programming, both for those with a background in C, and also for those without it.
Programming the IO Bus
editWhen programming IO bus controls, there are 5 major variations on how to handle it- the main thread poll, the multithread poll, the interrupt method, the interrupt+thread method, and using a DMA controller.
Main thread poll
editIn this method, whenever you have output ready to be sent, you check if the bus is free and send it. Depending on how the bus works, sending it can take a large amount of time, during which you may not be able to do anything else. Input works similarly- every so often you check the bus to see if input exists.
Pros:
- Simple to understand
Cons:
- Very inefficient, especially if you need to push the data manually over the bus (instead of via DMA)
- If you need to push data manually, you are not doing anything else, which may lead to problem with real time hardware
- Depending on polling frequency and input frequency, you could lose data by not handling it fast enough
In general, this system should only be used if IO only occurs at infrequent intervals, or if you can put it off when there are more important things to do. If your system supports multithreading or interrupts, you should use other techniques instead.
Multithread polling
editIn this method, we spawn off a special thread to poll. If there is no IO when it polls, it puts itself back to sleep for a predefined amount of time. If there is IO, it deals with it on the IO thread, allowing the main thread to do whatever is needed.
Pros:
- Does not put off the main thread
- Allows you to define the importance of IO by changing the priority of the thread
Cons:
- Still somewhat inefficient
- If IO occurs frequently, your polling interval may be too small for you to sleep sufficiently, starving other threads
- If your thread is too low in priority or there are too many threads for the OS to wake the thread in a timely fashion, data can be lost.
- Requires an OS capable of threading
This technique is good if your system supports threading, but does not support interrupts or has run out of interrupts. It does not work well when frequent IO is expected- the OS may not properly sleep the thread if the interval is too small, and you will be adding the overhead of 2 context switches per poll.
Interrupt architecture
edit(The interrupt architecture uses interrupts, which we discuss in more detail in chapter Embedded Systems/Interrupts).
In this method, the bus fires off an interrupt to the processor whenever IO is ready. The processor then jumps to a special function, dropping whatever else it was doing. The special function (called an interrupt handler, or interrupt service routine) takes care of all IO, then goes back to whatever it was doing.
Pros:
- Very efficient
- Very simple, requires only 1 function
Cons:
- If dealing with IO takes a long time, you can starve other things. This is especially dangerous if your handler masks interrupts, which can cause you to miss hardware interrupts from real time hardware.
- If your handler takes so long that more input is ready before you handle existing input, data can be lost.
This technique is great as long as dealing with the IO is a short process, such as when you just need to set up DMA. If its a long process, use multithreaded polling or interrupts with threads.
Interrupts and threads
edit- We discuss this technique in more detail in Embedded Systems/Interrupts
In this technique, you use an interrupt to detect when IO is ready. Instead of dealing with the IO directly, the interrupt signals a thread that IO is ready and lets that thread deal with it. Signalling the thread is usually done via semaphore- the semaphore is initialized to the taken state. The IO thread tries to take the semaphore, which fails and the OS puts it to sleep. When IO is ready, the interrupt is fired and releases the semaphore. The thread then wakes up, and handles the IO before trying to take the semaphore and being put back to sleep.
The routine the interrupt vector points at is the "first level interrupt handler". The thread that the OS later wakes up to handle the rest of the work is the "second level interrupt handler".
Pros:
- minimum latency—instead of all other interrupts being disabled until that interrupt is completely handled, interrupts are turned back on (at the end of the first level interrupt handler) as soon as possible.
- Does not put off the main thread
- Allows you to define the importance of IO by changing the priority of the thread
- Very efficient- only makes context changes when needed and does not poll.
- Very clean solution architecturally, allows you to be very flexible in how you handle IO.
- The second level interrupt handler can wait for a lock to be released (Embedded Systems/Locks and Critical Sections).
Cons:
- Requires an OS capable of threading
- Most complex solution
This solution is the most flexible, and one of the most efficient. It also minimizes the risk of starving more important tasks. Its probably the most common method used today.
DMA (Direct Memory Access) Controller
editIn some specialised situations, such as where a set of data must be transferred to a communications IO device, a DMA controller may be present that can automatically detect when the IO device is ready for more data, and transfer that data. This technique may be used in conjunction with many of the other techniques, for instance an interrupt may be used when the data transfer is complete.
Pros:
- This provides the best performance, since the I/O can happen in parallel with other code execution
Cons:
- Only applicable to a limited range of problems
- Not all systems have DMA controllers. This is especially true of the more basic 8-bit microcontrollers.
- Parallel nature may complicate a system
Dos.h
editThe Dos.h header file commonly included in many C distributions, especially on DOS and Windows systems. This file contains information on a number of different routines, but most importantly it contains prototypes for the inp( ) and outp( ) functions that can be used to provide port output directly from a C program. Many embedded systems however, will not have a Dos.h header file in their library, nor will they have any precompiled C routines to handle port input and output. The purpose of this chapter then, is to teach the reader how to "brew their own" input and output routines.
The <iohw.h> interface
editSome C compiler distributions include the <iohw.h> interface. It allows relatively portable hardware device driver code to be written. It is used to implement the standard C++ <hardware> interface. [1]
x86 Output Routines
editThe x86 instruction set contains 2 instructions: in and out both functions take 2 arguments, a port number, and then another parameter to receive the data from or to send the data to (depending on which command you use).
we can define 2 functions in assembly, using the CDECL calling convention, that we can link with our C programs, and call from our C programms to handle port output and input.
Synchronous and Asynchronous
editData can be transmitted either synchronously or asynchronously. synchronous transmissions are transmissions that are sent with a clock signal. This way the receiver knows exactly where each bit begins and ends. This way, there is less susceptibility to noise and jitter. Also, synchronous transmissions frequently require extensive hand-shakeing between the transmitter and receiver, to ensure that all timing mechanisms are synchronized together. Conversely, asynchronous transmissions are sent without a clock signal, and often without much hand-shaking.
Further reading
edit- ↑ "Technical Report on C++ Performance" by Dave Abrahams et. al. 2003