Some Programming Basics

Embedded Hello World

GPIO Outputs

Output Examples

GPIO Inputs

Input Examples


Interrupt Examples


Timer/Counter Examples

LCD Character Displays

Display Multiplexing

Key/Switch Multiplexing







Real Time Operating Systems

Coming Soon








AVR Freaks


GPIO (General Purpose Input/Output) Inputs

A GPIO pin configured as an input is used to read (to input) the value of one digital signal.  In this case the pin is converting the voltage being delivered to the pin into a logical value of 0 or 1 for subsequent use in the program.  By convention, when the voltage on the pin is “high” (near Vcc), reading the pin will result in reading a logic 1, while when the voltage is “low” (near GND), reading the pin will result in reading a logic 0.  Let’s talk a little bit more about what is an acceptable “high” voltage and an acceptable “low” voltage.  Simply put, if your high voltage is too high (too far above Vcc), or your low voltage is too low (too far negative with respect to GND), you will probably damage your chip.  On the other hand, if your high voltage isn’t high enough or your low voltage isn’t low enough, the chip will not reliably read the input value correctly.  To illustrate some example numbers, here are the required input range values for a typical microcontroller, as taken directly from the “Electrical Characteristics” section of the datasheet:

High input: 0.6Vcc to Vcc+0.5
Low input: -0.5 to 0.3Vcc

Thus if your Vcc is 5V, a valid “high” is 3 to 5.5V and a valid “low” is -0.5 to 1.5V.  If your input is connected to a standard output pin of another digital chip using the same Vcc you will get valid input voltages without any extra effort.  However, when your input is connected to some other hardware, or to chips running off of different supply voltages, the circuitry must be designed to guarantee that the input voltage levels are within spec. To allow yourself good noise margins (that is, margins where noise on the input does not drive the input voltage out of the valid '1' or '0' range), use the 10% rule: aim for highs ('1s') near Vcc (between Vcc and Vcc-10%) and lows ('0s') near GND (between GND and GND+10%), where 10% refers to 10% of the Vcc value.


What does it mean to “read” an input?

Reading an input means that your program executes an instruction designed to bring the logical value of the voltage present on the input pin into the CPU as a data bit.  Any μC will have one or more instructions which can read an entire processor word (being N bits wide) into the CPU.  Thus an 8-bit μC will have instructions to read 8 bits of input pin data into the CPU.  A 16-bit μC will have instructions to read 16 bits of input data, and may also have instructions to read 8 bits (it is common for CPUs with word widths greater than 8 bits to also have 8-bit data transfer instructions, since 8-bit data is so common).  And, not surprisingly, a 32-bit μC will have instructions to read 32 bits of input data, and may also have instructions to read 8 bits of data. 

Perhaps you noticed that we’ve gone from talking about reading a single input (one bit) to reading fields of 8, 16 or 32 bits.  But very often we really do just want to read that one bit, so what do we do?  In that case you can use the logic instructions of the μC to isolate a single bit from a field of N bits:

Many microcontrollers also have special instructions for reading and writing single bits.  This is one example of the type of instruction a μC is more likely to have than a traditional microprocessor.  When writing your program in assembly language you have direct access to these instructions.  When writing in C or another high level language, you may be able to access these instructions by using compiler-specific directives to embed assembler instructions into your program.  A good compiler may also be able to recognize suitable HLL statements and translate them into sequences of these special instructions.  The important thing is that, one way or another, it is a simple task to isolate the state of a single bit from a multi-bit field.


GPIO state on power-up and reset

