Ressourcen zum Schreiben von effizientem C-Code für Mikrocontroller? [geschlossen]


15

Hier ist ernsthafte Hilfe nötig. Ich liebe es zu programmieren. Ich habe in letzter Zeit ein paar Bücher (wie K & R) und Artikel / Foren für C online gelesen. Sogar versucht, in Linux-Code zu suchen (obwohl ich verloren war, wo ich anfangen sollte, aber das Durchsuchen kleiner Bibliotheken half?).

Ich habe als Java-Programmierer angefangen und in Java ist es ziemlich trocken und geschnitten. Wenn Programme zu groß werden, unterteilen Sie sie in Klassen und dann weiter in Funktionen. Richtlinien mögen, den Code lesbar halten und Kommentare hinzufügen. Verwenden Sie Ausblenden von Informationen und OOP-Techniken. Ein Teil davon gilt noch für C.

Ich habe jetzt in C programmiert und bis jetzt bekomme ich Programme, die auf die eine oder andere Weise funktionieren. Viele Leute sprechen über Leistung / Effizienz, Algorithmus / Design, Optimierung und Wartbarkeit. Manche Leute betonen das eine mehr als das andere, aber für nicht-professionelle Software-Entwickler hört man oft so etwas wie: Linux-Kernel-Entwickler nehmen nicht einfach irgendeinen Code.

Meine Frage lautet: Ich plane, einen Code für 8-Bit-Mikrocontroller zu schreiben, ohne Ressourcen zu verschwenden . Wisse, dass ich aus Java komme, damit die Dinge nicht mehr die gleichen sind ... Ressourcen / Bücher / Links / Tipps werden sehr geschätzt. Leistung und Größe sind jetzt wichtig. Ressourcen / Tricks zu effizientem (im Rahmen der Best Practices) C-Code für 8-Bit-Mikrocontroller?

Auch inline assemblyspielt eine entscheidende Rolle, als auch in der Nähe von Mikrocontrollern Standard zu halten. Aber gibt es eine allgemeine Faustregel für die Effizienz, die für alle gilt?

Zum Beispiel: register unsigned int variable_name;ist charjederzeit vorzuziehen . Oder mit, uint8_t wenn Sie keine großen Zahlen brauchen.

EDIT: Vielen Dank für alle Antworten und Vorschläge. Ich schätze die Bemühungen aller, Wissen zu teilen.


2
Dies ist auf einem x86-Prozessor nicht häufig der Fall, aber auf einem Mikrocontroller sollten Sie wahrscheinlich stattdessen die Assembly verwenden, wenn Sie sicherstellen möchten, dass Sie den letzten Leistungsabfall erzielen.
Rei Miyasaka

3
@Rei: Handgefertigte Assemblys verbrauchen selten, wenn überhaupt, weniger Speicher und sind schneller als moderne C-Compiler. Es ist eine Verschwendung von Zeit , um Code in der Montage , wenn es kann (wie soll) in C erfolgen
mattnz

1
@mattnz Von modern, wie neu sprichst du? Ehrlich gesagt habe ich seit fast einem Jahrzehnt keinen Code für einen Mikrocontroller geschrieben.
Rei Miyasaka

2
Ein einfacher Tipp: Bei Mikrocontrollern gilt im Allgemeinen immer noch: "Wenn es einfacher ist, ist es schneller". Auf komplexen Chips (ARM und höher) führt die Hardware so viele Optimierungen durch, dass Sie sie erst beim Testen kennen.
Javier

1
@ReiMiyasaka bei einem enormen Anstieg der Wartungskosten. Ein guter C-Compiler kann fast den gleichen Code erzeugen wie ein erfahrener Programmierer.

Antworten:


33

