Forum: Mikrocontroller und Digitale Elektronik Befehlsinterpreter auf MC und diverser anderer Kram


von G. Ast (Gast)


Lesenswert?

Hi Leute,
ich will grad folgendes auf meinem Controller implementieren:
Es geht wieder einmal um einen Befehls-Interpreter.
Der Controller ist über RS232 mit einem PC verbunden, dort kann man im 
Terminal befehle eingeben.
jetzt soll der Controller die eingegebenen Befehle erkennen, und ggf. 
eine entsprechende Funktion ausführen.
ABER: der Befehlsinterpreter soll auch Argumente, die man eingibt, 
ebenfalls erkennen. Also wenn ich eingebe

test 123 [ENTER]

Dann soll der Controller die entsprechende Funktion aufrufen (die halt, 
die für den Befehl "test" definiert wurde) und ihr den Parameter 123 
übergeben.

Kann mir einer verraten, wie man das elegant lösen könnte? Schön wäre es 
natürlich, wenn es mit Funktionspointern irgendwie ginge. Soweit bin ich 
schon, dass ich eine Tabelle von Funktionspointern habe, die der 
Controller dann durch pflügt, wenn man was eingibt.

Weiter möchte ich noch folgendes machen, was sicher ein bisschen 
exotisch ist (wie ich finde, aber durchaus sinnvoll sein kann). Und zwar 
möchte ich, dass die Liste der Befehle dynamisch ist. Das heisst: sie 
soll zur Laufzeit erweitert werden können.

Welchen Sinn hat dies?

Nun: Auf den Controller wird ein einzelnes "Base Package" runter 
geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein 
Startup-Code. Jetzt kann man das Base Package einfach auf den Controller 
runterladen, und dieses sucht dann nach einem "Application Package". 
Dieses enthält ann das eigentliche Programm, und dieses wiederum soll 
natürlich eigene Funktionen im Terminal anbieten können, wenn es das 
will.
Wie könnte man eine solche dynamische Befehlsliste realisieren? Ohne 
malloc wird das ja schwer, aber malloc auf Controllern ist ja 'böse'. 
Oder? ;-)

Weiter habe ich noch eine letzte Frage, die mich beisst:

Ich definiere im "Base Package" gewisse Hilfs-Funktionen, z.B. printf 
sitzt im Base Package. Jetzt will ich aber nicht bei jedem neuen 
Programm, das ich mache, den Code des Base Package includen! (Genau das 
ist ja der Sinn der Sache. Wenn man so will: Das Base Package stellt 
eine 'Library' zur Verfügung, u.a. mit Sachen wie printf, scanf, usw.). 
Wie kriege ich jetzt einen Pointer auf die Funktion printf, wenn ich 
doch im Application Package gar nicht wissen kann, wo die liegt?

Ich weiss, das ist ein seltsames Vorhaben. Ich weiss aber auch, dass es 
sowas gibt, weil ich schon mal damit gearbeitet habe. Da konnte man zur 
Laufzeit neue Software runterladen, und die hat ohne Reset des 
Controllers funktioniert. Wie macht man das bloss?

Also, wenn ihr mir ein paar Denkanstösse geben könntet, wär das ganz 
toll. Ich danke schon im Voraus!

Viele Grüsse

von Karl H. (kbuchegg)


Lesenswert?

Zu deinem ersten Problem.
Eine Befehlsliste mit zugehörigen Funktionen zur Laufzeit zu erweitern, 
ist nicht das grosse Problem. Der Weg wird über Funktionspointer führen. 
Soweit hast du das erkennt. Das eigentliche Problem besteht woanders. 
Die Argumentübergabe.
Du musst dir einen Mechanismus ausdenken, mit dem es dir möglich ist, 
eine beliebige Anzahl von beliebigen Datentypen an eine Funktion zu 
übergeben, und zwar möglichst so, dass die aufzurufenden Funktionen 
immer eine gleiche Argumentliste haben.
Wie könnte man das machen?
Ich werd jetzt einfach mal das Problem 'beliebige Datentypen' ignorieren 
und mich auf 'beliebige Anzahl' konzentrieren.
Man könnte zb. der Funkion ein Array übergeben, in dem alle Parameter 
enthalten sind. Dazu noch einen int, der die Anzahl der Argumente 
angibt.

Sagen wir einfach mal, dass eine Funktion beliebig viele int bekommen 
kann. Dann könnte die vorgeschrieben Signatur für eine Funktion zb so 
aussehen:
1
typedef void (*FktPtr)( int count, int* args );

Ein FktPtr ist also ein Pointer auf eine Funktion, die keinen Returnwert 
liefert und 2 Argument kriegt: einen int als ArgumentCounter und einen 
Pointer auf ein Array von ints, welche die Argument darstellen.

Soweit, so schlecht.
Was brauchst du noch?
Du brauchst logischerweise eine Tabelle, in der alle Kommandowörter 
aufgeführt sind und die dazu zugehörigen Funktionen, die aufzurufen 
sind.

