Gibt es eine Möglichkeit, eine Arduino-Kernfunktion zu ersetzen, ohne den Arduino-Code zu ändern?

7

(Lassen Sie uns einfach die Vorschläge überspringen, dass es die falsche Idee ist und für eine Weile unterbricht ...).

Gibt es eine Möglichkeit, die digitalWrite()Methode zu ersetzen (zum Beispiel), damit ich etwas hinzufügen kann, was dort passieren kann? Folgende Regeln müssen jedoch beachtet werden:

  • Ein Aufruf von digitalWrite(pin, value)muss die neue Methode aufrufen.
  • Ein Aufruf digitalWrite(pin, value)von einer anderen Bibliothek , die in meinem Code (SD.h zum Beispiel) muss die neue Methode aufzurufen.
  • Keine der Arduino-Dateien kann geändert werden (ich möchte dies nicht bei jedem Upgrade der Arduino-Version tun).
  • Alles muss in einer Header-Datei erfolgen, die ich einschließen kann, um die neue Funktion zu verwenden.

Ich habe dies versucht und es funktioniert, aber nur in meinem Code, nicht in externen Bibliotheken, die ich nach diesem Header einfüge:

#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)
PW
quelle
1
Diese Bibliothek scheint in der Lage zu sein, dies mithilfe von Vorlagen zu tun.
Gerben

Antworten:

3

Ich bin mir nicht sicher über die Arduino IDE (ich habe sie momentan nicht installiert), aber mit UECIDE ist es so einfach wie das Definieren einer neuen digitalWriteFunktion in Ihrem Code. Wenn der gesamte Shebang miteinander verknüpft ist, überschreibt die Funktion in Ihrem Code die Funktion in den Bibliotheken.

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!
}

Kommentieren Sie die digitalWrite-Funktion aus und die LED blinkt. Kommentieren Sie es aus und es blinkt nicht mehr.

Majenko
quelle
Ja, ich denke, es funktioniert auch in externen Bibliotheken! Auf diese Weise kann ich das Original digitalWrite()von dieser Methode nicht aufrufen , da sich die Methode selbst aufruft (Stapelüberlauf!). Kann das gelöst werden? Wenn meine Methode die verknüpfte war, dann denke ich, dass die ursprüngliche Methode überhaupt nicht enthalten war und nicht aufgerufen werden kann - habe ich recht?
PW
Du hast Recht. Sie müssen den Inhalt des ursprünglichen digitalWrite kopieren und in Ihre Variante einfügen, um ihn verwenden zu können.
Majenko
Ich habe das versucht, aber es wird nicht kompiliert. Einige Dinge fehlen. Wenn Sie einen Beispielcode für eine solche Methode erstellen können, indem Sie die ursprüngliche Methode überschreiben und aufrufen (und zu Ihrer Antwort hinzufügen), ist dies die Antwort, nach der ich suche.
PW
1
Das vorhandene digitalWrite enthält wahrscheinlich eine große Menge, die Sie nicht benötigen. Es wurde generisch erstellt, aber wenn Sie nur bestimmte Dinge mit bestimmten Pins ausführen möchten, können Sie diese Aufrufe der PORT-Register selbst hart codieren.
Majenko
Das Überschreiben einer einzelnen Funktion durch @PW funktioniert nur, wenn dies die einzige Funktion im Modul ist. "Die kleinste Einheit, die der Linker kennt, ist das Objektmodul". Wenn der Linker also eine passende Funktion in Ihrem Code findet, bringt er dieses Modul ein und ignoriert die restlichen Methoden in der Codebibliothek. Ich glaube, das ist es, was den von Ihnen erwähnten Compilerfehler verursacht. Weitere Infos in meiner Antwort unten.
RubberDuck
2

Ich werfe nur eine Idee. Keine Zeit zum Testen.

Bearbeiten : Ich habe die unten stehende Lösung getestet und sie funktioniert wie erwartet, sofern sowohl die __wrap_als __real_digitalWrite()auch deklariert sind extern "C".

