Wie können wir eine Funktion zwischen Hauptanwendung und Bootloader teilen?

Ich verwende STM32F4 (ARM-Cortex M4). Ich möchte eine Funktion zwischen Hauptanwendung und Bootloader teilen, ohne diese Funktion neu zu definieren. Stellen Sie sich zum Beispiel vor, dass ich eine Druckfunktion in der Hauptanwendung geschrieben habe und diese jetzt auch im Bootloader verwenden möchte. Der erste Weg ist, diese Funktion in den Bootloader zu kopieren und dann zu verwenden, aber das möchte ich nicht. Ich möchte diese Funktion 1 Mal im Speicher definieren und dann, wenn ich sie brauche, aufrufen.

Bearbeiten: Ich habe eine Idee und habe diesen Code für den Sprung zwischen zwei Programmen (App und Bootloader) geschrieben. Ich definiere diese Funktion in meinem Bootloader:

void Shared_Func(void)
{
while(1)
    {
     HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
     HAL_Delay(700);
    }
 }  
 typedef void (*ptr_shared_func_t)(void);
 ptr_shared_func_t ptr_shared_func;

und im Wesentlichen:

ptr_shared_func = Shared_Func;
ptr_shared_func();

Wenn ich diesen Code ausführe, funktioniert es und es gibt kein Problem. Dann finde ich im Debug-Modus die Adresse von Shared_Func und schreibe anstelle des Namens der Funktion:

unsigned int *add_shared_fun = (unsigned int *)0x08001055;// correct address is   0x08001054 ->for thumb2 change to 0x08001055
ptr_shared_func = (void(*)(void))(add_shared_fun);//Shared_Func;

In diesem Fall funktioniert der Code nicht! Mein letztes Ziel bei der Verwendung dieser Lösung war, dass ich nach der Überprüfung dieser Art von Sprung zur Funktion einen Zeiger in einer speziellen Adresse definiere und dann die Adresse von Shared_Func() darin speichere und dann im App-Programm diese in Ponter gespeicherte Adresse verwende, springe zu Shared_Func()!

Jetzt ist meine Frage, was ist die beste Lösung und was ist das Problem in meinem Code?

Auf die gleiche Weise teilen Sie jede Funktion. Mit Bibliotheken und Header-Dateien.
@DKNguyen, ich denke, OP spricht von 2 Projekten, einem Bootloader und einer Anwendung. Der Bootloader ist eigenständig und kennt die App nicht. Ich denke, OP möchte, dass diese Funktion(en) nicht zweimal Platz im Flash-Speicher beanspruchen. Wenn man diese Funktion einfach wie jede andere Bibliothek verwendet, indem man sie über in das Projekt einbindet #include "some_header.h", gibt es zwei Kopien derselben Funktion im Flash-Speicher.
Ein typischer Ansatz wäre, eine Schnittstelle zu definieren, um die Struktur abzufragen, die Zeiger auf die Funktionen enthält. Aber normalerweise ist es ein Bootloader, der Funktionen bereitstellt, die mit (möglicherweise mehreren) Anwendungen geteilt werden sollen, die von diesem Bootloader gestartet wurden, da sich der Bootloader nicht so oft ändert wie die Anwendungen.
Wie @Tagli sagte, spreche ich von zwei separaten Projekten, die 2 separate .hex-Dateien haben. Wenn wir also #include "*.h" verwenden, kopieren wir zweimal diesen Teil des Codes in den Speicher. Hier ist das Problem die Trennung von App und Bootloader!
@Vlad kannst du mehr erklären? oder verwenden Sie einen Beispielcode?
@HwSwDesigner Ein Code, der Funktionen bereitstellt, hostet auch die Struktur mit Zeigern auf diese Funktionen. Im einfachsten Fall wird ein Zeiger auf diese Struktur an einem vordefinierten Ort gespeichert, und dieser Ort wird Apps bekannt gemacht, indem er in einer .h-Datei zusammen mit Strukturdefinition und Funktionsprototypen definiert wird.
Es ist wahrscheinlich keine gute Idee, Hauptprogrammfunktionen vom Bootloader aufzurufen. Ein Fehler im Hauptprogramm könnte den Bootloader funktionsunfähig machen.
@HwSwDesigner nein, das zweimalige Einfügen eines Headers verbraucht in der Endbinärdatei auf keinen Fall Speicher. so funktioniert das alles nicht! Der Header sollte dem Compiler nur mitteilen, wie die in Ihrem Quellcode verwendeten Funktionen aussehen.

Antworten (3)

