AVR Libc Home Page | AVR Libc Development Pages | |||
Main Page | FAQ | Library Reference | Additional Documentation | Example Projects |
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:
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?.
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)) |
|
#include <avr/io.h>
Converts a bit number into a byte value.
|
|
#include <avr/io.h>
Test whether bit |
|
#include <avr/io.h>
Test whether bit |
|
#include <avr/io.h>
Wait until bit |
|
#include <avr/io.h>
Wait until bit |