2009-06-11 4 views
9

In vielen Embedded-Anwendungen gibt es einen Kompromiss zwischen dem Code sehr effizient zu machen oder den Code von der spezifischen Systemkonfiguration zu isolieren, um gegen sich ändernde Anforderungen immun zu sein.Wie Sie Ihren eingebetteten C-Code gegen Änderungen der Anforderungen immun machen, ohne zu viel Aufwand und Komplexität zu verursachen?

Welche C-Konstrukte verwenden Sie normalerweise, um das Beste aus beiden Welten zu erreichen (Flexibilität und Rekonfigurierbarkeit ohne Verlust der Effizienz)?

Wenn Sie die Zeit haben, lesen Sie bitte weiter, um genau zu sehen, worüber ich spreche.

Als ich Embedded-Software für Airbag-Controller entwickelte, hatten wir das Problem, dass wir einige Teile des Codes jedes Mal ändern mussten, wenn der Kunde seine Meinung zu den spezifischen Anforderungen änderte. Zum Beispiel hat sich die Kombination von Bedingungen und Ereignissen, die den Einsatz des Airbags auslösen würden, alle paar Wochen während der Entwicklung geändert. Wir hassten es, diesen Code so oft zu ändern.

Damals besuchte ich die Embedded Systems Conference und hörte eine brillante Präsentation von Stephen Mellor mit dem Titel "Mit wechselnden Anforderungen umgehen". Sie können das Papier lesen here (sie machen Sie registrieren, aber es ist kostenlos).

Die Hauptidee davon war, das Kernverhalten in Ihrem Code zu implementieren, aber die spezifischen Details in Form von Daten zu konfigurieren. Die Daten können Sie leicht ändern und sogar im EEPROM oder einem anderen Flash-Bereich programmieren.

Diese Idee klang großartig, um unser Problem zu lösen. Ich teilte das mit meinem Kollegen und wir begannen sofort mit der Überarbeitung einiger SW-Module.

Beim Versuch, diese Idee in unserer Codierung zu verwenden, stießen wir auf einige Schwierigkeiten bei der tatsächlichen Implementierung. Unsere Code-Konstrukte wurden für ein eingeschränktes Embedded-System furchtbar schwer und komplex.

Um dies zu veranschaulichen, werde ich das oben erwähnte Beispiel näher ausführen. Anstatt eine Reihe von if-Anweisungen zu haben, um zu entscheiden, ob die Kombination von Eingängen in einem Zustand war, der eine Airbag-Entfaltung erforderte, wechselten wir zu einer großen Tabelle von Tabellen. Einige der Bedingungen waren nicht trivial, also verwendeten wir viele Funktionszeiger, um viele kleine Hilfsfunktionen aufrufen zu können, die einige der Bedingungen irgendwie lösten. Wir hatten verschiedene Ebenen der Indirektion und alles wurde schwer zu verstehen. Um es kurz zu machen, nutzten wir viel Speicher, Laufzeit und Code-Komplexität. Das Debugging der Sache war auch nicht einfach. Der Chef hat uns einige Dinge geändert, weil die Module zu schwer wurden (und vielleicht hatte er Recht!).

PS: Es gibt eine ähnliche Frage in SO, aber es sieht so aus, als ob der Fokus anders ist. Adapting to meet changing business requirements?

+1

Ihre Einführung ist ziemlich lang und die Frage nicht sehr gut geben ... aber es ist immer noch ein guter. – jpinto3912

+0

Einverstanden. Sie werden wahrscheinlich mehr (und bessere) Antworten erhalten, wenn Sie die Frage ein wenig verschärfen. Mach es einfach zu lesen. Stellen Sie zuerst die Frage, und dann können Sie uns den relevanten Hintergrund geben und alle Details erklären. Nicht jemand möchte einen Roman lesen, bevor er überhaupt herausfindet, was die Frage * ist *) – jalf

+0

Ich denke, der Titel der Frage ist ein bisschen lang: P –

Antwort

3

Als eine andere Sichtweise auf sich ändernde Anforderungen gehen Anforderungen in , die den Code aufbauen. Warum also nicht eine Meta-Ansatz dazu:

  1. einzelne Teile des Programms, die wahrscheinlich
  2. Erstellen Sie ein Skript ändern, die Teile von Quelle kleben zusammen