Wenn Sie die IDE davon überzeugen können, der Kompilierungsbefehlszeile zusätzliche Optionen hinzuzufügen, können Sie:

  1. Nennen Sie __wrap_digitalWrite()Ihre Implementierung vondigitalWrite()
  2. Rufen __real_digitalWrite()Sie darin auf, wenn Sie die Implementierung vom Arduino-Kern erhalten möchten
  3. Fügen Sie -Wl,--wrap=digitalWritedem Kompilierungsbefehl des letzten Verknüpfungsschritts hinzu

Beispiel 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);
    }
}

Der Versuch, dies in eine Klasse zu bringen, hilft nicht weiter.

Vgl. Die Manpage von gnu ld .

Edgar Bonet
quelle
2
Bitte erläutern Sie Schritt 3 noch etwas weiter: Wie fügen wir " -Wl,--wrap=digitalWritedem Kompilierungsbefehl des letzten Linkschritts hinzu"?
Gabriel Staples
1

Haben Sie es mit einem C-Makro versucht?

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

Dies ergibt die Retval von post()

Sie können entweder die pre()oder die post()Funktionen / Makros weglassen .

Dies muss an einer Stelle platziert werden, die enthalten ist, wenn der Header für digitalWrite enthalten ist, damit er überschrieben werden kann.

Die einzige andere Alternative besteht darin, mit der Bibliotheksbinärdatei kreativ zu werden.

Oder seien Sie einfach vernünftig und ändern Sie den Quellcode. Es ist speziell verfügbar, damit Benutzer es ändern können.

Wenn Sie die Änderung nicht jedes Mal wiederholen, wenn es eine neue Arduino-Version gibt, sollte dies vermeidbar sein. Wenn Sie die Umgebung aus den Git-Quellen erstellen, können Sie Ihren Patch durch Zusammenführen / erneutes Basieren auf dem Hauptzweig verwalten.

Igor Stoppa
quelle
Ja, aber das ist der letzte Ausweg. Ihr Makro funktioniert genauso wie mein Makro (siehe letzte Zeile meines Codes) - funktioniert nicht in anderen Bibliotheken. Ich suche nach einem nicht-invasiven Weg, um es in andere Bibliotheken aufzunehmen. Irgendwelche Ideen?
PW
Sie verstoßen gegen jedes Entwurfsprinzip, das beim Entwerfen von Funktionsaufrufen und Bibliotheken verwendet wird :-D Was Sie tun möchten, ist näher am Hacken einer Binärdatei als am Ändern des Quellcodes eines Programms. Die einzige pseudo-legale Option ist, mit dem Linker zu spielen. Benennen Sie beispielsweise die ursprüngliche Funktion um und verwenden Sie --wrap, um sie zu entführen. Überprüfen Sie dies als Ausgangspunkt sourceware.org/binutils/docs-2.21/ld/Options.html#Options
Igor Stoppa
Übrigens, wenn Sie nur verfolgen möchten, was auf Pin-Ebene vor sich geht, können Sie einen logischen Analysator verwenden - er ist sehr billig und zuverlässiger als einige Ausdrucke. Außerdem wird dem Programm kein Overhead hinzugefügt. Siehe den Abschnitt "Sigrok und Pulsansicht" ungefähr in der Mitte der Antwort auf arduino.stackexchange.com/questions/22898/…
Igor Stoppa
Ich kenne. Ich denke darüber nach, Pins umzuleiten, nicht zu debuggen. Und mit Umleiten meine ich, sie zum Beispiel über ein Expander- oder Schieberegister zu senden, was die Originalbibliotheken einfach nicht können. Ich habe keine Pins mehr und möchte keine eigenen Versionen der Standardbibliotheken schreiben. Macht es jetzt Sinn?
PW
1

Arduino verwendet den avr-Compiler, um seine Standardbibliothek in eine core.aDatei zu kompilieren . Aufgrund der Funktionsweise des avr-gcc-Linkers und der Reihenfolge, in der Arduino die Linkreihenfolge angibt, können Sie das Verhalten von Kernfunktionen "überschreiben", indem Sie die Datei kopieren, die die Methode enthält, die Sie in Ihr Projekt ändern möchten.

