Today, you will learn how to turn individual pins of your micro-controller on and off, e.g. for turning on an LED, and how to detect whether a pin is set to ground or 5V, e.g. to detect the press of a button.

You can use your PC to write code that runs on the microcontroller and controls these operations. You will then need to “program” the microcontroller using a programmer. The programmer connects to the microcontroller via a 6-pin connector.

Goals

At the end of this model you will

  • understand basic programming
  • be familiar with binary and hexadecimal numbers
  • have a basic understanding of registers
  • be familiar with the ATTiny85 and connecting the AVR ISP
  • be familiar AVR studio
  • understand how to Control a LED (simple digital output)
  • understand pull-up and pull-down resistors
  • understand how to read a button/motion sensor

Required Materials

  • AVR Micro controller
  • AVR programmer
  • bread board
  • resistors
  • LED
  • button

 

Basic programming principles

Before you start programming microcontrollers, you need a few basics about programming in general. Basically, a written program is a list of commands that you want the computer or controller to execute.   These commands are passed to the computer/controller using a language-specific syntax.  We introduce some of the basic vocabulary and concepts using the C programming language.  A short list of general terms that you need to be familiar with are listed below:

  1. Variable: All programming can be broken down into the manipulation of variables.  Variables are just binary numbers saved with a given name.
  2. Control Statement: A statement that manipulates variables in a logical order, e.g. if, else, for, while
  3. Function: A Function is a group of code that completes a task. When starting any C/C++ program you need to have a main( ) function, which is the starting point for your program’s execution.
  4. Initialization Statements: These statements allow you to use variables and functions, they are very important in writing your code.  They tell the microcontroller that a task or variable exists.
  5. Include Statements: These statements allow you to use functions defined by someone else in your code.

 

A Simple program in C:

A very simple example program is shown below.  Although this program is simple, it highlights some of the key ideas for the programs you will be writing.  The following code can be written using a text editor, e.g. TextEdit, Notepad, Text Editor, Vim, Emacs, etc.  It is important not to use a word processor that may import hidden formatting characters.  The file may be saved under any name, but needs the .c file extension, e.g. hello.c could be used for our file.

#include <stdio.h>
int main(void) {
    printf("Hello World\n");
    return 0;
}

 

Include Statements:

This program starts with the include statement, #include <stdio.h>.  This statement tells the computer to import pre-written functionality to perform standard input and output.  This include statement allows us to call the printf( ) function that prints our greeting to the terminal.  The include statements that you will be using for this module are,

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

 

Functions:

Functions are blocks of code that can be called from almost anywhere in your program.  The power of functions is that they allow for the reuse of code.  For example, the printf( ) function above allows us to print as many times as we like without re-writing all the code necessary to print to the terminal.  The f in printf( ) stands for format and allows us to use the \n character which prints a new line, i.e. has the effect of hitting Enter, after printing “Hello World”.  Functions usually take arguments, i.e. some value or variable, when they are called and usually either do something internally, like print a line, or return a value back to the user or calling function.  In our program above the character string “Hello World\n” is the argument to the printf( ) function and the return 0 statement is a value that is returned from the main function; in this case returning 0 indicates a normal termination of the program.  We will see later on that return 1 can be used to indicate something went wrong with the program execution.  As a general rule when programming in C you should declare any functions that you create at the top of your program.  After the function has been declared at the top of the file, you can write the code for that function anywhere below.  For example, here is a modification to our code above that calls a function to add two numbers.  Note: there is another formatting character in the printf( ), %d, which means we are going to pass an integer value, here the value c, into the string when it is printed.

#include <stdio.h>
int get_sum(int a, int b);
int main(void) {
    int a = 3;
    int b = 2;
    int c;
    c = get_sum(a, b);
    printf("Hello World, our sum is %d\n", c);
    return 0;
}
int get_sum(int a, int b) {
    int c;
    c = a + b;
    return c;
}

