arduino / ArduinoCore-avr

The Official Arduino AVR core
https://www.arduino.cc
1.22k stars 1.04k forks source link

Virtual Pins #1

Open neu-rah opened 6 years ago

neu-rah commented 6 years ago

Virtualization of pins, allowing the environment to be extended with external pin over SPI or I²C and making them transparent to user and libraries.

This can be implemented on multiple ways:

1 - Add an extra burden to the pinMode/digitalRead/digital by installing an if (pin>nr_of_digital_pins) «call virtual pins handler here»

2 - Extend the pin tables with a predefined set of allowed extra virtual pins, then the actual if (port == NOT_A_PIN) already present on the pin functions could be used to call the virtual pins handler with no extra burden to pin functions.

about the virtual pins handler, i sugest it to be just a function address (x3) pointing to a dumb function (just return).

this way virtual pins would have only the extra 3 function pointers and a simple return function allowing it to be implemented as a library

also all this can be conditionally compiled... if there's a way of introducing preprocessor defines to the compiler environment (-D parameter), last time i checked there was not an easy way of doing it on arduino IDE.

option 2 has the advantage of being compatible with code that checks the pin tables https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Arduino.h#L177

I leave this to consideration and expect feedback of pros and cons before advancing.

I've done essays by extending the pin tables and they worked very well.

thanks

mjbcopland commented 6 years ago

Assuming I understand you correctly, this is pretty easy to do by overloading the pin manipulation functions. Here's a library I've put together which provides a wrapper around I/O member functions.

For example, SparkFun has a library for the SX1509 I/O expander used with something like io.digitalWrite(0, HIGH). My library provides a wrapper around the SparkFun class and allows for the syntax digitalWrite(io[0], HIGH) by overloading the global function with virtual pin objects. Is this the sort of thing you were thinking of?

Perhaps compare SparkFun's example with my virtual pin adaptation.

neu-rah commented 6 years ago

kind of, but doing this way does not help libraries and either virtual pins can not be passed to third party library object construction, thinking in the standard LiquidCrystal

with virtual pins embedded we can initialize the standard LCD with the virtual pins and therefor (with the appropriate virtual pins SPI drivers) use the same library over an SPI interface

that lib had no need to be changed to support SPI or I²C or whatever bus we want to support

mjbcopland commented 6 years ago

Ah, I see what you mean now. Something like this? It treats negative pin numbers as being virtual, and functions are 'attached' to pins in the same way that attachInterrupt() works.

There's currently no bounds checking (but the existing I/O functions don't do this either), no analogRead/Write (although that could be easily added in the same way), and is a bit tedious having to individually define multiple function pointers per pin, but I feel it otherwise comes pretty close to your original idea.

An example sketch could look something like this

VirtualPin pin = {
  [](uint8_t mode){ Serial.print("pinMode "); Serial.println(mode); },
  [](uint8_t val){ Serial.print("digitalWrite "); Serial.println(val); },
  [](){ Serial.println("digitalRead"); return -1; },
};

void setup() {
  Serial.begin(115200);
  attachVirtualPin(-1, &pin);
  pinMode(-1, OUTPUT);
}

void loop() {
  digitalWrite(-1, HIGH);
  delay(1000);
  digitalWrite(-1, LOW);
  delay(1000);
}

It could be cleaned up with polymorphism similar to my template approach from above, but that requires migrating the existing functions from C to C++ (not too difficult, but a larger change to the repository). Parts of STL's <functional> could also make things easier, but that's substantially more work to port to AVR.

My only concern is that I've seen -1 sometimes used to represent a null pin. Perhaps virtual pins should start at -2? This could get rather messy.

I'm not sure how useful this would be to the average user, but wrapping the changes in a preprocessor macro like you've suggested could be a simple solution.

As an aside, you can add extra command line arguments to platform.local.txt. It's a little tedious as the file is tucked away in the platform directory and any changes will affect every sketch, but afaik it's the easiest way to modify the build environment.

neu-rah commented 6 years ago

very interesting, I was thinking on the possibility of using negative pin numbers to express inverted logic, but that would require as you told some burden to the actual pin functions, and I understand its key and sensitive area.

The negative pins schema would involve an extra if there, and that can be shared to implement vpins. So there's a point in favor of an extra if added to the fact that no table extension had to be done, saving FLASH memory.

I would like pin 0 to mean "not used" and therefor ignored, but its unfortunate it is already taken.

neu-rah commented 6 years ago

I've also done a little survey over my local libraries to have an idea how many and what type of library does use pin tables (like digitalPinToPort), it happens to be on some very specialized graphics library like U8g2, and I guess that libraries like that wont be suitable for use over vpins.

So, not extending the pin tables will leave outside those kind of libraries, but be usable for simper ones like the LCD and for the user.

SPI shift registers were working very well doing both input and output and are transparent to the user, installing a button there or blinking a led are the exact same as for local pins. Apart the vpins initialization describing the geometry.

per1234 commented 6 years ago

Previously proposed at https://github.com/arduino/Arduino/issues/6175

neu-rah commented 6 years ago

thanks for linking it, the previous was on the bundle, now we have the avr-core separated and a much better discussion space.

neu-rah commented 6 years ago

also this on pin functions:

    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    volatile uint8_t *reg, *out;

    if (port == NOT_A_PIN) return;

can't it be replaced by:

    uint8_t port = digitalPinToPort(pin);
    if (port == NOT_A_PIN) return;
    uint8_t bit = digitalPinToBitMask(pin);
    volatile uint8_t *reg, *out;

avoiding one index to determine bit?

neu-rah commented 6 years ago

I can see that no checking against NUM_DIGITAL_PINS is currently done by the pin functions and therefor an arbitrary pin number can be successfully compiled and uploaded. Guess this was done to reduce the footprint of pin functions. Therefor adding a check can be against the arduino team philosophy and interests, even if having the benefits of preventing erratic behavior. It would be nice if we can have an official position on this (anyone?)

neu-rah commented 6 years ago

This schema would also allow all sort of software modified pins, in ex: providing a virtual pin that is a software debounced version of a physical pin, allowing software debounce to be composed a posterior and provided to libraries when needed.