Auf diese Weise können ist die Aufrechterhaltung kompatible Logikblock Bildung in C ... und dann diese Teile miteinander kompatibel am Ende kleben:

/* {conditions_for_airbag_placeholder} */ 
if(require_deployment) 
    trigger_gas_release() 

dann unabhängige Bedingungen aufrechtzuerhalten:

/* VAG Condition */ 
if(poll_vag_collision_event()) 
    require_deployment=1 

und andere

/* Ford Conditions */ 
if(ford_interrupt(FRONT_NEARSIDE_COLLISION)) 
    require_deploymen=1 

Ihr Build-Skript könnte wie folgt aussehen:

BUILD airbag_deployment_logic.c WITH vag_events 
TEST airbag_deployment_blob WITH vag_event_emitter 

wirklich denken outloud. Auf diese Weise erhalten Sie eine enge binäre Blob ohne in Config zu lesen. Dies ist eine Art der Verwendung von Overlayshttp://en.wikipedia.org/wiki/Overlay_(programming) aber tun es zur Kompilierzeit.

+0

Danke für die Einsicht. Jetzt, wo Sie es erwähnen, verwenden wir diesen Ansatz in Steuergeräten für Kraftfahrzeuge, um den CAN-Stack zu generieren oder das OSEK-Betriebssystem zu konfigurieren.Der Hersteller der CAN- oder OSEK-Software bietet Ihnen ein fantastisches Tool, um konfigurierten Code zu generieren. Wir hätten so etwas in den Airbag-Modulen machen können. Das Werkzeug musste nicht schick sein. – guzelo

+0

Grundlegende Konzepte funktionieren manchmal am besten, ich habe ein Python-Skript, das ich für alle Arten von solchen Dingen verwende. Von CMS-Vorlagen bis zur Code-Generierung basierend auf konfigurierten Code-Emittern. :) Spart viel Zeit, wenn Sie nur etwas heruntergekommenes brauchen –

2

Unser System ist in viele Komponenten unterteilt, mit exponierter Konfiguration und Testpunkten. Es gibt eine Konfigurationsdatei, die beim Start gelesen wird, die uns hilft, Komponenten zu instanziieren, sie miteinander zu verbinden und ihr Verhalten zu konfigurieren.

Es ist sehr OO-like, in C, mit dem gelegentlichen Hack, um etwas wie Vererbung zu implementieren.

In der Welt der Verteidigung/Avionik werden Software-Upgrades sehr streng kontrolliert, und Sie können SW nicht einfach aktualisieren, um Probleme zu beheben ... aus irgendeinem seltsamen Grund können Sie jedoch eine Konfigurationsdatei ohne großen Kampf aktualisieren. Daher war es für uns sehr nützlich, viele unserer Implementierungen in diesen Konfigurationsdateien angeben zu können.

Es gibt keine Magie, nur eine gute Trennung von Bedenken bei der Entwicklung des Systems und ein wenig Voraussicht seitens der Entwickler.

+0

Bedeutet das, dass Sie dynamische Speicherzuweisung verwenden? Wie instanziieren Sie Komponenten beim Start? – guzelo

+0

Ja, wir reservieren Speicher dynamisch, besonders beim Start des Systems. Die Konfigurationsdateien werden gelesen, Komponenten zugewiesen, Konfigurationen angewendet und dann Prozesse für die Komponenten generiert, die sie benötigen. –

0

Das Einhängen in eine dynamische Sprache kann ein Lebensretter sein, wenn Sie die Speicher- und Prozessorleistung dafür haben.

Lassen Sie das C mit der Hardware sprechen und geben Sie dann eine bekannte Menge von Ereignissen an eine Sprache wie Lua weiter. Lassen Sie das Lua-Skript das Ereignis und den Rückruf zu den entsprechenden C-Funktionen analysieren.

Sobald Ihr C-Code gut läuft, müssen Sie ihn nicht mehr berühren, wenn sich die Hardware nicht ändert. Die gesamte Geschäftslogik wird Teil des Skripts, das meiner Meinung nach viel einfacher zu erstellen, zu ändern und zu pflegen ist.

+2

Solange PHP sich nicht um meine Airbag-Systeme kümmert! –

+2

