Embedded Programming the Atmel AVR attiny44a

This week we experimented with different technologies for programming microcontrollers. I documented my process in this tutorial.

This week I focused on studying the ATTiny datasheet.

Minimalist Blinky Light Program

#include <avr/io.h>
#define F_CPU 2500000UL
#include <util/delay.h>

#define bit_get(p,m) ((p) & (m)) 
#define bit_set(p,m) ((p) |= (m)) 
#define bit_clear(p,m) ((p) &= ~(m)) 
#define BIT(x) (0x01 << (x)) 

void blink(int seconds)
{
  int i;
  for (i = 0; i < seconds; i++)
  {
    _delay_ms(500);
    bit_set(PORTA,BIT(7));

    _delay_ms(500);
    bit_clear(PORTA,BIT(7));

  }
}

int main()
{
  // Button  PA3 (pin 10)
  // LED     PA7 (pin 6)
  bit_set(PORTA,BIT(3));  // Turn button pull up resistor on by setting PA3(input) high
  bit_set(DDRA,BIT(7));   // Enable output on the LED pin (PB2)

  blink(5); // blink for 5 seconds

  while (1)
  {
    if(bit_get(PINA,BIT(3)))  // Button is up, turn on
      bit_set(PORTA,BIT(7));
    else                      // Button is down, turn off
      bit_clear(PORTA,BIT(7));
  }
}

delay.h and Clock Divider

Assume you are using the avr-gcc toolchain and you want to #include <util/delay.h>. As described in the delay.h documentation you need to specify the clock rate with #define F_CPU

With the default settings, use:

#define F_CPU 2500000UL

Now when you use a line like _delay_ms(1000) your program's execution will pause for the correct amount of time.

Why?

Data Types

How many bits in a long/int/etc? (source)

avr-gcc search path

On my installation delay.h and io.h are located here:

/usr/local/CrossPack-AVR-20130212/avr/include/avr/

I wanted to actually read the .h files that are hidden in the include path. To find them i used this command:

$ avr-gcc -v -c hello.ftdi.44.echo.c

Even with the extra info provided by the -v option, I still had to do some searching. Is there a better way to find #include files?

AVR - Options for Saving Power

4 software selectable power saving modes

  1. Idle Mode
    • Stops the CPU
    • SRAM, Timer/Counter, ADC, Analog Comparator, and Interrupt system to continue
  2. Power Down Mode
    • Registers keep their contents
    • All chip functions are disabled until next interrupt or hardware reset
  3. Standby Mode
    • The crystal/resonator oscillator keeps running
    • The rest of the device sleeps
    • Allows fast start up and low power consumption
  4. ADC Noise Reduction Mode
    • Minimize switching noise during ADC conversions...
    • ...by stopping the CPU and all I/O modules except ADC

Additional options

Registers

Named memory locations that have some specialized functionality.

In C code, we are provided with variable names that are assigned to the appropriate memory addresses.

Interrupts, Timers

Interrupt Vector - The memory address of an interrupt handler.

sei(); // Enables interrupts by setting the global interrupt mask.
cli(); // Disable interrupts by setting the global interrupt mask.

Timers work by incrementing a counter on each clock cycle.

An interrupt can move the stack pointer to a new memory location, thus interrupting the flow of execution.

By default, nested interrupts are disabled.

ATTINY44A Interrupt config:

PCI0 (Pin Change Interrupt 0) Interrupts are triggered by pins on PORTA
--------------------
Pin13   PA0   PCINT0
Pin12   PA1   PCINT1
Pin11   PA2   PCINT2
Pin10   PA3   PCINT3
Pin09   PA4   PCINT4
Pin08   PA5   PCINT5
Pin07   PA6   PCINT6
Pin06   PA7   PCINT7

PCI1 (Pin Change Interrupt 1) Interrupts are triggered by pins on PORTB
--------------------
PIN05   PB2   PCINT10 INT0
PIN04   PB3   PCINT11

Quote from the datasheet:

Pin change interrupts on PCINT11..0 are detected asynchronously, which means that these interrupts can be used for waking the part also from sleep modes other than Idle mode.

Why is the connection between PCINT10 and INT0 (on pin PB2)? What does toggling INT0 in the GIMSK register do?

Why don't they just #define "toggles" differently so that you can:

GIMSK |= PCIE0;

Instead of the very silly:

GIMSK |= (1 << PCIE0);

Here's a simple program for Using interrupts using interrupts for blinking

#include <avr/io.h>

#define F_CPU 2500000UL
#include <util/delay.h>
#include <avr/interrupt.h>

#define bit_get(p,m) ((p) & (m)) 
#define bit_set(p,m) ((p) |= (m)) 
#define bit_clear(p,m) ((p) &= ~(m)) 
#define BIT(x) (0x01 << (x)) 

void blink()
{
  bit_clear(PORTA,BIT(7));  // off
  _delay_ms(100);
  bit_set(PORTA,BIT(7));    // on
  _delay_ms(100);
  bit_clear(PORTA,BIT(7));  // off
  _delay_ms(100);
  bit_set(PORTA,BIT(7));    // on
}

// For some reason PCINT0_vect AND PCINT3_vect both work???
// This syntax is WTF weird
ISR(PCINT0_vect){ 
  blink();
}

int main()
{
  // Button  PA3 (pin 10)
  // LED     PA7 (pin 6)
  bit_set(PORTA,BIT(3));  // Turn button pull up resistor on by setting PA3(input) high
  bit_set(DDRA,BIT(7));   // Enable output on the LED pin 

  // GIMSK - General Interrupt Mask Register
  // When the PCIE0 bit is set (one) and the I-bit in the 
  // Status Register (SREG) is set (one), pin change interrupt
  //  0 is enabled. Any change on any enabled PCINT7..0 pin 
  // will cause an interrupt. The corresponding interrupt of 
  // Pin Change Interrupt Request is executed from the PCI0 
  // Interrupt Vector. 
  // PCINT7..0 pins are enabled individually by the PCMSK0 Register.
  GIMSK |= (1 << PCIE0);

  // PCMSK0 - Pin Change Mask Register 0 - one bit for each pin on PortA
  PCMSK0 |= (1 << PCINT3); // enable interrupt for change on pin3

  // globally enable interrupts
  sei(); 

  blink();

  while (1)
  {
  }
}

Ports, Pins

An AVR port is a grouping of <= 8 General Purpose I/O pins

Each port has three data register bits associated with it.

  1. PORTA, PORTB - The Data Register (Output)
    • Read/Write
    • Write a 0 - the output pin is driven LOW
    • Write a 1 - the output pin is driven HIGH
    • To enable the pull-up resistor:
      1. Ensure pin is configured to be input
      2. write a logic 1 to the PORTxn bit
  2. DDRA, DDRB - The Data Direction Register (configure pin IO functionality)
    • Read/Write
    • A logical 0 configures pin as input (DEFAULT)
    • A logical 1 configures pin as output
  3. PINA, PINB etc - The Port Input Pins (Input)
    • Read Only
    • Writing a logic one to a PINxn bit toggles the corresponding bit in the data register (PORTxn)

The eight bits in each register each control the behavior of the eight corresponding GPIO pins.