Any time a microcontroller is reset (including on power up), all the GPIO pins will be configured as inputs.  To configure any GPIO pin as an output requires that the software execute specific code to change the default input configuration once the device program starts running.  This reconfiguration will typically not happen until some tens or hundreds of milliseconds after power is applied.  This means that anytime the device is reset, all hardware that is connected to pins which are intended to be outputs must be able to handle the fact that the device pins are inputs.  For example, if a GPIO pin is connected to a logic gate input then that GPIO pin would configured in the program as an output, but for a brief time on reset the logic gate input would not be driven by an output.  In some cases this brief input-that-should-be-an-output state doesn’t matter, but in many others it matters a great deal and precautions must be taken in the circuit design to deal with this time when outputs are really inputs.  Just to give one example, imagine a uC output pin that triggers an automobile airbag.  If the brief invalid state of that pin on reset is not handled correctly in the hardware, the result could be all the airbags going off the instant the key is turned!


Pullup and pulldown resistors

An important rule in handling digital inputs is that they should never be left floating – that is, left unconnected.  If an input is unused it must still be connected to a valid input voltage, which could either be near GND or near Vcc.  Such an input can be connected directly to GND or Vcc, but in many cases there is good reason to connect the input to GND or Vcc through a resistor instead.  Such a resistor is called a pullup resistor if it is connected to Vcc, or a pulldown resistor if connected to GND.  Obviously we know the voltage on one end of the resistor (the Vcc or GND end).  But by Ohm’s Law we can only know the voltage at the pin if we know the current through the resistor – that is, the current flowing into or out of the pin itself.  It turns out than every μC or other digital device data sheet will give you this important information.  Here is the input current data for an ATmega48 and a 9SP12:

ATmega48: 1uA

If we pick a pullup (or pulldown) resistor of e.g. 10kOhms, we get a voltage drop across the resistor of 10mV for the ATmega48 and 100mV for the 89C51.  This gives μs an input voltage of (Vcc-0.01) for the ATmega48 and (Vcc-0.1) for the 89C51.  These values are very comfortably within the allowed range of input voltage for a “high”, so they will be read as a solid ‘1’ by the chip, which is what we want when we use a pullup (pulldown) resistor.

Now why did we go to this trouble when we could have just wired the pin directly to Vcc or GND?  One reason is that, especially during the software development phase, an error may result in an input pin mistakenly configured as an output.  An ouput pin wired to e.g. Vcc and set to ‘0’ (low voltage) is a conflict that could easily damage your chip, as the poor output transistor on the chip essentially trys to short out the power supply.  With a pullup or pulldown resistor on the pin, no damage can occur if the pin is mistakenly wired as an output, because the resistor limits the possible current flow to a value that is safe for the μC.  Another reason, also more important in the development phase, is that you may find you need another input you hadn’t counted on.  If the unused input is wired directly to Vcc or GND you’ll have to first cut (literally) that connection before using the pin as an input.  But if the unused input is connected to a pullup or pulldown, you can use the input without disconnecting the resistor or cutting any traces.  This is because the output driving the input will be able to override the pullup or pulldown, and drive the input to the desired state.

What is the allowable range of pullup/pulldown values?  Again, Ohm’s Law, combined with the limits given on the device datasheet, give μs the answer.  On the low side, the lowest resistor value is the one that will limit the current through the resistor to a value low enough that the output driving that resistor is within its allowed rating.  Thus if a typical digital output can drive 4mA (the specification of the 74HC logic family), for a Vcc of 5V the minimum resistor value is 5V/4mA or 1250 Ohms.  A more prudent minimum resistance might be 1800 or 2200 Ohms in this case.  On the high side, the maximum value must guarantee that, with the maximum leakage current flowing through the resistor, the input value is within range.  For modern low-leakage-current inputs, this resistance value can be quite large.  1.5V/1uA = 1.5MegOhm.  This is an very high value for digital circuitry and one that would not typically be used in an actual circuit.  A value in the range of perhaps 4.7k to 100k would be more suitable, with values higher than this not gaining you anything unless you are striving for the lowest possible power consumption.  Otherwise, if you want to remember just a single number, a value of 10k for your pullups and pulldowns should be suitable for modern devices in most circumstances.  A 10k resistor will pull an input very close to the rail (Vcc or GND), but is also easy for an output signal to override (requiring the output to source or sink only 0.5mA in a 5V design, less in a lower-voltage design).

