Suggestion: Flexible Pin Interface

This suggestion is an improvement for the core of Arduino library to allow unified GPIO pin access of the chip itself and certain expansion chips, in the same style as WiringPi does on Raspberry Pi.

Flexible Pin Interface (FPI) changes the behavior of pinMode(), digitalRead(), digitalWrite(), analogRead() and analogWrite() functions to place GPIO pins on the main chip and on expansion chips into a unified namespace. To do this, FPI adds one layer of abstraction between the aforementioned functions and the underlying hardware, allowing the appropriate GPIO driver of the pin to be resolved at run time.

Benefits of FPI interface:

  • This will allow most existing libraries work with both IO pins on the chip itself and on expansion chips without modifying it (like NewLiquidCrystal did to LiquidCrystal library.)
  • This will provide an unified, easy-to-understand interface for GPIO expanders (e.g. MCP23017), ADCs (e.g. ADC0832) and DACs (e.g. TLC5615)

A new function is introduced to insert GPIO drivers into the runtime:

class GPIO;

void insertGPIO(GPIO chip, uint8_t base);

extern GPIO Arduino;

All GPIO drivers, including the MCU GPIO pins represented by the global variable Arduino, inherits from a common base class:

class GPIO
{
  // Housekeeping
  virtual void begin(void);
  virtual uint8_t pinCount(void);

  // Pin manipulation methods
  virtual int32_t analogRead(uint8_t pin); // int32_t instead of int to allow more precision, for the DACs and ADCs
  virtual void analogWrite(uint8_t pin, int32_t value);
  virtual bool digitalRead(uint8_t pin);
  virtual void digitalWrite(uint8_t pin, int value);
  virtual void pinMode(uint8_t pin, uint8_t mode); // uint8_t instead of an enum to allow custom modes
};

The pin-related methods in this class and its derivitives are called with a pin number offset - the pin number given by the user, minus the pin umber base set when inserting the driver. When a driver is inserted, its begin method is called.

By default, the GPIO driver for the board is inserted at base address 0 to allow compatibility with legacy code.

Code examples:

  1. TLC5615 DAC
#include <SPI.h>
#include <TLC5615.h>

#define DAC_PIN 64
#define DAC_SS 10
TLC5615 dac(DAC_SS);

void setup()
{
  SPI.begin();
  insertGPIO(dac, DAC_PIN); // pin 64 = DAC output, can be manipulated by analogWrite()
}

void loop()
{
  analogWrite(DAC_PIN, (millis() & 0x3ff) << 2);
}
  1. PCF8574 IO expander and LCD
#include <Wire.h>
#include <LiquidCrystal.h>
#include <PCF8574.h>

#define EGPIO_ADDR 0x20
#define EGPIO_BASE 64
#define EGPIO(x) (EGPIO_BASE + (x))
PCF8574 egpio(EGPIO_ADDR);

#define LCD_RS EGPIO(0)
#define LCD_RW EGPIO(1)
#define LCD_EN EGPIO(2)
#define LCD_D4 EGPIO(7)
#define LCD_D5 EGPIO(6)
#define LCD_D6 EGPIO(5)
#define LCD_D7 EGPIO(4)
LiquidCrystal lcd(LCD_RS, LCD_RW, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

void setup()
{
  Wire.begin();
  insertGPIO(egpio, EGPIO_BASE); // Now we have GPIO pins 64 through 71 (inclusive) and LCD is there.
  lcd.begin(); // This is normal LiquidCrystal which is using new FPI interface.
  lcd.clear();
}

void loop()
{
}

How does that help vs just using something like:
PORTB = yourValue;

The idea would be to have your libraries be able to use classic digitalWrite/pinMode/etc, with pin numbers beyond the range of those that are actually in pins_arduino.h (ie, directly on the chip.)

Then, you'd do something like:

   // InsertGPIO(pinNumberStart, pinNumberEnd, handlerclass);
   InsertGPIO(100, 115, mcp23017Handler);  // add some pins on an IO expander chip.

and you could happily use your existing "LCD" class (or whatever) with pins 100-105 instead of having to give it "local" pins.

Alas, there are some problems with this:

  1. digitalWrite() and friends are already "bloated and slow" by many standards; this change would make them slower, even on local pins. (well, I suppose you could merge in a speed-up at the same time, but...)
  2. the handler to accomplish this could be arbitrarily slower, leading to the sort of "expectation mismatch" that also prevents faster versions of special-cased digitalWrite() from being implemented. (Intel Galileo put their digitalWrite() pins on an IO expander, leading to pin toggle rates of ~200Hz, and ... no one was very happy.)
  3. Some libraries just wouldn't work with that level of indirection (either because of speed, or because of side-effects that aren't present. (for example, Due/Zero already contains code to implement the "writing one to a port enables the internal pullups" side-effect from AVR.)

westfw:
The idea would be to have your libraries be able to use classic digitalWrite/pinMode/etc, with pin numbers beyond the range of those that are actually in pins_arduino.h (ie, directly on the chip.)

Then, you'd do something like:

   // InsertGPIO(pinNumberStart, pinNumberEnd, handlerclass);

InsertGPIO(100, 115, mcp23017Handler);  // add some pins on an IO expander chip.




and you could happily use your existing "LCD" class (or whatever) with pins 100-105 instead of having to give it "local" pins.

Alas, there are some problems with this:
1) digitalWrite() and friends are already "bloated and slow" by many standards; this change would make them slower, even on local pins. (well, I suppose you could merge in a speed-up at the same time, but...)
2) the handler to accomplish this could be arbitrarily slower, leading to the sort of "expectation mismatch" that also prevents faster versions of special-cased digitalWrite() from being implemented. (Intel Galileo put their digitalWrite() pins on an IO expander, leading to pin toggle rates of ~200Hz, and ... no one was very happy.)
3) Some libraries just wouldn't work with that level of indirection (either because of speed, or because of side-effects that aren't present. (for example, Due/Zero already contains code to implement the "writing one to a port enables the internal pullups" side-effect from AVR.)

