/avrIO

C++11/17/20 library for manipulating I/O port pins and I/O registers of AVR8

Primary LanguageC++MIT LicenseMIT

avrIO https://github.com/ricardocosme/avrIO/workflows/tests/badge.svg?branch=main https://github.com/ricardocosme/avrIO/workflows/demos/badge.svg?branch=main

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).

Single I/O port pin

Pb0 led{output};
Pb3 sw{pullup};

while(true) led.high(sw.is_low());

demo/c++11/led.cpp

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.

Multiples I/O port pins

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)));

demo/c++17/leds.cpp

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.

Developing API

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());
}

demo/c++20/api.cpp

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.

I/O registers

//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.

Performance

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.

demo/c++11/led.cpp [-std=c++11]

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>

demo/c++11/leds.cpp [-std=c++11]

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>

demo/c++20/api.cpp [-std=c++20]

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.

How to use it?

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:

  1. Check the requirements section.
  2. Add the include directory to your include path.
  3. Add #include <avr/io.hpp> to your source and enjoy it!

Supported microcontrollers

  1. ATtiny13A/13
  2. ATtiny25/45/85
  3. ATmega328P

Requirements

  1. avr-gcc with at least -std=c++11 (Tests with avr-gcc 10.2)
  2. Optimization level greater or equal to -O2. This library is designed with the optimization -Os in mind.