Ich habe mehr als 20 Jahre Erfahrung mit eingebetteten Systemen, hauptsächlich 8 und 16 Mikrometer. Die kurze Antwort auf Ihre Frage ist die gleiche wie bei jeder anderen Softwareentwicklung - optimieren Sie erst, wenn Sie wissen, dass Sie optimieren müssen, und optimieren Sie dann erst, wenn Sie wissen, was Sie optimieren müssen. Schreiben Sie Ihren Code so, dass er zuerst zuverlässig, lesbar und wartbar ist. Vorzeitige Optimierung ist in eingebetteten Systemen ein ebenso großes oder sogar größeres Problem

Wenn Sie "ohne Verschwendung von Ressourcen" programmieren, betrachten Sie Ihre Zeit als Ressource? Wenn nicht, wer bezahlt Sie für Ihre Zeit und wenn niemand, haben Sie etwas Besseres damit zu tun. Die Entscheidung, die ein Entwickler eines eingebetteten Systems treffen muss, hängt von den Kosten für Hardware und der Entwicklungszeit ab. Wenn Sie 100 Einheiten ausliefern, verwenden Sie ein größeres Mikro. Bei 100.000 Einheiten entspricht eine Einsparung von 1,00 USD pro Einheit der Entwicklung von 1 Mannjahr (ohne Berücksichtigung der Markteinführungszeit, Opportunitätskosten usw.) bei 1 Million Einheiten ROI erhalten, weil man von der Ressourcennutzung besessen ist, aber vorsichtig sein, weil viele eingebettete Projekte nie die Marke von 1 Million erreichten, weil sie eine Million verkaufen wollten (hohe Anfangsinvestition bei niedrigen Produktionskosten) und pleite gingen, bevor sie dort ankamen.

Das heißt, Dinge, die Sie bei (kleinen) eingebetteten Systemen berücksichtigen und beachten müssen, da diese auf unerwartete Weise nicht mehr funktionieren, sondern nur langsamer werden.

a) Stapel - Sie haben normalerweise nur eine kleine Stapelgröße und häufig begrenzte Stapelrahmengrößen. Sie müssen sich jederzeit darüber im Klaren sein, wie stark Ihr Stack ausgelastet ist. Seien Sie gewarnt, Stapelprobleme verursachen einige der heimtückischsten Mängel.

b) Heap - Auch hier sollten Sie bei kleinen Heap-Größen auf eine ungerechtfertigte Speicherzuweisung achten. Fragmentierung wird zum Thema. In diesen beiden Fällen müssen Sie wissen, was Sie tun, wenn das System leer ist. Auf einem großen System tritt dies nicht auf, da das Betriebssystem Paging unterstützt. dh wenn malloc NULL zurückgibt, prüfst du es und was tust du? Jede Malve braucht einen Scheck und Handler, Code aufblähen ?. Als Leitfaden - verwenden Sie es nicht, wenn es eine Alternative gibt. Die meisten kleinen Systeme verwenden aus diesen Gründen keinen dynamischen Speicher.

c) Hardware-Interrupts - Sie müssen wissen, wie Sie mit diesen sicher und rechtzeitig umgehen können. Sie müssen auch wissen, wie Sie sicheren Wiedereintrittscode erstellen. Beispielsweise sind C-Standardbibliotheken im Allgemeinen nicht wiedereintrittsfähig und sollten daher nicht in Interrupt-Handlern verwendet werden.

d) Montage - fast immer vorzeitige Optimierung. Es wird höchstens eine kleine Menge (inline) benötigt, um etwas zu erreichen, was C einfach nicht kann. Schreiben Sie als Übung eine kleine Methode in Handarbeit (von Grund auf neu). Machen Sie dasselbe in C. Messen Sie die Leistung. Ich wette, das C wird schneller, ich weiß, dass es besser lesbar, wartbar und erweiterbar sein wird. Nun zu Teil 2 der Übung: Schreiben Sie ein nützliches Programm in Assembly und C.
Schauen Sie sich als weitere Übung an, wie viel vom Linux-Kernel Assembler ist. Lesen Sie dann den folgenden Abschnitt über den Linux-Kernel.

Es lohnt sich zu wissen, wie man es macht, es lohnt sich vielleicht sogar, die Sprachen für ein oder zwei gängige Mikros zu beherrschen.

e) "register unsigned int variable_name", "register" war und ist ein Hinweis auf den Compiler und keine Anweisung. In den frühen 70er Jahren (vor 40 Jahren) ergab dies Sinn. Im Jahr 2012 ist es eine Verschwendung von Tastenanschlägen, da die Compiler so schlau sind und die Mikrobefehle so komplex sind.

Zurück zu Ihrem Linux-Kommentar - das Problem, das Sie hier haben, ist, dass es sich nicht nur um 1 Million Einheiten handelt, sondern um Hunderte von Millionen, mit einer Lebenszeit von Ewigkeit. Der Zeit- und Kostenaufwand für das Engineering, um es so optimal wie möglich zu gestalten, lohnt sich. Obwohl dies ein gutes Beispiel für die beste technische Praxis ist, wäre es für die meisten Entwickler eingebetteter Systeme kommerzieller Selbstmord, so umständlich zu sein, wie es der Linux-Kern erfordert.


4
mattnz: das ist eine der schönsten antworten auf .stackexchange sites.
Ahmed Masud

1
Ich kann diese Antwort nicht verbessern. Ich könnte nur hinzufügen , dass selten macht für die Leistung Sinn Assembler - Code eingefügt, aber es könnte wie Stossen I / O - Chips oder andere Hardware - Tricks Sinn für Dinge, die nicht einfach in C zu tun sein könnte
Mike Dunlavey

@mattnz Danke für die gut formulierte Antwort. +1
AceofSpades

1
@ MikeDunlavey Assembler wird manchmal für das genaue Timing benötigt. Gerade ein Video-Overlay fertiggestellt, das Bit-Banging verwendet, um ein NTSC-Video auf einem I / O-Pin zu erzeugen. Das Timing lautet: - Spannung hoch für 3-Uhr-Zyklen, dann niedrig für 6, dann ...
Martin Beckett

@Martin: Das macht durchaus Sinn. Es ist lange her, dass ich auf dieser Ebene codiert habe.
Mike Dunlavey

3

Ihre Frage ("ohne Verschwendung von Ressourcen") ist zu allgemein und daher schwer zu beantworten. Im wahrsten Sinne des Wortes, wenn Sie keine Ressourcen verschwenden möchten, sollten Sie vielleicht einen Schritt zurücktreten und abwägen, ob Sie überhaupt etwas tun müssen, dh ob Sie das Problem auf andere Weise lösen können.

Nützliche Ratschläge hängen auch stark von Ihren Einschränkungen ab. Welche Art von System bauen Sie und welche Art von CPU verwenden Sie? Ist es ein hartes Echtzeitsystem? Wie viel Speicher haben Sie für Code und Daten? Unterstützt es nativ alle C-Operationen (insbesondere Multiplikation und Division) und für welche Typen? Allgemeiner: Lesen Sie das gesamte Datenblatt und verstehen Sie es.

Der wichtigste Rat: Halten Sie es einfach.

ZB: Vergessen Sie komplexe Datenstrukturen (Hashes, Bäume, möglicherweise sogar verknüpfte Listen) und verwenden Sie Arrays mit fester Größe. Die Verwendung komplizierterer Datenstrukturen ist nur dann gerechtfertigt, wenn Sie durch Messung nachgewiesen haben, dass Arrays zu langsam sind.

Überdesignen Sie auch nicht (etwas, was Java / C # -Entwickler tendenziell tun): Schreiben Sie unkomplizierten prozeduralen Code, ohne zu viel zu überlagern. Abstraktion hat Kosten!

Machen Sie sich mit der Idee vertraut, globale Variablen zu verwenden, und gehen Sie zu [sehr nützlich für Bereinigungen ohne Ausnahmen];)

