Actions

Arduino-like pin definitions in C++

From Just in Time

Revision as of 11:41, 27 July 2014 by Danny (talk | contribs)
Work in Progress
This page describes a work in progress. All specifications may change and the final product may significantly deviate from what we describe here. It is very well possible that there will be no final product at all.
Warning.png

[[Revision timestamp::20140727114102|]]

We regularly design "bare" AVR devices (meaning: non-Arduino). While we're doing that, we often need to do some of the following:

  • When designing a single-sided PCB, completely re-assign many pins in order to avoid bridges.
  • Create a library for commonly used components like a hd44780 LCD display, or NRF24L01+ transceiver, making the pins that connect to these devices configurable.
  • Implement non-trivial signaling protocols while keeping the source code readable.

All of these scenarios require us to allocate pins or groups of pins to functions in such a way that we don't have to re-write our code when that allocation changes for some reason. At the same time we need to keep the code that actually uses these pins as clean as possible.

In AVR-land, there are two main schools of defining your pins:

  • The C-style way, using #defines for the register and bit positions of a pin, which results in code that performs well, but can be somewhat combersome and does not score high on readability.
  • The Arduino way, using digitalWrite( pin, value) and a single integer to designate a pin, which is arguably easier to read and easier to re-allocate, but can take many clock cycles to do something simple like setting a single bit.

This page describes our pin-definition library. This library allows for Arduino-like syntax for pin usage, while keeping the performance of 'manual' pin manipulations in C.

TL;DR

Pin-definitions is a header-only library, you can find the sources on GitHub, in file pin_definitions.hpp. Typical usage is as follows:

<source lang='cpp'>

  1. include "avr_utilities/pin_definitions.hpp"

// declare a single pin DECLARE_PIN( led1, D, 6); DECLARE_PIN( led2, B, 0);

// declare a consecutive range of pins in a single register DECLARE_PIN_GROUP( counter, D, 2, 3); // D2, D3 and D4 are a counter DECLARE_PIN_GROUP( rotary_encoder, D, 5, 2); // D5, D6 are some inpt, e.g. from a quadrature rotary encoder

void do_stuff() { // initialize data direction for the output pins. init_as_output(led1 | led2 | counter);

// start by setting both leds set( led1 | led2);

while (true) { // read the two input bits of the rotary encoder and // output the result to the bits of the counter write( counter, read(rotary_encoder)); if (read(rotary_encoder) == 10) { set( led1); reset( led2); } else { set( led2); reset( led1); } } } </source>

Defining pins on AVR and Arduino

AVR C style

Take a look at how a randomly chosen library (for driving HD44780 LCD displays) defines which pins have which function: <source lang="cpp">

  1. define LCD_PORT PORTA
  2. define LCD_DATA0_PORT LCD_PORT
  3. define LCD_DATA1_PORT LCD_PORT
  4. define LCD_DATA2_PORT LCD_PORT
  5. define LCD_DATA3_PORT LCD_PORT
  6. define LCD_DATA0_PIN 0
  7. define LCD_DATA1_PIN 1
  8. define LCD_DATA2_PIN 2
  9. define LCD_DATA3_PIN 3
  10. define LCD_RS_PORT LCD_PORT
  11. define LCD_RS_PIN 4
  12. define LCD_RW_PORT LCD_PORT
  13. define LCD_RW_PIN 5
  14. define LCD_E_PORT LCD_PORT
  15. define LCD_E_PIN 6

</source> There's nothing wrong with that library: this is how almost all AVR code defines their pins. Typically, each pin function is defined by using two (sometimes only one) #defines: one to define the port and one to define which bit of the port is being used. For an overly simple example. Suppose we want to use bit 5 of port B to control led1, we'd typically do this:

<source lang="cpp">

  1. define LED1_PORT PORTB
  2. define LED1_PIN 5

// ...

void f() {

   // ...
   // flash led 1
   LED1_PORT &= ~_BV( LED1_PIN);
   _delay_ms( 50);
   LED1_PORT |= _BV( LED1_PIN);
   // ...

} </source>