Actions

Difference between revisions of "Arduino-like pin definitions in C++"

From Just in Time

 
(19 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{WIP}}
 
 
[[File:Lotsapins.jpg|right|300px]]
 
[[File:Lotsapins.jpg|right|300px]]
We regularly design "bare" AVR devices (meaning: non-Arduino). While we're doing that, we often need to do some of the following:
+
In all non-trivial AVR projects, at some point in the software you need to define which pin is connected to what other device. Let's start with a manifesto:
* When designing a single-sided PCB, completely re-assign many pins in order to avoid bridges.
+
* Hard coding pin definitions–just using bit numbers, and port names at the places in code where you use pins–is evil
* Create a library for commonly used components like a hd44780 LCD display, or NRF24L01+ transceiver, making the pins that connect to these devices configurable.
+
* Raw AVR pin definitions, using the preprocessor to define ports and bit indexes can become cumbersome, makes for less readable ''set'' and ''reset'' functions and blocks optimization opportunities
* Implement non-trivial signaling protocols while keeping the source code readable.
+
* Arduino style pin definitions using functions like ''digitalWrite'' facilitate readable code, but are really, really slow.
  
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.
+
This page describes our [https://github.com/DannyHavenith/avr_utilities pin-definition library]. This library allows for Arduino-like syntax for pin usage, while keeping the performance of 'manual' pin manipulations in C. In short, it turns code like this:
 +
<source lang="cpp">
 +
#define LED_DDR        DDRC
 +
#define LED_PORT      PORTC
 +
#define LED_PIN        5
 +
// set up
 +
LED_DDR |= _BV(LED_PIN);
 +
   
 +
// assert pin
 +
LED_PORT |= _BV(LED_PIN);
  
In AVR-land, there are two main schools of defining your pins:
+
// clear pin
* The C-style way, using ''#define''s 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.
+
LED_PORT &= ~_BV(LED_PIN);
* 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.
+
</source>
 +
into code like the following, while maintaining the performance of the C-style original:
 +
<source lang="cpp">
 +
PIN_TYPE( D, 6) led1;
  
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.
+
make_output( led1);
 +
set( led1);
 +
clear( led1);
 +
</source>
  
 
==Pin definitions, the state of the art==
 
==Pin definitions, the state of the art==
 
===Raw AVR===
 
===Raw AVR===
Most microcontroller projects revolve in some way around toggling signals on the output pins or reading values from input pins in some fashion. Concentrating on output for now, changing the output signals is done by manipulating individual bits in the AVR memory space that have been mapped onto these pins. So for example, if I wanted to make sure that pin 28 (the "top right" pin) of my atmega88 is in a "high" state, I would have to know that that pin is mapped on bit 5 in port C and write the following code:
+
Most microcontroller projects revolve in some way around toggling signals on the output pins or reading values from input pins in some fashion. Concentrating on output for now, changing the output signals is done by manipulating individual bits in the AVR memory space that have been mapped onto these pins. So for example, if I wanted to make sure that pin 28 (the "top right" pin, which&ndash;confusingly&ndash;is pin 19 in Arduino's digital pin functions) of my atmega88 is in a "high" state, I would have to know that that pin is mapped on bit 5 in port C and write the following code:
 
<source lang="cpp">
 
<source lang="cpp">
 
     PORTC |= 1 << 5; // set pin 5 in port C
 
     PORTC |= 1 << 5; // set pin 5 in port C
Line 82: Line 96:
 
</source>
 
</source>
  
In summary, for each pin function we need to define the pin port, the bit position of that pin in the port and typically also the data direction register. As can be seen above, setting or resetting pins requires code that is not the easiest to read, although I guess you get used to statements like ''FIREWORKS_PORT &= ~(1<<FIREWORKS_PIN)'' (or the slightly more readable ''FIREWORKS_PORT &= ~_BV(FIREWORKS_PIN)'').
+
In summary, for each pin function we need to define the pin port, the bit position of that pin in the port and typically also the data direction register. As can be seen above, setting or resetting pins requires code that is not the easiest to read, although I guess you get used to statements like <code>''FIREWORKS_PORT &= ~(1<<FIREWORKS_PIN)''</code> (or the slightly more readable <code>''FIREWORKS_PORT &= ~_BV(FIREWORKS_PIN)''</code>).
 
 
  
 
===Arduino===
 
===Arduino===
Line 112: Line 125:
 
Changing the led pin is a matter of just adapting the initialization of led1. At the same time, making the output pin high or low is about as readable as it could get: ''digitalWrite(led1, LOW)''. You just know what that line does.
 
Changing the led pin is a matter of just adapting the initialization of led1. At the same time, making the output pin high or low is about as readable as it could get: ''digitalWrite(led1, LOW)''. You just know what that line does.
  
However, this readability comes at a significant cost. If we look at the implementation of digitalWrite<ref>[https://code.google.com/p/arduino/source/browse/trunk/hardware/arduino/cores/arduino/wiring_digital.c wiring_digital.c], arduino sources at Google Code</ref> we can see that cost:
+
However, this readability comes at a significant cost. If we look at the implementation of digitalWrite<ref>[https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_digital.c#L138 wiring_digital.c], arduino sources at GitHub</ref> we can see that cost:
  
 
<source lang="cpp">
 
<source lang="cpp">
Line 145: Line 158:
 
</source>
 
</source>
  
This will easily take 50 clock cycles<ref>[http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/ "To use or not use digitalWrite"]</ref>! That's a bit steep, if all we want is to change a single bit value. If we know which pin we're going to set at compile time, changing a single pin value should take at most 2 clock cycles (1 on an Attiny).
+
This will easily take 50 clock cycles!<ref>[http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/ "To use or not use digitalWrite"]</ref> That's a bit steep, if all we wanted is to change a single bit value. If we know which pin we're going to set at compile time, changing a single pin value should take at most 2 clock cycles (1 on an Attiny).
  
 
It would be nice if we could combine the readability and the single pin definition of Arduino's digitalWrite()-function with the performance of the raw AVR approach. pin_definitions is a library that offers just that.
 
It would be nice if we could combine the readability and the single pin definition of Arduino's digitalWrite()-function with the performance of the raw AVR approach. pin_definitions is a library that offers just that.
Line 156: Line 169:
  
 
// declare a single pin
 
// declare a single pin