Input and Output
When we come to apply C programming skills to Arduino projects then the programs are going to spend a lot of the time focused on the Arduino connections. We need to understand those connections and how they are manipulated in order to write practical and efficient programs.
At first sight, the pins presented by an Arduino as connections to the world of sensors and actuators can appear a pretty daunting array. Ignoring the pins associated with things like power and ground, there are two main groups. The most numerous are the pins designated as “digital” and then there are generally a smaller group deemed “analogue” although these can usually be pressed into service in a digital mode if, or when, required.
Digital Pins
The digital pins are broadly binary in nature. They can be configured to be in an INPUT mode and then used to read HIGH or LOW voltage values. When configured for OUTPUT then these same pins can be set to a HIGH or LOW voltage value ready to be interpreted by some external device or component. On ATmega based boards a HIGH value is 5 volts while on later 32 bit boards HIGH is represented by 3.3 volts. LOW is zero volts or “ground”.
INPUT, OUTPUT, HIGH, LOW and INPUT_PULLUP are all predefined constant values within the Arduino environment.
Unless otherwise set, a digital pin is configured for input. Input pins have a “high impedance state” which means that the pin is not being driven to a high or low logic level. Arduino input pins make very small demands upon any circuitry they are connected to and it takes very little current to change the state of an input pin from low to high or vice versa. Such sensitivity adds to the utility of an Arduino as input pins can be triggered by such things as capacitive touch sensors or photodiodes. The downside is that a pin attached to an open circuit or even with no connections may pick up random electrical noise in the environment and that can be read as a change in the pin state.
Random noise can be dealt with by using a resistor to connect an input pin to +5 volts (or +3.3v) when it is called a pull-up resistor or to ground when the resistor is called a pull-down resistor. A resistor value around 10k ohm (brown, black and orange stripes) would be a suitable value.
There are some pull-up resistors built into the Arduino boards. The exact values vary depending upon the chip used but an AVR based board supplies internal pull-up resistors with a minimum value of 20K ohm and, as another example, an Arduino Due delivers pull-ups with a minimum value of 50K ohm. A pull-up resistor may be set on for an input pin by setting the mode to INPUT_PULLUP in program code.
const byte intPin = 2; { pinMode(intPin, INPUT_PULLUP); }
Programs written with early versions of the Arduino IDE used the following rather counterintuitive method.
const byte intPin = 2; { pinMode(intPin, INPUT); digitalWrite(intPin, HIGH); // Enable pullup resistor on input pin }
The earlier method of setting a pull-up resister will help explain why, if a pin with a pull-up set is switched from INPUT to OUTPUT mode the pin will be set (or remain set) HIGH. In fact, the older approach is still used by programmers directly manipulating pins via ports and their associated registers – but that comes just a little later.
A digital input pin with a pull-up resistor set can be pulled low by external circuitry that effectively connects the pin to ground. This is usually the most effective way of communicating a “state change” from an external component to a running program.
The pin connected to the built-in board LED (most often pin 13) can present problems, as it is connected to an on-board resistor as well as the LED. The cumulative effect of setting the INPUT_PULLUP mode and the other components attached to the pin will be to lift the pin voltage to around 1.7 volts rather than the full HIGH voltage. You can though, still use the built in LED pin for input using an external pull-down resistor.
Digital pin input values can be read using the digitalRead() function with anticipated values of LOW or HIGH. LOW is defined as 0 and HIGH is 1. You might therefore wonder how a voltage on an input pin is converted to one of the two values. For 5 volt boards (like the Uno) if the voltage is 3.0 volts or more then the value is considered HIGH and if the voltage is less than 1.5 volts the value will be considered LOW. For 3.3 volt boards the threshold for HIGH is 2.0 volts or more and for LOW less than 1.0 volt. Circuit design needs to take into consideration the gap between the HIGH and LOW voltage thresholds so as to ensure that digital pins being used for input are not in an indeterminate state when they are read.
Digital Output
Digital pins configured in output mode can deliver current levels capable of powering many external components and sensors. The recommended limit for an individual pin is 20ma (milliamps) but an individual pin can deliver up to 40ma without damage to the Arduino. The total maximum power to be supplied from an Arduino Uno (for instance) is 200ma. Where higher current levels are required (say for running an electric motor) then an external power supply is required together with some circuitry to effectively isolate any controlling (digital or analogue) Arduino pins from the motor current drain.
While the voltage values on a digital output pin set to HIGH are 5 volts or 3.3 volts, an output pin set LOW will be at or very near 0 volts on all board types.
Drawing high current levels (in excess of the limits specified for your Arduino model) from a digital pin will inevitably result in damage to the board. This may be confined to the pin involved but may kill the whole processor chip.
Analog Pins
The analogRead() function is used to read a value from one of the designated analogue pins on an Arduino. The Arduino Uno has 6 designated analogue input pins (the Mega has 16) connected to a 6 channel 10 bit analogue to digital converter (ADC). The ADC will convert voltages between HIGH and LOW to an integer value in the range 1023 to 0.
The ADC takes around 100 microseconds to convert a voltage measured on an analogue input pin to the relevant integer value. This allows for a potential 10,000 reads per second which should meet most practical needs.
int res = analogRead(A3); // reads from the analogue pin 3 // (marked A3 on the board)
The upper voltage value (better called the reference voltage) for an analogue pin can be set to alternate values using the analogReference() function. This can set the reference voltage using a CONSTANT selected from the available values.
DEFAULT 5 volts or 3.3 volts depending upon the board voltage
INTERNAL 1.1 volts on the ATmega328 and 2.56 volts on the ATmega8
INTERNAL1V1 for 1.1 volts on the Arduino Mega only
INTERNAL2V56 for 2.56 volts again on the Mega only
EXTERNAL which uses an external reference voltage applied to the AREF pin which should not exceed 5 volts or be lower than 0 volts (ground).
[N.B. If using analogReference(EXTERNAL) and an external reference voltage on the AREF pin then this should be set before using analogRead(). Alternately the Arduino documentation advises connecting the external reference voltage to the AREF pin via a 5K ohm resistor though the voltage reduction resulting from that resister will have to be factored in to the design.]
Unlike the Arduino Due which has access to a digital to analogue converter (DAC), the ATMega based Arduino boards like the Uno make use of Pulse Width Modulation (PWM) and timers to simulate analogue output.
The analogWrite() function can be used with any available PWM enabled pin (3, 5, 6, 9, 10 and 11 on the Uno). The function is passed the pin number and a value between 0 and 255.
The pin then outputs a square wave on/off pattern that simulates a voltage between zero and 5 volts. If the argument value is (say) 127 then the square wave will set the pin to 5 volts for half the time and zero volts for the remainder with a frequency of either 490 or 690 hertz (cycles per second) depending upon the pin used. In that example, the switching between 5 and zero volts averages to approximately 2.5 volts. This approach is good enough for a wide range of applications and, ultimately, is pretty good at Pulse Width Modulation.
There is no pin mode setting required before using analogWrite().
analogWrite(6, 127); // would set the average output voltage // on pin 6 to aprox 2.5 volts.
32 bit boards and analogue pins
The Arduino Due has 12 analogue input pins connected to a 12 bit ADC although by default this is set to 10 bits for compatibility reasons. The maximum voltage that can be applied to an analogue pin on the Due is 3.3 volts. The analogReadResolution() function can be used on the Due and Zero (which also has a 12 bit ADC capability) to set the number of bits to be used in the value returned by analogRead().
The Arduino Due has 2 true analogue output pins fed from a 12 bit Digital to Analogue Converter (DAC) with an output range of 0.55 volts to 2.75 volts. The analogWrite() function is used to address these pins.
Using analogue pins for GPIO
The analogue pin set (A0 to A5 on the Uno) can be used as digital pins although restrictions can apply. The A6 and A7 pins on the Nano for instance are analogue only.
pinMode(A0, OUTPUT); digitalWrite(A0, HIGH);
Here A0 is an alias for the analogue pin 0. These pins do have regular pin numbers (A0 is pin 14 on a Uno) but these vary depending upon the Arduino being used so stick with the alias. There are even pull-up resistors available on these pins so your code say:
pinMode(A0, INPUT_PULLUP);
Please remember that using an analogue pin for output or setting the pull-up resistor would affect the reading on any subsequent analogRead() on the same pin unless the pin was reset by setting the mode to vanilla INPUT first.
Other Input/Output
Digital pins 2 and 3 on a Uno can be used to generate external interrupts. Please refer to the chapter on interrupts for all the details on usage as there is quite a bit to cover there. Interrupt handling is a key Arduino programming skill.
Digital pins 0 and 1 can be used to receive and transmit TTL serial data and these pins are also hooked to the corresponding pins on the on-board USB to TTL Serial chip.
My Arduino Uno pinout diagram identifies pins A4 (SDA) and A5 (SCL) as being available to support TWI communication using the Wire Library. Also, digital pins 10 (SS), 11 (MOSI), 12 (MISO) and 13 (SCK) are available to support Serial Peripheral Interface (SPI) communications using the SPI library. Both TWI (in the guise of I2C) and SPI interfaces get more coverage later in this book.
The pin marked RESET can be pulled low to reset the board. This pin is typically used by shields to replicate the reset button when this is physically obscured.
Ports
The chapter on interrupts includes a section on pin change interrupts where pins are organised into groups (or ports). When a pin change interrupt is enabled for a given pin then an ISR (interrupt service routine) is added to the program to be activated by a pin event. All pins that are part of the same port group share the same event handling function.
For later reference, digital pins D0 to D7 (port 2) share the pin change vector PCINT2_vect. The digital pins D8 to D13 (port 0) also share a pin change vector which is PCINT0_vect. Similarly, the analogue pins A0 to A5 (port 1) share the pin change vector PCINT1_vect.
These pin ports extend beyond the management of pin change interrupts. Each port is served by three cpu registers that support low level (and therefore fast) manipulation of the input and output functionality of the pins. Here the ports are designated B (digital pins 8 to 13), C (analogue pins A0 to A5) and D (digital pins 0 to 7). The DDRx registers sets pins as INPUT or OUTPUT. The PORTx registers sets pins designated as OUTPUT, HIGH or LOW and the read only PINx registers hold the state of any input pins for a given port.
So, register DDRB can be used to set the pins 8 through 13 as INPUT or OUTPUT (DDRC for port C and DDRD for port D). Register PORTB can set any output pins in the range 8 to 13 HIGH or LOW (PORTC and PORTD serving the same function for the other two ports). Register PINB can be used to read the values associated with a given INPUT pin in the same range (again PINC and PIND serving the same purpose for the other two ports).
As an example, the statement
DDRD = B11100000;
would set digital pins 5, 6 and 7 to OUTPUT and pins 0 to 4 to INPUT. However, if all you wanted to do was set the pins 5 to 7 then a better style would use a bitwise OR to just set those pins and leave the other pins sharing the port with their existing settings:
DDRD |= B11100000;
Also pins 5 and 7 could be set HIGH with
PORTD = B10100000;
and the bits in the PIND register can be inspected to check on the state of any INPUT pins in the range 0 to 4 (given the previous settings).
I have used port D for these example statements so that I can point out that digital pins 0 and 1 are used for serial communications and it is not a good idea to set values for those pins. Accidentally setting pin 0 as an OUTPUT pin could leave your board rather isolated as this would block incoming serial data and therefore new programs from the Arduino IDE.
You may be wondering why you might use the port registers instead of the standard pinMode(), digitalWrite() and digitalRead() statements. It is all about speed and synchronicity. Direct access to the port registers uses fewer machine instructions and is therefore faster. Plus, you can set multiple pins on the same port to a new state simultaneously and this may be important when working with some external components. There is also a saving in program size and while it is usually SRAM we run short of, using the port registers might just help cram in some extra functionality for a large program.
What are the downsides of using port registers? Well program readability suffers although you can go some way to deal with that with some informative comments. There is also a potential portability issue as ports may not always be consistent between devices while (say) digitalWrite() will definitely work on all Arduinos. As always, the technique is there to be used and we programmers have the task of evaluating each specific use case and deciding on the approach that best serves a particular requirement.
Ports get properly exercised in a parallel communications demonstration in a later chapter where you can get some practice with port register manipulation.