Der Compiler kompiliert eine einzelne Hochsprachendatei (z. B. C-Sprache) in eine einzelne Objektmoduldatei. Der Linker (ld) kann nur mit Objektmodulen arbeiten, um sie miteinander zu verknüpfen. Objektmodule sind die kleinste Einheit, mit der der Linker arbeitet.

In der Regel geben Sie in der Linker-Befehlszeile eine Reihe von Objektmodulen (die zuvor kompiliert wurden) und anschließend eine Liste von Bibliotheken an, einschließlich der Standard C-Bibliothek. Der Linker nimmt die in der Befehlszeile angegebenen Objektmodule und verknüpft sie miteinander. Danach wird es wahrscheinlich eine Reihe von "undefinierten Referenzen" geben. Eine Referenz ist im Wesentlichen ein Funktionsaufruf. Eine undefinierte Referenz ist ein Funktionsaufruf ohne definierte Funktion, die dem Aufruf entspricht.

Der Linker durchläuft dann die Bibliotheken, um die undefinierten Referenzen mit den Funktionsdefinitionen in den Bibliotheken abzugleichen. Wenn die Funktion gefunden wird, die dem Aufruf entspricht, verknüpft der Linker das Objektmodul, in dem sich die Funktion befindet. Dieser Teil ist wichtig: Die Linker-Links im GESAMTEN OBJEKTMODUL, in dem sich die Funktion befindet. Denken Sie daran, dass der Linker außer Symbolnamen (z. B. Funktionsnamen) nichts über die Funktionen eines Objektmoduls weiß. Die kleinste Einheit, mit der der Linker arbeitet, sind Objektmodule.

Wenn keine undefinierten Referenzen mehr vorhanden sind, hat der Linker alles verknüpft und ist fertig und gibt die endgültige Anwendung aus.

....

Der Linker durchsucht Bibliotheken in der Reihenfolge, in der sie in der Befehlszeile angezeigt werden. Welche Funktion zuerst gefunden wird, die mit der undefinierten Referenz übereinstimmt, wird verknüpft.

Die Verknüpfung von Arduino erfolgt in dieser Reihenfolge:

  • Projektdateien
  • Bibliotheken
  • core.a (Arduino std lib)

Wenn Sie also die Datei vom Arduino-Installationsort ( C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino) in Ihr Projekt kopieren und ändern, wird Ihre geänderte Implementierung verwendet.

Dies wird es sicherlich für alle Dateien in Ihrem Projekt überschreiben , und ich glaube, es wird es auch in allen Bibliotheken überschreiben, die Sie ebenfalls verwenden. Seien Sie also vorsichtig mit diesem Ansatz.

Quietscheentchen
quelle
0

Definieren Sie in Ihrem Codemodul Ihre eigene Wrapper-Funktion und verwenden Sie den globalen Bereichsoperator, um auf die Arduino-Funktion zu verweisen:

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);
  }
}

Denken Sie daran, dass der Linker Regeln hat, auf die er verweist. Der globale Bereichsoperator zwingt den Linker, den global verfügbaren auszuwählen, während die benutzerdefinierte Wrapper-Funktion vom Modul privat angezeigt wird.

Flukee
quelle
Um die Anforderungen des OP zu erfüllen, sollte der Wrapper im globalen Bereich arbeiten. In dieser Situation ergibt Ihre Technik eine Endlosschleife.
Edgar Bonet
0

"Vielleicht" kann die Verwendung der c ++ - Funktion "Funktionen überschreiben" helfen

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

Wenn Sie also Ihre Hauptanwendung aufrufen:

digitalWrite(pin_button, 1, 0); 

Die verpackte Version wird aufgerufen

und wenn Sie dies verwendet haben:

digitalWrite(pin_button, 1); 

Das Original wird aufgerufen


Sie können auch diese Definition verwenden:

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 
}

Um es zu verwenden, müssen Sie jedoch eine Variable mit doppeltem Typ definieren:

double pin;
pin = 13;
digitalWrite(13,1);
Yahya Tawil
quelle
1
Dies erfüllt nicht die ersten beiden Anforderungen des OP.
Edgar Bonet
Ich weiß, ich dachte nur, dass dies irgendwie helfen wird (und vielleicht durch andere Kommentare verbessert wird).
Yahya Tawil
@EdgarBonet sehen Sie das neue Update, wenn Sie interessiert sind. Dies erfüllt alle angeforderten.
Yahya Tawil
Mit Ausnahme von Punkt 2 beim Aufrufen aus einer vorhandenen Bibliothek.
Edgar Bonet
0