Dies geschieht ständig auf Ihrem Computer. .dll- und .so-Dateien sind im Grunde Code, der nicht in Ihr Programm kompiliert wird, aber Funktionen enthält, die Ihr Programm aufruft. In welche Richtung Sie gehen möchten, spielt keine Rolle, Bootloader ruft App auf, App ruft Bootloader auf, Sie können über die Vernunft und die Gefahren entscheiden, aber es ist in beiden Fällen sehr wohl möglich.

Was Sie nicht sehen, wenn Sie

#include <stdio.h>
int main ( void ) { printf("Hello World!\n"); return 0; }

gcc hello.c -o hello

Ist, na ja, jede Menge Zeug, aber in diesem Zusammenhang. Die höchstwahrscheinlich verknüpfte C-Bibliothek enthält weder die printf-Funktion noch die Tausenden anderer Funktionen, auf die printf angewiesen ist. Was es enthält, ist etwas, das in das Design und die Implementierung der Bibliothek eingebaut ist, die die C-Bibliothek findet und die Funktionszeiger so repariert, dass dies funktioniert.

Sie befinden sich anscheinend auf einem Bare-Metal-System, also müssen Sie all dies selbst tun, genau wie die Leute, die C-Bibliotheken erstellen, außer dass sie wissen, dass sie auf einem bekannten Betriebssystem sitzen und ihre Lösung darauf basierend implementieren.

Es gibt Linker-Spiele, die Sie so spielen können, dass Sie beide Binärdateien gleichzeitig erstellen und der Linker die ganze Arbeit erledigt. Im Grunde erstellen Sie eine Binärdatei, die Sie jedoch in zwei Teile aufteilen, und es funktioniert NUR, wenn Sie die Binärdateien haben vom selben Link kombiniert, mischen und anpassen, und Sie erhalten massive Fehler. Ein Merkmal dieses Ansatzes ist beispielsweise, dass Sie mehrere Binärdateien erstellen können, Sie könnten einen Bootloader und mehrere Anwendungen haben, die von diesem Bootloader geladen werden könnten, und die Anwendungen nahtlos Funktionen im Bootloader aufrufen lassen, die gesamte Arbeit liegt im Linker-Skript und dem Extrahieren der verschiedenen Binärdateien, es muss kein Code in den Bootloader oder die Anwendungen selbst geschrieben werden. Dies ist eine sehr spezialisierte Lösung und wird im Allgemeinen fehlschlagen.

Die richtige Antwort ist, dass Sie Funktionszeiger verwenden, bei denen eine Binärdatei die andere aufruft. Wie und wann Sie dies tun, hängt von Ihrem Design/Ihrer Lösung ab.

Du könntest haben

int hello ( int );
int fun ( int x )
{
   return(hello(x+1));
}

damit Sie diesen Code wie gewohnt schreiben, aber

int *_hello ( int x );
int hello ( int x )
{
    if(_hello==NULL)
    {
        _hello=((volatile int *)0x00001100));
    }
    return(_hello(x));
}

Oder irgendwo hast du eine

void init_remotes ( void )
{
    _hello=point at fixed/known address for hello;
    _there=point at fixed/known address for there;
    _world=point at fixed/known address for world;
}

Ich habe keine Verwendung für Funktionszeiger, also ist meine Syntax kaputt, aber das sollte keine Rolle spielen, vielleicht wissen Sie es bereits oder verbringen die Minute damit, es zu googeln.

Structs sind auch eine (faule) Lösung, aber verstehen Sie, dass Sie Structs niemals über Kompilierdomänen hinweg verwenden sollten, da dies zu Schmerzen und Fehlern und regelmäßiger Wartung führt.

Strukturen, die sich nicht kreuzen, werden oft für solche Dinge verwendet, nicht immer eine Remote-/Shared-Library, sondern im Allgemeinen eine Möglichkeit, etwas zu abstrahieren. Angenommen, ich habe ein Speichergerät und möchte eine Funktion zum Lesen und Schreiben von Dateien haben. Ich könnte dies mit Strukturen der Funktionen storage->read(something) implementieren. Speicher -> schreiben (etwas). Und je nachdem, ob es sich um einen Flash- oder einen USB-Stick oder eine Festplatte handelt, können die Lese-/Schreibfunktionen auf flash_read(), usb_read(), hd_read() zeigen. Irgendwann vor der Verwendung einer der Funktionen werden die Elemente in der Speicherstruktur auf die medienspezifische Funktion verwiesen, wenn usb dann storage->read = usb_read so etwas mit der richtigen Syntax.

