4. Digital inputs and outputs.

0

This tutorial will explain what the digital input/output ports in a microcontroller are and how to use them, there will be a practical exercise based on a ATtiny841 to understand the functioning of digital pins.

Among the several functions that a microcontroller pin is capable of carrying out, possibly the most common one is to set it as a digital input/output pin, the practical aspect of the tutorial is dependant on a board with a ATtiny841.

The datasheet provides information regarding the digital pinout mapping of the microcontroller, the pins are named as PAx and PBx.

PAx, PBx: digital input/output pins. ATtiny841

PAx, PBx: digital input/output pins. ATtiny841

The names in each pin determine what internal hardware is accessible in the microcontroller, hence the functionality is coupled to the different pins. The digital input/output pins are labelled as PAx and PBx, therefore all the pins except for the power supply ones could be used as digital pins in this case.

If we were to route the lines PB0 and PB1 so that they get connected to an external crystal through pins PB0 and PB1 (XTAL1 and XTAL2), those pins will be then dedicated to such purpose and consequently ruled out as general purpose digital input/output pins.

The same case can be expanded to the RESET pin, the microcontroller would then need to have that pin assigned to that unique RESET function so that the programming function is available and couldn’t be use as a generic pin.

PA4, PA5 and PA6 are part of the programming lines for the microcontroller, however they could be used for something else but that being the case the designer will have to understand what else they connect to and prevent any interference as it was discussed in this tutorial.

ecTiny841 with an ATtiny841.

ecTiny841 with an ATtiny841.

ecTiny841 has 11 input/output pins (B3 is reserved as a RESET). If our board had an external crystal for the microcontroller clock reference, then there will be 9 digital pins available for general usage and B0, B1 and B3 can’t be connected to anything else.

How does a digital output pin work?

A digital output pin can either be set to a “logic 1” or reset to a “logic 0”, when set to 1 the voltage level will be close to the microcontroller input supply voltage (5V, 3.3V, etc…). And when reset to zero the voltage output will be a positive value very close to 0V.

Therefore an output pin could either sink or source current. As an example the picture below has pins PA7 and PA6 configured as digital outputs, and the output states can be a logic 1 or 0.

PA7 and PA6 configured as digital outputs.

PA7 and PA6 configured as digital outputs.

In this case when PA7 takes a logic 1 state the voltage level right at its output will be very near to Vcc, and as result there will be no current drawn since the voltage drop isn’t high enough across the LED. Similarly, when PA7 gets a 0 logic level the pin will get a voltage level very close to GND, then current in that trace will be (Vcc-Vled)/R1 and the LED will lit up. In the case of having the LED connected to pin PA7 turned on then that pin will be drawing that current.

Equivalently when PA6 drops to a logic 0 its voltage level will be close to GND, then Led2 will be connected to GND and the voltage drop out across the LED is sufficient to turn it on. If pin PA6 sets to a logic 1 its voltage value will be around Vcc, that being the case the voltage across the top of the Led2 and the bottom of R2 would equate to Vcc and the current going through that trace would be ( Vcc-Vled)/R2 which would turn the LED on. Pin PA6 sources current in this case.

There is no difference between sinking or sourcing current for a pin, all in all the current is supplied by the battery/power source, but the idea to get is that the load connected to the pin could be excited by setting a logic 0 or 1 state, based on what the other end of the load is connected to (Vcc or GND).

The amount of a current which a digital pin is able to source or sink is very limited, in the datasheet (page 246) there is a statement with a limit of 40 mA per pin (in many cases it is good practice to keep it under 10 mA at 5V), and the total current of all the pins cannot exceed 100 mA. If the current were to go up those 40/100 mA the microcontroller would overheat and get damaged irreversibly.

A microcontroller pin should be used to interact with loads that require very little current, if the pin couldn’t cope with the load some sort of driver/transistor should be fitted between the pin and the load.

The typical circuit to drive a load through a transistor would be as below:

Circuit to drive a load that exceeds the maximum current output of a microcontroller pin.

Circuit to drive a load that exceeds the maximum current output of a microcontroller pin.

If more current would be needed to go through a load then the microcontroller pin would interface with a driver such a mosfet as described in the previous circuit. It is worth noting the use of resistors R1 and R2.

Even though the current flowing through the gate in a Mosfet is very little, the transistor has a stray capacitance between the gate and the source, therefore when the microcontroller changes its output state (1 or 0) and the Mosfet switches, then that stray capacitance requires dis/charging and results in a high current spike, therefore it is recommended to add a resistor in series at the gate to protect the microcontroller pin from that current spike.

When the microcontroller goes through a reset or boots up, all of its ports are configured as an input by default until it reaches the section of our code that sets them as outputs, it takes a few milliseconds for all of that to happen. During that sequence the pin connected at the gate of the Mosfet will have an unknown voltage state, and therefore the load could switch on or off, then to avoid that the pull-down R2 resistor should be fitted.

How does a digital input pin work?

The input pin only checks if the voltage level is close to Vcc or GND, if the former the logic read will be 1 and 0 for the latter. If the voltage level is in between both or just floating (connected to nothing) the reading could be either one of the two possibilities, or even transitioning between 1 and 0 and consequently increasing the current consumption of the microcontroller.