This discussion on pullups/pulldowns should help to remind you that every input and every output has electrical limits that must be observed if the circuit is to be reliable, or even function at all.


Reading switches and buttons

Switches and buttons are, needless to say, very common in embedded systems.  Switches are current devices – open they allow no current flow, closed they do allow current flow.  Since our input pins respond to voltage, not current, we need to convert our current-controlling switches to voltage signals.  As it turns out this is extremely simple to do, requiring only the addition of a single resistor (which in many cases is already built in to the μC).  In fact, interfacing switches is one of the main uses of pullup/pulldown resistors. 

A switch can be connected to a GPIO input in either of two ways, as shown below.  Of the two arrangements, connecting the switch to GND (Fig. A) is usually preferred.  We will first examine the case of a switch and a pullup resistor.  When the switch is open the pullup resistor is doing what pullup resistors do, pulling the input to a valid “high” state.  Thus when the switch is open the input will read a ‘1’.  Now close the switch and, clearly, the voltage at the input drops to zero.  The switch is now passing to GND the current flowing through the pullup resistor, as well as the input current.  When the switch is closed, the input will read a ‘0’.  The fact that an unclosed switch / unpressed button will read ‘1’ and a closed switch / pressed button will read ‘0’ is counterintuitive to many, but it is simply a consequence of wiring one end of the switch to GND.  It is not a problem at all – your software simply has to recognize that an open switch reads as a ‘1’ and a closed switch reads as a ‘0’.

One can also use a pulldown resistor and wire the switch to Vcc rather than GND.  Now when the switch is open the pulldown resistor causes the input to read ‘0’, and when the switch is closed the input reads ‘1’.  This arrangement should only be used when an open switch (or disconnected output, see section XYZ) must be read as a ‘0’.  One example when this may be necessary is in a RESET input, which are usually active-low.  There may be times when a loss of input to the RESET line must result in the device being RESET, to prevent invalid operation.  In this case the pulldown resistor will perform that function.

In the drawing below, diagram A shows a switch wired with a pulldown resistor, and diagram B shows a switch wired with a pullup resistor. The input signal in diagram A is active-high (the input will go high when the switch is closed) and the input signal in diagram B is active-low (the input will go low when the switch is closed).


Switch Inputs

Connecting a Switch to an Input


A problem – switches are not perfect and microcontrollers are very fast

You might think that throwing a switch from closed to open or open to closed was a simple matter, but things aren’t that easy.  A mechanical switch will actually bounce a number of times in the milliseconds it takes to settle.  This bouncing is insignificant in human time frames, but a μC that is executing millions of instructions per second will have no trouble reading each of these switch bounces as a new switch event.  Thus one pushbutton press can register to the software as dozens of presses and releases.  This is almost always a Bad Thing, and therefore in most cases your software should be designed to ignore the switch bounces while still reading the overall change of switch state from open to closed or closed to open.  That’s just a long way of saying that what is intended to be a single switch change should indeed be seen by the software as a single switch change.

There are a number of ways to debounce a switch.  One way is to use hardware to filter out the brief (high frequency) pulses generated by switch bounce.  This makes the software easy (it can ignore the problem of switch bounce completely), but it makes the hardware more complicated and more expensive, since additional components must be incorporated.  For this reason, switch debouncing is commonly done in software rather than hardware.  Two methods of debouncing switches in software are (a) after detecting a switch event, wait long enough for the switch to stop bouncing, or (b) after detecting a switch event, wait for a pre-established number (N) of consecutive matching switch reads.  The first method explicitly relies on a time interval to ignore any bounce, while the second method implicitly relies on a time interval, specifically, the time it takes to read the switch state N times.  Examples of both methods are given below.