math formulas

code

Saturday, February 16, 2013

Taking apart our first program

As announced in the last post, I wanted to take apart the code that made our LED flash to explain what's happening here.

The code:

#define F_CPU 1200000UL

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

int main (void)
{
  // port B pins 0, 1, 2 as output
  DDRB = (1 << PB2) | (1 << PB1) | (1 << PB0);

  unsigned int i = 0;
  while( true )
  {
    PORTB = i & ( (1 << PB2) | (1 << PB1) | (1 << PB0) );

    i = (i+1) % 8;
    
    _delay_ms( 500 );
  }
}

A basic understanding of C/C++ is required to understand the code - if you don't know C or C++, have a look at some tutorials =)

So what's happening here? Line 1 contains a definition - F_CPU (F for frequency). Many modules/headers that can be used for programming an AVR need some information about how the processor is clocked. For example, the delay routine further below needs this information to know how long to wait, other functions that perform some I/O tasks also need this value for timing purposes. An ATtiny13 is, if its fuses are not modified, clocked at approximately 1.2MHz, or 1200000Hz. If the internal RC oscillator is used (as it is by default), the real frequency can vary by several percent and is dependent on ambient temperature and supply voltage, among other things. If a more exact frequency is needed, the AVRs can be clocked by an external source, for example by a quartz oscillator.

The include in line 3 provides the port definitions, like 'DDRB' and 'PB2', the next include allows us to use the '_delay_ms()' function.

In line 9, the data direction register for port B 'DDRB' is configured. Writing a one to the bit corresponding to a port pin tells the controller that we want to use this port as an output port. By default, all 'DDRB' bits are set to zero, so all ports are configured as inputs. We write ones to the bits corresponding pins 0, 1 and 2 of port B, since we have attached our LEDs to these pins.

The main program loop outputs the value of i as binary value to the three port B pins, loops i though 0..7 and waits 500 milliseconds.

The output in line 14 is done via the port B data register, PORTB. Each pin of port B corresponds to a bit in the PORTB register - e.g. writing a one to the bit at position PB0 sets the the output of the corresponding pin to high level, so an LED connected to this pin lights up.

Masking i by the bit pattern for PB0..PB2 doesn't really make sense here since i will have no other bits set, but I left it in to make clear what's happening here.

The call to '_delay_ms()' in line 18 causes the program to wait for half a second. This doesn't mean the processor is halted. The value '500 ms' is used internally together with the frequency defined via 'F_CPU' to calculate the right amount of processor cycles that need to be wasted to cause the right delay.

That's all there is to it. The most important thing to be taken from this example is that the I/O ports of the controller can be used like normal variables, their physical state is reflected in the variable's memory, they are memory mapped.

Next up: Using a button to interact with our micro controller.




3 comments:

  1. Any particular reason why one would want to use a #define instead of a const? I usually only use const anymore these days. You can even make them static or extern, to your liking. Plus it gives you a bit more type safety, and I think every current compiler should be able to handle them in their optimizations, similar to a #define.

    ReplyDelete
    Replies
    1. Ok, got it. The API just uses that #define! So not your choice. :)

      Delete