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)
1
  • 1
    This library seem to be able to do it using templates.
    – Gerben
    Commented Apr 22, 2016 at 12:31

7 Answers 7

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.

7
  • Yes, I think it's working in external libraries too! But this way I can't invoke the original digitalWrite() from this method because the method is calling itself (stack overflow!). Can this be solved? If my method was the linked one, then I think the original method was not included at all and can't be called - am I right?
    – P.W.
    Commented Apr 22, 2016 at 8:05
  • You are correct. You will have to copy and paste the content of the original digitalWrite into your variant to use it.
    – Majenko
    Commented Apr 22, 2016 at 8:10
  • I have tried that but it doesn't compile. Some things are missing. If you can create an example code of such method, overriding and calling the original method (and add it to your answer), then it will be the answer I'm looking for.
    – P.W.
    Commented Apr 22, 2016 at 8:23
  • 1
    There is probably a huge amount in the existing digitalWrite that you don't need. It's been made to be generic, but if you only want to do specific things with specific pins you could hard-code those calls to the PORT registers yourself.
    – Majenko
    Commented Apr 22, 2016 at 9:32
  • @P.W. overwriting a single function will only work if it's the only function in the module. "The smallest unit the linker knows about is the object module". So, if the linker finds a matching function in your code, it will bring that module in and ignore the rest of the methods in the code lib. I believe that's what's causing the compiler failure you mentioned. More info in my answer below.
    – RubberDuck
    Commented Nov 28, 2016 at 23:32
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.

4
  • Yeah, but that's the last resort. Your macro works just like my macro (see last line of my code) - does not work in other libraries. I'm searching for a non-invasive way to make it included into other libraries. Any ideas?
    – P.W.
    Commented Apr 21, 2016 at 22:31
  • You are going against every design principle used in designing function calls and libraries :-D What you want to do is closer to hacking a binary than altering the source code of a program. The only pseudo-legal option left is to play with the linker. For example rename the original function and use --wrap to hijack it. Check this as starting point sourceware.org/binutils/docs-2.21/ld/Options.html#Options Commented Apr 21, 2016 at 22:44
  • Btw, if you only want to track what is going on at pin level, you could use a logical analyzer - it's very cheap and more reliable than some prints. Plus it adds zero overhead to the program. See the section "sigrok and pulseview" about in the middle of the answer to arduino.stackexchange.com/questions/22898/… Commented Apr 21, 2016 at 22:50
  • I know. I am thinking about redirecting pins, not debugging. And by redirecting I mean sending them through an expander or shift register for example, which the original libraries just cannot do. I'm out of pins and I don't want to write my own versions of the standard libraries. Does it make sense now?
    – P.W.
    Commented Apr 22, 2016 at 8:19
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.

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.

1
  • 2
    Please explain step 3 some more: How do we "add -Wl,--wrap=digitalWrite to the compile command of the final link step"? Commented Jun 16, 2017 at 20:44
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.

1
  • To satisfy the OP's requirements, the wrapper should work in global scope. In this situation your technique yields an infinite loop. Commented Jul 18, 2017 at 11:11
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);
4
  • 1
    This doesn't satisfy the first two requirements of the OP. Commented Dec 7, 2017 at 17:02
  • I know, I just thought that this will help in someway (and maybe enhanced by others comments). Commented Dec 7, 2017 at 19:45
  • @EdgarBonet see the new update if you're interested. This satisfies all the requested. Commented Dec 8, 2017 at 18:51
  • Except point 2, when calling from an existing library. Commented Dec 8, 2017 at 20:28
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;
  }
}

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.