Da machen wir uns gleich mal eine Struktur, die einen solchen Eintrag 
beschreibt.
1
struct Command
2
{
3
  const char*  Keyword;
4
  FktPtr       Function;
5
};

und eine Tabelle, die die Einträge aufnimmt. Jetzt musst du dich 
entscheiden: Willst du das voll dynamisch machen (also mit malloc 
arbeiten) oder reicht es dir, wenn dein Interpreter max. 20 Kommandos 
halten kann.
Auch hier wieder: pragmatisch geh ich mal den ersten Weg
1
#define MAX_COMMANDS 20
2
struct Command Commands[ MAX_COMMANDS ];
3
int NrCommands = 0;

Jetzt noch eine Funktion, die ein neuese Kommando einfügt. Beachte: Der 
Keyword Pointer in struct Command soll immer auf einen String zeigen, 
der sicher existiert. D.h. man sollte der gleich folgenden Add Funktion 
immer einen konstanten String übergeben und kein char Array. Das ist 
jetzt natürlich eine Konvention, die ich hier akzeptieren würde (vor 
allem in Hinblick darauf, dass es sich hier um einen MC handelt mit 
traditionell knappen Resourcen)
1
unsigned char AddCommand( const char* Keyword, FktPtr Function )
2
{
3
  if( NrCommands >= MAX_COMMANDS )
4
    return FALSE;
5
6
  Commands[NrCommands].Keyword = Keyword;
7
  Commands[NrCommands].Function = Function;
8
  NrCommands++;
9
10
  return TRUE;
11
}

Gut, Kommandos hinzufügen geht also schon mal ...
1
void TestFkt( int count, int* args )
2
{
3
}
4
5
void EchoFkt( int count, int* args )
6
{
7
  int i;
8
9
  for( i = 0; i < count; ++i )
10
    printf( "%d\n", args[i] );
11
}
12
13
int main()
14
{
15
  AddCommand( "test", TestFkt );
16
  AddCommand( "echo", EchoFkt );
17
}

... aber irgendwann kommt die Stunde der Wahrheit und vom Benutzer kommt 
eine Eingabezeile. Was ist zu tun?
Das erste Wort aus der Einagbezeile ist zu extrahieren. Damit wird in 
die Kommandotabelle gegangen und der entsprechende Eintrag gesucht. Wird 
einer gefunden, wird die zugehörie Funktion aufgerufen (Die Argumente 
ignorieren wir erst mal).