Wenn Sie sich mit Interrupts auseinandersetzen müssen, lesen Sie mehr über Wiedereintritte. Das Schreiben von wiedereintrittsfähigem Code ist nicht trivial.


3

Ich stimme der Antwort von mattnz zu - größtenteils. Ich habe vor über 30 Jahren angefangen, auf dem 8085 zu programmieren, dann auf dem Z80 und bin dann schnell auf den 8031 ​​umgestiegen. Danach bin ich zu den Mikrocontrollern der 68300-Serie gegangen, dann zu 80x86, XScale, MXL und (seit kurzem) 8-Bit-PICS, die ich Vermutung bedeutet, dass ich den Kreis geschlossen habe. Für die Aufzeichnung kann ich sagen, dass FAEs bei mehreren großen Mikroprozessorherstellern immer noch Assembler verwenden, wenn auch in einer objektorientierten Weise für eine zweckmäßige Wiederverwendung von Code.

Was ich in der genehmigten Antwort nicht sehe, ist eine Erörterung des Zielprozessortyps und / oder der vorgeschlagenen Architektur. Ist es ein bitteres 0.50 $ 8 mit begrenztem Gedächtnis? Handelt es sich um einen ARM9-Core mit Pipelining und 8Meg Flash? Speicherverwaltung Coprozessor? Wird es ein Betriebssystem haben? Eine while (1) -Schleife? Ein Consumer-Gerät mit einer Erstauflage von 100000 Stück? Ein Start-up-Unternehmen mit großen Ideen und Träumen?

Obwohl ich der Meinung bin, dass moderne Compiler hervorragende Optimierungsleistungen erbringen, habe ich in 30 Jahren noch nie an einem Projekt gearbeitet, bei dem ich den Debugger nicht angehalten und den generierten Assemblycode angezeigt habe, um zu sehen, was unter der Haube vor sich geht ( Zugegeben, ein Albtraum, wenn Pipelining und Optimierung ins Spiel kommen. Daher sind Kenntnisse in der Montage wichtig.

Und ich hatte noch nie einen CEO, VP of Engineering, einen Kunden, der mich nicht dazu gedrängt hat, eine Gallone in einen Quart-Container zu füllen oder mit einer Softwarelösung, die ein Hardwareproblem behebt, 0,05 USD zu sparen (hey, es ist nur Software) richtig? Was ist so schwer?). Die Speicheroptimierung (Code oder Daten) zählt immer.

Mein Punkt ist, wenn Sie das Projekt aus einer reinen Programmierperspektive betrachten, erhalten Sie eine Lösung mit engerem Anwendungsbereich. Mattnz hat es richtig gemacht - bringen Sie es zum Laufen, bringen Sie es dann schneller, kleiner, besser zum Laufen, aber Sie müssen noch VIEL Zeit mit den Anforderungen und Ergebnissen verbringen, bevor Sie überhaupt über das Codieren nachdenken.


Hallo Gio, bitte vermeide unnötiges HTML in deinen Beiträgen und verwende stattdessen die Markdown-Syntax . Für <br />Sie nur drücken könnten eintreten, und für die Absätze nur eine Leerzeile zwischen ihnen verlassen. Wenn Sie auf eine andere Antwort verweisen, fügen Sie bitte einen Link hinzu. Zu diesem Zeitpunkt gibt es vielleicht nur wenige Antworten, aber es gibt möglicherweise mehr, verteilt auf viele Seiten, und es ist nicht ganz klar, welche Antwort Sie meinen. Sehen Sie sich den Revisionsverlauf an, um meine Änderungen zu sehen.
Yannis

@Gio Vielen Dank, dass Sie andere wichtige Faktoren erwähnt haben. +1 :)
AceofSpades

+1 - Schöne Erweiterung meiner Antwort.
Mattnz