Nun zu dem, was Sie eigentlich fragen. DU musst hier die Arbeit machen, DU musst die Verbindung entwerfen, egal in welche Richtung du gehst. Wenn zum Beispiel der Bootloader die gemeinsamen/gemeinsamen Funktionen enthält, zwei einfache Ansätze.

Einer ist, dass sich der Bootloader oft in einem bekannten Adressraum befindet, sagen wir 0x00000000. Sie könnten es also so gestalten, dass sich die Funktionsadressen an einem bekannten Offset in der Bootloader-Binärdatei befinden. Sie können so weit gehen, Markierungen und Versionen zu setzen

[0x00001000] 0x12345678  some marker to help prevent crashing in case the pointers are not here
[0x00001004] 0x00000104  a version number so that your application may know what functions are there or what version of them 
[0x00001008] 0x0000200C  address to the function hello();
[0x0000100C] 0x00003120  address to the function world();

Und die Anwendung muss irgendwann nach ihrem Start und bevor sie diese entfernten/gemeinsam genutzten Funktionen aufruft, den Funktionszeiger der Anwendung auf den entfernten/gemeinsam genutzten Funktionszeiger zeigen. Dann funktioniert es einfach.

Auf der Bootloader-Seite ist es in diesem Fall nicht schwierig, dass die Tools die Arbeit des Ausfüllens der Adressen für Sie übernehmen.

Sie könnten in der Anwendung zum Beispiel haben

.globl hello
hello:
  ldr r3,=0x00001008
  ldr r3,[r3]
  bx r3

Dies ist eine grobe Lösung (nicht das asm, sondern fest codierte Adressen), die funktionieren wird. Eine einfachere Lösung besteht darin, etwas vom Bootloader an die Anwendung zu übergeben (auf diese Weise erhält Linux im Grunde seine Parameter, ein Register zeigt auf eine verknüpfte Liste oder einen Gerätebaum mit Informationen, die Linux analysiert). Wählen Sie also ein Register aus, r2 kann von Ihnen definiert werden, da Sie das Design von all dem besitzen, die Adresse (natürlich innerhalb des Bootloaders) zur Funktionszeigertabelle in einer von Ihnen entworfenen Form.

Und so hässlich und unerwünscht es auch ist, Sie werden so etwas oft finden, entschuldigen Sie die Fehler in der Syntax.

typedef struct
{
   unsigned int marker;
   unsigned int version;
   int *_hello (); //LOL I cant remember how to do this, it is probably another typedef
   int *_world;
} my_library;

my_library ml = 
{
   0x12345678,
   0x0104,
   hello,
   world
};

int hello ( int x )
{
  return(5+x);
}
int world ( int x )
{
  return(7*x);
}

//loader calling app after loading into ram
branch_to_application(LOAD_ADDRESS,something,ml);

.globl branch_to_application
branch_to_application:
   bx r0

dann verwenden Sie die passende Struktur in der Anwendung, verbinden Sie r2 auf irgendeine Weise mit der Struktur in der Anwendung (übergeben Sie r2 an Ihren C-Einstiegspunkt, tun Sie es im Bootstrap usw.)

und dann

something=ml.hello(3);

Eine andere ähnliche Lösung besteht darin, die Struktur an den Bootloader zu übergeben, z. B. kann eine Adresse, die in einem Register an die Anwendung übergeben wird, die Funktion sein, an die Sie Ihre Struktur übergeben, dann füllt der Bootloader diese Struktur aus. Oder Sie könnten es in einem ASCII tun Mode, diese Adresse könnte eine Funktion im Bootloader sein und Sie rufen sie einmal für jede Funktion auf, an die Sie die Adresse senden möchten ... was auch immer.

Hinweis Ich bin sicher, dass Sie herausfinden können, wie Sie es ohne Strukturen tun und das Risiko verringern, wenn Sie dieses Risiko nicht eliminieren, aber die gleichen Ergebnisse erzielen.

Ich lasse Sie die Syntax sortieren, da sie für diese Antwort nicht relevant ist. Es passiert auf magische Weise auf einem Betriebssystem mit C oder anderen Sprachen, wenn diese Bibliotheken aufgerufen werden, da die Tools, die Bibliothek und das Betriebssystem alle darauf ausgelegt sind, dieses Problem gemeinsam genutzter Bibliotheken zu lösen (eine Binärdatei, die Funktionen in einer anderen Binärdatei aufruft). Für Bare Metal müssen Sie die Arbeit selbst erledigen, die Tools kennen Ihr "Betriebssystem" nicht, also müssen Sie es ihnen irgendwie mitteilen. Wie bei einer C-Bibliothek setzt sich jemand hin und entwirft eine Lösung, um eine Binärdatei mit den Funktionen in einer anderen zu verbinden. Und es läuft letztendlich darauf hinaus, dass Sie irgendwann, bevor Sie die Remote-/Shared-Funktion verwenden, darauf zeigen müssen, und/oder in der Stub-Version der Funktion in Ihrem Code geht und die Adresse der Funktion findet und dann zu ihr verzweigt .

