13
\$\begingroup\$

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
}
\$\endgroup\$
9
  • \$\begingroup\$ What microcontroller are we talking here? \$\endgroup\$
    – Majenko
    Commented Sep 4, 2011 at 16:20
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Sep 4, 2011 at 17:26
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Oli Glaser
    Commented Sep 4, 2011 at 17:42
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Majenko
    Commented Sep 4, 2011 at 22:10
  • 1
    \$\begingroup\$ I did a talk on meeting C++ in Berlin about my work on this subject. It can be found on youtube: youtube.com/watch?v=k8sRQMx2qUw Since then I switched to a slightly different approach, but the talk might still be interesting. \$\endgroup\$ Commented May 11, 2015 at 8:33

5 Answers 5

3
\$\begingroup\$

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.
\$\endgroup\$
5
\$\begingroup\$

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);
\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ answered in the question body \$\endgroup\$ Commented Sep 4, 2011 at 19:09
2
\$\begingroup\$

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.

\$\endgroup\$
8
  • 3
    \$\begingroup\$ A quote from the question: "I am not looking for 'hey, you might do it this way' type of answers" ... \$\endgroup\$ Commented Sep 5, 2011 at 6:12
  • \$\begingroup\$ 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. \$\endgroup\$
    – supercat
    Commented Sep 5, 2011 at 23:48
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Sep 6, 2011 at 5:33
  • \$\begingroup\$ @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. \$\endgroup\$
    – supercat
    Commented Sep 6, 2011 at 15:55
  • \$\begingroup\$ @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. \$\endgroup\$
    – supercat
    Commented Sep 6, 2011 at 16:02
1
\$\begingroup\$

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).

\$\endgroup\$
1
  • \$\begingroup\$ 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 \$\endgroup\$ Commented Aug 18, 2017 at 10:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.