fbpx

Input / Output Ports – LEDs

How do we connect LEDs to an ATtiny85 microcontroller and work with them by manipulating the Input/Output ports through the bits in the registers?

This is a topic as old as microcontrollers so let’s dive into it.

Hardware

We’re going to use a Tinusaur board with an ATtiny85 microcontroller and an additional board such as Shield LEDx1, LEDx2, or LEDx3 with 1, 2, or LEDs, respectively.

Everything described here should work on any other similar configuration.

Software – How to program them?

Where and how to write our C code?

Tinusaur Blocktinu web-based development environment

To write our code in C programming language we will use the Tinusaur Blocktinu web-based development environment but not in its block-based mode. Instead, we will switch to its advanced mode for writing C code.

Let’s start with the structure of a program written in C language.

Program structure

This is the absolute bare minimum for a C program.

int main(void) {	// The main function
	return 0;	// End of the program.
}

If we’re going to use input/output ports, we need to include their definitions, which are located in the “avr/io.h” file.

#include <avr/io.h>

int main(void) {	// The main function
	return 0;	// End of the program.
}

Many programs include an infinite infinite loop where the main code resides.

#include <avr/io.h>

int main(void) {	// The main function
	for (;;) {	// Infinite loop
	}			// End of the infinite loop
	return 0;	// End of the program.
}

1 LED

Let’s start with just 1 LED.

The LED is connected to the PB0 port.

First, we need to initialize the port the LED uses as an input by changing a bit in the DDRB register.

	DDRB |= (1 << PB0);		// LED: Set port as output

To make the LED blink, we need to set its bit to “1” in the PORTB register.

	  PORTB |= (1 << PB0);	// LED: Set port to high

Then, clear its bit to “0” in the PORTB register.

	  PORTB &= ~(1 << PB0);	// LED: Set port to low

This will make the LED blink, but it will be so fast that we’ll not be able to perceive it.

We need to add a small delay between the blinks by using the _delay_ms() function from the  “util/delay.h” library. This requires to include the definitions, which are located in the “util/delay.h” file.

#include <util/delay.h>

Here is the modified piece of code:

	  PORTB |= (1 << PB0);	// LED: Set port to high
	  _delay_ms(1000);		// Delay of 1000 milliseconds.
	  PORTB &= ~(1 << PB0);	// LED: Set port to low
	  _delay_ms(1000);		// Delay of 1000 milliseconds.

The final program looks like this:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {	// The main function
	// Initialization
	DDRB |= (1 << PB0);			// LED: Set port as output
	for (;;) {	// Infinite loop
		PORTB |= (1 << PB0);	// LED: Set port to high
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB0);	// LED: Set port to low
		_delay_ms(1000);		// Delay of 1000 milliseconds.
	}	// End of the infinite loop
	return 0;	// End of the program.
}

2 LEDs

Let’s see what it looks like to use 2 LEDs.

LED1 is connected to PB0,
LED2 is connected to PB1.

The program for 2 LEDs is not much more complicated.

Here are the steps that we should always follow:
(1) First, we need to initialize the input/output ports by manipulating the bits in the DDRB register;
(2) then start the infinite loop for( ; ; ) {    } where …
(3) we switch the LEDs on and off by manipulating the bits in the PORTB register.

#include <avr/io.h>
#include <util/delay.h>

int main(void) {	// The main function
	// Initialization
	DDRB |= (1 << PB0);		// LED: Set port as output
	DDRB |= (1 << PB1);		// LED: Set port as output
	for (;;) {	// Infinite loop
		PORTB |= (1 << PB0);	// LED: Set port to high
		PORTB &= ~(1 << PB1);	// LED: Set port to low
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB0);	// LED: Set port to low
		PORTB |= (1 << PB1);	// LED: Set port to high
		_delay_ms(1000);		// Delay of 1000 milliseconds.
	}	// End of the infinite loop
	return 0;	// End of the program.
}

3 LEDs

Let’s see what it looks like to use 3 LEDs.

LED1 is connected to PB0,
LED2 is connected to PB1,
LED3 is connected to PB2.

#include <avr/io.h>
#include <util/delay.h>

int main(void) {	// The main function
	// Initialization
	DDRB |= (1 << PB0);		// LED: Set port as output
	DDRB |= (1 << PB1);		// LED: Set port as output
	DDRB |= (1 << PB2);		// LED: Set port as output
	for (;;) {	// Infinite loop
		PORTB |= (1 << PB0);	// LED: Set port to high
		PORTB &= ~(1 << PB1);	// LED: Set port to low
		PORTB &= ~(1 << PB2);	// LED: Set port to low
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB0);	// LED: Set port to low
		PORTB |= (1 << PB1);	// LED: Set port to high
		PORTB &= ~(1 << PB2);	// LED: Set port to low
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB0);	// LED: Set port to low
		PORTB &= ~(1 << PB1);	// LED: Set port to low
		PORTB |= (1 << PB2);	// LED: Set port to high
		_delay_ms(1000);		// Delay of 1000 milliseconds.
	}	// End of the infinite loop
	return 0;	// End of the program.
}

Optimization

Part of the efforts to write good programs for microcontrollers is to optimize them to use less memory and to run faster when necessary.

The above code could be reduced slightly. For example, we could remove the lines for switching the LED off when it has already been switched off.

Here is the modified program:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {	// The main function
	// Initialization
	DDRB |= (1 << PB0);		// LED: Set port as output
	DDRB |= (1 << PB1);		// LED: Set port as output
	DDRB |= (1 << PB2);		// LED: Set port as output
	for (;;) {	// Infinite loop
		PORTB |= (1 << PB0);	// LED: Set port to high
		PORTB &= ~(1 << PB2);	// LED: Set port to low
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB0);	// LED: Set port to low
		PORTB |= (1 << PB1);	// LED: Set port to high
		_delay_ms(1000);		// Delay of 1000 milliseconds.
		PORTB &= ~(1 << PB1);	// LED: Set port to low
		PORTB |= (1 << PB2);	// LED: Set port to high
		_delay_ms(1000);		// Delay of 1000 milliseconds.
	}	// End of the infinite loop
	return 0;	// End of the program.
}

Conclusion

Playing with LEDs is a great way to practice input/output port manipulation.

When using the standard methods, we can use up to 5 LEDs simultaneously on the ATtiny85. However, there is another way of connecting and working with LEDs using fewer pins on the microcontroller – it is called Charlieplexing and is the subject of another tutorial.

Here are some of the Tinusaur Shields that have LEDs.

CC BY-SA Icon badge

Copyright © 2024 Tinusaur. All Rights Reserved. Authors: The Tinusaur Team
License: CC-BY-SA-3.0, Creative Commons Attribution–ShareAlike License.
Retain in your derivative work a note about the original authors and a link to the Tinusaur website.

Item added to cart.
0 items - $0.00