rewriting a specific Arduino function without touching Arduino libraries

Hi
I am trying to extend digitalRead and digitialWrite to do a little more than just set and read the pins. I can add those functions in my code and they compile fine. However when linking the compiler tells me that the rewritten modules exist twice which is correct. Anyway to resolve this.
I noticed both digitalRead and digitalWrite both use uint8_t as parameters. If I use unsigned int in my rewritten functions, they get called instead of the original ones. So this is one option. but I was wondering if I could direct the linker to use my functions instead of the standard ones.

What I am trying to do is output pin changes and digital reads to serial output (all this works) in CSV format and then copy all that into excel. For the read part I could make testcases and send them to the pins when a digitalRead takes place.

End result would be a simple automatic testing system with just a simple statement (#include "test.h") and include the library. Leave that statement out and you get the original code. Include and I can rerun testcases.

Timing is of course out of the question, but I often write software for customers where a sequence of pin changes (read and written) are performed on specific conditions and combinations.

I would consider it as bad programming. Reading the sketch should clearly show what is going on.

For examples this: digitalWrite( 3);
Will that be the library or your function ?

You can make a wrapper around it, with a function name like: "myDigitalWrite" or "digitalWritePlusExtras" or the ugly "myDiWri".

int myDigitalWrite( int pin)
{
  digitalWrite( pin);
  ....  // do more
}

You could make a macro to be able to switch between them.

// Choose one of the macros
#define _digitalWrite(a) digitalWrite(a)
// #define _digitalWrite(a) myDigitalWrite(a)

In your sketch, you would have to replace every digitalWrite with _digitalWrite.

That is just what I don't want to do. I don't want to change the sketch code :grin:

this was my proof of concept:

debug.h containing the new routines

/**
 * debug.h
 *
 *  Created on	: 24 aug. 2013
 *  Author		: Nico Verduin
 *	Email		: info@verelec.com
 *  Website		: www.verelec.nl
 *
 * Revision Control
 * 
 * Latest Revsion
 * ____________________
 *
 * Revision	: $Revision$
 * Date		: $Date$
 * Author	: $Author$
 *
 */

//
// test cases
//
int inputs[] = {0,1,2,3,4,5,6,7,8,9};
int input_index = 0;


#ifndef DEBUG_H_
#define DEBUG_H_
// Forcing this inline keeps the callers from having to push their own stuff
// on the stack. It is a good performance win and only takes 1 more byte per
// user than calling. (It will take more bytes on the 168.)
//
// But shouldn't this be moved into pinMode? Seems silly to check and do on
// each digitalread or write.
//
// Mark Sproul:
// - Removed inline. Save 170 bytes on atmega1280
// - changed to a switch statment; added 32 bytes but much easier to read and maintain.
// - Added more #ifdefs, now compiles for atmega645
//
//static inline void turnOffPWM(uint8_t timer) __attribute__ ((always_inline));
//static inline void turnOffPWM(uint8_t timer)
static void turnOffPWM(uint8_t timer) {
	switch (timer) {
#if defined(TCCR1A) && defined(COM1A1)
	case TIMER1A: cbi(TCCR1A, COM1A1); break;
#endif
#if defined(TCCR1A) && defined(COM1B1)
	case TIMER1B: cbi(TCCR1A, COM1B1); break;
#endif

#if defined(TCCR2) && defined(COM21)
	case TIMER2: cbi(TCCR2, COM21); break;
#endif

#if defined(TCCR0A) && defined(COM0A1)
	case TIMER0A: cbi(TCCR0A, COM0A1); break;
#endif

#if defined(TIMER0B) && defined(COM0B1)
	case TIMER0B: cbi(TCCR0A, COM0B1); break;
#endif
#if defined(TCCR2A) && defined(COM2A1)
	case TIMER2A: cbi(TCCR2A, COM2A1); break;
#endif
#if defined(TCCR2A) && defined(COM2B1)
	case TIMER2B: cbi(TCCR2A, COM2B1); break;
#endif

#if defined(TCCR3A) && defined(COM3A1)
	case TIMER3A: cbi(TCCR3A, COM3A1); break;
#endif
#if defined(TCCR3A) && defined(COM3B1)
	case TIMER3B: cbi(TCCR3A, COM3B1); break;
#endif
#if defined(TCCR3A) && defined(COM3C1)
	case TIMER3C: cbi(TCCR3A, COM3C1); break;
#endif

#if defined(TCCR4A) && defined(COM4A1)
	case TIMER4A: cbi(TCCR4A, COM4A1); break;
#endif
#if defined(TCCR4A) && defined(COM4B1)
	case TIMER4B: cbi(TCCR4A, COM4B1); break;
#endif
#if defined(TCCR4A) && defined(COM4C1)
	case TIMER4C: cbi(TCCR4A, COM4C1); break;
#endif
#if defined(TCCR4C) && defined(COM4D1)
	case TIMER4D: cbi(TCCR4C, COM4D1); break;
#endif

#if defined(TCCR5A)
	case TIMER5A: cbi(TCCR5A, COM5A1); break;
	case TIMER5B: cbi(TCCR5A, COM5B1); break;
	case TIMER5C: cbi(TCCR5A, COM5C1); break;
#endif
}
}



/**
 * @name digitalRead(uint_8 pin)
 * @param pin pin number on Arduino board
 * @returns int value of pin reading
 *
 * This function translates the pin number to the correct pin and port numbers. Switches off a PWM if it exists
 * reads the pin.
 * The value is returned as an integer.
 *
 * This source is copied from the original Arduino core sources and adapted to be able to fake a reading
 */
int digitalRead(int pin){
	//
	// get all the necessary data about this pin including
	// mappings to different Arduino's
	//
	uint8_t timer 	= digitalPinToTimer(pin);
	uint8_t bit 	= digitalPinToBitMask(pin);
	uint8_t port	= digitalPinToPort(pin);
	uint8_t status	= 0;						// return value of this operation
	//
	// if it is not a pin just return a LOW
	//
	if (port == NOT_A_PIN) return LOW;
	//
	// if this pin supports a PWM we need to turn the PWM output off
	// before getting a digital reading
	//
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);
	//
	// get the port value and return the correct reading
	//
	if (*portInputRegister(port) & bit) {
		status = HIGH;
	} else {
		status = LOW;
	}
	//
	// fake status field
	//
	status = inputs[input_index];
	input_index++;

	if (input_index == 10) input_index = 0;

	Serial.print("digitalRead took place value = ");
	Serial.println(status);
	return status;
}
/**
 * @name digitalWrite(uint_8 pin, uint8_t val)
 * @param pin pin number on Arduino board
 * @param val value to write to pin ("0" or "1")
 *
 * This function translates the pin number to the correct pin and port numbers. Switches off a PWM if it exists
 * reads the pin.
 * The value is returned as an integer.
 *
 * This source is copied from the original Arduino core sources and adapted to be able to fake a reading
 */

