avrIO icon indicating copy to clipboard operation
avrIO copied to clipboard

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

** avrIO [[https://github.com/ricardocosme/avrIO/actions?query=workflow%3A%22tests%22][https://github.com/ricardocosme/avrIO/workflows/tests/badge.svg?branch=main]] [[https://github.com/ricardocosme/avrIO/actions?query=workflow%3A%22demos%22][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 [[file:demo/c++11/led.cpp][demo/c++11/led.cpp]]) as also be used to develop flexible and concise APIs(see [[file:demo/c++20/api.cpp][demo/c++20/api.cpp]]).

**** Single I/O port pin

#+BEGIN_SRC C++ Pb0 led{output}; Pb3 sw{pullup};

while(true) led.high(sw.is_low()); #+END_SRC [[file:demo/c++11/led.cpp][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 #+BEGIN_SRC C++ 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))); #+END_SRC [[file:demo/c++17/leds.cpp][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 [[file:demo/c++11/leds.cpp][demo/c++11/leds.cpp]].

**** Developing API

#+BEGIN_SRC C++ 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());

} #+END_SRC [[file:demo/c++20/api.cpp][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 [[file:demo/c++11/api.cpp][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

#+BEGIN_SRC C++ //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. #+END_SRC 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 [[https://github.com/ricardocosme/avrIO][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 #+BEGIN_SRC 00000022

: 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> #+END_SRC

**** demo/c++11/leds.cpp [-std=c++11] 70 bytes #+BEGIN_SRC 00000022

: 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> #+END_SRC

**** demo/c++20/api.cpp [-std=c++20] 56 bytes #+BEGIN_SRC 00000022

: 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> #+END_SRC

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.