Dazu schreiben wir gleich mal eine Funktion. Wieder: zur Vereinfachung 
nehm ich einfach mal an, dass Kommandos eine maximale Länge haben. Ich 
will ja hier keine Stringverarbeitung zeigen, sondern Kommandoauswertung 
:-) (Ich nehm für die Stringverarbeitung auch Array Syntax und keine 
Pointersyntax. Einfach nur, weil man dann leichter verfolgen kann, was 
passiert.)
1
void Execute( const char* Input )
2
{
3
  size_t i, j;
4
  char Command[20];
5
6
  // Whitespace am Anfang überlesen
7
8
  while( Input[i] != '\0' && ( Input[i] == ' ' || Input[i] == '\t' ||
9
                               Input[i] == '\n' ) )
10
    i++;
11
  if( Input[i] == '\0' )
12
    return
13
14
  j = 0;
15
  while( Input[i] != '\0' && !( Input[i] == ' ' || Input[i] == '\t' ||
16
                                Input[i] == '\n' ) ) {
17
    Command[j++] = Input[i++];
18
19
  for( i = 0; i < NrCommands; ++i ) {
20
    if( stricmp( Commands[i].Keyword, Command ) == 0 ) {
21
      (*Commands[i].Function)();
22
      return;
23
    }
24
  }
25
26
  printf( "Don't know how to \'%s\'\n", Command );
27
}

Soweit so gut. Wenn ich jetzt keinen Fehler eingebaut habe, dann müsste 
schon mal die korrekte Funktion aufgerufen werden.

Wie gehts weiter?
Argumente: Momentan haben wir ja vereinbart: nur int Argumente.
Mann müsste also beim zerlegen der EIngabezeile weiter machen, die 
jeweils nächsten Wörter extrahieren, das jeweilge Wort in einen int mit 
gleichen Wert wandeln, alle diese int in einem Array sammeln und beim 
Aufruf der Funktion mitgeben.

Das überlass ich mal dir, das zu machen.

Schlussendlich, wollen wir noch die Einschränkung aufheben, das nur int 
Argumente möglich sind. Wie könnte man das machen?
Nun, dafür gibt es ein Vorbild! Sieh dir einfach mal an, wie du in 
main() die Argumente von deinem Betriebssystem kommt. Die vollständige 
Signatur sieht so aus
1
int main( int argc, char* argc[] )
2
{
3
  ...
4
}

main bekommt also einen Counter (argc), der die Anzahl der Argumente 
angibt, und ein Array von char-Pointern, wobei jeder Pointer auf einen 
String zeigt, der das jeweilige Argument in Stringform enthält. Na das 
ist doch was! Das kann man kopieren.

von Route_66 (Gast)


Lesenswert?

Hallo G. ast, Du beschreibst gerade die Funktionalität der 
Programmiersprache FORTH. Die ist zwar schon uralt (um 1970), stellt 
aber - vom PC aus betrachtet - genau dieses Verhalten zur Verfügung. 
Allerdings ist sie durch die umgekehrt polnische Notation (UPN) seeehr 
gewöhnungsbedürftig. Wenn man die Tricks erst mal drauf hat, ist man 
jedoch ganz schnell mit der Anwendungsentwicklung fertig. Es ergibt sich 
kompakter schneller Code, da die Vorteile von Interpreter UND Compiler 
zum Tragen kommen.
Allerdings muss der Programmierer stets wissen was er tut: es gibt keine 
Typkonventionen oder gar -prüfungen.
Ausführliche Informationen zu den verschiedensten 
FORTH-Implementierungen findes Du im Netz.

von G. Ast (Gast)


Lesenswert?

Hi Karl heinz,
Danke für deine ausführlichen Erläuterungen. Dein Code sieht plausibel 
aus, ich versuch das dann gleich mal!
Jetzt, hast du auch noch  einen Lösungsansatz für mein letzteres 
Problem:
Ich will ja nicht bei jedem Programm, das ich schreibe, die ANSI-C 
library einbinden. Schön wäre es, wenn man die 1x ins Flash rutnerladen 
könnte, und die ist dann für immer und ewig dort. Funktionen wir printf 
oder so liegen dann irgendwo in diesem Flash-Bereich, wo ich das 
runtergeladen habe.
Wie könnte ich nun herausfinden, WO GENAU printf liegt, damit ich es in 
meinem Awnendungsprogramm aufrufen kann?
Der Gedanke dabei ist nämlich, dass man dann ähnlich, wie bei einem 
Betriebssystem, gewisse oft verwendete Grundfunktionen zur Verfügung 
stellt, die dann von einer nwendung benutzt werden können.

Viele Grüsse & Danke

von G. Ast (Gast)


Lesenswert?

@ Karl heinz Buchegger:
Hi Karl heinz,
ich hab noch ne Frage!
Und zwar, kannst du mir vielleicht einen Tipp geben, wie ich das ganze 
lösen könnte, wenn es Interruptgesteuert ablaufen soll? Also über Rx und 
Tx Interrupt des UART?
Im Moment polle ich die entsprechenden Bits nämlich. Aber das ist 
hässlich, umständlich und unzuverlässig, denn wenn der MC nebenbei auch 
noch andere Sachen machen soll, kann es ja passieren dass er einige 
empfangene Bytes 'verpasst', weil er das Rx-Flag nicht rechtzeitig 
abfegragt hat.
Wie könnte man dem abhelfen?

Vielen Dank & Gruss

von Karl H. (kbuchegg)


Lesenswert?

G. Ast wrote:

> Wie könnte ich nun herausfinden, WO GENAU printf liegt, damit ich es in
> meinem Awnendungsprogramm aufrufen kann?

Das wird so nicht gehen.
Du wirst dir eine Tabelle bauen müssen, in der Funktionspointer auf 
deine Librry Funktionen liegen. Diese Tabelle muss dann im Flash (oder 
im EEPROM) an immer der gleichen Stelle liegen.
Die tatsächlichen Funktionsaufrufe nehmen dann den Umweg über diese 
Funktionspointer.
Wenn du die Tabelle zb. im EEPROM platzierst, könnte ich mir vorstellen, 
das das Ganze einfacher wird. Dein Usercode lädt als erstes die Tabelle 
vom EEPROM ins SRAM und stellt somit den Umleitungsfunktionen die 
Funktionspointer zur Verfügung.

von Karl H. (kbuchegg)


Lesenswert?

G. Ast wrote:
> @ Karl heinz Buchegger:
> Hi Karl heinz,
> ich hab noch ne Frage!
> Und zwar, kannst du mir vielleicht einen Tipp geben, wie ich das ganze
> lösen könnte, wenn es Interruptgesteuert ablaufen soll? Also über Rx und
> Tx Interrupt des UART?

Ganz einfach.
In der ISR werden die Zeichen in einer globalen String Variablen 
gesammelt, bis die ISR zb. den Return erkennt. Danach setzt sie ein 
Jobflag, der aussagt, dass eine komplette Eingabezeile fertig vorliegt 
und beim nächsten Durchlauf der mainloop wird dieses Jobflag ausgwertet 
und die Eingabezeile der Verarbeitung zugeführt.

von Olaf (Gast)


Lesenswert?

> Kann mir einer verraten, wie man das elegant lösen könnte?

Klar, kein Problem. :-)