void digitalWrite(int pin, int val) {
	uint8_t timer 	= digitalPinToTimer(pin);
	uint8_t bit 	= digitalPinToBitMask(pin);
	uint8_t port 	= digitalPinToPort(pin);

	volatile uint8_t *out;
	//
	// check if this port is valid
	//
	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);
	//
	// get the right output register
	//
	out = portOutputRegister(port);
	//
	// save SREG
	//
	uint8_t oldSREG = SREG;
	//
	// disable interrupts(). This has no effect after processing the pin as we restore the SREG back
	//
	cli();
	//
	// clear or set the bit in the output register thus setting the right port pin
	//
	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}
	//
	// restore SREG so that the global interrupt flag (bit 7) is restored and interrupts (if set) work again
	//
	SREG = oldSREG;
	//
	// add own code
	//
	Serial.print("DigitalWrite took place value = ");
	Serial.println(val);
}




#endif /* DEBUG_H_ */

And this is the actual program. It does not know the difference.

// Do not remove the include below
#include "testOverload.h"
#include "debug.h"

//The setup function is called once at startup of the sketch
void setup()
{
// Add your initialization code here
	Serial.begin(115200);
	pinMode(3, INPUT);
	pinMode(4, OUTPUT);
}

// The loop function is called in an endless loop
void loop()
{
	digitalWrite(4,digitalRead(6));
//Add your repeated code here
}

