C++11/17/20 library for manipulating I/O port pins and registers of AVR8. The purpose of this library is to provide a higher level of abstraction for operating I/O ports and general I/O registers with zero-overhead in mind. It is a header-only library that does not require any external dependencies. The only requirement is to compile with avr-gcc
using -Os
optimization and have at least C++11 support. avr-libc
is a reference for this work and avrIO can be seen as a C++ approach for avr/io.h
It can be useful in the application layer, delivering an expressive code(see demo/c++11/led.cpp) as also be used to develop flexible and concise APIs(see demo/c++20/api.cpp).
Pb0 led{output};
Pb3 sw{pullup};
while(true) led.high(sw.is_low());
A LED connected to the pin PB0
is turned on when the switch connected to PB3
is on.
It illustrates the operation of on one pin at a time.
auto [swA, swB] = in(pullup, pb2, pb1);
auto [ledA, ledB] = out(pb0, pb4);
while(true)
set(ledA(lazy::is_low(swA)), ledB(lazy::is_low(swB)));
A LED connected to the pin PB0
is turned on when the switch connected to PB2
is on and a LED connected to PB4
is turned on when the switch connected to PB1
is on.
It illustrates the operation of more than one pin at once. There is a C++11 version of demo at demo/c++11/leds.cpp.
template<avr::io::Pin Pin>
struct led_t {
Pin pin;
led_t(Pin ppin) : pin(ppin) { out(pin); };
void on(bool v = true) { high(pin, v); }
};
int main() {
led_t led{pb0};
Pb3 sw{pullup};
while(true) led.on(sw.is_low());
}
The version above has support to C++20 Concepts and the template parameter of the class template led
is implicitly deducted. There is a version demo/c++11/api.cpp with support to C++11.
This illustrates how to design an API that receives an I/O port pin.
This library can help the designer to offer an API that needs to handle pins. It can receive the information using a flexible and minimal representation. The argument Pin
can be anything that models the concept avr::io::Pin
and with only one object the the location of the pin can be represented. There is no need, for example, to ask for the registers that are related to the pin in question.
//enables the power-down sleep
mcucr = (mcucr & ~sm0) | sm1 | se;
//or something more expressive and concise
set(se, sm1, sm0(off));
//an integer can still be assigned
portb = 0x07;
//or something more expressive that doesn't use bitwise operators
portb = {pb3, pb2, pb1};
portb = pb0 | pc1; //compile error because it isn't allowed to mix
//bits from different registers.
portb = pb0 | pc1.bv(); //Ok. The byte value can be obtained to bypass
//the type system.
Note: Using the ATtiny13A here to illustrate some operations on register but the same thing can be done at any other MCU that is supported by the library.
The goal here is to compare the code generated using avrIO with a hypothetical reference code that doesn’t use any expressive abstration, like the one that uses sbi
or cbi
instructions directly in the code.
Builds using avr-gcc-10.2 -mmcu=attiny13a -Os
.
56 bytes
00000022 <main>: 22: b8 9a sbi 0x17, 0 ; 23 24: bb 98 cbi 0x17, 3 ; 23 26: c3 9a sbi 0x18, 3 ; 24 28: b3 99 sbic 0x16, 3 ; 22 2a: 02 c0 rjmp .+4 ; 0x30 <main+0xe> 2c: c0 9a sbi 0x18, 0 ; 24 2e: fc cf rjmp .-8 ; 0x28 <main+0x6> 30: c0 98 cbi 0x18, 0 ; 24 32: fa cf rjmp .-12 ; 0x28 <main+0x6>
70 bytes
00000022 <main>: 22: ba 98 cbi 0x17, 2 ; 23 24: b9 98 cbi 0x17, 1 ; 23 26: c2 9a sbi 0x18, 2 ; 24 28: c1 9a sbi 0x18, 1 ; 24 2a: b8 9a sbi 0x17, 0 ; 23 2c: bc 9a sbi 0x17, 4 ; 23 2e: 90 e0 ldi r25, 0x00 ; 0 30: b2 9b sbis 0x16, 2 ; 22 32: 91 60 ori r25, 0x01 ; 1 34: b1 9b sbis 0x16, 1 ; 22 36: 90 61 ori r25, 0x10 ; 16 38: 88 b3 in r24, 0x18 ; 24 3a: 8e 7e andi r24, 0xEE ; 238 3c: 89 2b or r24, r25 3e: 88 bb out 0x18, r24 ; 24 40: f6 cf rjmp .-20 ; 0x2e <main+0xc>
56 bytes
00000022 <main>: 22: b8 9a sbi 0x17, 0 ; 23 24: bb 98 cbi 0x17, 3 ; 23 26: c3 9a sbi 0x18, 3 ; 24 28: b3 99 sbic 0x16, 3 ; 22 2a: 02 c0 rjmp .+4 ; 0x30 <main+0xe> 2c: c0 9a sbi 0x18, 0 ; 24 2e: fc cf rjmp .-8 ; 0x28 <main+0x6> 30: c0 98 cbi 0x18, 0 ; 24 32: fa cf rjmp .-12 ; 0x28 <main+0x6>
As we can see, there is no overhead due to the library in the above examples.
This is a header only library that doesn’t require any external dependency to work. It should be enough add the path to the include
directory to your project:
- Check the requirements section.
- Add the
include
directory to your include path. - Add
#include <avr/io.hpp>
to your source and enjoy it!
- ATtiny13A/13
- ATtiny25/45/85
- ATmega328P
avr-gcc
with at least-std=c++11
(Tests withavr-gcc 10.2
)- Optimization level greater or equal to
-O2
. This library is designed with the optimization-Os
in mind.