Da dein Problem ein echtes Standardproblem ist gibt es dafuer eigene
Programme namens flex,yacc,bison. Die sind auf jedem Unix installiert,
und man darf vermuten das es sie auch fuer andere weniger wichtige
Betriebssystem gibt.

http://flex.sourceforge.net/
http://www4.tu-ilmenau.de/ate/TET/nlnet/flex_bison.html

Du beschreibst dann dein Problem in einer Art Metasprache und diese
Programme erzeugen dir daraus einen Parser in C-Code den du nur noch
in dein Programm einbinden musst.

Olaf

von Karl H. (kbuchegg)


Lesenswert?

Olaf wrote:
>> Kann mir einer verraten, wie man das elegant lösen könnte?
>
> Klar, kein Problem. :-)
>
> Da dein Problem ein echtes Standardproblem ist gibt es dafuer eigene
> Programme namens flex,yacc,bison. Die sind auf jedem Unix installiert,
> und man darf vermuten das es sie auch fuer andere weniger wichtige
> Betriebssystem gibt.

Das möcht ich sehen, wie du mit flex & bison einen Parser für eine 
Sprache baust, die zur Compilezeit nicht feststeht, weil der Benutzer 
neue Module hinzufügen kann. Und um einen String in Wörter aufzudröseln, 
ist flex dann ja wohl ein klein wenig Overkill :-)

von Stephan (Gast)


Lesenswert?

Hallo zusammen,
also lex&yacc einzusetzen für so einen Trivial-Parser, ist gelinde 
gesagt, mit Kanonen auf Spatzen geschossen. Ich habe mal eine Shell, a 
la bash, damit gemacht (mit &-operator, usw), selbst dafür war das 
Vorgehen noch fast zu klein. Außerdem ist der Einarbeitungsaufwand in 
yacc (bzw. bison) für das Ziel zu hoch, vor allem, wenn man die nötigen 
theoretischen Grundlagen nicht parat hat (was war nochmal ein 
shift-reduce-Konflikt?).

Mein Tip: Mach sowas wie den DOS-Kommandinterpreter, aber noch mit 
Einschränkungen: Du brauchst wahrscheinlich keine Pipes und auch nicht 
mehrere Befehle pro Zeile. Deine "Kommandos" sind keine separat ladbaren 
Progrämmchen, sondern Funktionen mit entsprechenden Argumenten 
(argc,argv), die in einer Tabelle gehalten werden. Eleganter ist es 
natürlich, wenn die konkreten Kommandos Ableitungen einer abstrakten 
Klasse Befehl sind.

Gruß
Stephan

von G. Ast (Gast)


Lesenswert?

Hi Jungens,
vielen Dank für eure Hilfe!

Wie könnte ich das Problem mit der Funktionspointer-Tabelle lösen? 
Einfach fix an eine beliebige Adresse speichern?

Zu dem Befehlsinterpreter:
Das mit dem Rx-Interrupt ist klar, das klappt nun soweit auch. Nun 
möchte ich aber Ausgabestrings auch Interruptgesteuert senden, mit Hilfe 
des Tx-Interrupts. Also ohne Polling! Wie könnte ich das lösen?

Nun, noch das aufsplitten in die Teilstrings.
Beispiel, ich gebe ein:

test 123 abc

nun soll die Funktion test aufgerufen werden, die sieht doch so aus:

void test(int argc, char* argv)

und argc ist jetzt 2, argv ist ein String-Array mit "123" und "abc" 
drin.
Wie nur kann ich "123" und "abc" aus dem Eingabestring elegant raus 
pfriemeln? Irgendwie krieg ich das nicht gebacken, es funktioniert nicht 
richtig.
Könnt ihr mir da noch eine Hilfestellung geben?

Vielen Dank, und Grüsse

von Karl H. (kbuchegg)


Lesenswert?

G. Ast wrote:

> Zu dem Befehlsinterpreter:
> Das mit dem Rx-Interrupt ist klar, das klappt nun soweit auch. Nun
> möchte ich aber Ausgabestrings auch Interruptgesteuert senden, mit Hilfe
> des Tx-Interrupts. Also ohne Polling! Wie könnte ich das lösen?

Da müsste sich in der Codesammlung auch was dazu finden lassen.
Im Prinzip: Die UART kann dich mit einem Interrupt benachrichtigen, wenn 
sie ein Zeichen fertig abgeschickt hat und wieder frei ist.
Dein Ausgabestring kommt in einen Buffer, das erste Zeichen geht gleich 
zur UART. Wenn der Interrupt kommt, wird nachgesehen ob noch was im 
Buffer ist, wenn ja: nächstes Zeichen in die UART stellen.