The FPI indirection is not as "bloated" as you think - it have a similar semantic as virtual method call and with proper optimization it can be as efficient as a C++ virtual method call. The original digitalWrite() (now Arduino::digitalWrite()) and friends can be left untouched.

In fact with FPI the pins_arduino.h can be ditched (made private) too - since I would allow pin number overlaying you can get the lower end of pins overlaid with expansion chips and put onboard pins into a higher range. That is why I keep onboard pins available as a GPIO object so it can be overlaid just like others.

And if we start to implement FPI we can thin down the original implementation so it can run faster.

as efficient as a C++ virtual method call.

IIRC, that's not that good, on an AVR...

if we start to implement FPI we can thin down the original implementation so it can run faster.

Perhaps; while "bloated" to an outside observer, there's not a lot of actual wasted code in there. "Bloated" means 40 or 50 instructions instead of 1 (and one is a special case), not 300 instead of 200 instructions. And you can't really do a lot of "generic" things in 50 instructions.

Do you have testable, tweakable, code? Or are you just theorizing?

westfw:
IIRC, that's not that good, on an AVR...
Perhaps; while "bloated" to an outside observer, there's not a lot of actual wasted code in there. "Bloated" means 40 or 50 instructions instead of 1 (and one is a special case), not 300 instead of 200 instructions. And you can't really do a lot of "generic" things in 50 instructions.

Do you have testable, tweakable, code? Or are you just theorizing?

Theorizing for now, but since this new layer of indirection introduces just a small vector search it would add 20 instructions worst case scenario.

The C++ virtual call can be replaced with C struct in PROGMEM to further increase performance.

I remember doing this last year. I was running out of pins on 328P. Tried to add these virtual pins to run LCD on port extender, OP was calling flexible pins. I forgot why I stopped. Maybe it was too much modification to the core to be portable. Anyway, I later started using 1284P. Enough pins, memory, and 2 serial ports. I think, overall, it's going to be a good practice. I might try it again sometime when I go back to 328P to save a few bucks.

liudr:
I remember doing this last year. I was running out of pins on 328P. Tried to add these virtual pins to run LCD on port extender, OP was calling flexible pins. I forgot why I stopped. Maybe it was too much modification to the core to be portable. Anyway, I later started using 1284P. Enough pins, memory, and 2 serial ports. I think, overall, it's going to be a good practice. I might try it again sometime when I go back to 328P to save a few bucks.

I don't think portability would be a problem, as FPI inserts a layer of indirection between the existing pin-manipulation functions and user code, taking existing function signatures.

Can you still tell beginners how to mod core files to enable flex pins? Not as easily as just add a folder in sketchbooks to add 1284P. Thats what I mean. When you are ready to upgrade ide, be perpared to pick out your mod and reapply to new ide version. It is a bit hassle.

liudr:
Can you still tell beginners how to mod core files to enable flex pins? Not as easily as just add a folder in sketchbooks to add 1284P. Thats what I mean. When you are ready to upgrade ide, be perpared to pick out your mod and reapply to new ide version. It is a bit hassle.

A few files in the core libraries are replaced or patched and that is it. If the Foundation is willing I can submit the patch to be released in mainline for the next release.

This has been suggested before. Please see the developer forum.

The fundamental issue is that all pin implementations do not meet timing requirements. For instance, the 1-Wire device driver is impossible to implement using a GPIO implementation over I2C IO Expander. Having said that I must add that there is a large range of applications where a GPIO interface to abstract device driver or application/sketch implementation is possible (1-10 ms range).

Actually the Arduino pin functions are really slow compared to what is possible with the compiler. Cosa's implementation(s) are X5-X10 faster. The fastest pin operation is a single instruction (62.5 ns/16 MHz).

Cheers!

kowalski:
This has been suggested before. Please see the developer forum.

The fundamental issue is that all pin implementations do not meet timing requirements. For instance, the 1-Wire device driver is impossible to implement using a GPIO implementation over I2C IO Expander. Having said that I must add that there is a large range of applications where a GPIO interface to abstract device driver or application/sketch implementation is possible (1-10 ms range).

Actually the Arduino pin functions are really slow compared to what is possible with the compiler. Cosa's implementation(s) are X5-X10 faster. The fastest pin operation is a single instruction (62.5 ns/16 MHz).

Cheers!

For the most part this issue is non-issue. If you need absolutely FAST GPIO you are pretty much restricted to onboard GPIO and direct register manipulation. Anything that can get by with library functions are not that frequency sensitive.