DIGITAL INPUTS
INTRODUCTION:
Digital input hardware is a bit harder than outputs but nothing too crazy. The AVR digital input pins look for a HIGH (Vcc) signal or a LOW (GND) signal. Once again it is important to note that this is not an ON or OFF. Once again, ON and OFF would imply that a physical break takes place in the wire that prevents current flow.
HARDWARE:
If you look at the AVR data sheet, the second page shows the pin configuration of the AVR chip. All the PA#, PB#, PC# and PD# could be configured as an outputs.
Figure 1: ATmega8 - Input Pins
If you look at the AVR data sheet, the second page shows the pin configuration of the AVR chip. All of the PA#, PB#, PC# and PD# could be configured as a digital input.
In the AVR world, the value of less then 0.2V is read as a LOW and the value of greater than 0.6V as a HIGH. However, theoretically we will always receive GND (LOW) or Vcc (HIGH) on the input pin, so from now on this documentation will refer to HIGH/LOW as GND/Vcc.
Digital Inputs from Digital Devices:
We don't have to do any work if we want to connect a AVR to a digital device because most digital devices outputs send out a HIGH (Vcc) or LOW (GND) signal, which is exactly the signals that the AVR digital input pins are looking for.
Pull-up Resistors:
Now we have a problem the AVR looks for a HIGH (Vcc) or LOW (GND) but the most common digital input is a button, which provides us with an ON or OFF state. If we hook up one side of the switch to the GND and the other to the AVR, we will get LOW (GND) when the button is pressed and OFF (no signal) when the button is not pressed. If we connect one side of the button to Vcc and the other side of the button to the AVR, we will get a HIGH (Vcc) signal when the button is pressed and OFF (no signal) when the button is not pressed. If you have a button connected to the AVR in ether of these ways, you will see all kinds of strange things. The most common things include nothing happening or things working intermittently.
So lets solve the problem. Using a pull-up resistor.
Figure 2: Pull-up Resistor
R1 is the star of the show, when the button is not pressed current will flow from Vcc, through R1(which reduces the current because it is in series with the pin) and into the digital input pin. When the button is pressed current will flow through R1 into the button and down into ground. Because the input pin is on the low side (GND side) of R1 the digital input pin and the AVR will think it is connected to ground when the switch is pressed. At this time R1 acts like a load, and since it has a high value it will only draw a small current (I = V/R).
The best news is that the AVR has a built in pull-up resistor that can be activated whenever we need it, so we do not have to use an external pull-up resistor.
Figure 3: Suggested Way To Hook Up A Switch With a Pull-up Activated
De-bounce in Hardware:
Another issue that one might have with ON/OFF type devices is that they are prone to contact bounce. Contact bounce occurs because current tends to arc (or jump) between both contacts when they are brought close enough to each other. So just before the contacts meet or just as there are separated the contacts will arc and the microcontroller will see several rapid on/off states. This often leads to a single button press as been seen by the AVR as several presses.
This problem can be solved in hardware or in software. These days however, most engineers opt to solve this in software because it is what schools teach (for 2 reasons: it lowers costs by reducing components and it is the easier to teach programming then it is teach hardware).
The previous generation of engineers used an RC circuit, this basically create a filter that will clean up the ripples in the voltage spikes caused by the arcs. The problem is that this filtering effect causes inputs to lag slightly, so its not a good solution for a high speed applications but, for push buttons on remotes, keyboards and even game controllers, this works out quite well.
Figure 4: RC Debounce Circuit
For those of you who wish to know the math and theory behind this please read AnalogAngle By Ron Mancini. But I found that the values above work really well on most tactile switches, if you find that you are still getting multiple hits every time you press the button just use a bigger capacitor.
Maximum Input Capabilities:
The most important thing to note is that digital inputs are limited to GND - 0.5V and Vcc + 0.5V this means that on a standard 5V device we could go as high as 5.5V and as low as -0.5V. Anything outside those values will result in damages to the AVR. So take extra care when the input comes from a different power supply. These values are shown in the "Absolute Maximum Ratings" chart at the beginning of the "Electrical Characteristics" chapter of the data sheet.
THEORY OF OPERATION:
The one thing that I don't understand is why input/output section in the Atmega8 and Atmega168/328 is so poor. Every other part of the data sheet has a really nice description of each register at the end of each section that describes everything.
Under the I/O section of the data sheet you will find the following table:
DDxn | PORTxn | PUD | I / O |
Pull-up |
Comments |
0 | 0 | x | input | No | Tri-state (Hi-Z) |
0 | 1 | 0 | input | Yes | Pxn will source current if external pulled low. |
0 | 1 | 1 | input | No | Tri-state (Hi-Z) |
1 | 0 | x | output | No | Output Low (Sink) |
1 | 1 | x | output | No | Output High (Source) |
Since we are talking about inputs, we will only use the first 3 rows. Notice that the difference between an input and an output is that the DDxn register (Data Direction Register), leaving the pin LOW (0) will make it an input but setting it to HIGH(1) will make it an output. When dealing with inputs we have an extra bite the PUD (Pull Up Disable) and it is a pin located within the SFIOR (Special Function IO Register). When this bit is set to HIGH(1) it will disable the pull-up on all inputs, so be very careful whenever you use this option.
The 1st row sets the pin with no pull-up. What is important to note is that this method ignores the state of the PUD bit. This is the preferred way to set an input with no pull-up because it does not disable the pull-up on all pins, simply the pin you want. Simply leave DDxn and PORTxn LOW(0).
The 2nd row sets the pin to an input with pull-up, simply set the PORTxn HIGH(1) and leave the DDxn LOW(0).
The 3rd row sets the pin to an input and disables the pull-up on all pins. This is not commonly used because setting the PUD pin HIGH(1) will disable the pull-up on ALL input pins.
So what's with the xn? "x" defines which Port we are using B, C or D (there is no A port on the ATmega328, but some like the Atmega32 do have them). The "n" defines the pin number. So this chart is telling us that if we want to make the PD0 pin an input we have to turn on DDD0 LOW(0) and after that when we change PORTD0 LOW(0) or HIGH(1) depending on if we want to use the pull-up on or off.
Up to this point we have talked about how to set the input but how do we get the value of the input? well, we have to read the values of a new type of register. The PINx register:
7 bit | 6 bit | 5 bit | 4 bit | 3 bit | 2 bit | 1 bit | 0 bit | |
---|---|---|---|---|---|---|---|---|
PINx | PINx7 | PINx6 | PINx5 | PINx4 | PINx3 | PINx2 | PINx1 | PINx0 |
Port x Input Pins Address Register
So if we wanted to read the value of PD0 for example, we would have to read the value of PIND0 within the PIND register.
NOTE: A lot of programmers will simply use 0-7 instead of PINx0-PINx7, so don't be surprised if you see that if your going though peoples code.
SOFTWARE:
Flow 1: Digital Inputs Software Flow Diagram
Finally some code, but please note, that since we use a pull-up resistor inside the AVR we tend to hook up the buttons to ground, this means that the register will read HIGH (1) when the button is not pressed, and LOW (0) when it is pressed:
ATmega8 & ATmega168/328 Code:
// this code sets PB5 to an input with a pull-up enable #include <avr/io.h> int main(void) { DDRB &= ~(1 << DDB5); // Clear the PB5 pin // PB5 is now an input PORTB |= (1 << PORTB5); // turn On the Pull-up // PB5 is now an input with pull-up enabled while (1) { if( (PINB & (1 << PINB5)) == 0) { // do something when PD5 is on } } }
ATmega8 & ATmega168/328 Code:
// this code sets PD0 to an input with a pull-up disabled #include <avr/io.h> int main(void) { DDRD &= ~(1 << DDD0); // Clear the PD0 pin // PD0 is now an input PORTD |= (1 << PORTD0); // turn On the Pull-up // PDO is now an input with pull-up enabled while(1) { if( (PIND & (1 << PIND0)) > 0) { // do something if PD0 is off } } }
So a final question; When do we use the pull-up and when do we not use the pull-up? Whenever, the input is coming from a digital device (a device that outputs GND or Vcc) you do not need a pull-up. If you are using an ON/OFF device (sends a signal or breaks a signal) you need to use the pull-up.
De-bounce in Software:
There are a lot of ways to do a software de-bounce, but the theory is always the same, look for an input to change, after it changes the first time ignore all the changes on that input for a short time (10-100ms or so).
ATmega8 & ATmega168/328 Code:
// this code sets PD0 to an input with a pull-up enable #include <avr/io.h> #include <util/delay.h> int main(void) { DDRD &= ~(1 << DDD0); // Clear the PD0 pin // PD0 is now an input PORTD |= (1 << PORTD0); // turn On the Pull-up enabled // PDO is now an input with pull-up enabled while (1) { if( (PIND & (1 << PIND0)) == 0) // is the pin set { _delay_ms(25); // wait a 25ms if( (PIND & (1 << PIND0)) == 0) // is the pin still set { // do something because its a valid input } else { // do nothing because the input is invalid } } } }
Now there is plenty of different ways to de-bounce feel free to experiment, and if you come up with any really neat way to do it feel free to let the rest of us know.
The time has come, the song is over, thought I'd something more to say.
Cheers
Q