> Nun, noch das aufsplitten in die Teilstrings.
> Beispiel, ich gebe ein:
>
> test 123 abc
>
> nun soll die Funktion test aufgerufen werden, die sieht doch so aus:
>
> void test(int argc, char* argv)
Nein. So sieht sie nicht aus (schau dir nochmal main() genau an!)

Die sieht sie aus:
void test(int argc, char* argv[])

> und argc ist jetzt 2, argv ist ein String-Array mit "123" und "abc"
> drin.

Schau dir main an!
argv ist ein Array von character Pointern!

> Wie nur kann ich "123" und "abc" aus dem Eingabestring elegant raus
> pfriemeln? Irgendwie krieg ich das nicht gebacken, es funktioniert nicht
> richtig.
> Könnt ihr mir da noch eine Hilfestellung geben?

Sicher.
Du kannst zb, ein Array bauen, welches Pointer auf den Originalstring 
enthält. Im Originalstring fügst du an strategischen Stellen '\0' ein. 
So vermeidest du, dass du Strings allokieren musst bzw. umkopieren 
musst.


Von der UART kriegst du den String hier
   "TEST 123 abc"


  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
  | T | E | S | T |   | 1 | 2 | 3 |   | a | b | c | \0|   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+


Du überschreibst die Spaces mit \0 und setzt gleichzeitig ein Pointer 
Array auf, so dass du erhältst

  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
  | T | E | S | T | \0| 1 | 2 | 3 | \0| a | b | c | \0|   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    ^                  ^                ^
    |                  |                |
    +----------+       |                |
               |       |                |
  argv         |       |                |
  +-------+    |       |                |
  |   o--------+       |                |
  +-------+            |                |
  |   o----------------+                |
  +-------+                             |
  |   o---------------------------------+
  +-------+
  | NULL  |
  +-------+
  |       |
  +-------+
  |       |


Wenn du jetzt deine Funktion mit den Argumenten 3 und argv aufrufst, hat 
die Funktion alles was sie braucht.

Sie kann die Argumente ganz einfach durchgehen

void test( int argc, char* argv[] )
{
  for( int i = 0; i < argc; ++i )
    printf( "%d: %s\n", i, argv[i] );
}

von G. Ast (Gast)


Lesenswert?

Hi Karl heinz,
vielen Dank. Ich werde das dann testen, aber ich denke wenn du das so 
beschreibst wird das sicher funktionieren :-)

Noch eine Frage zum Senden mit Interrupt:

Nehmen wir an, ich habe in meinem UART-Modul einen Buffer von 64 Bytes 
reserviert für Sendedaten.
Was jetzt, wenn ich einen String senden muss, der länger als 64 Bytes 
ist?

Da hakt es bei mir irgendwie noch.

Vielen Dank,

Gruss

von Karl H. (kbuchegg)


Lesenswert?

G. Ast wrote:
> Hi Karl heinz,
> vielen Dank. Ich werde das dann testen, aber ich denke wenn du das so
> beschreibst wird das sicher funktionieren :-)
>
> Noch eine Frage zum Senden mit Interrupt:
>
> Nehmen wir an, ich habe in meinem UART-Modul einen Buffer von 64 Bytes
> reserviert für Sendedaten.
> Was jetzt, wenn ich einen String senden muss, der länger als 64 Bytes
> ist?

Du hast eine Funktion, die den String in den Buffer schreibt.
Diese Funktion muss natürlich warten, bis tatsächlich Platz im Buffer 
ist um wieder ein Zeichen in den Buffer stellen zu können.
Wie kann Platz entstehen?
Der Interrupt sorgt dafür, dass wieder ein Zeichen aus dem Buffer per 
UART auf die Reise geschickt wird. Dadurch wird dann wieder Platz für 
ein neues Zeichen.

Irgendwann hat dann die Sendefunktion (möglicherweise auch dadurch, dass 
sie warten musste) alle auszugebenden Zeichen in den Buffer gepfriemelt 
und kann retournieren. Das heist nicht, das deswegen schon alles 
gesendet wurde. Es heist lediglich, dass die Sendefunktion alle Bytes 
loswerden konnte. Einige wurden schon übertragen, ein anderes ist gerade 
im Übertragen, andere warten im Sendebuffer.

Studier mal die UART Library vom Peter Fleury (zu finden mit Google).

von G. Ast (Gast)


Lesenswert?

Hi Karl Heinz,
das heisst: Beim Senden von Daten muss ich IMMER warten, egal ob ich 
Interrupts verwende oder Polling. Richtig?
Die Sendefunktion wartet dann halt einfach, bis der Buffer wieder leer 
ist.
Ich habe mir das irgendwie so vorgestellt, dass ich einfach eine 
Sendefunktion habe, der übergebe ich die Daten, die ich senden will, und 
kann nachher gleich weiterarbeiten. Der Rest wird im UART erledigt. Aber 
ich hab leider grade keine Idee, wie sich sowas realisieren liesse, 
deshalb meine Fragerei.

Grüsse