Right under the include statement is our function prototype, int get_sum(int a, int b); , which declares the new function we wrote to the rest of the program and the compiler.  The code for this function, i.e. the actual implementation, is written below the main function.  Inside the main we can call the function by name and pass in the two arguments a and b.  You will notice that there are a lot of int terms floating around, this is what is called a type and stands for Integer.  The int in front of get_sum( ) and main( ) tell the program to expect and integer coming out of these functions as a return type.  The other int statements are initialization statements.

 

Initialization Statements:

In order to write functional code, you need to tell the program what variables you are planing on using and what their type is.  The most commonly seen initialization statement is a variable declaration:

int a = 3;

This statement declares a variable a and sets its value to 3.  Once this step has been completed you can then do other standard math manipulations with this variable or pass it into a function.  The variable type is also required when defining the parameters of a function, e.g. get_sum(int a, int b), to tell the function what type of value to expect.

 

Program Control:

In order for your program to perform more complex tasks, we need the ability to control the flow of the execution.  Below are some basic commands that you can use to increase the power of your applications.

 

If Statements:

If statements provide your program the ability to make a decision.  If statements allow you to execute a block of code only when the conditions are met.  The code below is executed if the variable a is less than two, but not otherwise.

if(a < 2){
     Executable code if condition is met
}

 

Else Statements:

The else control statement is always paired with an if statement and provides an alternate direction if the condition is not met.

if(a < 2){
     Executable code if condition is met
} else{
     Executable code if condition is not met
}

 

For Loops:

These statements allow you to execute a block of code a pre-specified number of times.  In the example below, i is a counter that starts at zero, 10 is the limiting condition, i.e. this will count up to but not include the number 10 (there will still be 10 runs since we count starting with 0), and the term ++i is a shortcut operation that increments i by one each run.  This will add the numbers (2+0), (2+1), (2+2),…,(2+9) to the cumulative sum b.

int i;
int a = 2;
int b = 0;
for(i = 0; i < 10; ++i) {
    b = a + i;
}

A commonly seen piece of code for microcontrollers is the infinite for loop:

for(;;){

}

 

While Loops:

Another way to repeat code multiple times is using a while loop.  A while loop will repeat the code inside their block as long as the condition is true.  Notice in this case we have to keep track of the count ourselves, although there will usually be some other part of your program that will be altering the elements involved in the condition statement.

int i = 0;
while(i < 10) {
    printf("Our count is now %d\n",i);
    i = i + 1;
}

This code repeats itself until the variable i is greater than 1.  A common piece of code seen in microcontrollers is the infinite while loop.  Since the value 1 will never by false, this loop will run until something else terminates the loop.

while(1){
}

 

 

Binary and Hexadecimal Numbers

Binary and hexadecimal numbers also arise when using microcontrollers.  Both these systems offer us a different base in which to count.  We are all familiar with the decimal system, which has 10 distinct digits (the prefix dec means 10), 0-9.  In the decimal system we move to the next value position on the tenth count, i.e. we count 10 ones and then move into the tens place to continue counting, we then count more ones and tens until we get 10 tens, and then we move to the hundreds place.  We can think of the decimal system in terms of powers of tens.  For example the number 2743 means we have 2 thousands (10^3), 7 hundreds (10^2), 4 tens (10^1), and 5 ones (10^0).  In the binary system there are only two digits, 0’s and 1’s, and we move to the next value position after every two counts.  This relates to a single bit which can either be on or off.  In this case we can think about the numbers in terms of powers of two.  For example, reading the number 1101 from left to right, we have 1 eight (2^3), 1 four (2^2), 0 twos (2^1), and 1 one (2^0) for a total of 13 in our decimal system.  Hexadecimal numbers are similar, but work off of a base of 16.  In this case we use the digits 0-9 and then keep counting using capital letters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.  The same principal applies where we can look at a number, e.g. D6, as 13 sixteens (16^1) and 6 ones (16^0), which means D6 translates into 214 in decimal.  A nice tool that converts between decimal, binary, hexadecimal is http://www.mathsisfun.com/binary-decimal-hexadecimal-converter.html.

 

Register Basics:

Registers hold bits of information so the computer/controller can access all bits in the register simultaneously.  Registers have a size in bits and you can think of each bit in the register as a light switch, which is either on (1) or off (0).  To represent this on/off behavior, registers use a binary representation.  For example, if you save a number to a register, it encodes as a binary number.  For example,

registerA = 5;

means that you are actually setting the right-most bits to the values 101 in the register.  If register A is an 8 bit register, it will look like “00000101” after this line of code.  Make sure you have some understanding of the topic  “Binary and Hexadecimal Numbers” above.

 

Wiring the Micro-controller

You can program the ATtiny85 while it is mounted on the bread-board (“in circuit”) using the AVR mkII programmer that connects to the USB port of your computer. The programmer uses 6 pins of the chip that can then be used for other things once programming is done.

The Datasheets of the microcontroller are available here:

These datasheets contain a lot of information, most of which you will not need for completing this workshop, don’t worry! Skim through the data-sheet and try to find the description of the individual pins. You should find a drawing like this one:

Pin-out of the AtTiny85 microcontroller

Every pin, except for the power (often labeled VCC) and ground pins (often labeled GND), can have multiple functions.

Try to match the acronyms on the first page of the data-sheet to the high-level description of the MCU features, page 9. What does ADCx stand for, for example?

The Atmel chip can be programmed using the AVR mkII programmer. The mkII programmer has a 6-pin socket that can interface with the bread-board using a 6-pin header. Here is the header’s pin-out, i.e., when looking at the header in your breadboard (not the plug itself) from above:

MISO      VCC
SCK       MOSI
RESET     GND

Here is how it works. The chip can be programmed using a Serial Peripheral Interface Bus using the MISO, MOSI and SCK wires. The RESET line allows the programmer to reset the chip and enter programming mode.

The circuit that you want to create looks like the following:

Once you have this simple circuit set up you are ready to start programing your microcontroller.
In order to protect the pins of the microcontroller (and for not having to wire this basic setup again and again), please leave the microcontrollers and wires in place.

AVR Studio

The software to program Atmel microcontrollers is called “AVR Studio” and is available for free from this page. Unfortunately, you need to register on the Atmel website to download the program. Watch the instructional videos, in particular the one on how to setup a new C project. The most important part in setting up a new project is choosing the microcontroller we are working with (ATTINY85).
Create a new project for the AtTiny85 in AVRstudio. Call it “myfirstproject” or whatever else you like.

Your first program: turning a LED on and off

Before explaining how you properly set values in registers to turn on and off LED’s lets put together a simple and easy to understand example that we can upload to the microcontroller and run.

 #include <util/delay.h>
 #include <avr/io.h> 
 int main(void)
 {
    //enable all port B pins for output
    DDRB = 0xff;
    while(1)
    {
      //set all port B pins as high
      PORTB = 0xff;
      //delay 1000ms or 1sec
      _delay_ms(1000);
      //set all port B pins as low
      PORTB = 0;
      //delay 1sec
      _delay_ms(1000);
    }
 return 1;
 }

Think real quick about what this program does. Numbers preceded by “0x” are in hexadecimal format. Thus “0xFF” corresponds to 255 in decimal. Hexadecimal numbers are customary in programming due to the 8-bit (16-bit, 32-bit) architecture of most. 4 bit can encode 16 values, corresponding to 0x0 to 0xf in hexadecimal. The first value in 0xff therefore allows setting the higher four bits of the variable and the second value allows setting the lower four bits. If this does not make sense, watch this screencast:

You now need to combine your LED circuit with your microcontroller circuit:

The pin PB3 is part of PORTB and will change from “on” to “off” when you run the program above. When PB3 is “on”, its voltage with respect to ground will be 5V and the LED will shine.

Your microcontroller is rated to “source” up to 20mA. If you do not use a resistor that limits the current through the LED, you might not only break the LED, but also the microcontroller.

It is now time to connect the programmer to the pin header and download the program to the microcontroller. The first step in doing this connecting your programmer to the computer, connecting the ISP cable to the breadboard, and powering your breadboard.