1

Die Manttz-Antwort bringt die wichtigsten Punkte zur hardwarenahen Programmierung auf den Punkt. Das ist schließlich, wofür C gedacht ist.

Ich möchte jedoch hinzufügen, dass das strenge Schlüsselwort "Class" in C zwar nicht existiert - es jedoch recht einfach ist , im Hinblick auf die objektorientierte Programmierung in C zu denken, selbst wenn Sie in der Nähe der Hardware sind.

Sie können diese Antwort in Betracht ziehen: OO-Best Practices für C-Programme, in denen dieser Punkt erläutert wird.

Hier sind einige Ressourcen, die Ihnen helfen, guten objektorientierten Code in C zu schreiben.

ein. Objektorientierte Programmierung in C
b. Dies ist ein guter Ort, an dem Menschen Ideen austauschen.
c. und hier ist das ganze Buch

Eine weitere gute Ressource, die ich Ihnen vorschlagen möchte, ist:

Die Write Great Code-Reihe . Dies ist ein zweibändiges Buch. Das erste Buch befasst sich mit sehr wesentlichen Aspekten von Maschinen auf niedrigerer Ebene. Das zweite Buch befasst sich mit "Denken auf niedrigem Niveau - Schreiben auf hohem Niveau"


1

Sie haben ein paar Probleme. Möchten Sie, dass dieses Projekt / dieser Code portierbar ist? Portabilität kostet Sie Leistung und Größe. Kann Ihre Plattform Ihrer Wahl und Aufgabe, die Sie implementieren, die Übergröße und die geringere Leistung tolerieren?

Ja, absolut auf einem 8-Bit-Computer, der nicht signierte Zeichen anstelle von nicht signierten Ints oder Shorts zurückgibt, ist dies eine Möglichkeit, die Leistung und Größe zu verbessern. Verwenden Sie auf einer 16-Bit-Maschine ebenfalls vorzeichenlose Shorts und auf einer 32-Bit-Maschine vorzeichenlose Ints. Sie können jedoch leicht erkennen, dass dieser Code ein Riesenschwein ist, wenn Sie beispielsweise überall nicht signierte Ints für die Portabilität auf dem System verwendet haben, das sie übernehmen (z. B. ARM, das seinen Weg in die Märkte mit der niedrigsten Leistung und den kleinsten Geräten bahnt) ein 8 bit micro. Sie können natürlich auch unsigned ohne int oder short oder char verwenden und den Compiler die optimale Größe auswählen lassen.

Nicht nur Inline-Assembler, sondern Assemblersprache im Allgemeinen. Inline-Assemblierung ist nicht portierbar und schwieriger zu schreiben als das Aufrufen einer asm-Funktion. Ja, Sie brennen den Anrufaufbau und kehren zurück, jedoch auf Kosten einer einfacheren Entwicklung, besseren Wartung und Kontrolle. Die Regel gilt weiterhin. Schreiben Sie sie nur dann in asm, wenn Sie sie wirklich benötigen. Haben Sie die Arbeit getan, um zu dem Schluss zu gelangen, dass die Compilerausgabe Ihr Problem in diesem Bereich ist und wie viel Performance-Gewinn Sie erzielen können, wenn Sie sie von Hand ausführen? Kehren Sie dann zu Portabilität und Wartung zurück, sobald Sie beginnen, C und asm zu mischen, und jedes Mal, wenn Sie C und asm mischen, können Sie Ihre Portabilität beeinträchtigen und das Projekt möglicherweise weniger wartbar machen, je nachdem, wer noch daran arbeitet oder ob dies der Fall ist ist ein Produkt, das Sie gerade entwickeln und das ein anderer auf dem laufenden halten muss. Sobald Sie diese Analyse durchgeführt haben, wissen Sie automatisch, ob Sie inline oder mit gerader Montage arbeiten müssen. Ich bin seit mehr als 25 Jahren auf dem Gebiet tätig, schreibe jeden Tag C- und ASM-Mischungen, lebe auf / in der Hardware- / Softwareschicht und verwende nie Inline-ASM. Es ist selten die Mühe wert, zu compilerspezifisch, ich schreibe compilerspezifischen Code wo immer möglich (fast überall).