Die Informationen von Rubber Duck sind korrekt, aber unvollständig.

Ich benötige eine große Menge an digitalem PWM-Ausgang für die Steuerung von Hobbyzügen.

Also habe ich 4 x 74HC595 an mein At Mega angeschlossen, das über 0,5 ms mit einem 2-kHz-Bitmuster gespeist werden muss. unterbricht.

Meine serielle Verschiebungsfunktion, die 32 Bit in die Register verschiebt, benötigt 562 us pro Zyklus. Unmöglich.

Ich habe eine optimierte Version von digitalWrite und Read erstellt, für die wir 328 benötigen. Jetzt ist es möglich, mit 2 kHz zu PWM. Der Trick ist, dass meine optimierte Funktion die letzten 2 Pins einschließlich ihrer Bitmasken und Outports speichert; und ich überspringe die Timer-Prüfung. Es ist threadsicher. Für ein einzelnes digitalWrite dauert dies etwas länger. Durch wiederholtes Schreiben auf Datapin und Clockpin wird der Standard Arduino digitalWrite um ca. 42% verbessert.

Übrigens: Mit der digitalWriteFast-Bibliothek ergibt sich ein Ergebnis von 45,8 µs, eine wirklich enorme Verbesserung. Diese Bibliothek verarbeitet jedoch nur Schreibvorgänge, wenn die PIN-Nummer zur Kompilierungszeit bekannt (und damit fest!) Ist. Dies ist bei meiner LiquidCrystal-Bibliothek nicht der Fall, bei der entweder 4 oder 8 Bits parallel über 4 aufeinanderfolgende Pins in die Abschirmung geschrieben werden.

Ich habe mein optimiertes digitalWrite nur für meine Anwendung einschließlich LiquidDisplay verwendet, als ich die Bibliothek C: \ Users \ Dell \ Downloads \ arduino-1.8.5 \ hardware \ arduino \ avr \ cores \ arduino in einen Unterordner im Ordner von my kopiert habe Anwendung, genannt

C: \ Benutzer \ Dell \ Google Drive \… \… .. \ AppFolder \ Bibliotheken .

Als ich in beiden Ordnern die Datei wiring_digital.c durch meine optimierte Version ersetzte , funktionierte es.

Ich konnte es nicht zum Laufen bringen, als ich nur die Datei wiring_digital.c in der Arduino-Bibliothek ersetzte (der LiquidCrystal übernahm die Standardfunktion woanders).

Das Hinzufügen von nur wiring_digital.c im Unterordner meiner Anwendung führte zu einer Vielzahl von Verknüpfungsfehlern.

Daher habe ich den gesamten Arduino-Ordner (der nicht so groß ist) in meinen Unterordner in der Anwendung kopiert und er kann problemlos kompiliert werden. Keine sehr elegante Lösung, da die gesamte Bibliothek repliziert wird. Aber es funktioniert.

Schlussfolgerung zum Überschreiben einer Arduino-Kernfunktion:

• Fügen Sie Ihre geänderten Funktionen (.c und / oder .h) in die Arduino-Bibliothek ein und kopieren Sie diese Bibliothek vollständig in den Unterordner "Bibliotheken" in Ihrem Anwendungsordner. Dann verwenden auch alle anderen Bibliotheken Ihre eigene geänderte Funktion.

Fazit Leistung digitalWrite in einer echten 32-Bit-Serienverschiebung:

• Ein optimiertes generisches digitalWrite und Read übertrifft das Standardmodell mit 42% deutlich, wenn 2 Pins wiederholt verwendet werden. • digitalWriteFast (mit bestimmten Pins zur Kompilierungszeit) übertrifft das Standard-digitalWrite in einer echten 32-Bit-Shift-Anwendung mit 92% (ist 12,2-mal schneller).

Hoffe das hilft anderen Arduino Anwendern… ..

//**********************
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;
  }
}
Jan van der Zanden
quelle