von Karl H. (kbuchegg)


Lesenswert?

G. Ast wrote:
> Hi Karl Heinz,
> das heisst: Beim Senden von Daten muss ich IMMER warten, egal ob ich
> Interrupts verwende oder Polling. Richtig?

Nein.
Wenn du Interrupts zum Senden und einen Buffer benutzt, musst du nur 
dann warten, wenn kein Platz im Buffer ist. Wenn der Buffer Platz zum 
aufnehmen der Zeichen hat, muss überhaupt niemand warten

> Ich habe mir das irgendwie so vorgestellt, dass ich einfach eine
> Sendefunktion habe, der übergebe ich die Daten, die ich senden will, und
> kann nachher gleich weiterarbeiten. Der Rest wird im UART erledigt.

Dann müsste der Buffer im UART sein.
Irgendwo muss ein Buffer sein, der das zu Sendene zwischenzeitlich 
aufnimmt. Und wenn dieser voll ist, dann muss gewartet werden, bis er 
wieder was aufnehmen kann. Ob der Buffer jetzt in der Hardware-UART 
integriert ist, oder ob du den Softwaremässig machen musst, ändert 
nichts am Prinzip.

Beispiel aus dem realen Leben:
Dein kannst deinem Kumpel Nachrichten schicken.
Die Nachrichten schreibst du auf ein Papier, welches du von einem 
Vorratsstapel Papier nimmst und legst sie deinem Kumpel in den 
Eingangskorb. Wenn dein Kumpel die Nachricht gelesen hat, radiert er sie 
aus und legt das Papier wieder auf den Stapel Vorratspapier.

Solange genug Vorratspapier da ist: kein Problem. Du schreibst und 
irgendwann liest dein Kumpel.
Was aber tust du, wenn kein Vorratspapier mehr da ist? Du wartest, bis 
dein Kumpel mit dem Lesen wieder nachgekommen ist und wieder ein paar 
Blätter freiradiert hat.

von Peter D. (peda)


Lesenswert?

G. Ast wrote:
> Nun: Auf den Controller wird ein einzelnes "Base Package" runter
> geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein
> Startup-Code. Jetzt kann man das Base Package einfach auf den Controller
> runterladen, und dieses sucht dann nach einem "Application Package".
> Dieses enthält ann das eigentliche Programm, und dieses wiederum soll
> natürlich eigene Funktionen im Terminal anbieten können, wenn es das
> will.


Dann brauchst Du ein Betriebssystem, welches es gestattet verschiedene 
Programme zu starten und ihnen einen jeweils eigenen Speicher 
zuzuweisen.
Und einen Compiler, der dazu passende Programme erzeugen kann.

Welcher MC soll es denn überhaupt sein?

Auf nem AVR kannste es vergessen, der hat keine Speicherverwaltung.
Jedes Programm verfügt über den gesamten SRAM und weiß nicht, ob noch 
andere Programme im Flash sind.
Ein Programm kann zwar ein anderes starten, dieses wird aber sofort den 
Speicher des Aufrufers zerstören. Eine Rückkehr oder gar Funktionsaufruf 
ist daher unmöglich.
Außerdem kann nur ein Programm Interrupts benutzen, da diese nicht 
verschieblich sind.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger wrote:
> G. Ast wrote:
>> Nun: Auf den Controller wird ein einzelnes "Base Package" runter
>> geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein
>> Startup-Code. Jetzt kann man das Base Package einfach auf den Controller
>> runterladen, und dieses sucht dann nach einem "Application Package".
>> Dieses enthält ann das eigentliche Programm, und dieses wiederum soll
>> natürlich eigene Funktionen im Terminal anbieten können, wenn es das
>> will.
>
>
> Dann brauchst Du ein Betriebssystem, welches es gestattet verschiedene
> Programme zu starten und ihnen einen jeweils eigenen Speicher
> zuzuweisen.

An sowas hab ich auch zunächst gedacht.
Ich glaube aber, er möchte sich die Funktionalität seines Programmes zur 
Laufzeit zusammenstellen, in dem er 'Packages' runterlädt, die sich dann 
ins Programm einbinden können. Es gibt also eigentlich nur ein einziges 
Program, welches zur Laufzeit aus Komponenten zusammengestellt wird. Ich 
denke mit einem spezialisiertem Bootloader müsste sowas möglich sein. In 
dem Fall wäre dann der Bootloader sowas wie ein Mini-Betriebssystem, 
welches dafür sorgt, dass die Einzelteile im Speicher sauber angeordnet 
werden und nach dem Download eine Art 'init'-Funktion in jedem Paket 
aufgerufen wird, damit sich die Komponente beim Programmkern anmelden 
kann. Irgendsowas in der Art.

Ich find die Idee an sich interessant, wenn auch sicherlich nicht 
trivial zu implementieren. Ganz und gar nicht trivial.

> Und einen Compiler, der dazu passende Programme erzeugen kann.