The unused microcontroller pins will be configured as an output or an input with the internal pull-up resistor active, so that the voltage state is well defined at the input instead of being bouncing unnecessarily between states.

A frequent usage of microcontroller input pins is to connect them to momentary switches as represented in the schematic:

Circuit to connect a momentary switch to a digital input pin.

Circuit to connect a momentary switch to a digital input pin.

If we were to configure PA7 as a digital input pin then it wouldn’t sink/source any current at all since its input impedance will be very high. If the momentary switch is opened the input pin is at a voltage level defined by Vcc (provided that no current is going through the trace then no voltage drop across the resistors will exist) and the reading will correspond with a logic 1, if the switch closes the pin drops to ground through R2 and the reading would see a logic 0.

The reality with momentary switches is that by pressing them they don’t instantly open and close cleanly, instead their physical nature makes to bounce, that means that the switch opens and closes many times in a very short time interval, after that interval the desired state settles (on or off). To avoid any of that confusion for a digital input pin perspective, it is customary to add an RC filter as depicted by the diagram above.

The maximum voltage level that a digital pin can bear ranges from -0.5 V to Vcc + 0.5 V, if we apply a voltage level higher than those values the microcontroller could get damaged.

When the microcontroller pins interface with other circuits, the voltage pin compatibility requires looking at so that the digital states are well defined (1 or 0) and the communication performs as expected. All that information about voltage levels and the thresholds for each state is gathered in the manufacturer’s datasheet.

Let’s suppose a 5V microcontroller that gets connected to some 3.3V circuitry, then when the microcontroller outputs a logic 1, the voltage level will be near to 5V. In this case that output must not connect directly to 3.3V rated input, it will require some level conditioning interface in between.

Coding: how to use the digital input/output pins.

If we had gone through the former tutorial we would know how to configure and control the internal hardware available in the microcontroller that is mapped through its SRAM memory.

By the time the microcontroller boots up or resets, and prior to any program instructions execute, all the pins which are independent of the fuses (crystal, reset, etc…) are configured as digital inputs.

In order to configure and set up the digital pins (port A and port B) where the input/output pins are, we have to alter the registers related to the port via our software, the details are defined at page 71 in the datasheet.

Generally, there are 4 registers to configure for each port:

Register to set the pins as an inputs or outputs.

Register to set the pins as an inputs or outputs.

DDRx: by writing 1 to its bits the relevant pins will behave as outputs, setting the bits to 0 and the pins become inputs. For example, if we wish to configure pin PA7 as an output we will have to write a 1 to bit 7 (DDA7) in DDRA.

In order to write a single bit or collection of them to a register, we will follow the explanations given in the previous tutorial by using the &, | operators and a mask.

Driving to either 1 or 0 an output pin.

Driving to either 1 or 0 an output pin.

PORTx: by writing 1 or 0 its bits the associated output pin will take a state of 1 or 0 for each corresponding pin. For instance, if PA7 has already been set up as an output, and the desired voltage level is high, we will have to write a 1 to bit 7 (PORTA7) in PORTA.

PINA contains the readings of the input values.

PINA contains the readings of the input values.

PINx: this registers provides an image of the state of the what the pins configured as digital inputs. As an example, if PA5 is connected to a momentary switch, and that switch drops down to ground pin PA5, bit 5 (PINA5) in PINA would reflect a 0 bit state. On the contrary if the switch sets the pin to Vcc, then PINA5 will store a logic 1.

The operator used to read the bit in a register will be the & operator in conjunction with a mask to extract the bit of interest. Going through an example for this case, if there is a switch routed to pin A5 which is configured as an input and we will like to read its state the following line of code would do that.

if(PINA & (1<<PINA5)) //The condition will be non-zero if A5 is set
{	
}
PUEA activates or deactivates the internal pull-up resistors at each pin.

PUEA activates or deactivates the internal pull-up resistors at each pin.

PUEx: this register manages the internal pull-up resistors for each pin, a write with a 1 activates the matching resistor. The pull-up resistor will get activated for convenience to have to fit an external resistor, good case is when the microcontroller pin is configured as an input and left unused, the pull-up resistor will fix its input pin value to 1 and will prevent it from oscillating and drawing current unnecessarily.

The different available configuration combinations of a digital input/out pin based on the previous registers can be condensed in a table as bellow:

Possible configuration of a digital input/output pin.

Possible configuration of a digital input/output pin.

Practical example

The best way to reinforce all of the above is through a simple practical example, so let’s grab our breadboard and follow these instructions:

  • Connect the positive terminal of each of 4 leds (anode) to pins A0, A1, A2 and A3 of the ecTiny841 and the negative terminal (cathode) to ground. These pins will be configured as digital outputs in order to turn the leds on/off, the led will lit up if the digital output is commanded so with a logic 1.
  • Connect a momentary switch to pin A7, which will act as a digital input and get read to determine the state of the switch and that way command an on/off to the leds. By default, the pins of the microcontroller get initialized as digital inputs.

A layout of the previous based on eleCrab boards will look something like this:

Breadboard setup ecTiny41 + ecLeds + ecSwitch + ecPower.

Breadboard setup ecTiny41 + ecLeds + ecSwitch + ecPower.

The green cable that connects the momentary switch with the microcontroller pin configured as an input can be conveniently replaced by a resistor (1 K ohm) instead when programming is required. This way if by any mistake A7 was configured as an output instead of input the resistor will protect from a short circuit happening as a result of resetting the pin to 0 and the switch side to 1, or the other way round, with a resistor there wouldn’t be any damage.

Once the breadboard contains all the hardware elements properly connected it is time to have a look at the software that will run in the microcontroller. So if Atmel Studio is our development tool, we can follow the documented steps in this tutorial.

Assuming that we have a ready to go project in the IDE, we can get the lines of code, compile them and downloaded to the microcontroller:

#include <avr/io.h>
#define F_CPU 8000000UL	//clock frequency
//#include <util/delay.h>
 
#define button PINA7
#define led0   PORTA0
#define led1   PORTA1
#define led2   PORTA2
#define led3   PORTA3
 
int main(void)
{
    //A0, A1, A2, A3 outputs 
    DDRA |= ((1<<DDRA0) | (1<<DDRA1) | (1<<DDRA2) | (1<<DDRA3));
 
    while(1)
    {
	if(PINA & (1<<button)) //check if the button is pressed.
	{	
		PORTA |= ((1<<led0) | (1<<led3));	//set: A0, A3
	        PORTA &= ~((1<<led1) | (1<<led2));	//clear: A1, A2
	}
	else
	{
		PORTA = 0x06;	//set: A2, A1 clear: A0, A3	
	}
    }
}

The compiler that comes with the Atmel studio is avr-gcc, and with it a set of libraries get installed, AVR Libc, which come handy for many projects, therefore their user manual should be used when needed.

Everytime we write software for the microcontroller in C the library #include <avr/io.h> will be a constant, where all the definitions for the hardware memory locations (registers) are stored as well as the name of the position for every bit which compose those registers.

Right after the inclusions the functions, global variables, defines, etc… will come as per any other software in C prior to the main function that forms the foundations of our C program.

If we compare the code with those that have any experience with Arduino, what goes prior to the while() loop is equivalent to the section enclosed by void setup(){} in an Arduino Sketch. The part before the while() executes only once.

The section that runs only once at the beginning of the microcontroller code is where the hardware configuration, including pin behaviour, be defined. In this case the next line is needed:

DDRA |= ((1<<DDRA0) | (1<<DDRA1) | (1<<DDRA2) | (1<<DDRA3));

That line is responsible for configuring the pins where the led ones will have to be set as digital outputs in order to turn them on/off. There is a mask formed as (00001111) to run an OR operation on the DDRA register, that will set to 1 the 4 least significant bits and will leave the rest unchanged.

There is no need to configure the pin that received the momentary switch connection as an input, since that is how they get initialized by default during the boot up process.

The while(1) loop equates to void loop(){} in Arduino, what goes in that loop executes endlessly, since the condition given to the while (“1”) is always true. Microcontrollers always need an endless loop, because the instructions execute sequentially and after a cycle we don’t want our code to reach the end of program memory and an error to happen from a microcontroller perspective.

Within the while loop in this case our code will be checking the state of the momentary switch and operate on the leds with the following lines:

if(PINA & (1<<button)) //check if the button is pressed.
{	
	PORTA |= ((1<<led0) | (1<<led3));	//set: A0, A3
	PORTA &= ~((1<<led1) | (1<<led2));	//clear: A1, A2
}
else
{
	PORTA = 0x06;	//set: A2, A1 clear: A0, A3	
}

The momentary switch connected to pin A7 will get a logic 1 or 0 at the bit 7 in the PINA resgister when it gets pressed or released. If pressed bit 7 in PINA will be 1, if not bit 7 will have a 0.

An & operation over PINA with the mask (10000000) leaves bit 7 with whatever state it was, and the rest of the bits all reset no matter what they were before.

If bit 7 is set to 1 at the time the & operation executes and the if condition is different from zero then the microcontroller will run that section. If bit 7 was 0 at the time of the & operation happening and the if statement was 0 instead the corresponding section would be skipped and the else would execute.

The code section enclosed by the if statement switches the leds on/off via the & and | operators as it was explained in the former tutorial and the rest of the bits of the port are left unchanged. The else however has exactly the same effect but using a different approach, the values are written directly in the PORTA register changing the whole 8 bits of the port.

The video provides a visual representation of the execution, when the momentary switch is pressed the leds connected to A0 and A3 light up and when released those connected to A1 and A2 will do instead.

Pin B3 routes the RESET signal and the video shows that when the voltage level drops down to 0, through the green wire, the microcontroller reset.

This tutorial assembles the first tutorial about microcontroller digital ports, by writing and reading the appropriate registers we can set or reset the wanted bits when they have been configured as outputs or just read what binary voltage levels pins experience when set as inputs.

The next tutorials will address interrupts, very useful when we want to still detect the state change of a pin but without the need of polling the PINx register continuously.

Related links: