13

I am looking for C++ abstractions for hardware I/O points or pins. Things like in_pin, out_pin, inout_pin, maybe open_collector_pin, etc.

I surely can come up with such a set of abstractions myself, so I am not looking for 'hey, you might do it this way' type of answers, but rather the 'look at this library that has been used in this and this and this project'.

Google did not turn up anything, maybe because I don't know how others would call this.

My aim is to build I/O libraries that are based on such points, but also provide such points, so it would be easy to for instance connect an HD44780 LCd to either the IO pins of the chip, or an I2C (or SPI) I/O extender, or any other point that can somehow be controlled, without any change to the LCD class.

I know this is on the electronics/software edge, sorry if it does not belong here.

@leon: wiring That is a big bag of software, I will need to look closer. But is seems they do not use a pin abstraction like I want. For instance in the keypad implementation I see

digitalWrite(columnPins[c], LOW);   // Activate the current column.

This implies that there is one function (digitalWrite) that knows how to write to an I/O pin. This makes it impossible to add a new type of I/O pin (for instance one that is on an MCP23017, so it has to be written via I2C) without rewriting the digitalWrite function.

@Oli: I googled an Arduino IO example, but the seem to use about the same approach as the Wiring library:

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}
Wouter van Ooijen
  • 48,407
  • 1
  • 63
  • 136
  • What microcontroller are we talking here? – Majenko Sep 04 '11 at 16:20
  • That is irrelevant; for a particular microcontroller the io pins of that uC wil will implement the appropriate interfaces. But this is for C++, so think of 32-bit chips like ARM, Cortex and MIPS. – Wouter van Ooijen Sep 04 '11 at 17:26
  • 1
    I've never used one, but doesn't Arduino abstract all the pins like this? You may (or may not) get some useful info looking at the way they have done things. – Oli Glaser Sep 04 '11 at 17:42
  • That is exactly the Arduino interface quoted there ;) And yes, it does matter which uC you are using as different ones will provide a different interface to the hardware. For instance the PIC uses memory mapped registers, and I often do something like: unsigned char *MyTRIS = ⧍ - often in association with a struct: struct pin { unsigned char *tris; unsigned char *lat; unsigned char *port; unsigned char pinno; }; struct pin pins[8] = { {&TRISA,&LATA,&PORTA,0}, {&TRISB,&LATB,&PORTB,3}...}; That is specific to PIC and I doubt it would work on ARM, or Atmel, etc. – Majenko Sep 04 '11 at 22:07
  • 1
    And as for rewriting the digitalWrite function - look at "overloading" in C++. I have just a few moments ago written an overloaded digitalWrite function for an IO expander board for the Arduino. As long as you use different parameters (I replaced the first "int" with a "struct") it will pick your digitalWrite in preference to the default one. – Majenko Sep 04 '11 at 22:10
  • Though it doesn't answer your question, you may want to take a look at the "Technical Report on C++ Performance". It contains several sections on interfacing to hardware. http://www.open-std.org/jtc1/sc22/wg21/docs/18015.html – starblue Sep 05 '11 at 11:18
  • @starblue: nice reading stuff, but nothing on the subject I am interested in. Which might in itself be an indication :( – Wouter van Ooijen Sep 05 '11 at 13:59
  • I have not found anything like I was looking for, so I'll try for myself without any 'prior art' input... I've started a blog here http://embeddedrelated.com/showarticle/101.php about the construction of such a library, and to serve as an introduction to the use of OO on (top-end) microcontrollers liek the LPC1xxxx. – Wouter van Ooijen Oct 10 '11 at 14:41
  • 1
    I did a talk on meeting C++ in Berlin about my work on this subject. It can be found on youtube: https://www.youtube.com/watch?v=k8sRQMx2qUw Since then I switched to a slightly different approach, but the talk might still be interesting. – Wouter van Ooijen May 11 '15 at 08:33

5 Answers5

5

Allow me to shamelessly plug my open source project https://Kvasir.io . The Kvasir::Io portion provides pin manipulation functions. You must first define your pin using a Kvasir::Io::PinLocation like so:

constexpr PinLocation<0,4> led1;    //port 0 pin 4
constexpr PinLOcation<0,8> led2;

Notice that this does not actually use RAM because these are constexpr variables.

Throughout your code you can use these pin locations in 'action factory' functions like makeOpenDrain, set, clear, makeOutput and so on. An 'action factory' does not actually execute the action, rather it returns a Kvasir::Register::Action which can be executed using Kvasir::Register::apply(). The reason for this is that apply() merges the actions passed to it when they act on one and the same register so there is an efficiency gain.

apply(makeOutput(led1),
    makeOutput(led2),
    makeOpenDrain(led1),
    makeOpenDrain(led2));

Since the creation and merging of actions is done at compile time this should yield the same assembler code as the typical hand coded equivalent:

PORT0DIR |= (1<<4) | (1<<8);
PORT0OD |= (1<<4) | (1<<8);
odinthenerd
  • 238
  • 1
  • 4
  • 11
3

The Wiring project uses abstraction like that:

http://wiring.org.co/

and the compiler is written in C++. You should find plenty of examples in the source code. The Arduino software is based on Wiring.

Leon Heller
  • 38,774
  • 2
  • 60
  • 96
3

Short answer: sadly, there is no library to do what you want. I've done it myself numerous times but always in non open-source projects. I'm considering putting something up on github but I'm not sure when I can.

Why C++?

  1. Compiler is free to use dynamic word-size expression evaluation. C propagates to int. Your byte mask/shift can be done faster/smaller.
  2. Inlining.
  3. Templatizing operations lets you vary word size and other properties, with type-safety.
PeterJ
  • 17,131
  • 37
  • 56
  • 91
2

In C++, it's possible to write a class so that you can use I/O ports as though they were variables, e.g.

  PORTB = 0x12;  /* Write to an 8-bit port */
  if (RB3) LATB4 = 1;  /* Read one I/O bit and conditionally write another */

without regard for the underlying implementation. For example, if one is using a hardware platform which doesn't support bit-level operations but does support byte-level register operations, one could (probably with the aid of some macros) define a static class IO_PORTS with an inline read-write properties called bbRB3 and bbLATB4, such that the last statement above would turn into

  if (IO_PORTS.bbRB3) IO_PORTS.bbLATB4 = 1;

which would in turn be processed into something like:

  if (!!(PORTB & 8)) (1 ? (PORTB |= 16) : (PORTB &= ~16));

A compiler should be able to notice the constant expression in the ?: operator and simply include the "true" part. It might be possible to reduce the number of properties created by having the macros expand to something like:

  if (IO_PORTS.ppPORTB[3]) IO_PORTS.ppPORTB[4] = 1;

or

  if (IO_PORTS.bb(addrPORTB,3)) IO_PORTS.bbPORTB(addrPORTB,4) = 1;

but I'm not sure a compiler would be able to in-line the code as nicely.

I by no means wish to imply that using I/O ports as though they are variables is necessarily a good idea, but since you mention C++ it's a useful trick to know. My own preference in C or C++, if compatibility with code that uses the aforementioned style was not required, would probably be to define some type of macro for each I/O bit, and then define macros for "readBit", "writeBit", "setBit", and "clearBit" with the proviso that the bit-identifying argument passed to those macros must be the name of an I/O port intended for use with such macros. The above example, for instance, would be written as

  if (readBit(RB3)) setBit(LATB4);

and translated as

  if (!!(_PORT_RB3 & _BITMASK_RB3)) _PORT_LATB4 |= _BITMASK_LATB4;

That would be a bit more work for the preprocessor than would the C++ style, but it would be less work for the compiler. It would also allow optimal code generation for many I/O implementations, and decent code implementation for almost all.

supercat
  • 45,939
  • 2
  • 84
  • 143
  • 3
    A quote from the question: "I am not looking for 'hey, you might do it this way' type of answers" ... – Wouter van Ooijen Sep 05 '11 at 06:12
  • I guess I'm not clear quite what you are looking for. Certainly I would expect that many people who are interested in classes for I/O pin reconstruction would also be interested to know that using properties one can make code which is written for one style of I/O use just about anything else. I've used properties to make a statement like "LATB3=1;" send out an I/O request to a TCP stream. – supercat Sep 05 '11 at 23:48
  • I tried to be clear in my question: I want to be able to accomodate new types of IO pins without rewriting the code that uses IO pins. You write about user-defined type conversions and assignment operators, which are surely interesting, I use them all the time, but not a solution to my problem. – Wouter van Ooijen Sep 06 '11 at 05:33
  • @Wouter van Ooijen: What "new types of I/O pins" would you be anticipating? If the source code is written with syntax like "if (BUTTON_PRESSED) MOTOR_OUT = 1;", I would expect that for just about any mechanism by which the processor might read a button control or a motor one could write a library so the above source code would turn on the motor if the button is pushed. Such a library might not represent the most efficient way of turning on the motor, but it should work. – supercat Sep 06 '11 at 15:55
  • @Wouter van Ooijen: One could perhaps improve efficiency if one required that the source code invoke an UPDATE_IO() or UPDATE_INPUTS() macro sometime before reading any input, and perform an UPDATE_IO() or UPDATE_OUTPUTS() some time after any output, with the semantics that inputs could be sampled either at the code that reads them, or at the previous UPDATE_INPUTS()/UPDATE_IO() invocation. Likewise outputs could either occur immediately or be deferred. If an I/O is implemented using something like a shift register, deferring actions would allow multiple operations to be consolidated. – supercat Sep 06 '11 at 16:02
  • what "new types of I/O pins": for instance pins on an I2C or SPI I/O extender, think MCP23017. Or, to be realy perverse, an MCP23017 which in turn (via its IO pins) controls an MPC23S17. (@ supercat, but SE eliminates that at the beginning!) – Wouter van Ooijen Sep 06 '11 at 19:43
  • improve efficiency: of course, but that must be done on a more fine-grained level. Not *all* I/O should be flushed because 2 remote pins must be updated. But again, I already have those ideas! I posted the question because I am looking for an OO library that has implemented those (or alternative) ideas and used them in non-trivial applications. – Wouter van Ooijen Sep 06 '11 at 19:47
  • @Wouter van Ooijen: There are always trade-offs between source code verbosity, compiled-code efficiency, and adaptability to hardware changes. I tend to find that the best technique is usually to write or adapt an I/O abstraction layer for each project (it would be silly to have an ENERGIZE_LIFT_MOTOR() macro in a general-purpose I/O library, when 99% of my projects won't have any sort of lift motor). – supercat Sep 06 '11 at 22:26
1

If you're looking for something really awesome to abstract the hardware, and you're confident in your C++ skills, then you should try this pattern:

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

I've used it in one attempt to abstract hardware for a Cortex-M0 chip. I haven't yet written anything about this experience (I will do it someday), but believe me it has been very useful because of its static polymorphic nature: the same method for different chips, at no cost (compared with dynamic polymorphism).

  • In the years since this post I stettled on separate "classes" for pin_in, pin_out, pin_oc and pin_in_out. For optimal performance (size and speed) I use static classes, passed as template parameters. [I talked about this on Meeting C++ in Berlin](https://www.google.nl/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cad=rja&uact=8&ved=0ahUKEwjKudzhxuDVAhWMmbQKHTCNDfgQtwIIOTAC&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dk8sRQMx2qUw&usg=AFQjCNECNndNmZziO_-KxzVpvaSjqEZhdw) – Wouter van Ooijen Aug 18 '17 at 10:18