8

(Let's just skip the suggestions that it's the wrong idea and interrupts for a while...).

Is there a way to replace the digitalWrite() method (for example) so I can add something more to happen in there? But the following rules must be obeyed:

  • A call to digitalWrite(pin, value) must invoke the new method.
  • A call to digitalWrite(pin, value) from a different library included in my code (SD.h for example) must invoke the new method.
  • None of the Arduino files can be modified (I don't want to do it on every Arduino version upgrade).
  • Everything must be done in a header file that I can include to use the new function.

I have tried this and it works, but only in my code, not in external libraries that I include after this header:

#include <Arduino.h>

class PinsHook {
  public:
    static void digitalWriteHook(uint8_t pin, uint8_t value) {
      digitalWrite(pin, value);
      if (pin == 8) {
        Serial.print("Pin ");
        Serial.print(pin);
        Serial.print(" changed to ");
        Serial.println(value);
      }
    }
};

#define digitalWrite(pin, value) PinsHook::digitalWriteHook(pin, value)
dda
  • 1,595
  • 1
  • 12
  • 17
P.W.
  • 317
  • 6
  • 15

7 Answers7

4

Not sure about the Arduino IDE (I don't have it installed right now) but using UECIDE it's as simple as defining a new digitalWrite function in your code. When the whole shebang gets linked together the function in your code overrides the function in the libraries.

void setup() {
    pinMode(13, OUTPUT);
}

void loop() {
    digitalWrite(13, HIGH);
    delay(100);
    digitalWrite(13, LOW);
    delay(800);
}

void digitalWrite(int pin, int level) {
    // Not gonna do anything!
}

Comment out the digitalWrite function and the LED blinks. Uncomment it and it doesn't blink any more.

Majenko
  • 105,851
  • 5
  • 82
  • 139
2

Did you try with a C macro?

#define digitalWrite(a, b) \
(pre(), digitalWrite(a, b), post())

this evaluates to the retval of post()

you can omit either the pre() or the post() functions/macros.

This needs to be placed somewhere that is included whenever the header for digitalWrite is included, so that it can override it.

The only other alternative is to become creative with the library binary.

Or, just be reasonable and modify the source code. It's specifically available so that people can modify it.

Wrt not repeating the modification every time there is a new Arduino release, that should be avoidable. If you build the environment from the git sources, you can maintain your patch by merging/rebasing onto the master branch.

Igor Stoppa
  • 2,125
  • 1
  • 15
  • 20
1

Arduino uses the avr compiler to compile its standard library into a core.a file. Due to the way the avr-gcc linker works and the order in which Arduino specifies the link order, you're able to "override" the behavior of core functions by copying the file that contains the method you want to alter into your project.

The compiler compiles a single high-level language file (C language, for example) into a single object module file. The linker (ld) can only work with object modules to link them together. Object modules are the smallest unit that the linker works with.

Typically, on the linker command line, you will specify a set of object modules (that has been previously compiled) and then a list of libraries, including the Standard C Library. The linker takes the set of object modules that you specify on the command line and links them together. Afterwards there will probably be a set of "undefined references". A reference is essentially a function call. An undefined reference is a function call, with no defined function to match the call.

The linker will then go through the libraries, in order, to match the undefined references with function definitions that are found in the libraries. If it finds the function that matches the call, the linker will then link in the object module in which the function is located. This part is important: the linker links in THE ENTIRE OBJECT MODULE in which the function is located. Remember, the linker knows nothing about the functions internal to an object module, other than symbol names (such as function names). The smallest unit the linker works with is object modules.

When there are no more undefined references, the linker has linked everything and is done and outputs the final application.

....

The linker will search libraries in the order that they appear on the command line. Whichever function is found first that matches the undefined reference, it will be linked in.

Arduino's linking occurs in this order:

  • Project Files
  • Libraries
  • core.a (Arduino std lib)

So, copying the file from the Arduino installation location (C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino) into your project and modifying it will result in it using your modified implementation.

This will certainly over-ride it for any files in your project, and I believe it will also override it in any libraries you're using as well, so be careful with this approach.

RubberDuck
  • 332
  • 1
  • 10
1

Just throwing an idea. No time to test now.

Edit: I tested the solution below, and it works as expected, provided both the __wrap_ and __real_digitalWrite() are declared extern "C".

If you can convince the IDE to add extra options to the compile command line, you could:

  1. name __wrap_digitalWrite() your implementation of digitalWrite()
  2. inside it, call __real_digitalWrite() when you want to get the implementation from Arduino core
  3. add -Wl,--wrap=digitalWrite to the compile command of the final link step

Example wrapper:

extern "C" {
    void __wrap_digitalWrite(uint8_t, uint8_t);
    void __real_digitalWrite(uint8_t, uint8_t);
}

// Calls to digitalWrite() are diverted to this.
void __wrap_digitalWrite(uint8_t pin, uint8_t value)
{
    __real_digitalWrite(pin, value);  // call implementation from Arduino core
    if (pin == 8) {
        Serial.print(F("Pin 8 changed to "));
        Serial.println(value);
    }
}

Trying to put this inside a class won't help.

C.f. the man page of gnu ld.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
0

In your code module, define your own wrapper function, and use the global scope operator to reference the Arduino function:

void pinMode(int pin, int pinmode)
{
  /*use my custom function?*/
  if(pin >= MY_OWN_PIN_CODES_BASE)
  {
    /*todo : my own custom pin access*/
    example_pinmode(pin, pinmode);
  }
  else /*use regular Arduino function*/
  {
    /*use global scope operator :: to call Arduino library version*/
    ::pinMode(pin, pinmode);
  }
}

Remember, the linker has rules for which one it references. The global scope operator forces the linker to choose the globally available one, while the custom wrapper function is seen privately by the module.

flukee
  • 9
  • 1
0

"Maybe" using the c++ "functions overriding" feature can help

void digitalWrite(uint8_t P, int8_t V, bool _wrap)
{
  Serial.print("wrap digitalwrite");
  digitalWrite(P,V);
}

Thus, if you call in you main application:

digitalWrite(pin_button, 1, 0); 

The wrapped version will be called

and if you used this:

digitalWrite(pin_button, 1); 

The original one will be called


You may also use this definition:

void digitalWrite(double P, int8_t V)
{
  Serial.print("wrap digitalwrite");
  //digitalWrite(P,V); Calling it even with (int) P will cause a recursive loop 
}

But in-order to use it you need to define a variable with double type:

double pin;
pin = 13;
digitalWrite(13,1);
Yahya Tawil
  • 141
  • 3
0

The information from Rubber Duck is correct but incomplete.

I need a huge amount of digital PWM output for hobby train control.

So I connected 4 x 74HC595 to my At Mega which must be fed with a 2 kHz bit pattern through 0,5 ms. interrupts.

My serial shift function that shifts 32 bits into the registers takes 562 us per cycle. Impossible.

I made an optimized version of digitalWrite and Read, which takes 328 us. Now it is possible to PWM with 2 kHz. The trick is that my optimized function remembers the last 2 Pins including its bitmasks and outports; and I skip the Timer check. It is thread safe. For a single digitalWrite this takes a tiny bit more time. With repeating writing to datapin and clockpin it improves the standard Arduino digitalWrite with about 42%.

By the way: With the digitalWriteFast library the result is 45,8 µs, a really enormous improvement. But this library handles only writes, if the pin number is known (and thus fixed!) at compile time. That is not the case with my LiquidCrystal library, where either 4 or 8 bits parallel are written to the shield via 4 sequential pins.

I got my optimized digitalWrite only working for my application including the LiquidDisplay when I copied the C:\Users\Dell\Downloads\arduino-1.8.5\hardware\arduino\avr\cores\arduino library to a subfolder in the folder of my application, called

C:\Users\Dell\Google Drive\ …\…..\AppFolder\libraries.

When I replaced in both folders the wiring_digital.c with my optimized version, it worked.

I could not get it at work, when I only replaced the wiring_digital.c in the Arduino library (the LiquidCrystal took the standard function somewhere else).

Adding only wiring_digital.c in de subfolder of my application generated a mass of linking errors.

Thus I copied the whole Arduino folder (which is not that large) to my subfolder in the application and it compiles without any problem. Not a very elegant solution, because there is a replication of the whole library; but it works.

Conclusion to override a core Arduino function:

• Put your changed function(s) (.c and/or .h) in the Arduino library, and copy this library entirely to the subfolder "libraries" in your application folder. Then all other libraries will also use your own changed function.

Conclusion performance digitalWrite in a real 32 bit serial shift :

• An optimized generic digitalWrite and Read easily outperforms the standard one with 42% if 2 pins are used in repetition. • digitalWriteFast (with determined pins at compile time) outperforms the standard digitalWrite in a real 32 bit shift application with 92% (is 12,2 times faster).

Hope this helps other Arduino users…..

//**********************
void digitalWrite(uint8_t pin, uint8_t val)
//**********************
{
uint8_t 
  timer,
  bit,
  port;

volatile uint8_t *out; // volatile because bitmask may be changed by (higher) interrupt

uint8_t oldSREG;

static uint8_t pin1 = 0,
               pin2 = 0;

static bool    p1   = true; // boolean toggle memory

static uint8_t // Store 2 sets of port/out adresses static

//          timer1, //now exclued. Safety threat?
          bit1,
          *out1,
//          timer2,
          bit2,
          *out2; 

  oldSREG = SREG;  // CLI for thread proof function
  cli();
  if ( (pin == pin1) || (pin == pin2) )
  {
    if (pin == pin1)   //Compiler optimizes this excellent (see ASM listing)
    {
      if (val == LOW) 
      {
        *out1 &= ~bit1;
      } else 
      {
        *out1 |= bit1; 
      } 
    }
    else
    if (pin == pin2)
    {
      if (val == LOW) 
      {
        *out2 &= ~bit2;
      } else 
      {
        *out2 |= bit2; 
      }   
    }

    SREG = oldSREG;    
  }
  else  //normal clumsy digitalWrite operation
  {
    SREG = oldSREG;   //Enable interrupts again
    timer = digitalPinToTimer(pin);
    bit = digitalPinToBitMask(pin);
    port = digitalPinToPort(pin);
    if (port == NOT_A_PIN) return;

    // If the pin that support PWM output, we need to turn it off
    // before doing a digital write.
//    if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    out = portOutputRegister(port);

    oldSREG = SREG;
    cli();

    if (val == LOW) {
      *out &= ~bit;
    } else {
      *out |= bit;
    }

    if (p1)        // Save this port, bit and out, also atomic
    {
      pin1  = pin;
      bit1  = bit;
      out1  = out;  // save the pointer, not the value    
    }
    else
    {
      pin2  = pin;
      bit2  = bit;
      out2  = out;  // save the pointer, not the value    
    }
    p1 = !p1;
    SREG = oldSREG; //enable interrupts
  }
}

//**********************
int digitalRead(uint8_t pin)
//**********************
{

uint8_t 
  oldSREG,
  timer,
  bit,
  port;

static uint8_t pin1 = 0;

bool           readBit;

static uint8_t // Store 2 sets of port/out adresses static

//          timer1, //now exclued. Safety threat?
              port1,
              bit1;

  oldSREG = SREG;
  cli();
  if (pin == pin1)
  {
    readBit = (*portInputRegister(port1) & bit1);
    SREG = oldSREG;
    return readBit;           
  }
  else
  {
    SREG = oldSREG;  
    timer = digitalPinToTimer(pin);
    bit = digitalPinToBitMask(pin);
    port = digitalPinToPort(pin);

    if (port == NOT_A_PIN) return LOW;

    // If the pin that support PWM output, we need to turn it off
    // before getting a digital reading.
  //  if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    oldSREG = SREG;
    cli();
    pin1 = pin;       //Atomic operation pin - bit combi must be correct
    bit1 = bit;
    port1 = port;
    SREG = oldSREG;

    if (*portInputRegister(port) & bit) return HIGH;
    return LOW;
  }
}