Now you need to build and compile your program, the short cut’s for this are “F7” and “alt-F7” you can also go into the build menu at the top and manually click them.
Once you have built and complied your project you need to browse for the proper .hex file under the program tab and press the program button.
How to do this in AVRstudio is explained in this . There are many different programming tools available from Atmel. We are using the “AVR ISP mkII“.
Download your program to the microcontroller and unplug the programmer. Verify that the system behaves as you expect.

Your second program: Understanding registers

It is now time to start to understand this microcontroller on a deeper level and understanding what register’s are. If you refer back to the last piece of code you created, you will notice that the first piece of non-importing code that isn’t a function is:

DDRB = 0xff;

Here you are saving the hex value 0xff, decimal value 255, or binary value 0b11111111 to register DDRB which is defined as this micro controller’s Data Direction Register for port B. If you read about this register in the data sheet you will find that it is an 8 bit register where you determine whether the designated pin PB0-PB6 is an input or an output, 1 denoting an output and 0 being an input.

The next micro controller specific line of code to be aware of is:

PORTB = 0xff;

PORTB is another 8 bit register, but here you can save configurations about the states of the digital pins, 1 being high (5V) and 0 being low (0V). Internally, the chunk of memory that is associated with DDRB is directly tied to the output pins. This is known as “memory-mapped I/O”.

Using this knowledge you can now be very explicit with what pins you use for what purpose. Assuming you still have one LED connected in series with a resistor on pin PB3 of the micro controller, you can set DDRB = 0x8 and PORTB = 0x8 to turn on only PB3. You can also toggle two LED’s (connect the second to PB4) by setting DDRB = 0x18 (8+16 in hex) and toggle between the values 0 for turning both LED’s off; 0x8, only PB3 on; 0x10, only PB4 on; and 0x18 turning on both PB3 and PB4.

Pulling it all together, here is code that uses 4 LED’s connected to PB0-PB4 and counts in binary!

 #include <util/delay.h>
 #include <avr/io.h>
 int main(void)
 {
      //enable all port B pins for output (were not using any as inputs so its ok)
      DDRB = 0xff;
      int i;
      while(1)
      {
           //start i as zero (note you don't need to use hex values integers
           //are just fine)
           i = 0;
           for(i; i<16; i++) //set PORTB = i to light up LED's
           {
                PORTB = i;
                _delay_ms(500);
           }
      }
      return 1;
 }
Implement a circuit that ties 4 LEDs to PB0-PB3 and try out the program above. Notice that you will need a current-limiting resistor for every LED!

Your third program: Reading Registers

Now that you understand the idea of registers and that certain pins of the micro controller are represented by binary values of those registers, you are ready to both read and write digital signals.  If you read through the data sheet (page 57 of the long version) you will find that if you wish to read the values of pins set as inputs (pins set to 0 in the DDRB register) you need to read values in the PINB register.  You also need to make sure that you only use the information about the pins configured to be inputs, all the other data is invalid.

 #include <util/delay.h>
 #include <avr/io.h>
 int main(void)
 {
//Enable pins PB0-1 as outputs
 DDRB = 0x3;
//make sure all output's are low at start
 PORTB = 0;
 int i,j;
 while(1)
 {
//to read digital values use register PINB
 i = PINB;
 //You need to know which channels you are using as inputs
 //here we are using PB2&PB3
 j = i>>2;
 PORTB = j;

}

}

This code example will read the pin’s PB2 and PB3 and output their digital value to pins PB0 and PB1.

Implement a circuit that ties 2 LEDs to PB0 and PB1, replace the two LED’s on pins PB2 and PB3 with the push button switch and a wire that you can manually move from VS to GND and try out the program above (the kit only includes one button, another option is to connect the switch to both PB2 and PB3).  You will still need a current-limiting resistor for every LED!
 

One Response to Digital Input and Output

  1. […] values to registers, much in the same way you did when configuring your pins to be outputs in Module 2.  First you need to configure the chip’s ADMUX register which includes information about […]

Leave a Reply

Set your Twitter account name in your settings to use the TwitterBar Section.