Der Schlüssel zu Ihrer ganzen Frage ist, Ihren C-Code zu zerlegen. Erfahren Sie, was der Compiler mit Ihrem Code macht, und lernen Sie mit der Zeit, wenn Sie dies wünschen, den Compiler so zu manipulieren, dass er den gewünschten Code generiert, ohne auf asm zurückgreifen zu müssen. Mit der Zeit können Sie lernen, den Compiler so zu manipulieren, dass effizienter Code auf mehreren Zielen erstellt wird, sodass der Code portabler ist, ohne auf asm zurückgreifen zu müssen. Sie sollten kein Problem damit haben zu sehen, warum das vorzeichenlose Zeichen als Statusrückgabe einer Funktion besser funktioniert als ein vorzeichenloses Zeichen auf einem 8-Bit-Mikro. Ebenso wird das vorzeichenlose Zeichen auf 16- und 32-Bit-Systemen teurer (einige Architekturen helfen Ihnen dabei) raus, manche nicht).

Einige 8-Bit-Mikrocontroller sind sehr unfreundlich und kein Compiler erzeugt guten Code. Es gibt nicht genug Nachfrage, um einen Compiler-Markt für diese Geräte zu schaffen, um einen großartigen Compiler für diese Ziele zu schaffen, so dass die Compiler, die dort sind, mehr Unternehmen anziehen, die keine asm-Programmierer sind, und nicht, weil der Compiler besser ist als handgeschriebene asm . Arm und Mips, die in diese Welt kommen, verändern dieses Modell, da Sie Ziele haben, auf denen Compiler arbeiten, die ziemlich guten Code produzieren usw. Für Mikros mit Prozessoren wie diesen haben Sie natürlich immer noch In dem Fall, dass Sie zu asm wechseln müssen, dies jedoch nicht so häufig der Fall ist, ist es viel einfacher, dem Compiler zu sagen, was er tun soll, als es nicht zu verwenden. Beachten Sie, dass das Manipulieren eines Compilers kein hässlicher, nicht lesbarer Code ist. in der Tat ist es das Gegenteil, schöner, sauberer, unkomplizierter Code, der vielleicht ein paar Elemente neu anordnet. Wenn Sie die Größe Ihrer Funktionen und die Anzahl der Parameter steuern, macht dies einen großen Unterschied in der Compiler-Ausgabe. Vermeiden Sie Ghee-Whiz-Funktionen des Compilers oder der Sprache KISS, halten Sie es einfach dumm, produzieren Sie oft viel besseren und schnelleren Code.


Sie geben nicht die Art der Produkte an, die Sie produzieren. Ich würde davon ausgehen, dass dies entweder ein sehr hohes Volumen, eine geringe Gewinnspanne oder ein spezifischer Nischenmarkt mit verrückten Gewinnspannen ist. Dies ist von entscheidender Bedeutung für die Entscheidung auf Unternehmensebene, ob Sie ein kleines 8-Bit-Mikro und einen handgefertigten Assembler oder ein größeres Mikro und C verwenden möchten. Als Reaktion auf Ihren (gelöschten?) Kommentar arbeite ich jedoch mit 8-Bit-Mikros Beginnen Sie mit einem Mikro, das größer als erforderlich ist, und korrigieren Sie es nur dann, wenn Stücklistenkosten zu einem Problem werden.) Die Markteinführungszeit, die Opportunitätskosten und die amortisierten Entwicklungskosten ermöglichen es uns, der Stückliste 10 oder 20 Cent hinzuzufügen.
Mattnz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.