Nun zu Ihrer Frage: Erklären Sie, wie der Bootloader etwas in einer Anwendung ausführen wird. Normalerweise würden Sie den Ausdruck in den Bootloader legen und die einmal ausgeführte Anwendung ruft ihn auf. Wenn Sie möchten, dass der Bootloader Code in der Anwendung aufruft, funktioniert das, ist aber nur sehr seltsam. Das bedeutet, dass der Bootloader nicht drucken kann, bis er die Anwendung lädt, die Arbeit erledigt, um den Zeiger auf die Funktion zu finden, und sie dann aufruft. Andersherum ergibt sich zwar ein größerer Bootloader, aber kleinere Anwendungen und weniger verbrauchter Flash-Speicherplatz, da Sie nur eine Druckfunktion an einer Stelle (dem Bootloader) benötigen und nur Stubs in den Anwendungen oder einer Struktur oder was auch immer benötigen.

Im Allgemeinen hat dies Vor- und Nachteile, die Nachteile sind, dass Sie möglicherweise ein Produkt haben, das den Bootloader im Laufe der Zeit weiterentwickelt hat, und Kunden möglicherweise eine Version des Produkts mit einem 5 Jahre alten Bootloader haben, und Sie haben eine neue Anwendung für dieses Produkt und es muss irgendwie mit dem 5 Jahre alten Bootloader, dem 4 Jahre alten Bootloader, dem 3,5 Jahre alten Bootloader, dem 3 Jahre alten ... Sie bekommen das Bild. Ist das Speichern von Flash und RAM so wichtig, wie viel sparen Sie? Nach zwei oder drei Jahren haben Sie erkannt, dass Sie mehr gemeinsame Funktionen hätten haben sollen, jetzt müssen alle neuen Binärdateien die neuen Funktionen Burning Ram und Flash tragen. Aktualisieren Sie den Bootloader vor Ort, treffen Sie dies als Kundenentscheidung (und bleiben damit hängen, dass Sie alle zuvor veröffentlichten Versionen für jede neue Anwendung unterstützen müssen). Wie stellen Sie sicher, dass Sie das Produkt nicht mauern, wenn Sie das Feld-Upgrade durchführen ... Vielleicht können Sie Hunderte bis Tausende von Bytes sparen, wenn Sie saubereren Code schreiben und besser optimieren, und vielleicht reicht das für alle Binärdateien, die sie nicht haben Bibliotheken zu teilen. Vielleicht ist die gemeinsam genutzte Bibliothek selbst eine ladbare Anwendung, der Bootloader hat nur das eingebaut, was er benötigt, aber Sie laden die gemeinsam genutzte Bibliothek irgendwo aus dem Flash, dann laden Sie die Anwendung irgendwohin und geben der Anwendung beim Start einen Zeiger auf die gemeinsam genutzte Bibliothek . Und wenn Sie den neuen Satz von Anwendungen vor Ort aktualisieren/installieren, aktualisieren Sie die gemeinsam genutzte Bibliothek. Vielleicht ist die gemeinsam genutzte Bibliothek selbst eine ladbare Anwendung, der Bootloader hat nur das eingebaut, was er benötigt, aber Sie laden die gemeinsam genutzte Bibliothek irgendwo aus dem Flash, dann laden Sie die Anwendung irgendwohin und geben der Anwendung beim Start einen Zeiger auf die gemeinsam genutzte Bibliothek . Und wenn Sie den neuen Satz von Anwendungen vor Ort aktualisieren/installieren, aktualisieren Sie die gemeinsam genutzte Bibliothek. Vielleicht ist die gemeinsam genutzte Bibliothek selbst eine ladbare Anwendung, der Bootloader hat nur das eingebaut, was er benötigt, aber Sie laden die gemeinsam genutzte Bibliothek irgendwo aus dem Flash, dann laden Sie die Anwendung irgendwohin und geben der Anwendung beim Start einen Zeiger auf die gemeinsam genutzte Bibliothek . Und wenn Sie den neuen Satz von Anwendungen vor Ort aktualisieren/installieren, aktualisieren Sie die gemeinsam genutzte Bibliothek.

