Special function registers(3)

NAME
Special function registers -

Detailed Description

When working with microcontrollers, many of the tasks usually consist of controlling the peripherals that are connected to the device, respectively programming the subsystems that are contained in the controller (which by itself communicate with the circuitry connected to the controller).

The AVR series of microcontrollers offers two different paradigms to perform this task. There's a separate IO address space available (as it is known from some high-level CISC CPUs) that can be addressed with specific IO instructions that are applicable to some or all of the IO address space (in, out, sbi etc.). The entire IO address space is also made available as memory-mapped IO, i. e. it can be accessed using all the MCU instructions that are applicable to normal data memory. The IO register space is mapped into the data memory address space with an offset of 0x20 since the bottom of this space is reserved for direct access to the MCU registers. (Actual SRAM is available only behind the IO register area, starting at either address 0x60, or 0x100 depending on the device.)

AVR Libc supports both these paradigms. While by default, the implementation uses memory-mapped IO access, this is hidden from the programmer. So the programmer can access IO registers either with a special function like outb():

        #include <avr/io.h>
        outb(PORTA, 0x33);

or they can assign a value directly to the symbolic address:

        PORTA = 0x33;

The compiler's choice of which method to use when actually accessing the IO port is completely independent of the way the programmer chooses to write the code. So even if the programmer uses the memory-mapped paradigm and writes

        PORTA |= 0x40;

the compiler can optimize this into the use of an sbi instruction (of course, provided the target address is within the allowable range for this instruction, and the right-hand side of the expression is a constant value known at compile-time).

The advantage of using the memory-mapped paradigm in C programs is that it makes the programs more portable to other C compilers for the AVR platform. Some people might also feel that this is more readable. For example, the following two statements would be equivalent:

        outb(DDRD, inb(DDRD) & ~LCDBITS);
        DDRD &= ~LCDBITS;

The generated code is identical for both. Without optimization, the compiler strictly generates code following the memory-mapped paradigm, while with optimization turned on, code is generated using the (faster and smaller) in/out MCU instructions.

Note that special care must be taken when accessing some of the 16-bit timer IO registers where access from both the main program and within an interrupt context can happen. See Why do some 16-bit timer registers sometimes get trashed?.

Porting programs that use sbi/cbi.RS 4

As described above, access to the AVR single bit set and clear instructions are provided via the standard C bit manipulation commands. The sbi and cbi commands are no longer directly supported. sbi (sfr,bit) can be replaced by sfr |= _BV(bit) .

ie: sbi(PORTB, PB1); is now PORTB |= _BV(PB1);

This actually is more flexible than having sbi directly, as the optimizer will use a hardware sbi if appropriate, or a read/or/write if not. You do not need to keep track of which registers sbi/cbi will operate on.

Likewise, cbi (sfr,bit) is now sfr &= ~(_BV(bit));

  Modules
Additional notes from <avr/sfr_defs.h>

  Bit manipulation
#define _BV(bit) (1 << (bit))

  IO register bit manipulation
#define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))
#define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))
#define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit))
#define loop_until_bit_is_clear(sfr, bit) do { } while (bit_is_set(sfr, bit))

Define Documentation

  #define _BV(bit) (1 << (bit))

 #include <avr/io.h>

Converts a bit number into a byte value.

Note:
    The bit shift is performed by the compiler which then inserts the result into the code. Thus, there is no run-time overhead when using _BV().

  #define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))

 #include <avr/io.h>

Test whether bit bit in IO register sfr is clear. This will return non-zero if the bit is clear, and a 0 if the bit is set.

  #define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))

 #include <avr/io.h>

Test whether bit bit in IO register sfr is set. This will return a 0 if the bit is clear, and non-zero if the bit is set.

  #define loop_until_bit_is_clear(sfr, bit) do { } while (bit_is_set(sfr, bit))

 #include <avr/io.h>

Wait until bit bit in IO register sfr is clear.

  #define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit))

 #include <avr/io.h>

Wait until bit bit in IO register sfr is set.