Actions

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

From Just in Time

 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
[[File:Lotsapins.jpg|right|300px]]
 
[[File:Lotsapins.jpg|right|300px]]
Let's start with a manifesto:
+
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:
* Hard coding pin definitions is evil
+
* Hard coding pin definitions–just using bit numbers, and port names at the places in code where you use pins–is evil
 
* 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
 
* 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
 
* Arduino style pin definitions using functions like ''digitalWrite'' facilitate readable code, but are really, really slow.
 
* Arduino style pin definitions using functions like ''digitalWrite'' facilitate readable code, but are really, really slow.
  
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. In short, it turns code like this:
+
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">
 
<source lang="cpp">
 
#define LED_DDR        DDRC
 
#define LED_DDR        DDRC
Line 21: Line 21:
 
into code like the following, while maintaining the performance of the C-style original:
 
into code like the following, while maintaining the performance of the C-style original:
 
<source lang="cpp">
 
<source lang="cpp">
DECLARE_PIN( led1, D, 6);
+
PIN_TYPE( D, 6) led1;
  
 
make_output( led1);
 
make_output( led1);
 
set( led1);
 
set( led1);
reset( led1);
+
clear( led1);
 
</source>
 
</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 96: 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 126: 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 170: Line 169:
  
 
// declare a single pin
 
// declare a single pin
DECLARE_PIN( led1, D, 6);
+
PIN_TYPE( D, 6) led1;
DECLARE_PIN( led2, B, 0);
+
PIN_TYPE( B, 0) led2;
  
 
// declare a consecutive range of pins in a single register
 
// 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( 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
+
DECLARE_PIN_GROUP( rotary_encoder, D, 5, 2); // D5, D6 are some input, e.g. from a quadrature rotary encoder
  
 
void do_stuff()
 
void do_stuff()
Line 225: Line 224:
  
 
==Introducing pin_definitions.hpp==
 
==Introducing pin_definitions.hpp==
With AVR-GCC we have a fully functional ''C++'' compiler at our disposal. It should be possible to use this fact to create a library that allows Arduino's intuitive digitalWrite function with native C performance. With this in mind, we wrote pin_definitions.hpp. This pin definition library makes extensive use of meta programming. Its implementation may not be easy to read if you're not familiar with C++ template metaprogramming, but if you only use the library, you should find it very intuitive&mdash;plus I've done my stinking best to not let any compilation errors end up deep inside the source code of this library...
+
With AVR-GCC we have a fully functional ''C++'' compiler at our disposal. It should be possible to use this fact to create a library that allows Arduino's intuitive digitalWrite function with native C performance. pin_definitions.hpp was written with that goal in mind. Although it makes heavy use of template metaprogramming, if you only use the library, you should find it very intuitive&mdash;plus I've done my stinking best to not let any compilation errors end up deep inside the source code of this library...
  
 
===Usage===
 
===Usage===
Line 233: Line 232:
 
</source>
 
</source>
  
Then you can declare your pins and pin groups. A pin is a single input- or output pin on the AVR, for example pin B5 (that would be pin number 13 on an Arduino). A pin is identified by a port (depending on your AVR this is any port from port A to port F) and a bit number (0-7) on that port. A pin group is a range of consecutive pins on the same port, for example B0-B4. Pin groups are identified by the port and bit number of the first pin and the pin count:
+
Then you can declare your pins and pin groups. A pin is a single input- or output pin on the AVR, for example pin B5 (that would be [http://playground.arduino.cc/Learning/Pins pin number 13 on an Arduino]). A pin is identified by a port (depending on your AVR this is any port from port A to port F) and a bit number (0-7) on that port. A pin group is a range of consecutive pins on the same port, for example B0-B4. Pin groups are identified by the port and bit number of the first pin and the pin count:
  
 
<source lang="cpp">
 
<source lang="cpp">

Latest revision as of 22:37, 16 November 2016