Fünf Jahre C ++ für Projekte für Mikrocontroller in der Produktion

In diesem Artikel werde ich Ihnen erzählen, wie ich die Unternehmen, für die ich über fünf Jahre gearbeitet habe, von der Verwaltung von Projekten für Mikrocontroller in C auf C ++ übertragen habe und was daraus entstanden ist (Spoiler: Alles ist schlecht).

Ein bisschen über dich


Ich begann unter C-Mikrocontrollern zu schreiben, hatte nur Schulerfahrung mit Pascal, dann studierte ich Assembler und verbrachte ungefähr 3 Jahre damit, verschiedene Mikrocontroller-Architekturen und deren Peripheriegeräte zu studieren. Dann gab es die Erfahrung der realen Arbeit in C # und C ++ mit ihrem parallelen Studium, das mehrere Jahre dauerte. Nach dieser Zeit kehrte ich wieder und lange Zeit zur Programmierung von Mikrocontrollern zurück und hatte bereits die notwendige theoretische Grundlage für die Arbeit an realen Projekten.

Erstes Jahr


Ich hatte nichts gegen den prozeduralen Stil von C, aber das Unternehmen, das meine eigentliche Praxis mit realen Projekten begann, verwendete "C-Programmierung in einem objektorientierten Stil". Es sah ungefähr so ​​aus.

typedef const struct _uart_init { USART_TypeDef *USARTx; uint32_t baudrate; ... } uart_cfg_t; int uart_init (uart_cfg_t *cfg); int uart_start_tx (int fd, void *d, uint16_t l); int uart_tx (int fd, void *d, uint16_t l, uint32_t timeout); 

Dieser Ansatz hatte folgende Vorteile:

  1. Der Code war weiterhin C-Code. Daraus ergeben sich folgende Vorteile:
    • Es ist einfacher, "Objekte" zu steuern, da leicht nachverfolgt werden kann, wer und wo was und in welcher Reihenfolge verursacht (mit Ausnahme von Unterbrechungen, jedoch nicht in diesem Artikel).
    • um den "Zeiger auf das Objekt" zu speichern, reicht es aus, sich an den zurückgegebenen fd zu erinnern;
    • Wenn das "Objekt" gelöscht wurde, erhalten Sie beim Versuch, es zu verwenden, einen entsprechenden Fehler im Rückgabewert der Funktion.
  2. Die Abstraktion solcher Objekte über die dort verwendete HAL ermöglichte es, für die Aufgabe anpassbare Objekte aus ihrer eigenen Initialisierungsstruktur zu schreiben (und bei fehlender HAL-Funktionalität könnte man den Zugriff auf die Register innerhalb der "Objekte" verbergen).

Nachteile:

  1. Wenn jemand das „Objekt“ gelöscht und dann ein neues Objekt eines anderen Typs erstellt hat, kann es vorkommen, dass das neue Objekt das fd des alten erhält und das weitere Verhalten nicht bestimmt wird. Dieses Verhalten kann leicht auf Kosten eines geringen Speicherverbrauchs für eine verknüpfte Liste geändert werden, anstatt ein Array mit einem Schlüsselwert zu verwenden (das Array für jeden Index fd hat einen Zeiger auf die Struktur des Objekts gespeichert).
  2. Es war unmöglich, Speicher unter "globalen Objekten" statisch zu markieren. Da in den meisten Anwendungen „Objekte“ einmal erstellt und nicht weiter gelöscht wurden, sah es aus wie eine „Krücke“. Hier wäre es beim Erstellen eines Objekts möglich, einen Zeiger auf seine interne Struktur zu übergeben, die während des Layouts statisch zugewiesen wurde. Dies würde jedoch den Initialisierungscode weiter verwirren und die Kapselung unterbrechen.

Auf die Frage, warum C ++ beim Aufbau der gesamten Infrastruktur nicht ausgewählt wurde, antworteten sie wie folgt: „Nun, C ++ führt zu starken zusätzlichen Kosten, unkontrollierten Speicherkosten sowie einer umfangreichen ausführbaren Firmware-Datei.“ Vielleicht hatten sie recht. Tatsächlich gab es zu Beginn des Entwurfs nur GCC 3.0.5, das nicht besonders freundlich zu C ++ war (wir müssen noch damit arbeiten, um Programme unter QNX6 zu schreiben). Es gab kein constexpr und C ++ 11/14, mit dem Sie globale Objekte erstellen konnten, bei denen es sich im Wesentlichen um Daten im Bereich .data handelte, die in der Kompilierungsphase berechnet wurden, und Methoden für diese.