You can use function overloading only if the overloading function is distinctly different from the overloaded function - i.e., the parameter list is different enough as to be unambiguous.

You can do that either by providing an additional parameter to make it unique, or by changing the data type of one of the variables to be distinctly different (note that byte and int are not distinctly different).

I have used a struct before now to create a new pin type that calls a different overloading digitalWrite() function for accessing IO expanders:

struct BigIOPin {
    unsigned char address;
    unsigned char pin;
};

#define BIG_0_0 (struct BigIOPin) {0,0}
#define BIG_0_1 (struct BigIOPin) {0,1}
#define BIG_0_2 (struct BigIOPin) {0,2}
// ...

void digitalWrite(struct BigIOPin p,unsigned char v)
{
    BigIO._digitalWrite(p,v);
}

Of course, than means changing your sketch to then reference the special struct defined pins instead of the normal pins. That, however, could be an advantage as you could use #define macros in your sketch to define what pins you want to use, and have some on the original digitalWrite and some on your new digitalWrite and change them as and when you want, so it's not a global on-off, but you can select which pins.

Thanks for the reply but as I said before I don't want to change the sketch. I was hoping that the linker would search the Arduino libraries for any unresolved functions. Thus any functions put in my code would be skipped when resolving the rest. Unfortunately the linker said I have a duplicate function/module. Which by itself is correct.

Then I'm sorry, but it can't be done.

It's like you and your wife both having identical cars parked on the driveway - both the same model, both the same color, both the same license plates... Which one do you get into when you go to work? Argh! Brain overload! Can't cope!

Now, if they had different license plates then it would be easier - you could find which was your car by looking at the plates.

That's just what the linker is like. It needs some simple clues as to which function is which. If they are both the same from the outside (yes, your car may have chocolate wrappers scattered around in it, and the wife's may have a lipstick in the ash tray, but you don't know that until you are inside the car [btw, they both have tinted windows :stuck_out_tongue: ]), then what is the linker to do? It's head explodes in confusion :wink:

I disagree from the latter part :grin:
Once upon a time a long time ago (70'ies-80íes) I wrote quite a lot software for CP/M & MsDos. To my recollection, one could tel the linker which modules to use an which libraries to search for unresolved. But now writing this down I guess the problem is that we are not linking libraries. These are complete modules. I wonder what would happen if I make a fulle copy dof Digital_pins.c. And check if I can tell the linker to ignore duplicates. It will always find my modules first anyway.

The Arduino IDE does do a kind of library linkage (though it doesn't do it right - like so many things about the Arduino). It creates a "core.a" archive which contains all the compiled cores/arduino files. It then links that against your object files and the library object files, not with proper linkage (-l...) but just by adding the core.a file to the list of object files. Not the most wonderful way of doing it.

You could try experimenting with UECIDE for this (link in my sig). It compiles the sketch properly, and does proper linkage. You might be able to get it to override better, though it's not something I have tried. UECIDE first compiles the core files into a libcore.a file, and the libraries into individual libLibName.a files, and stores them away in a cache folder. Your sketch is then properly linked against these library files using -lcore -lLibName etc. Worth a try, anyway :wink:

Hi @nico. You want the new digitalWrite() called (if you active the debug mode) for all libraries or only for code in your sketch?

If only in your sketch maybe using precompiler #define ?
(But unfortunately in your sketch not use digitalWrite() but a new name, I know. It's only an idea)

Suppose you have standard digitalWrite() and your newdigitalWrite()

#define DEBUG 1

#if DEBUG==1
  // testing 
  #define xdigitalWrite(pin,val); newdigitalWrite(pin,val);
#else
  // standard 
  #define xdigitalWrite(pin,val); digitalWrite(pin,val);
#endif

void setup()
{}

void loop()
{ xdigitalWrite(1,2);
}

void newdigitalWrite(int pin,int val)
{}

@nid69ita
Thanks you for the reply. No this is not an option :grin: too simple :grin:

nicoverduin:
That is just what I don't want to do. I don't want to change the sketch code :grin:

this was my proof of concept:

debug.h containing the new routines

Just #define digitalRead and digitalWrite in debug.h

majenko:
The Arduino IDE does do a kind of library linkage (though it doesn't do it right - like so many things about the Arduino). It creates a "core.a" archive which contains all the compiled cores/arduino files. It then links that against your object files and the library object files, not with proper linkage (-l...) but just by adding the core.a file to the list of object files. Not the most wonderful way of doing it.

You could try experimenting with UECIDE for this (link in my sig). It compiles the sketch properly, and does proper linkage. You might be able to get it to override better, though it's not something I have tried. UECIDE first compiles the core files into a libcore.a file, and the libraries into individual libLibName.a files, and stores them away in a cache folder. Your sketch is then properly linked against these library files using -lcore -lLibName etc. Worth a try, anyway :wink:

By itself an interesting option. I work with ECLIPSE so I wonder if it will work also.

Regards
Nico

Is this an option?

//
//    FILE: undef.ino
//  AUTHOR: Rob Tillaart
// VERSION:  
// PURPOSE: experimental
//     URL:
//
// Released to the public domain
//
#include "digitalRead.h"

void setup() 
{
  Serial.begin(9600);
  Serial.println(digitalRead(12));
}

void loop() 
{
}

in the same folder the include file

//
//    FILE: digitalRead.h
//  AUTHOR: Rob Tillaart
// VERSION:  
// PURPOSE: experimental
//     URL:
//
// Released to the public domain
//
#undef digitalRead()    // <<<<<<<<< the trick

int digitalRead(int pin)
{
  return 42;
}

Seems to work

Now that is a good idea (Bedankt :grin: = thank you in dutch :grin:)
I'll give it a shot.

majenko:
Then I'm sorry, but it can't be done.

It's like you and your wife both having identical cars parked on the driveway - both the same model, both the same color, both the same license plates... Which one do you get into when you go to work? Argh! Brain overload! Can't cope!

Actually, you can do that with the GNU linker using the -Wl,--wrap,symbol option (if you are calling the linker directly and not through the GCC wrapper that would be --wrap symbol as two separate arguments). The GNU linker changes any references to symbol to __wrap_symbol, and from within your __wrap_symbol function, you would call __real_symbol.

An alternative approach given that the whole Arduino library is recompiled each time you do the IDE is to do an -include file option, which includes file before the first line of the code, and you can put the #define in there (and in your version of the digitalWrite, you would #undef it first, and then redefine it after the end of the function).

The trouble is the IDE doesn't give you any way of dealing with the compiler at that level. So you would have to learn how to bypass the IDE, and deal with invoking the compiler manually from the command line (oh the horror).

MichaelMeissner:

majenko:
Then I'm sorry, but it can't be done.

It's like you and your wife both having identical cars parked on the driveway - both the same model, both the same color, both the same license plates... Which one do you get into when you go to work? Argh! Brain overload! Can't cope!

Actually, you can do that with the GNU linker using the -Wl,--wrap,symbol option (if you are calling the linker directly and not through the GCC wrapper that would be --wrap symbol as two separate arguments). The GNU linker changes any references to symbol to __wrap_symbol, and from within your __wrap_symbol function, you would call __real_symbol.

An alternative approach given that the whole Arduino library is recompiled each time you do the IDE is to do an -include file option, which includes file before the first line of the code, and you can put the #define in there (and in your version of the digitalWrite, you would #undef it first, and then redefine it after the end of the function).

The trouble is the IDE doesn't give you any way of dealing with the compiler at that level. So you would have to learn how to bypass the IDE, and deal with invoking the compiler manually from the command line (oh the horror).

At the time I was assuming the Arduino IDE, which means you can't do jack :wink:

robtillaart:
Is this an option?

//

//    FILE: undef.ino
//  AUTHOR: Rob Tillaart
// VERSION: 
// PURPOSE: experimental
//     URL:
//
// Released to the public domain
//
#include "digitalRead.h"

void setup()
{
  Serial.begin(9600);
  Serial.println(digitalRead(12));
}

void loop()
{
}




in the same folder the include file


//
//    FILE: digitalRead.h
//  AUTHOR: Rob Tillaart
// VERSION: 
// PURPOSE: experimental
//     URL:
//
// Released to the public domain
//
#undef digitalRead()    // <<<<<<<<< the trick

int digitalRead(int pin)
{
  return 42;
}




Seems to work

Unfortunately it does not work. What you are doing is what I did in my PoC. define digitalRead with an int. The original defines it with uint8_t which makes this definition diiferent to the original. This works fine even without the undef. But that is where I am already.

majenko:

MichaelMeissner:

majenko:
Then I'm sorry, but it can't be done.

It's like you and your wife both having identical cars parked on the driveway - both the same model, both the same color, both the same license plates... Which one do you get into when you go to work? Argh! Brain overload! Can't cope!

Actually, you can do that with the GNU linker using the -Wl,--wrap,symbol option (if you are calling the linker directly and not through the GCC wrapper that would be --wrap symbol as two separate arguments). The GNU linker changes any references to symbol to __wrap_symbol, and from within your __wrap_symbol function, you would call __real_symbol.

An alternative approach given that the whole Arduino library is recompiled each time you do the IDE is to do an -include file option, which includes file before the first line of the code, and you can put the #define in there (and in your version of the digitalWrite, you would #undef it first, and then redefine it after the end of the function).

The trouble is the IDE doesn't give you any way of dealing with the compiler at that level. So you would have to learn how to bypass the IDE, and deal with invoking the compiler manually from the command line (oh the horror).

At the time I was assuming the Arduino IDE, which means you can't do jack :wink:

I am too happy with ECLIPSE to switch to another environment. Now I have ECLIPSE with Arduino incorporate in it, Doxygen incorporated in it, SVN (my version control on anaother remote diskdrive) incorporated in it and it all works fine. So I am happy with it.

This seems to work:

adding an inline statement before the actual function like so:

inline int digitalRead(uint8_t pin){
	//
	// get all the necessary data about this pin including
	// mappings to different Arduino's
	//
	uint8_t timer 	= digitalPinToTimer(pin);
	uint8_t bit 	= digitalPinToBitMask(pin);
	uint8_t port	= digitalPinToPort(pin);
	uint8_t status	= 0;						// return value of this operation

instead of the standard

int digitalRead(uint8_t pin){
	//
	// get all the necessary data about this pin including
	// mappings to different Arduino's
	//
	uint8_t timer 	= digitalPinToTimer(pin);
	uint8_t bit 	= digitalPinToBitMask(pin);
	uint8_t port	= digitalPinToPort(pin);
	uint8_t status	= 0;						// return value of this operation

This would be a good solution for me. I still have test it though. Prpbalbly tomorrow.
Dinner time here and time for social desirable behaviour :grin: :grin: :grin:

And thank you all for this thinking with me for this rather non standard problem :grin:

But an inline function is not a simple substitution on every call instead a call to a unique subroutine? This made your code bigger.