Dies wäre ein Problem, da die Dinge so schnell wie möglich laufen müssen. Dies ist ein Echtzeitproblem. Wenn es etwas langsamer geht, ist das Schlimmste, was passieren kann, nicht "ein bisschen Verzögerung", sondern ein Tod, weil ein Airbag eine Millisekunde später einsetzt, als er eigentlich hätte. C ist die höchste Stufe, die Sie für diese Art von Systemen verwenden können. – Earlz

+0

Die Frage wurde nicht als spezifisch für eingebettete Echtzeitsysteme formuliert, obwohl das Beispiel zufällig so war. – patros

1

Ich nehme an, was Sie tun könnten, ist mehrere gültige Verhaltensweisen basierend auf einem Byte oder Datenwort angeben, die Sie aus dem EEPROM oder einem E/A-Port abrufen können, und generischen Code erstellen, um alle möglichen Ereignisse zu behandeln diese Bytes.

Zum Beispiel, wenn Sie hatte ein Byte, das die Anforderungen für die Freigabe der Airbag spezifiziert es so etwas wie sein könnte:

Bit 0: Heckaufprall

Bit 1: Geschwindigkeit über 55 Stundenmeilen (Bonuspunkte für verallgemeinernde der Geschwindigkeitswert)

Bit 2: Beifahrer im Auto

...

Etc

Dann ziehen Sie ein weiteres Byte ein, das sagt, was passiert ist, und vergleichen Sie die beiden. Wenn sie gleich sind, führe deinen Befehl aus, wenn nicht, tu das nicht.

+0

Wenn ich mich richtig erinnere, haben wir das gemacht. – guzelo

2

Was möchten Sie genau speichern? Aufwand der Code-Überarbeitung? Der bürokratische Aufwand für eine Softwareversion?

Es ist möglich, dass das Ändern des Codes einigermaßen unkompliziert und möglicherweise einfacher ist als das Ändern von Daten in Tabellen. Das Verschieben Ihrer sich häufig ändernden Logik von Code zu Daten ist nur dann hilfreich, wenn Sie aus irgendeinem Grund weniger Daten ändern müssen als Code. Dies könnte der Fall sein, wenn die Änderungen besser in einer Datenform ausgedrückt werden (z. B. numerische Parameter, die im EEPROM gespeichert sind). Oder es könnte wahr sein, wenn die Anfragen des Kunden die Veröffentlichung einer neuen Version der Software erforderlich machen und eine neue Softwareversion eine kostspielige Prozedur ist (viel Papierkram oder vielleicht vom Chiphersteller gebrannte OTP-Chips).

Modularität ist sehr gutes Prinzip für diese Art von Dingen. Hört sich an, als ob du es schon zu einem gewissen Grad machst. Es ist gut zu versuchen, den sich oft ändernden Code auf einen möglichst kleinen Bereich zu beschränken und den Rest des Codes ("Helfer" -Funktionen) getrennt (modular) und so stabil wie möglich zu halten.

1

Zur Anpassung an wechselnde Anforderungen würde ich mich darauf konzentrieren, den Code modular und einfach zu ändern, z. durch Verwendung von Makros oder Inline-Funktionen für Parameter, die sich wahrscheinlich ändern.

W.r.t. eine Konfiguration, die unabhängig vom Code geändert werden kann, ich hoffe, dass die Parameter, die rekonfigurierbar sind, auch in den Anforderungen spezifiziert sind. Speziell für sicherheitskritische Sachen wie Airbag-Controller.

2

Ich mache den Code nicht immun gegen Anforderungsänderungen per se, aber ich markiere immer einen Codeabschnitt, der eine Anforderung implementiert, indem eine eindeutige Zeichenfolge in einen Kommentar eingefügt wird. Wenn die Anforderungs-Tags vorhanden sind, kann ich leicht nach diesem Code suchen, wenn die Anforderung geändert werden muss. Diese Praxis erfüllt auch einen CMMI-Prozess.

Zum Beispiel in dem Dokument Anforderungen:

Die folgende Liste von Anforderungen an die RST bezogen werden:

  • [RST001] Juliet die RST mit 5 Minuten Verspätung beginnen soll, wenn Die Zündung ist ausgeschaltet.

Und im Code:

/* Delay for RST when ignition is turned off [RST001] */ 
#define IGN_OFF_RST_DELAY 5 

...snip... 

         /* Start RST with designated delay [RST001] */ 
         if (IS_ROMEO_ON()) 
         { 
          rst_set_timer(IGN_OFF_RST_DELAY); 
         }