Auf die Frage, warum nicht in die Register schreiben - ich habe eine klare Antwort erhalten, dass die Verwendung von "Objekten" es Ihnen ermöglicht, den gleichen Anwendungstyp "an einem Tag" zu konfigurieren.

Als ich all dies erkannte und erkannte, dass C ++ jetzt nicht mehr dasselbe ist wie mit GCC 3.0.5, begann ich, den Hauptteil der Funktionalität in C ++ neu zu schreiben. Arbeiten Sie zunächst mit den Hardware-Peripheriegeräten des Mikrocontrollers und anschließend mit den Peripheriegeräten externer Geräte. Tatsächlich war dies nur eine bequemere Hülle gegenüber dem, was zu dieser Zeit verfügbar war.

Jahr zwei und drei


Ich habe alles, was ich für meine Projekte brauchte, in C ++ umgeschrieben und sofort neue Module in C ++ geschrieben. Dies waren jedoch nur Shells über C. Nachdem ich festgestellt hatte, dass ich C ++ nicht genug verwendete, begann ich, seine Stärken zu nutzen: Vorlagen, Nur-Header-Klassen, constexpr und mehr. Alles lief gut.

Viertes und fünftes Jahr


  • Alle Objekte sind global und enthalten Verknüpfungen in der Kompilierungsphase (entsprechend der Architektur des Projekts).
  • Allen Objekten wird in der Phase des Layouts Speicher zugewiesen.
  • nach Klassenobjekt für jeden Pin;
  • ein Objekt, das alle Pins kapselt, um sie mit einer Methode zu initialisieren;
  • ein RCC-Steuerobjekt, das alle Objekte kapselt, die sich auf den Hardwarebussen befinden;
  • Das CAN <-> RS485-Konverterprojekt gemäß Kundenprotokoll enthält 60 Objekte;
  • Wenn sich auf der HAL- oder Klassenebene eines Objekts etwas auf dieser Ebene befindet, müssen Sie nicht nur das Problem beheben, sondern auch überlegen, wie es behoben werden kann, damit dieses Update bei allen möglichen Konfigurationen dieses Moduls funktioniert ;;
  • Die verwendeten Vorlagen und constexpr können nicht berechnet werden, bevor die Map-, ASM- und Bin-Dateien der endgültigen Firmware angezeigt werden (oder das Debugging im Mikrocontroller gestartet wird).
  • Im Falle eines Fehlers in der Vorlage wird eine Nachricht mit einer Länge von einem Drittel der Projektkonfiguration von GCC ausgegeben. Etwas davon zu lesen und zu verstehen ist eine separate Leistung.

Zusammenfassung


Jetzt verstehe ich folgendes:
  1. Die Verwendung von "Universalmodulkonstruktoren" erschwert das Programm nur unnötig. Es ist viel einfacher, die Konfigurationsregister für ein neues Projekt anzupassen, als sich mit den Beziehungen zwischen Objekten und dann auch mit der HAL-Bibliothek zu befassen.
  2. Haben Sie keine Angst, C ++ zu verwenden, aus Angst, dass es "viel Speicher verschlingt" oder "weniger optimiert ist als C". Nein das ist nicht so. Sie müssen befürchten, dass die Verwendung von Objekten und vielen Abstraktionsebenen den Code unlesbar macht und das Debuggen eine Heldentat ist.
  3. Wenn Sie nichts „Komplizierendes“ wie Vorlagen, Vererbung und andere attraktive Reize von C ++ verwenden, warum dann überhaupt C ++ verwenden? Nur um der Objekte willen? Lohnt es sich? Und für statische globale Objekte ohne Verwendung von Neu / Löschen bei einigen Projekten verboten?

Zusammenfassend lässt sich sagen, dass sich die scheinbare Einfachheit der Verwendung von C ++ nur als Ausrede herausstellte, um die Komplexität des Projekts wiederholt zu erhöhen, ohne an Geschwindigkeit oder Speicher zu gewinnen.

Source: https://habr.com/ru/post/de461113/


All Articles