Multifunktionaler DIY GPSLogger. Teil 1

    Bild

    Ich besitze ein wundervolles Gerät - den GPS-Logger Holux M-241. Das Ding ist sehr praktisch und nützlich auf Reisen. Mit Hilfe eines Loggers schreibe ich einen GPS-Track der Reise, auf dem Sie Ihren Weg im Detail sehen und die aufgenommenen Fotos an GPS-Koordinaten binden können. Er hat auch einen kleinen Bildschirm, der zusätzliche Informationen anzeigt - Stunden, aktuelle Geschwindigkeit, Höhe und Richtung, Kilometerzähler und vieles mehr. Hier habe ich mal eine kurze Rezension geschrieben.

    Mit all den Vorteilen eines Stück Eisens fing ich an, daraus zu wachsen. Mir fehlen ein paar kleine, aber nützliche Extras: ein paar Kilometerzähler, die die vertikale Geschwindigkeit anzeigen und die Parameter eines Streckenabschnitts messen. Es sieht nach kleinen Dingen aus, aber die Firma Holux fand dies nicht nützlich genug für die Implementierung in der Firmware. Ich mag auch einige Parameter der Hardware nicht und einige Dinge sind seit 10 Jahren veraltet ...

    Irgendwann wurde mir klar, dass ich selbst einen Logger mit den Funktionen erstellen kann, die ich benötige. Zum Glück sind alle notwendigen Komponenten recht günstig und erschwinglich. Ich begann meine Implementierung auf Basis von Arduino. Unter dem Schnitt ein Bautagebuch, in dem ich meine technischen Lösungen zu malen versuchte.

    Merkmale identifizieren


    Viele werden sich fragen, warum ich meinen eigenen Logger bauen muss, wenn es sicher etwas gibt, das von namhaften Herstellern hergestellt wurde. Möglicherweise. Ehrlich gesagt habe ich nicht wirklich danach gesucht. Aber es wird mit Sicherheit etwas fehlen. In jedem Fall ist dieses Projekt ein Fan für mich. Warum bauen wir nicht unser Traumgerät?

    Also, für was ich meinen Holux M-241 schätze.

    • Der Bildschirm macht die „Black Box“, deren Ergebnisse erst nach einer Reise zur Verfügung stehen, ein sehr komfortables Werkzeug, dessen Aussage ist hier und jetzt zur Verfügung. Mit einem Bildschirm sind fast alle Funktionen dieser Liste möglich.
    • Eine Uhr ist an sich nützlich. Auf GPS-Fahrten ist der Logger, der an einer Schnur um den Hals baumelt, oft näher als ein Handy in der Tasche oder im Rucksack. Die Uhr unterstützt alle Zeitzonen (allerdings mit manueller Umschaltung)
    • Mit der POI-Taste können Sie die aktuelle Koordinate auf der Spur markieren. Zum Beispiel, um die Landmarke zu notieren, die aus dem Fenster des Busses gerutscht ist, über die ich später googeln möchte.
    • Mit dem Kilometerzähler können Sie die zurückgelegte Distanz von einem bestimmten Punkt aus messen. Zum Beispiel die pro Tag zurückgelegte Strecke oder die Länge einer Strecke.
    • Die aktuelle Geschwindigkeit, Höhe und Richtung helfen Ihnen, sich im Weltraum wiederzufinden
    • Die Überlebensdauer von 12 bis 14 Stunden mit einer AA-Batterie ermöglicht es Ihnen in den meisten Fällen, nicht an Probleme mit der Stromversorgung zu denken. Das heißt fast immer genug Ladung für einen ganzen Reisetag.
    • Kompakt und einfach zu bedienen - die Dinge in der modernen Welt sind sehr schön

    Einige Dinge könnten jedoch etwas besser gemacht werden:

    • Das Energiesubsystem mit AA-Batterien wird von vielen als eindeutiges Plus bezeichnet - eine Batterie hält lange und Sie können die Versorgung in jeder Wildnis wieder auffüllen. Sie können sich für mindestens einen Monat autonomes Campen leisten.

      Aber für mich ist die Batterielebensdauer reine Hämorrhoiden. Man muss eine Handvoll Batterien bei sich haben und wer weiß, wie hochwertig sie sind (plötzlich lagen sie 5 Jahre in einem Regal und waren bereits selbstentladen). Mit Batterien ist die Blutung sogar noch größer. Mein Ladegerät kann nur paarweise aufgeladen werden. Wir müssen die Batterien entladen, damit sie den gleichen Entladungsgrad haben. Infolgedessen erinnern Sie sich nie, wo bereits und wo noch nicht entladen.

      Während 6 Jahren mit dem Logger bin ich nur ein paar Mal ohne Strom in der Wildnis gelandet. In der Regel bekomme ich mindestens einmal am Tag Zugang zum Outlet. In diesem Fall wäre die eingebaute Lithiumbatterie viel praktischer. Nun, im Extremfall habe ich eine Straßenbank.

    • Die Anzeige des Entladegrades erfolgt sehr blöd - die Anzeige beginnt zu blinken, wenn der Akku entladen wird. Darüber hinaus kann es in 5 Minuten sterben, und vielleicht noch eine Stunde arbeiten. Es ist sehr leicht, diesen Moment zu verpassen und einen Teil des Logs zu verlieren.

    • Als luftfahrtinteressierter Mensch wäre es für mich sehr interessant, die aktuelle vertikale Geschwindigkeit zu beobachten .

    • Ein paar Kilometerzähler - oft ist es interessant, mehr als eine Strecke zu messen. Zum Beispiel die zurückgelegte Entfernung pro Tag und für die gesamte Reise.

    • Der Kilometerzähler wird zurückgesetzt, wenn Sie das Gerät ausschalten oder den Akku austauschen. Das ist furchtbar unangenehm. Wenn Sie in einem Café eine Mahlzeit eingenommen haben, kann der GPS-Logger nicht ausgeschaltet werden, da der Wert zurückgesetzt wird. Wir müssen es eingeschaltet lassen und es windet sich weiter Kilometer und frisst die Batterie. Es wäre viel bequemer, den Kilometerzähler auf Pause zu stellen und die Werte zwischen den Einschlüssen zu speichern .

    • Messung von Standortparametern . Beim Skifahren interessiert mich zum Beispiel die Länge der Abfahrt, die Höhe, die durchschnittliche und maximale Geschwindigkeit auf der Baustelle sowie die aufgewendete Zeit. Was willst du wissen, es ist sofort und nicht zu Hause, wenn du den Track herunterlädst.

    • Die Genauigkeit ist schlecht. Wenn du dich schnell bewegst - sonst nichts. Wenn die Geschwindigkeit auf der Strecke jedoch gering ist, sind „Geräusche“ von + - 50 m deutlich zu erkennen. Und für eine Stunde Stehen können Sie fast einen Kilometer „darauf bestehen“. Der Nutzen der Technologie seit 10 Jahren ist weit fortgeschritten und moderne Empfänger bieten eine viel größere Genauigkeit.

    • Die Zusammenführungsgeschwindigkeit von Tracks beträgt nur 38.400. Nein, es ist 2017 nicht ernst gemeint, den COM-Port für die Übertragung großer Datenmengen zu verwenden. Das Zusammenführen von 2 MB internem Flash dauert länger als 20 Minuten.

      Darüber hinaus kann nicht jedes Programm das Format von zusammengeführten Titeln verarbeiten. Das native Dienstprogramm ist sehr elend. Glücklicherweise gibt es das BT747, mit dem der Track adäquat zusammengeführt und in ein verdauliches Format konvertiert werden kann.

    • Die Größe des Flash-Laufwerks beträgt nur 2 MB. Dies reicht zum einen für eine zweiwöchige Reise, bei der alle 5s Punkte gespart werden. Zum einen muss das interne Paketformat
      konvertiert werden, zum anderen kann das Volumen nicht erhöht werden
    • Massenspeicher aus irgendeinem Grund ist jetzt nicht in Mode. Moderne Schnittstellen versuchen, das Vorhandensein von Dateien zu verbergen. Ich arbeite seit 25 Jahren mit Computern und die direkte Arbeit mit Dateien ist für mich viel praktischer als in jeder anderen Hinsicht.

    Hier gibt es nichts, was ohne nennenswerten Aufwand nicht zu realisieren wäre.

    Alles andere. Ich benutze es nicht selbst, aber plötzlich ist jemand nützlich:

    • Zeigt aktuelle Koordinaten (Breitengrad, Längengrad)
    • Auf der linken Seite des Bildschirms befinden sich verschiedene Symbole, an deren Inhalt ich mich ohne Handbuch nicht erinnern kann.
    • Es gibt Umschaltmeter / km - Fuß / Meilen.
    • Der Bluetooth-Logger kann ohne GPS mit Mobiltelefonen verbunden werden.
    • Die absolute Entfernung zum Punkt.
    • Protokollierung nach Zeit (alle N Sekunden) oder Entfernung (alle X Meter).
    • Unterstützung für verschiedene Sprachen.

    Wähle Eisen


    Die Anforderungen sind mehr oder weniger definiert. Es ist Zeit zu verstehen, wie dies alles implementiert werden kann. Die Hauptkomponenten, die ich haben werde, sind:

    • Mikrocontroller - Ich habe keine Pläne für ausgefeilte Rechenalgorithmen, daher ist die Rechenleistung des Kernels nicht besonders wichtig. Ich habe auch keine besonderen Anforderungen an die Befüllung - eine Reihe von Standard-Peripheriegeräten reicht aus.

      Zur Hand gab es nur ein paar verschiedene Arduinoes sowie ein paar stm32f103c8t6. Ich habe mich für AVR entschieden, was ich auf der Ebene Controller / Register / Peripherie gut kenne. Wenn ich auf Einschränkungen stoße, wird es einen Grund geben, das STM32 zu spüren.

    • Der GPS-Empfänger wurde aus den Modulen NEO6MV2, Beitan BN-800 und Beitan BN-880 ausgewählt. Foren für einige Zeit gegoogelt. Erfahrene Leute sagten, dass der erste Empfänger das letzte Jahrhundert ist. Die beiden anderen unterscheiden sich nur in der Position der Antenne - beim BN-800 hängt sie am Draht und beim BN-880 ist sie mit einem Sandwich auf das Hauptmodul geklebt. Nahm eine BN-880 .

    • Bildschirm - Das Original verwendet ein 128 x 32 LCD mit Hintergrundbeleuchtung. Ich habe nicht genau das gleiche gefunden. Ich habe eine OLED 0,91 Zoll für den SSD1306-Controller und einen 1,2-Zoll-LCD-Bildschirm für den ST7565R-Controller gekauft . Ich habe mich entschieden, von vorne anzufangen, weil Es ist einfacher, einen Standardkamm nach I2C oder SPI anzuschließen. Es ist jedoch etwas kleiner als das Original, und es funktioniert auch nicht, um das Bild aus Gründen der Kraftstoffeffizienz ständig anzuzeigen. Die zweite Anzeige sollte weniger überfrachtet sein, aber darunter müssen Sie den kniffligen Stecker auslöten und herausfinden, wie die Hintergrundbeleuchtung mit Strom versorgt wird.

    Von den kleinen Dingen:

    • Buttons kaufte einmal eine ganze Tasche;
    • Schild mit für SD-Karte - auch zur Hand herumliegen;
    • Ich habe ein paar verschiedene Laderegler für Lithiumbatterien gekauft, aber ich habe es immer noch nicht verstanden.

    Ich habe beschlossen, die Platine ganz am Ende zu entwerfen, wenn die Firmware fertig ist. Zu diesem Zeitpunkt werde ich endgültig über die Hauptkomponenten und das Schema für ihre Aufnahme entscheiden. In der ersten Phase entschied ich mich für das Debuggen auf einem Steckbrett, indem ich Komponenten mit Patchkabeln verband.

    Aber zuerst müssen Sie sich für ein sehr wichtiges Thema entscheiden - die Ernährung der Komponenten. Es erschien mir vernünftig, alles von 3,3V mit Strom zu versorgen: GPS und den Bildschirm nur drauf und wissen, wie man arbeitet. Dies ist auch die native Spannung für USB und SD. Zusätzlich kann die Schaltung von einer Lithiumdose gespeist

    werden.Die Wahl fiel auf den Arduino Pro Mini, der in der Version 8MHz / 3,3V zu finden ist. Aber sie hatte kein USB an Bord - ich musste einen USB-UART-Adapter verwenden.

    Erste Schritte


    Ursprünglich wurde das Projekt in Arduino IDE erstellt. Aber um ehrlich zu sein, traut sich meine Sprache nicht, sie als IDE zu bezeichnen - wie einen Texteditor mit einem Compiler. Auf jeden Fall kann ich nach Visual Studio, in dem ich die letzten 13 Jahre gearbeitet habe, in der Arduino IDE nichts Ernstes tun, ohne Tränen und Mütter.

    Zum Glück gibt es ein kostenloses Atmel Studio, in das sogar Visual Assist integriert ist !!! Das Programm weiß alles, was benötigt wird, alles ist bekannt und an seinem Platz. Nun, fast alles (ich habe nicht herausgefunden, wie man nur eine Datei kompiliert, um beispielsweise die Syntax zu überprüfen) habe ich

    Bild

    vom Bildschirm aus gestartet - dies ist erforderlich, um das Grundgerüst der Firmware zu debuggen und es dann mit Funktionen zu füllen. Er hielt an der ersten verfügbaren Bibliothek für Adafruit SSD1306 an . Sie weiß alles, was benötigt wird und bietet eine sehr einfache Oberfläche.

    Spielte mit Schriften. Es stellte sich heraus, dass eine Schriftart bis zu 8 KB aufnehmen kann (die Größe der Buchstaben beträgt 24 KB) - Sie können in einem 32-KB-Controller nicht besonders viel bewegen. Zum Anzeigen der Uhrzeit werden beispielsweise große Schriftarten benötigt.

    Beispielcode für eine Schriftart
    #include 
    #include 
    #include 
    #include 
    #include 
    ...
    #include 
    #include 
    #include 
    struct font_and_name
    {
    	const char * PROGMEM name;
    	GFXfont * font;
    };
    #define FONT(name) {#name, &name}
    const font_and_name fonts[] = {
    //	FONT(FreeMono12pt7b),
    	FONT(FreeMono18pt7b),
    	/*
    	FONT(FreeMono24pt7b),
    	FONT(FreeMono9pt7b),
    	FONT(FreeMonoBold12pt7b),
    	...
    	FONT(FreeSerifItalic9pt7b),
    	FONT(TomThumb)*/
    };
    const unsigned int fonts_count = sizeof(fonts) / sizeof(font_and_name);
    unsigned int current_font = 0;
    extern Adafruit_SSD1306 display;
    void RunFontTest()
    {
    	display.clearDisplay();
    	display.setCursor(0,30);
    	display.setFont(fonts[current_font].font);
    	display.print("12:34:56");
    	display.setCursor(0,6);
    	display.setFont(&TomThumb);
    	display.print(fonts[current_font].name);
    	display.display();
    }
    void SwitchToNextFont()
    {
    	current_font = ++current_font % fonts_count;
    }


    Schriften mit der Bibliothek sind sehr ungeschickt. Die Monospace-Schrift erwies sich als sehr breit - die Zeile „12:34:56“ passt nicht, Serif - alle Zahlen haben unterschiedliche Gewichte. Es sei denn, die Standardschrift 5x7 in der Bibliothek sieht essbar aus.

    Bild

    Bild

    Es stellte sich heraus, dass diese Schriftarten von einigen Open-Source-TTF-Schriftarten konvertiert wurden, die einfach nicht für kleine Auflösungen optimiert sind.

    Ich musste meine Schriften zeichnen. Genauer gesagt, graben Sie zunächst einzelne Symbole aus den fertigen aus. Das Symbol ':' in der ASCII-Tabelle ist direkt nach den Zahlen sehr nützlich und kann in einem Block gekauft werden. Es ist auch praktisch, dass Sie eine Schriftart nicht für alle Zeichen erstellen können, sondern nur für einen Bereich, z. B. von 0x30 ('0') bis 0x3a (':'). T.O. Von FreeSans18pt7b stellte sich heraus, dass eine sehr kompakte Schriftart nur für die erforderlichen Zeichen erstellt wurde. Zwar musste ich die Breite etwas anpassen, damit der Text in die Breite des Bildschirms passte.

    Patch Schriftart FreeSans18pt7b
    // This font consists only of digits and ':' to display current time.
    // The font is very based on FreeSans18pt7b.h
    //TODO: 25 pixel height is too much for displaying time. Create another 22px font
    const uint8_t TimeFontBitmaps[] PROGMEM = {
    	/*
    	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x20, 0x3F, 0xFC, 0xE3, 0xF1,
    	0xF8, 0xFC, 0x7E, 0x3F, 0x1F, 0x8E, 0x82, 0x41, 0x00, 0x01, 0xC3, 0x80,
    ...
    	0x03, 0x00, 0xC0, 0x60, 0x18, 0x06, 0x03, 0x00, 0xC0, 0x30, 0x18, 0x06,
    	0x01, 0x80, 0xC0, 0x30, 0x00, */0x07, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C,
    	0x3C, 0x78, 0x1E, 0x70, 0x0E, 0x70, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
    	0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
    	0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0E, 0x70, 0x0E, 0x78, 0x1E, 0x3C,
    	0x3C, 0x1F, 0xF8, 0x1F, 0xF0, 0x07, 0xE0, 0x03, 0x03, 0x07, 0x0F, 0x3F,
    	0xFF, 0xFF, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xE0, 0x1F, 0xF8,
    	0x3F, 0xFC, 0x7C, 0x3E, 0x70, 0x0F, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07,
    	0x00, 0x07, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8,
    	0x03, 0xF0, 0x07, 0xC0, 0x1F, 0x00, 0x3C, 0x00, 0x38, 0x00, 0x70, 0x00,
    	0x60, 0x00, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
    	0x07, 0xFE, 0x07, 0xFF, 0x87, 0x83, 0xC3, 0x80, 0xF3, 0x80, 0x39, 0xC0,
    	0x1C, 0xE0, 0x0E, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x7F, 0x00, 0x3F, 0x00,
    	0x1F, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0xF0, 0x01,
    	0xF8, 0x00, 0xFE, 0x00, 0x77, 0x00, 0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F,
    	0xF8, 0x07, 0xF0, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x78, 0x00, 0xF8,
    	0x00, 0xF8, 0x01, 0xF8, 0x03, 0xB8, 0x03, 0x38, 0x07, 0x38, 0x0E, 0x38,
    	0x1C, 0x38, 0x18, 0x38, 0x38, 0x38, 0x70, 0x38, 0x60, 0x38, 0xE0, 0x38,
    	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
    	0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x1F, 0xFF, 0x0F, 0xFF, 0x8F, 0xFF,
    	0xC7, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x39,
    	0xF0, 0x3F, 0xFE, 0x1F, 0xFF, 0x8F, 0x83, 0xE7, 0x00, 0xF0, 0x00, 0x3C,
    	0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xFC, 0x00,
    	0xEF, 0x00, 0x73, 0xC0, 0xF0, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xE0, 0x00,
    	0x03, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C, 0x1E, 0x38, 0x0E, 0x70, 0x0E,
    	0x70, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE3, 0xE0, 0xEF, 0xF8, 0xFF, 0xFC,
    	0xFC, 0x3E, 0xF0, 0x0E, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
    	0x60, 0x07, 0x70, 0x0F, 0x70, 0x0E, 0x3C, 0x3E, 0x3F, 0xFC, 0x1F, 0xF8,
    	0x07, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0E,
    	0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0xE0,
    	0x00, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x03, 0x80, 0x07, 0x00,
    	0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00,
    	0x1C, 0x00, 0x1C, 0x00, 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0xFF, 0x87, 0x83,
    	0xC7, 0x80, 0xF3, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x78, 0x0F, 0x1E,
    	0x0F, 0x07, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xE0, 0xF9, 0xC0, 0x1D,
    	0xC0, 0x0F, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0xF7, 0x00,
    	0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xF0, 0x00, 0x07, 0xE0,
    	0x1F, 0xF8, 0x3F, 0xFC, 0x7C, 0x3C, 0x70, 0x0E, 0xF0, 0x0E, 0xE0, 0x06,
    	0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0F, 0x78, 0x3F,
    	0x3F, 0xFF, 0x1F, 0xF7, 0x07, 0xC7, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0E,
    	0x70, 0x0E, 0x70, 0x1C, 0x78, 0x3C, 0x3F, 0xF8, 0x1F, 0xF0, 0x07, 0xC0,
    	0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x80 /*, 0xFF, 0xF0, 0x00, 0x00,
    	0x00, 0x07, 0xFF, 0xB6, 0xD6, 0x00, 0x00, 0x80, 0x03, 0xC0, 0x07, 0xE0,
    	0x0F, 0xC0, 0x3F, 0x80, 0x7E, 0x00, 0xFC, 0x01, 0xF0, 0x00, 0xE0, 0x00,
    ...
    	0x38, 0x38, 0xF8, 0xF0, 0xE0, 0x38, 0x00, 0xFC, 0x03, 0xFC, 0x1F, 0x3E,
    	0x3C, 0x1F, 0xE0, 0x1F, 0x80, 0x1E, 0x00
    	*/
    };
    //TODO Recalc offset numbers
    const GFXglyph TimeFontGlyphs[] PROGMEM =
    {
    	{   449-449,  16,  25,  19,    2,  -24 },   // 0x30 '0'
    	{   499-449,   8,  25,  19,    4,  -24 },   // 0x31 '1'
    	{   524-449,  16,  25,  19,    2,  -24 },   // 0x32 '2'
    	{   574-449,  17,  25,  19,    1,  -24 },   // 0x33 '3'
    	{   628-449,  16,  25,  19,    1,  -24 },   // 0x34 '4'
    	{   678-449,  17,  25,  19,    1,  -24 },   // 0x35 '5'
    	{   732-449,  16,  25,  19,    2,  -24 },   // 0x36 '6'
    	{   782-449,  16,  25,  19,    2,  -24 },   // 0x37 '7'
    	{   832-449,  17,  25,  19,    1,  -24 },   // 0x38 '8'
    	{   886-449,  16,  25,  19,    1,  -24 },   // 0x39 '9'
    	{   936-449,   3,  19,   7,    2,  -20 },   // 0x3A ':'
    };
    const GFXfont TimeFont PROGMEM = {
    	(uint8_t  *)TimeFontBitmaps,
    	(GFXglyph *)TimeFontGlyphs,
    0x30, 0x3A, 20 };

    Es stellte sich heraus, dass die 18pt-Schriftart tatsächlich 25 Pixel hoch ist. Aus diesem Grund passt es leicht auf eine andere Inschrift: Die

    Bild

    invertierte Darstellung hilft übrigens zu verstehen, wo die Ränder des Zeichenbereichs tatsächlich sind und wie die Linie relativ zu diesem Rand liegt - die Darstellung hat sehr große Rahmen.

    Lange Zeit haben wir vorgefertigte Schriften gegoogelt, aber sie passten weder in der Größe noch in der Form oder im Inhalt. Zum Beispiel im Internet eine 8x12-Font-Welle (Speicherauszüge von VGA-Karten-Zeichengeneratoren). Tatsächlich sind diese Schriftarten jedoch 6 x 8, d. H. viel spaziergang - bei einer so geringen auflösung und größe wie meiner ist das entscheidend.

    Ich musste meine eigenen Schriften zeichnen, da das Schriftformat der Adafruit-Bibliothek sehr einfach ist. Ich habe das Bild in Paint.net vorbereitet - ich habe einfach die Buchstaben in der richtigen Schriftart gezeichnet und sie dann mit einem Bleistift ein wenig korrigiert. Ich habe das Bild als PNG gespeichert und es dann schnell an das Python-Skript gesendet, das auf meinem Knie geschrieben ist. Dieses Skript hat einen halbfertigen Code generiert, der in der IDE bereits direkt in den Hex-Codes punktweise regiert.

    Bild

    So sieht beispielsweise der Vorgang zum Erstellen einer 8x12-Schriftart mit nur geringen Buchstaben- und Zeilenabständen aus. Am Ende war jedes Zeichen ungefähr 7x10 groß und nahm standardmäßig 10 Bytes ein. Es wäre möglich, jedes Zeichen in 8-9 Bytes zu packen (die Bibliothek erlaubt dies), aber ich habe mich nicht darum gekümmert. Darüber hinaus können Sie in diesem Formular einzelne Pixel direkt im Code bearbeiten.

    Schrift 8x12
    // A simple 8x12 font (slightly modifier Courier New)
    const uint8_t Monospace8x12Bitmaps[] PROGMEM = {
    	0x1e, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x1e, //0
    	0x18, 0x68, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x7f, //1
    	0x3e, 0x41, 0x41, 0x01, 0x02, 0x0c, 0x10, 0x20, 0x41, 0x7f, //2
    	0x3e, 0x41, 0x01, 0x01, 0x0e, 0x02, 0x01, 0x01, 0x41, 0x3e, //3
    	0x02, 0x06, 0x0a, 0x12, 0x12, 0x22, 0x3f, 0x02, 0x02, 0x0f, //4
    	0x7f, 0x41, 0x40, 0x40, 0x7e, 0x01, 0x01, 0x01, 0x41, 0x3e, //5
    	0x1e, 0x21, 0x40, 0x40, 0x5e, 0x61, 0x41, 0x41, 0x41, 0x3e, //6
    	0x7f, 0x41, 0x01, 0x02, 0x02, 0x04, 0x04, 0x04, 0x08, 0x08, //7
    	0x1e, 0x21, 0x21, 0x21, 0x1e, 0x21, 0x21, 0x21, 0x21, 0x1e, //8
    	0x1e, 0x21, 0x21, 0x21, 0x23, 0x1d, 0x01, 0x01, 0x22, 0x1c, //9
    	0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, //:
    };
    const GFXglyph Monospace8x12Glyphs[] PROGMEM =
    {
    	{   0,    8,  10,  8,  0,  -11 },   // 0x30 '0'
    	{   10,   8,  10,  8,  0,  -11 },   // 0x31 '1'
    	{   20,   8,  10,  8,  0,  -11 },   // 0x32 '2'
    	{   30,   8,  10,  8,  0,  -11 },   // 0x33 '3'
    	{   40,   8,  10,  8,  0,  -11 },   // 0x34 '4'
    	{   50,   8,  10,  8,  0,  -11 },   // 0x35 '5'
    	{   60,   8,  10,  8,  0,  -11 },   // 0x36 '6'
    	{   70,   8,  10,  8,  0,  -11 },   // 0x37 '7'
    	{   80,   8,  10,  8,  0,  -11 },   // 0x38 '8'
    	{   90,   8,  10,  8,  0,  -11 },   // 0x39 '9'
    	{   100,  8,  10,  8,  0,  -11 },   // 0x3A ':'
    };
    const GFXfont Monospace8x12Font PROGMEM = {
    	(uint8_t  *)Monospace8x12Bitmaps,
    	(GFXglyph *)Monospace8x12Glyphs,
    0x30, 0x3A, 12 };
    


    Rahmen


    Das Originalgerät bietet eine sehr einfache und komfortable Oberfläche. Informationen werden in Kategorien gruppiert, die auf einzelnen Seiten (Bildschirmen) angezeigt werden. Mit der Schaltfläche können Sie durch die Seiten blättern und mit der zweiten Schaltfläche das aktuelle Element auswählen oder die in der Signatur unter der Schaltfläche angegebene Aktion ausführen. Dieser Ansatz erscheint mir sehr zweckmäßig und es besteht keine Notwendigkeit, irgendetwas zu ändern.

    Ich mag die Schönheit von OOP, weil ich sofort ein kleines Interface geblendet habe. Jede Seite implementiert das Interface so, wie es benötigt wird. Die Seite kann sich selbst zeichnen und implementiert die Reaktion auf die Schaltflächen.

    class Screen
    {
    	Screen * nextScreen;
    public:
    	Screen();
    	virtual ~Screen() {}
    	virtual void drawScreen() = 0;
    	virtual void drawHeader();
    	virtual void onSelButton();
    	virtual void onOkButton();
    	virtual PROGMEM const char * getSelButtonText();
    	virtual PROGMEM const char * getOkButtonText();
    	Screen * addScreen(Screen * screen);
    };

    Schaltflächen können je nach aktuellem Bildschirm verschiedene Aktionen ausführen. Daher habe ich den Beschriftungen für die Buttons den oberen Bildschirmrand mit einer Höhe von 8 Pixeln zugewiesen. Text für Signaturen hängt vom aktuellen Bildschirm ab und wird von den virtuellen Funktionen getSelButtonText () und getOkButtonText () zurückgegeben. Auch in der Kopfzeile werden noch Serviceartikel wie GPS-Signalstärke und Akkuladung angezeigt. Die übrigen ¾ Bildschirme bieten nützliche Informationen.

    Wie gesagt, die Bildschirme können umgedreht werden, was bedeutet, dass es irgendwo eine Liste von Objekten für verschiedene Seiten geben sollte. Auf mehr als einem Bildschirm können Sie wie in einem Untermenü verschachteln. Ich habe sogar die ScreenManager-Klasse gestartet, die diese Listen verwalten sollte, aber dann fand ich die Lösung einfacher.

    Jeder Bildschirm hat also einfach einen Zeiger auf den nächsten. Wenn Sie auf dem Bildschirm das Untermenü aufrufen können, wird dem Bildschirm dieses Untermenüs ein weiterer Zeiger hinzugefügt

    class Screen
    {
    	Screen * nextScreen;
    …
    };
    class ParentScreen : public Screen
    {
    	Screen * childScreen;
    …
    };

    Standardmäßig ruft die Schaltflächenbehandlungsroutine einfach die Bildschirmänderungsfunktion auf und übergibt ihr den gewünschten Zeiger. Die Funktion erwies sich als trivial - sie hat lediglich den Zeiger auf den aktuellen Bildschirm verschoben. Um die Schachtelung der Bildschirme zu gewährleisten, habe ich einen kleinen Stapel gemacht. So passt der gesamte Bildschirmmanager in 25 Zeilen und 4 kleine Funktionen.

    Screen * screenStack[3];
    int screenIdx = 0;
    void setCurrentScreen(Screen * screen)
    {
    	screenStack[screenIdx] = screen;
    }
    Screen * getCurrentScreen()
    {
    	return screenStack[screenIdx];
    }
    void enterChildScreen(Screen * screen)
    {
    	screenIdx++; //TODO limit this
    	screenStack[screenIdx] = screen;
    }
    void backToParentScreen()
    {
    	if(screenIdx)
    		screenIdx--;
    }

    Der Code zum Füllen dieser Strukturen sieht zwar nicht besonders gut aus, wurde aber bisher nicht besser erfunden.

    Screen * createCurrentTimeScreen()
    {
    	TimeZoneScreen * tzScreen = new TimeZoneScreen(1, 30);
    	tzScreen = tzScreen->addScreen(new TimeZoneScreen(2, 45));
    	tzScreen = tzScreen->addScreen(new TimeZoneScreen(-3, 30));
    	// TODO Add real timezones here
    	CurrentTimeScreen * screen = new CurrentTimeScreen();
    	screen->addChildScreen(tzScreen);
    	return screen;
    }

    Der Gedanke
    Die Strukturierung hat sich natürlich als sehr gut erwiesen, aber ich befürchte, dass sie viel Gedächtnis frisst. Sie müssen gegen sich selbst vorgehen und eine große statische Tabelle mit Zeigern zafigachit.

    Mach weiter. Bei der Implementierung der Benutzeroberfläche wollte ich so etwas wie eine Nachrichtenbox erstellen - eine kurze Nachricht, die ein oder zwei Sekunden lang angezeigt wird und dann verschwindet. Wenn Sie zum Beispiel die POI-Taste (Point Of Interest) auf dem Bildschirm mit den aktuellen Koordinaten drücken, ist es hilfreich, dem Benutzer zusätzlich zum Schreiben des Punkts auf die Spur die Meldung „Wegpunkt gespeichert“ anzuzeigen (im Originalgerät wird nur für eine Sekunde ein zusätzliches Symbol angezeigt). Wenn der Akku fast leer ist, können Sie den Benutzer mit einer Meldung „aufheitern“.

    Bild

    Da die GPS-Daten ständig übermittelt werden, kann von Sperrfunktionen keine Rede sein. Daher musste ich eine einfache Zustandsmaschine (state machine) erfinden, die in der loop () -Funktion auswählt, was zu tun ist - den aktuellen Bildschirm oder das aktuelle Meldungsfeld anzeigen.

    enum State
    {
    	IDLE_DISPLAY_OFF,
    	IDLE,
    	MESSAGE_BOX,
    	BUTTON_PRESSED,
    };

    Es ist auch bequem, das Drücken von Knöpfen mit der Zustandsmaschine zu handhaben. Vielleicht wäre es durch Interrupts richtig, aber es stellte sich auch gut heraus. Das funktioniert so: Wenn eine Taste im IDLE-Status gedrückt wurde, merken Sie sich die Zeit, zu der sie gedrückt wurde, und wechseln Sie in den Status BUTTON_PRESSED. In diesem Zustand warten wir, bis der Benutzer den Knopf loslässt. Hier können wir die Dauer berechnen, in der die Taste gedrückt wurde. Kurze Antworten (<30 ms) werden einfach ignoriert - höchstwahrscheinlich sind dies mehrere Kontakte. Lange Fahrten können bereits als Knopfdruck interpretiert werden.

    Ich plane, sowohl kurzes Drücken von Tasten für normale Aktionen als auch langes Drücken (> 1c) für spezielle Funktionen zu verwenden. Zum Beispiel startet / pausiert ein kurzer Tastendruck den Kilometerzähler, ein langer Tastendruck setzt den Zähler auf 0 zurück.

    Vielleicht werden andere Staaten hinzugefügt. So ändern sich beispielsweise im ursprünglichen Logger nach dem Wechsel zur nächsten Seite die Werte auf dem Bildschirm häufig und seltener nach ein paar Sekunden - einmal pro Sekunde. Dies kann durch Hinzufügen eines weiteren Status erfolgen.

    Als der Rahmen fertig war, begann ich bereits, GPS zu verbinden. Aber hier gab es Nuancen, die mich veranlassten, diese Aufgabe zu verschieben.

    Firmware-Optimierung


    Bevor ich weitermache, muss ich mich von einigen technischen Details ablenken lassen. Tatsache ist, dass ich an ungefähr diesem Ort anfing, mit zunehmendem Speicherverbrauch zu stoßen. Es stellte sich heraus, dass die Zeile, die ohne den Modifikator PROGMEM zu Beginn der Firmware rücksichtslos deklariert wurde, in den Arbeitsspeicher kopiert wird und dort während der gesamten Laufzeit Platz beansprucht.

    Verschiedene Architekturen
    Kurzgesagt. Auf großen Computern wird die Von Neumann-Architektur verwendet, bei der sich Code und Daten im selben Adressraum befinden. Das heißt Daten von RAM und ROM werden auf die gleiche Weise gelesen.

    Mikrocontroller verwenden normalerweise die Harvard-Architektur , bei der Code und Daten getrennt sind. T.O. Sie müssen verschiedene Funktionen verwenden, um Speicher und Flash zu lesen. Aus Sicht der C / C ++ - Sprache sehen Zeiger gleich aus, aber beim Schreiben eines Programms müssen wir genau wissen, auf welchen Speicher unser Zeiger zeigt, und die entsprechenden Funktionen aufrufen.

    Glücklicherweise haben sich die Bibliotheksentwickler bereits teilweise darum gekümmert. Die Hauptklasse der Anzeigebibliothek - Adafruit_SSD1306 - wird von der Print-Klasse aus der Arduino-Standardbibliothek übernommen.

    Dies bietet uns eine Reihe verschiedener Modifikationen der Druckmethode - zum Drucken von Zeichenfolgen, einzelnen Zeichen, Zahlen und etwas anderem. Es verfügt also über zwei separate Funktionen zum Drucken von Zeilen:

    
        size_t print(const __FlashStringHelper *);
        size_t print(const char[]);
    

    Der erste weiß, dass Sie eine Zeile von einem Flash-Laufwerk drucken müssen, und lädt sie zeichenweise. Der zweite druckt Zeichen aus dem RAM. Tatsächlich nehmen beide Funktionen einen Zeiger auf eine Zeichenfolge, und zwar nur aus unterschiedlichen Adressräumen.

    Lange habe ich im Arduino-Code nach diesem __FlashStringHelper gesucht, um zu lernen, wie man die gewünschte print () -Funktion aufruft. Es stellte sich heraus, dass die Jungs den Trick taten: Sie deklarierten diesen Typ einfach mit Forward-Deklaration (ohne den Typ selbst zu deklarieren) und schrieben ein Makro, das blitzschnell Zeiger auf Zeilen des Typs __FlashStringHelper umsetzte. Nur damit der Compiler die notwendige überladene Funktion auswählt

    class __FlashStringHelper;
    #define F(string_literal) (reinterpret_cast(PSTR(string_literal)))
    

    Dies ermöglicht es Ihnen, wie folgt zu schreiben:

    display.print(F(“String in flash memory”));


    Aber Sie können nicht so schreiben
    const char text[] PROGMEM = "String in flash memory"; 
    display.print(F(text));

    Und anscheinend bietet die Bibliothek nichts, was auf diese Weise getan werden könnte. Ich weiß, dass es nicht gut ist, private Bibliothekselemente in meinem Code zu verwenden, aber was soll ich tun? Ich habe mein Makro gezeichnet, das genau das tat, was ich brauchte.

    #define USE_PGM_STRING(x) reinterpret_cast(x)
    

    So begann die Hutzeichnungsfunktion wie folgt auszusehen:

    void Screen::drawHeader()
    {
    	display.setFont(NULL);
    	display.setCursor(20, 0);
    	display.print('\x1e');
    	display.print(USE_PGM_STRING(getSelButtonText()));
    	display.setCursor(80, 0);
    	display.print('\x1e');
    	display.print(USE_PGM_STRING(getOkButtonText()));
    }

    Nun, seit ich mich mit der Firmware befasst habe, habe ich mich eingehender mit der Funktionsweise befasst.

    Im Allgemeinen müssen die Leute, die sich Arduino ausgedacht haben, ein Denkmal errichten. Sie bildeten eine einfache und bequeme Plattform für Prototyping und Handwerk. Eine große Anzahl von Menschen mit minimalen Kenntnissen in Elektronik und Programmierung konnten in die Welt von Arduino eintreten. Aber all dies ist glatt und schön, während Sie Müll wie Blinker mit LEDs oder das Ablesen des Thermometers tun. Sobald Sie etwas Ernstes bedrohen, müssen Sie sofort von Anfang an tiefer verstehen, als Sie wollten.

    Nach jeder hinzugefügten Bibliothek oder Klasse habe ich festgestellt, wie schnell der Speicherverbrauch zunimmt. Zu diesem Zeitpunkt war ich mit mehr als 14 KB 32 KB Flash und 1300 Byte RAM (von 2 KB) beschäftigt. Jede unachtsame Bewegung addierte weitere 10 Prozent zu der bereits verwendeten. Ich habe jedoch noch keine Verbindung zu GPS- und SD / FAT32-Bibliotheken hergestellt, während die Katze selbst geweint hat. Ich musste den Disassembler Checker abholen und untersuchen, was der Compiler tat.

    Insgeheim hoffte ich, dass der Linker nicht genutzte Funktionen ausschaltet. Es stellte sich jedoch heraus, dass einige von ihnen den Linker fast vollständig einfügten. In der Firmware habe ich die Strichzeichnungsfunktionen und einige andere Funktionen aus der Bibliothek zum Arbeiten mit dem Bildschirm gefunden, obwohl ich sie im Code zu diesem Zeitpunkt offensichtlich nicht aufgerufen habe. Implizit sollten sie auch nicht aufgerufen werden - warum brauche ich eine Strichzeichnungsfunktion, wenn ich nur Buchstaben aus Bitmaps zeichne? Mehr als 5,2 KB aus heiterem Himmel (und das gilt nicht für Schriftarten).

    Zusätzlich zur Anzeigesteuerungsbibliothek fand ich auch:

    • 2.6 kb - auf SoftwareSerial (Ich habe es irgendwann in das Projekt gezogen)
    • 1,6 kb - I 2 C
    • 1,3 kb - HardwareSerial
    • 2 kb - TinyGPS
    • 2,5 kb auf dem aktuellen Arduino (Initialisierung, Pins, alle Arten von Tabellen, der Haupttimer für die Funktionen millis () und delay ()),

    Die Zahlen sind sehr bezeichnend, wie Der Optimierer mischt ernsthaft Code. Einige Funktionen können an einer Stelle gestartet werden, und eine andere Funktion aus einer anderen Bibliothek, die von der ersten aufgerufen wird, kann unmittelbar darauf folgen. Darüber hinaus können sich separate Zweige dieser Funktionen am anderen Ende des Blitzes befinden.

    Auch im Code habe ich gefunden:

    • Bildschirmverwaltung durch SPI (obwohl ich es über I2C angeschlossen habe)
    • Methoden von Basisklassen, die selbst nicht aufgerufen werden, weil in den Erben neu definiert
    • Destruktoren, die niemals von Entwurf aufgerufen werden
    • Zeichenfunktionen (und nicht alle - der Linker warf noch einen Teil der Funktionen)
    • malloc / free, während in meinem Code alle Objekte im Wesentlichen statisch sind

    Aber nicht nur der Verbrauch von Flash-Speicher, sondern auch von SRAM nimmt sprunghaft zu:

    • 130 Bytes - I2C
    • 100 Bytes - SoftwareSerial
    • 157 Bytes - Seriell
    • 558 Bytes - Anzeige (von denen 512 der Einzelbildpuffer ist)

    Nicht weniger unterhaltsam war der .data-Bereich. Es gibt ungefähr 700 Bytes und dieses Ding wird zu Beginn von einem Flash in den RAM geladen. Es stellte sich heraus, dass es reservierte Speicherplätze für Variablen gibt, und zwar zusammen mit den Initialisierungswerten. Hier leben die Variablen und Konstanten, die Sie vergessen haben, als const PROGMEM zu deklarieren.

    Darunter befand sich ein umfangreiches Array mit einem „Begrüßungsbildschirm“ auf dem Bildschirm - den Anfangswerten des Bildpuffers. Theoretisch können Sie die Blume und die Adafruit-Inschrift sehen, wenn Sie den display () - Bildschirm unmittelbar nach dem Start anzeigen. In meinem Fall ist es jedoch sinnlos, dafür Flash-Speicher zu verwenden.

    Der Abschnitt .data enthält auch vtables. Sie werden aus Gründen der Effizienz in der Laufzeit von einem Flash-Laufwerk in den Speicher kopiert. Aber Sie müssen ein ziemlich großes Stück RAM opfern - über ein Dutzend Klassen mit mehr als 150 Bytes. Darüber hinaus scheint es keinen Compilerschlüssel zu geben, der die Leistung beeinträchtigt und virtuelle Tabellen im Flash-Speicher belässt.

    Was kann man damit machen? Ich weiß es noch nicht. Es wird davon abhängen, wie der Verbrauch weiter wachsen wird. Für gut gefundene Pfosten müssen gnadenlos repariert werden. Anscheinend muss ich alle Bibliotheken explizit in mein Projekt einbinden und dann gründlich abdecken. Außerdem müssen Sie möglicherweise einige Teile anders schreiben, um den Speicher zu optimieren. Oder wechseln Sie zu leistungsfähigerer Hardware. In jedem Fall weiß ich jetzt um das Problem und es gibt eine Strategie, wie man es behebt.

    UPDATE:
    Geringe Fortschritte bei der Ressourceneffizienz. Ich mache ein Update zu diesem Teil, weil im nächsten möchte ich mich auf ganz andere dinge konzentrieren.

    In Kommentaren gibt es einige Verwirrung über die Verwendung von C ++. Insbesondere, warum ist er so schlecht und hält vtable in wertvollen RAM? Im Allgemeinen sind virtuelle Funktionen, Konstruktoren und Destruktoren überlastet. Warum? Lass es uns herausfinden!

    Hier finden Sie Statistiken zum Arbeitsspeicher in einem bestimmten Stadium des Projekts:
    Programmgröße: 15.458 Byte (verwendet 50% eines Maximums von 30.720 Byte) (2,45 Sekunden)
    Mindestspeicherverbrauch: 1258 Byte (61% eines Maximums von 2048 Byte)

    Versuch Nr. 1 - umschreiben auf C.

    Ich warf die Klassen, schrieb alles auf den Tabellen mit Zeigern auf Funktionen um. Da Bildschirme eigentlich immer die gleiche Struktur haben, sind alle Datenelemente zu gewöhnlichen globalen Variablen geworden.

    Statistik nach Refactoring
    Programmgröße: 14.568 Byte (verwendet 47% von maximal 30.720 Byte) (2,35 Sekunden)
    Minimale Speichernutzung: 1176 Byte (57% von maximal 2048 Byte)

    Gesamt. Gewann 900 Bytes Flash und 80 Bytes RAM. Was genau der Blitz übrig ließ, grub sich nicht. 80 Bytes RAM sind genau so groß wie vtables. Alle anderen Daten (Klassenmitglieder) sind irgendwie geblieben.

    Ich muss sagen, dass ich nicht alles verdorben habe - ich wollte nur das große Ganze sehen, ohne zu viel Zeit darauf zu verwenden. Nach dem Refactoring habe ich verschachtelte Screenshots „verloren“. Mit ihnen wäre der Konsum etwas höher.

    Das Wichtigste bei diesem Experiment ist jedoch, dass sich die Qualität des Codes erheblich verschlechtert hat. Der Code einer Funktion wurde auf mehrere Dateien verteilt. Bei einigen Daten hat "ein Eigentümer" aufgehört zu existieren, einige Module sind in die Erinnerung anderer geraten. Der Code ist pauschal und hässlich geworden.

    Experiment 2 - Komprimieren von Bytes aus C ++

    Ich habe mein Experiment zurückgesetzt und beschlossen, alles als Klassen zu belassen. Nur dieses Mal werden statisch verteilte Objekte angezeigt. Die Struktur der Seiten auf meinen Bildschirmen ist festgelegt. Sie können es bei der Kompilierung angeben, ohne new / delete zu verwenden.

    Programmgröße: 15.408 Bytes (verwendet 50% eines 30.720 - Byte maximal) (2,60 sec)
    Minimale Speichernutzung: 1273 Bytes (62% eines 2048 - Byte - Maximum)

    Der RAM-Verbrauch hat leicht zugenommen. Aber das ist in der Tat zum Besseren. Der Anstieg des RAM-Verbrauchs erklärt sich aus der Verschiebung von Objekten vom Heap in einen statisch verteilten Speicherbereich. Das heißt Tatsächlich wurden zuvor Objekte erstellt, nur ging dies nicht in die Statistik. Und jetzt werden diese Objekte explizit berücksichtigt.

    Aber den Blitzverbrauch deutlich zu reduzieren, hat nicht geklappt. Der Code enthält weiterhin die Konstruktoren selbst, die beim Start noch aufgerufen werden. Das heißt Der Compiler konnte sie nicht im Voraus ausführen und alle Werte in vorab zugewiesenen Bereichen ablegen. Und es gab immer noch Destruktoren im Code, obwohl es für den Igel klar ist, dass Objekte niemals gelöscht werden.

    Um zumindest ein wenig zu sparen, habe ich alle Destruktoren in der Hierarchie gelöscht, insbesondere den virtuellen Destruktor in der Basisklasse. Die Idee war, ein paar Bytes in jeder vtable freizugeben. Und dann wartete eine Überraschung auf mich:

    Programmgröße: 14.704 Bytes (verwendet 48% eines Maximums von 30.720 Bytes) (2,94 Sekunden)
    Minimale Speichernutzung: 1211 Bytes (59% eines Maximums von 2048 Bytes)

    Es stellte sich heraus, dass vtable verschwunden war nicht von einem Zeiger, sondern schon von 2. Was hat das mit dem Destruktor zu tun? Nur ein Destruktor ist leer (anscheinend für Objekte auf dem Stapel) und der andere mit einem freien Aufruf, der für Objekte auf dem Heap sichtbar ist (-12 Bytes RAM). Auch die mit der Hüfte verknüpften Variablen (8 Byte) und die Bezeichnungen von Objekten, die nie erstellt wurden (Screen, ParentScreen - 40 Byte), sind nicht mehr vorhanden

    Flash-Verbrauch deutlich gesunken - um 700 Bytes. Nicht nur die Destruktoren selbst, sondern auch die malloc / free / new / delete-Implementierungen sind weg. 700 Bytes für einen leeren virtuellen Destruktor! 700 Bytes, Carl!

    Das würde nicht hin und her gehen, hier sind alle Zahlen an einem Ort

    WarCC ++
    Flash15 45814.56814.704
    RAM125811761211


    Fazit: Der Verbrauch in C ++ war nahezu der gleiche wie in C. Gleichzeitig sind Verkapselung, Vererbung und Polymorphismus Stärken. Ich bin bereit, dies mit einem gewissen Anstieg des Verbrauchs zu überbezahlen. Vielleicht kann ich einfach nicht schön in C schreiben, aber warum, wenn ich schön in C ++ schreiben kann?

    Nachwort


    Anfangs wollte ich am Ende des Projekts einen Artikel schreiben. Da sich die Banknoten jedoch im Verlauf der Arbeit mit hoher Geschwindigkeit ansammeln, droht der Artikel sehr groß zu werden. Also habe ich beschlossen, es in mehrere Teile zu zerlegen. In diesem Teil habe ich über die Vorbereitungsphasen gesprochen: Verstehen, was ich wirklich will, Auswählen einer Plattform, Implementieren eines Anwendungsframeworks.

    Im nächsten Teil werde ich mich mit der Implementierung der Grundfunktionalität befassen - der Arbeit mit GPS. Ich bin bereits auf ein paar interessante Rechen gestoßen, von denen ich erzählen möchte.

    Seit mehr als 10 Jahren habe ich nicht mehr ernsthaft für Mikrocontroller programmiert. Es stellte sich heraus, dass ich durch die Fülle an Ressourcen großer Computer etwas verwöhnt wurde und es in der Realität von ATMega32 eng ist. Daher musste ich mir verschiedene Sicherungsoptionen überlegen, z. B. die Funktionalität von Bibliotheken zu reduzieren oder die Anwendung im Namen einer effizienten Speichernutzung neu zu gestalten. Ich schließe auch die Möglichkeit des Wechsels zu leistungsstärkeren Controllern - ATMega64 oder etwas aus der STM32-Linie - nicht aus.

    Der Artikel entpuppt sich stilistisch als eine Art Baumagazin. Und ich freue mich über konstruktive Kommentare - es ist nicht zu spät, etwas zu ändern. Wer möchte, kann sich meinem Projekt auf dem Github anschließen .

    Das Ende des ersten Teils.

    Zweiter Teil

    Jetzt auch beliebt: