Hints on AVR Programming

On this page I have collected some hints for programming of AVR microcontrollers.

I/O-Pins

To control the behaviour of each I/O-Pin there are three registers.

  • PORTx: State of the pin, low (0) or high (1) / without (0) or with (1) pull-up resistor
  • DDRx (Data Direction Register): This determines if the pin is in high-impedance, so that you can measure the applied voltage level, or low-impedance (1), so that the pin is able to source or sink current.
  • PINx: Reading this register returns the currently applied voltage level to this port pin (high or low level).

So you can assign one of four different modes to any i/o-pin by combination of the PORTx and DDRx register:

  • (PORTx=1, DDRx=1): High level: The controller tries to pull the pin to high level.
  • (PORTx=0, DDRx=1): Low: The controller pulls the pin to ground.
  • (PORTx=0, DDRx=0): High Impedance, High-Z: The pin does not source or sink current but you can read back the applied voltage level from the PINx register.
  • (PORTx=1, DDRx=0): Pull-up: The controller connects the pin over a resistor of appx. 60kOhm to VCC. So it is possible to pull down the pin by connecting it to GND. This is an easy way to connect a switch or push button to the controller.

Generation of a modulated signal (e.g. RC5)

To generate a modulated signal, e.g. to generate a infrared signal for a remote control, it is convenient to use the waveform generation mode (WGM) available in most of the recent AVR controllers. This is a part of the timer counter peripheral that lets you generate a RC5 signal with nearly no cpu load.

  • Set up the timer, so that it generates an output compare match with a constant frequency.
  • Adjust the output compare mode, so that it toggles one of the output compare pins (OCx) on every output compare match.
  • Now you only need to switch the WGM on or off at the right time to generate a modulated signal on the OCx pin.

Example code for a AT Tiny 2313:

// setup waveform generation to 36kHz on timer 0
TCCR0A |= _BV(WGM01);
// clear timer on compare match
TCCR0B |= _BV(CS00);
// timer source is system clock OCR0A = 107;
// output compare value for 36kHz @ 8 MHz
// output compare pin to output mode, low level
DDRB |= _BV(PB2);
// port PB2 as output
PORTB &= ~_BV(PB2);
// PB2 is low
// setup half-bit clock (555Hz) on timer 1
TCCR1B = _BV(WGM12);
// Clear timer on compare match OCR1A = 7049;
// ~ 889us @ 8MHz
TIMSK |= _BV(OCIE1A);

// output compare interrupt enable for timer 1a
ISR(TIMER1_COMPA_vect)
{
  if(...)
  {
    // enable modulated signal
    TCCR0A |= _BV(COM0A0);
  }
  else
  {
    // disable modulated signal
    TCCR0A &= ~_BV(COM0A0);
  }
}

Data structures in flash rom

The GNU C compiler for AVR controllers does support the storage of data in the flash rom of the controller. To store something in flash rom, you need to include the PROGMEM keyword in the variable definition. This tells the controller, to store the data in rom, but you cannot access the data as if it has been stored like a normal variable in RAM. This is caused by the independent data busses and address ranges for ram and rom accesses. It causes strange behaviour of the code if you try to pass a variable stored in rom as a function parameter. You will end up with random data from ram.

To be able to use the data from flash rom, you need to copy it to the ram before using it. There is a function in the avrlibc that encasulates the LPM (load program memory) assembler call. The following example is intended to copy a whole data structure from the rom into a ram structure.

uint8_t memdrv_read(uint8_t *buffer, memaddr_t address, uint8_t numBytes)
{
  uint8_t i;

  if((address + numBytes) >= FLASH_SIZE)
  {
    return MEM_ERROR;
  }

  for(i = 0; i < numBytes; ++i)
  {
    buffer[i] = pgm_read_byte_near(address + i);
  }

  return MEM_SUCCESS;
}

With this function you can copy the rom located structure

struct mystruct_t flash_var PROGMEM = {7, 0x13, 0xffff};

to ram by a call like

struct mystruct_t rambuf_var;
memdrv_read(&rambuf_var, (memaddr_t)(&flash_var), sizeof(flash_var));

without thinking about the layout of the single bytes in ram or rom. You can generalize this concept, so that it can be used for other memories attached to the controller like an external eeprom or the ram from a real-time clock. It is sufficient if the interface api for the memory provides a function for reading a single byte by address (and perhaps write a single byte, so you can also store back data from the controller ram).

LinkedIn logo mail logo