All dies ist meiner Meinung nach nur dann wirklich relevant, wenn Sie mehrere mögliche Anwendungen pro Bootloader haben. Wenn Sie nur einen Bootloader und eine Anwendung haben und die Bootloader vorhanden sind, damit der Benutzer die Anwendung vor Ort aktualisieren kann, dann braucht es nur genug, um dies zu tun, und die Anwendung enthält alles, was sie benötigt, ja, Sie haben möglicherweise einige verschwendete Überschneidungen.

Wenn es sich um einen primären und redundanten Bootloader zusammen mit einer primären und redundanten Anwendung handelt, können Sie Feldaktualisierungen für beide mit geringerem Risiko durchführen, das Produkt zu beschädigen. Sie brennen bereits viel Flash, wie viel sparen Sie wirklich?

Unterm Strich, da dies (vermutlich) Bare Metal auf einer MCU ist, müssen Sie den Mechanismus selbst entwerfen. Entdecken Sie die Funktionsadressen in der anderen Binärdatei und zeigen Sie auf sie in der aktuell ausgeführten Binärdatei. Sie tun dies entweder in einem Stub/Wrapper für jede Funktion (sehr verschwenderisch) oder Sie erstellen idealerweise eine einmalige Initialisierung, bevor Sie eine Remote-Funktion verwenden, die eine Tabelle mit Adressen auf der Remote-Seite hat, deren Tabelle/Liste Sie füllen Funktionszeiger auf der nahen Seite. Dann nutzen Sie einfach diese Funktionen. Ihr Grad an Paranoia/Angst/Erfahrung bestimmt, wie einfach oder kompliziert Sie dies machen möchten. Beginnen Sie offensichtlich einfach mit einer einzelnen Funktion als Experiment und komplizieren Sie von dort aus.

Ein weiterer zu erwähnender Punkt ist, dass man dem Bootloader und dem Hauptprogramm dauerhaft separate Bereiche des statischen Speichers zuweisen sollte (oder den Bootloader so entwerfen sollte, dass keine der gemeinsam genutzten Funktionen einen statischen Speicher erfordert).
Danke für die Beantwortung meiner Frage Freunde.

Ich musste dies einmal tun, um Flash/ROM-Speicherplatz zu sparen. Es gab einige CRC- und Hardware-Treiberfunktionen, die zwischen dem Bootloader und der Anwendung geteilt werden konnten. Die einfache Antwort lautet: Erstellen Sie einen Funktionszeiger, weisen Sie den Funktionszeiger der entsprechenden Adresse der Funktion zu, die Sie aufrufen möchten, und rufen Sie die Funktion über den Funktionszeiger auf. Sie können kreativ werden, wie Sie die Funktionsadressinformationen an jedes Programm übergeben. Der schnelle und schmutzige Weg: Hartkodieren Sie die Adressen, indem Sie sich die Funktionsadresse aus einer von Ihrem Compiler generierten Zuordnungs-/Listendatei ansehen. Ein ausgefeilterer Weg: Erstellen Sie eine "Funktionsadressen-Nachschlagetabelle", die immer an einer bestimmten Adresse im Speicher platziert wird (Sie müssen Linker-Dateidirektiven verwenden, um dies zu erreichen). Auf diese Weise, Sie können den Bootloader oder die Anwendung aktualisieren (was bedeutet, dass sich die Funktionsadressen ändern können) und trotzdem beide synchronisiert bleiben. Sie müssen wahrscheinlich auf eine compilerspezifische Syntax zugreifen, um die Nachschlagetabelle der Funktionsadressen zu generieren.

können Sie einen Beispielcode verwenden?

Ich schreibe diesen Code, um das Problem zu lösen, und ich habe ihn getestet. IDE: keil uvision v5.22

Im Bootloader-Programm:

  //Shared_Func is a sample function for sharing between two app
   void Shared_Func(void)
  {
    while(1)
    {
        //do somethings
    }
}

typedef void (*ptr_shared_func_t)(void);//define new function pointer type
const ptr_shared_func_t ptr_const_shared_fun __attribute__((at(USER_APP_BASE_ADDRESS -4))) = Shared_Func;
//save address of Shared_Func in special address. To avoid non-interference between application code and address of shared function ,i defined that at (USER_APP_BASE_ADDRESS -4).

Im Bewerbungsprogramm:

typedef void (*ptr_shared_fun_t)(void);
ptr_shared_fun_t ptr_shared_fun;

in der Hauptfunktion oder überall dort, wo wir Shared_Func aufrufen möchten:

unsigned int *add_shared_fun = (unsigned int *)(USER_APP_BASE_ADDRESS-4);
ptr_shared_fun = (void(*)(void))(*add_shared_fun);
ptr_shared_fun();//call SharedFunc with function pointer