Das wird wohl eines der Hauptprobleme an der ganzen Sache sein. 
Compiler/Linker/Librarian dazu zu überreden, prinzipiell ausführbare 
Module zu erzeugen, die in sich bereits gelinkt wurden, aber trotzdem 
keine fertigen EXE darstellen.

von Michael G. (graf)


Lesenswert?

Moin,

hab auch schon die ganze zeit beim durchlesen gedacht wann das 
schwierigste aller probleme hier angesprochen wird.

eine möglichkeit währe relativ gelinkter code. sprich im modul werden 
nur refferenzielle sprünge durchgeführt, keine absoluten. somit könnte 
der Code überall liegen.

Speicher müsste über malloc verwaltet werden. Der Pointer selber muss 
wie z.B. die Funktionstabelle für die Lib Funktionen an einer zentralle 
stelle liegen.

Alternative dazu eine CPU mit MMU

gruss

von G. Ast (Gast)


Lesenswert?

¨Hey Karl Heinz,
du hast es genau erfasst! Genau das will ich machen. Es gibt ein 
Mini-"Betriebssystem", das nach dem Reset gestartet wird. Danach wird 
der Speicher (also das angeschlossene Flash) nach 'Packages' durchsucht. 
Die liegen immer an bestimmten Adressen, nämlich genau an den 
Sektorgrenzen des Flash. Und dort schaut jetzt das System nach dem Reset 
nach, ob ein Package vorhanden ist. Wenn ja, dann führt es dessen 
Init-Funktion aus. Die kann z.B. weitere Befehle zum Terminal 
hinzufügen, gewisse Peripherie-Bausteine initialisieren usw.
Genau so soll das gehen!
Dass es nicht einfach ist, habe ich schon bemerkt :-)
Aber es wäre recht praktisch, wenn man sich das gewünschte Programm 
nachher aus einzelnen Modulen 'zusammenbasteln' kann, fast nach einem 
Baukasten-System. Man setzt dann einfach die gewünschten Komponenten 
zusammen, lädt das Zeuch runter - fertig ist das Programm!

Ausserdem, das mit der Funktionspointer-Tabelle hat noch einen anderen 
Hintergrund. Beispiel: Ich verwende oft Funktionen wie strcmp oder 
printf. Die müssten dann ja jedes mal mit compiliert werden und 
runtergeladen werden. Wenn die schon fix im Flash wären (da also eine 
'Library' mit bereits vordefinierten Funktionen läge) dann wäre das 
natürlich praktisch. In Windows-Programmen z.B. muss man sich ja auch 
nicht mehr um Dinge wie "Erstelle eine Datei" oder sowas kümmern, man 
macht einfach einen bestimmten Funktionsaufruf an das Betriebssystem und 
das erledigt alles für einen.

@Peter Dannegger
Ich würde das gerne auf einem ARM7-Controller realisieren. Es stehen 256 
MB SDRAM zur Verfügung, Flash gibts auch ausreichend (1 MB).

Grüsse

von G. Ast (Gast)


Lesenswert?

@Michael Graf
> sprich im modul werden
> nur refferenzielle sprünge durchgeführt, keine absoluten. somit könnte
> der Code überall liegen.

Dafür dient glaube ich die Compiler-Option "generate position 
independent code", oder nicht? Ich meine, irgendwo sowas mal gesehen zu 
haben.

von G. Ast (Gast)


Lesenswert?

@Karl heinz:
> Das wird wohl eines der Hauptprobleme an der ganzen Sache sein.
> Compiler/Linker/Librarian dazu zu überreden, prinzipiell ausführbare
> Module zu erzeugen, die in sich bereits gelinkt wurden, aber trotzdem
> keine fertigen EXE darstellen.

Das Problem habe ich nun, denke ich zumindest, einigermassen angehen 
können. Ein Ansatz wäre doch der:
Und zwar besitzt das 'Betriebssystem' (ich schreib das in Gänsefüsschen, 
weils ja gar kein BS ist) einen 'Control Block', der nichts anderes ist 
als ein grosses struct:

typedef struct __TestControlBlock
{
  void (*Funktion1)(void);
  int (*Funktion2)(char test, int blubb);
  ...
} TestControlBlock;

In dem Struct sind also Pointer auf alle Funktionen hinterlegt, die das 
Betriebssystem veröffentlichen will. Beispielsweise könnte man hier ja 
auch einen Funktionspointer für printf definieren oder die Funktion, die 
auf einer SD/MMC-Karte Dateien einliest oder dergleichen.
Und jetzt wird bei jedem Package beim Start die Funktion InitPackage 
aufgerufen und ein Pointer auf diesen struct übergeben! Das Package 
'kennt' nun alle Betriebssystem-Funktionen, denn es kann ja über den 
struct zugreifen. Wo die Funktionen liegen, ist wurscht, das BS gibt die 
Pointer ja vor. Somit muss man bei einem neuen Package nur noch das 
Header-File includen, wo definiert ist, wie der struct aussieht. Den 
Rest erledigt das BS. Mit Makros könnte man dann noch solche Konstrukte 
machen, wenn man will:

#define Funktion1() TestControlBlock->Funktion1
#define Funktion2(a, b) TestControlBlock->Funktion2(a, b)

So sieht der C-Quellcode dann aus, wie wenn das alles 'normale' 
funktionen wären.
Im Mainloop des BS wird dann für jedes (mittlerweile dem System 
bekannten) Package eine 'PackageMain'-Funktion aufgerufen, so kann man 
irgendwelchen Code in den Packages platzieren. Also eine Art 
kooperatives Multitasking.

Die Frage ist jetzt nur noch: Wie findet das BS heraus, wo die 
InitPackage-Funktionen sind?
Darüber bin ich mir noch nicht im klaren. Ein Ansatz wäre, dass man 
festlegt: ein Package kann nur immer an einer Sektorgrenze des 
Flash-Speichers liegen. Dann bräuchte das BS nur noch an den jeweiligen 
Adressen zu schauen, ob da was sinnvolles steht oder nicht, und wenn ja, 
dann wird das ausgeführt. Das schöne wäre daran auch, dass man dann 
einzelne Sektoren des Flashs löschen kann, und so immer einzelne 
Packages entfernen kann (oder hinzufügen, wenn man in einen Sektor was 
neues rein programmiert).

Was ist von meiner Idee zu halten?

Das mit dem UART scheint jetzt übrigens zu funktionieren. Vielen Dank!

von Michael G. (graf)


Lesenswert?

Hi

jedes paket hat am anfan einen kleinen info header, in dem z.B. der 
Pointer der Startfunktion hinterlegt ist. wie gross das Paket selber 
ist, ...

weiter noch eine Magic number, das ist eine zahl, so konstruiert, das 
sie nicht zufällig zustande kommen kann. Grub verwendet z.B. sowas. 
Diese zahl sollte so im Programm nicht vorkommen. man kann dann nach so 
einer zahl suchen, um z.B. so einen Headerblock zu identifizieren. 
Natürlich sollte so ein block noch zusätzlich durch z.B. eine prüfsumme 
gesichert werden, im falle das doch das eintritt, was nicht eintreten 
sollte.

gruss

von G. Ast (Gast)


Lesenswert?

Hi Michael,
das mit der MAgic Number und dem Info-Header klingt interessant. Hast du 
eine Idee, wie man die MAgic Number generieren könnte?
Ich stelle mir das nicht so einfach vor. Denn die Magic Number muss ja 
so kinzipiert sein, dass sie garantiert anders aussieht, als alle Daten, 
die in dem Paket vorkommen können. Wenn die Magic Number z.B. 0x12345678 
ist, brauchst du ja in dem Programmcode nur irgendwo eine Variable mit 
dem Wert 0x12345678 zu verwenden - und schon hast du ein Problem, weil 
der Loader dies dann auch als Magic Number interpretiert.....

Kann man den Compiler irgendwie dazu bringen, dass er den Info-Header 
IMMER ganz am Anfang des Programmcodes platziert? Irgendwo muss der 
Loader ja mit der Suche nach dem Header beginnen. Oder soll er einfach 
Byte für Byte vom Speicher durchgehen, bis er eine Magix Number findet?

Gruss

von G. Ast (Gast)


Lesenswert?

@Michael Graf
ich hab mir jetzt das nochmal ganz genau überlegt, wie das mit den 
Paketen funktionieren könnte. Und ich habe immer noch keine gute Lösung 
gefunden!
Das einzig schlaue, was mir eingefallen ist, wäre folgendes:

ein struct wird definiert, mit folgenden Inhalt:

typedef struct _paketinfo
{
  long magicnumber;
  void (*init)(void);
  long segmentsize;
} paketinfo;

magicnumber ist die Magic Number, 32 Bits breit. init ist ein Zeiger auf 
die Init-Funtkeion des Pakets.
Der Loader geht nun nach dem Start den Speicher durch, bis er die erste 
Magic Number findet. Anhand der wird dann eine paketinfo-Struktur 
generiert, und init aufgerufen. Nun wird der Speicher weiter durchsucht, 
aber erst wieder ab der Adresse (aktuelle adresse + segmentsize). 
Dadurch wird verhindert, dass eine im Programm vorkommende Konstante, 
die zufälligerweise den selben Wert hat wie die Magic Number, als Paket 
identifiziert wird.
Die Frage ist jetzt nur:

1. Wie könnte so eine Magic Number aussehen?
2. Wie kann ich das Feld segmentsize berechnen, um dort den richtigen 
Wert reinzuschreiben?

Gruss

von Route_66 (Gast)


Lesenswert?

FORTH !
Angucken, Prinzip verstehen und nachmachen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.