„Jetzt zeige ich dir ein Porträt ... Hmm ... ich warne dich, dass dies ein Porträt ist ... Wie auch immer, bitte behandle ihn wie ein Porträt ...In diesem Beitrag werden wir über die Entwicklung und das Debuggen von Programmen für den CC1350 MK in der vom Hersteller empfohlenen CCS-Entwicklungsumgebung sprechen. Die Vorzüge (und sie sind) und die Nachteile (und wie ohne sie) der oben genannten Produkte werden beeinflusst. Der Text enthält keine Screenshots, die den Speicherort des Kompilierungssymbols in der integrierten Programmierumgebung oder die Auswahl einer Datei in einem Verzeichnis anzeigen (eingekreist). Ich erkenne die grundsätzliche Möglichkeit von Artikeln in einem ähnlichen Stil und werde versuchen, mich auf konzeptionelle Fragen zu konzentrieren, in der Hoffnung, dass mein Leser die Details herausfinden kann.
Der Zweck dieses Opus ist neben dem Austausch der gesammelten Erfahrungen der Versuch, bei einheimischen MK-Herstellern, die direkte Konkurrenten von TI sind ("in dem Land, in dem wir gedeihen"), gesunden Neid zu erregen - die Aufgabe ist offen gesagt undankbar, aber sie sagen, dass ein Stein einen Tropfen abnutzt.
Ich werde sofort betonen, dass es sich nur um Windows 7 (außerdem nur) Version 7 handeln wird, obwohl die TI-Website eine Option für Mac und Linux bietet. Ich habe sie nicht ausprobiert. Ich bin ziemlich bereit zu glauben, dass dort nicht alles so cool ist, aber warum sollte ich darüber nachdenken? über das Schlechte (oder umgekehrt, dort ist alles großartig, aber warum dann Neid).
Was lehrt uns die TI-Website? Um mit Evaluierungsmodulen arbeiten zu können, müssen Sie drei notwendige Schritte ausführen:
- Evaluierungsmodule kaufen - fertig.
Hinweis zu den Rändern (PNP): Sie müssen dies auch tun, da ich persönlich in der fraglichen Programmierumgebung (leider) nicht die Möglichkeit gefunden habe, Hardware für das Debuggen zu emulieren, zumindest dort, wo ich gesucht habe. - Installieren Sie die Entwicklungsumgebung - laden Sie das Installationsprogramm herunter, führen Sie es aus, alles hat funktioniert. Wir verbinden das Evaluierungsmodul mit USB - das Brennholz steigt von selbst auf und alles hat wieder geklappt - fertig. Wenn Sie versuchen, das Gerät zu programmieren, erhalten wir eine Meldung über die Notwendigkeit, die Firmware zu aktualisieren. Wir sind uns einig, und es hat sich erneut alles herausgestellt. Im Allgemeinen gibt es nichts zu schreiben, wenn es immer und überall war ...
- Gehen Sie und studieren Sie den Kurs TI SimpleLink Academy 3.10.01 für SimpleLink CC13x0 SDK 3.10 - ein seltsamer Vorschlag, den ich zu lehren scheint - nur um zu verderben, aber so sei es, ich öffne den entsprechenden Link und bin fassungslos - wie viel ist da drin.
Hier finden Sie Schulungsunterlagen zur Arbeit mit SYS / BIOS-Hardwaretreibern und zum TI-RTOS-Betriebssystem sowie zur Verwendung des NDK-Netzwerkstapels, einschließlich USB, und zur Verwendung von drahtlosen Protokollen und vielen weiteren Aspekten der Arbeit mit Vertretern verschiedener MK-Familien, die vom Unternehmen hergestellt wurden. Und all dieser Reichtum wird von gebrauchsfertigen Beispielen begleitet, und wenn wir das Vorhandensein von Benutzerhandbüchern und Modulbeschreibungen berücksichtigen, gibt es vielleicht nichts mehr zu wünschen. Es gibt aber auch Dienstprogramme, die das Vorbereiten und Konfigurieren von Programmcode, das Flashen und Debuggen auf verschiedene Weise erleichtern, und diese Fülle ist auch gut dokumentiert.
Pnp: Wenn jemand dazu neigt, dieses Material als Werbung in Bezug auf das Unternehmen, seine Produkte und sein Programmiersystem zu betrachten, dann wird es höchstwahrscheinlich richtig sein und ich bin wirklich sehr beeindruckt von der Menge der erkannten Software. Seine Qualität wird weiter diskutiert und ich hoffe, mein Verdacht auf Voreingenommenheit wird zerstreut, ich war nicht völlig geblendet von dem Gefühl und ich sehe weiterhin perfekt die Fehler des Beschreibungsobjekts, so dass dies nicht die Liebe zur Jugend ist, sondern ein ernstes Gefühl eines erwachsenen Spezialisten. Ich habe Angst, mir die Höhe der Materialkosten vorzustellen, die für die Erstellung und Wartung eines solchen Volumens an Software und Dokumentation erforderlich sind, aber dies wurde offensichtlich nicht in einem Monat erledigt, und das Unternehmen versteht wahrscheinlich, was es tut.
Okay, bis wir das Studium der Materialien für später verschieben, werden wir alles „auf dem Weg mit dem Nugget“ verstehen und CCS mutig öffnen. Es implementiert das Konzept der Arbeitsbereiche, die vom übergeordneten Eclipse empfangen wurden. Persönlich ist mir das Projektkonzept näher, aber niemand stört uns, genau ein Projekt im Raum zu halten, also lasst uns weitermachen.
Aber dann wird es etwas schlimmer - wir öffnen den Arbeitsbereich (RP) für unser Debugging-Board und sehen viele Projekte (in der Regel in zwei Versionen - unter RTOS und für „Bare Iron“). Wie ich bereits sagte, ist dies kein Verbrechen, aber die Tatsache, dass viele Projekte dieselben Dateien mit identischen Softwaremodulen enthalten, ist überhaupt nicht großartig. Der Code wird viele Male dupliziert und unterstützende Änderungen werden zu einer nicht trivialen Aufgabe. Ja, mit einer solchen Lösung ist es viel einfacher, das Projekt durch einfaches Kopieren des Verzeichnisses zu übertragen, aber für solche Dinge wird das Projekt exportiert und es ist recht gut implementiert. Links zu Dateien im Projektbaum werden angemessen unterstützt, sodass die Entscheidung, die Dateien selbst in die bereitgestellten Beispiele aufzunehmen, nicht als zufriedenstellend angesehen werden kann.
Wir setzen unsere Forschung fort - wir werden mit einem abgeschlossenen Projekt arbeiten, aber keine LED blinken lassen, obwohl sich zwei davon auf der Debug-Platine befinden, sondern mit einer seriellen Schnittstelle, einem vorgefertigten Uartecho-Beispiel. Wir erstellen eine neue RP, nehmen das für uns interessante Projekt auf und ... nichts kommt dabei heraus. Aus der Nachricht geht hervor, dass es notwendig ist, ein verwandtes Projekt in die RP aufzunehmen. Es ist nicht sehr klar, warum dies getan wird, aber es ist nicht schwierig, die Anforderungen der Umgebung zu erfüllen, wonach das Projekt mit der Montage beginnt.
Pnp: Auf dem Heimcomputer habe ich den Befehl Projekt importieren verwendet, und alle erforderlichen Einschlüsse wurden von selbst vorgenommen. Wo genau verwandte Projekte angegeben sind, weiß ich nicht, lassen wir die Analyse dieses Aspekts für die Zukunft.
Wir kompilieren, flashen und beginnen mit dem Debuggen. Wir finden ein interessantes Phänomen - die schrittweise Ausführung wird nicht angemessen angezeigt, wenn man die Bibliothek für die Arbeit mit einer seriellen Schnittstelle betrachtet - Optimierungskosten. Wir deaktivieren die Optimierung in den Compiler-Einstellungen (welche Einstellungen sind nicht vorhanden, gibt es wirklich Leute, die sie alle kennen und darüber hinaus alle verwenden), bauen das Projekt erneut zusammen - und nichts ändert sich. Es stellt sich heraus, dass nur die Dateien enthalten sind, die im Projektbaum enthalten sind, zumindest in Form von Links. Wir fügen Links zu den Bibliotheksquellen hinzu und nach dem Wiederherstellen wird alles korrekt debuggt (vorausgesetzt, wir haben die Option, Debugging-Informationen zu generieren).
Pnp: Ich habe jedoch Optionen gefunden, um die MISRA-C-Konformitätsprüfung zu aktivieren.
Pnp: Eine andere Möglichkeit besteht darin, den Befehl "Clean ..." für die nachfolgende Assembly zu verwenden. Der Befehl "Build All" wirkt sich aus irgendeinem Grund nicht auf das zugehörige Projekt aus.
Dann stellen wir fest, dass nicht immer alles normal debuggt wird. Manchmal befinden wir uns in Bereichen des Maschinencodes, für die der Compiler die Quelle nicht findet. Da die Programmierumgebung uns alle für die Arbeit erforderlichen Dateien zur Verfügung stellt - das Ergebnis des Präprozessors, des Assembler-Codes und der Linkerkarte (Sie müssen nur daran denken, die entsprechenden Optionen zu aktivieren), wenden wir uns letzterem zu. Wir finden zwei Bereiche des Programmcodes - ab 0x0000. und ab 0x1000. (32-Bit-Architekturen sind für alle gut, aber das Schreiben von Adressen ist nicht ihre Stärke). Wir wenden uns der Dokumentation für die Mikroschaltung zu und stellen fest, dass sich im Inneren ein ROM-Bereich befindet, der speziell auf 0x1000 abgebildet ist. Er enthält den integrierten Teil der Bibliotheken. Es wird argumentiert, dass die Verwendung von Routinen daraus die Leistung verbessert und den Verbrauch im Vergleich zum 0x000-Adressraum reduziert. Während wir MK beherrschen, sind wir nicht so an den letzten Parametern interessiert, aber die Bequemlichkeit des Debuggens ist entscheidend. Sie können die Verwendung von ROM deaktivieren (jedoch für unsere Zwecke), indem Sie die Option NO_ROM auf den Compiler setzen, was wir tun und das Projekt neu zusammensetzen.
PNP: Der Übergang zur Subroutine im ROM sieht sehr lustig aus - es gibt keinen langen Übergang im Befehlssystem, daher erfolgt der Übergang zunächst mit einer Rückkehr zum Zwischenpunkt im unteren Adressbereich (0x0000), und dort liegt bereits der PC-Startbefehl, dessen Parameter vom Disassembler nicht erkannt werden. Etwas, das ich nicht glauben kann, als ob man mit solchen Gemeinkosten an Geschwindigkeit gewinnen kann, obwohl für lange Routinen - warum nicht.
Eine interessante Frage ist übrigens, wie im Allgemeinen garantiert wird, dass der Inhalt des ROM den vom Unternehmen freundlicherweise zur Verfügung gestellten Quellcodes entspricht. Ich kann sofort einen Mechanismus zum Einbetten zusätzlicher (natürlich Debugging- und Service-) Funktionen in das ROM vorschlagen, der für den Benutzer - MK-Programmierer - völlig unsichtbar ist. Und ich persönlich habe keinen Zweifel daran, dass die Entwickler des Chips auch viele andere Mechanismen kennen, die solche Funktionen implementieren, aber wir werden den Angriff der Paranoia beenden.
Andererseits kann ich das Erscheinen eines solchen Analogons des BIOS nur begrüßen, da dies auf lange Sicht den Traum der Entwickler von einer echten Portabilität von Code zwischen verschiedenen MK-Familien mit einem Kern Wirklichkeit werden lässt. Wir stellen auch die Besonderheit der Implementierung der Interaktion mit "eingebetteten" Softwaremodulen fest. Wenn in frühen Versuchen, einen ähnlichen Mechanismus zu erstellen, der in TivaC-Modellen implementiert ist, ein Anruf-Supervisor mit der Gruppennummer und der Nummer des Einstiegspunkts in das Unterprogramm aufgerufen wurde, was einen erheblichen Overhead verursachte, liegt die Auflösung der Kommunikation aufgrund doppelter Namen von Funktionen und auf Linker-Ebene Direkte Weitsprünge zu Unterprogrammen im ROM werden eingefügt. Dies ist viel schneller in der Ausführung, erfordert jedoch eine Neukompilierung des Projekts, wenn das Nutzungsmodell geändert wird.
Nachdem wir uns auf das bequeme Debuggen vorbereitet haben, kehren wir zu unserem Projekt zurück und beginnen, das Programm mit Zugriff auf die Quellcodes der Module leise zu debuggen (nun, das habe ich mir gedacht ...), um uns eine Meinung über die Qualität dieser Texte zu bilden. Das untersuchte Projekt implementiert einen Spiegel des seriellen Kommunikationskanals und ist für Schulungszwecke äußerst praktisch. Natürlich haben wir die Option mit RTOS gewählt, ich sehe nicht den geringsten Grund, sie in unserer Konfiguration nicht zu verwenden (viel Speicher und Programmspeicher).
Wir stellen sofort fest, dass die Quellcodes in C dargestellt werden. Dies ist oft nicht sehr praktisch. Viele Sprachkonstrukte sehen im Vergleich zu ihren Analoga auf den Pluspunkten umständlich aus, aber die Entwickler waren mehr an der Codekompatibilität als an syntaktischem Zucker interessiert. Obwohl es möglich wäre, eine C ++ - Version von Bibliotheken zu erstellen, ist die bedingte Kompilierung seit langem bekannt und wird überall verwendet, was jedoch zusätzliche Materialkosten mit sich bringt. Sicher weiß das Management des Unternehmens, was es tut, und meine Kommentare sind eine Art "gerissene Analyse", aber es scheint mir, dass ich auch das Recht auf meine Meinung habe.
Ich kenne auch den umgekehrten Ansatz, wenn die Bibliothek mit den neuesten C ++ - Tools erstellt wird und wenn gefragt wird, was für Entwickler zu tun ist, die Compiler verwenden, die nicht den neuesten Spezifikationen entsprechen, ist die perfekte Antwort, auf neue Versionen zu aktualisieren oder nicht diese Bibliothek (ich empfehle dringend die zweite Option in solchen Fällen). Meine persönliche Meinung ist, dass wenn wir wirklich wollen, dass unser Produkt verwendet wird (und TI es eindeutig will und die Bibliothek nicht nach dem Prinzip "Lass es mich fallen, hier ist eine neue Trommel für dich" erstellt wird), sein Ansatz sicherlich wahr ist.
Der Quellcode des Programms sieht klassisch aus: Initialisieren der Hardware- und Softwareumgebung, Erstellen von Aufgaben und Starten eines Shedulers im Hauptmodul, der Aufgabentext in einem separaten Kompilierungsmodul. In dem betrachteten Beispiel ist die Aufgabe genau eine - mainThread, der Zweck ist aus dem Namen nicht ganz klar, und auch, was mich etwas verwirrt - der Name der Datei, die den Quelltext enthält, stimmt nicht mit dem Namen der Funktion überein (uartecho.c - obwohl der Name hier spricht) gut ja Die Suche in der Programmierumgebung wird auf standardmäßige Weise implementiert (Kontextmenü oder F3 für den Entitätsnamen), und dies ist kein Problem.
Das Festlegen der Aufgabenparameter vor dem Start wird erwartet:
- eine Parameterstruktur erstellen (natürlich lokal),
- gib ihm Standardwerte,
- andere als die Standardparameter einstellen und
- Verwenden Sie die Struktur beim Erstellen der Aufgabe.
Trotz der Art der Natürlichkeit dieser Operationen ist dies nicht für alle Bibliotheksautoren offensichtlich, und ich habe verschiedene Implementierungen gesehen, bei denen es zum Beispiel keine Stufe 2 gab, die zu einem lustigen (für einen externen Beobachter, nicht für einen Programmierer) Programmverhalten führte. In diesem Fall ist alles in Ordnung. Die einzige Frage, die sich gestellt hat, ist, warum die Standardwerte nicht konstant sind. Wahrscheinlich ist dies ein Erbe der verdammten Vergangenheit.
PNP: Im bekannten FREE-RTOS wird ein etwas anderer Ansatz gewählt, wobei die Aufgabenparameter direkt im Hauptteil des Aufrufs der API der Aufgabenerstellungsfunktion angegeben sind. Die Vor- und Nachteile dieser Ansätze sind wie folgt:
- + ermöglicht es Ihnen, Parameter, die mit den Standardwerten übereinstimmen, nicht explizit anzugeben, + erfordert nicht, sich die Reihenfolge der Parameter zu merken, - ausführlicher, - größere Speicherkosten, - Sie müssen die Standardparameter kennen, - erstellt ein benanntes Zwischenobjekt
- - erfordert die Angabe aller Parameter, - erfordert das Speichern der Reihenfolge der Parameter, + ist kompakter, + erfordert weniger Speicher, + erfordert keine benannten Zwischenobjekte.
Es gibt eine dritte Methode, die vom Autor dieses Beitrags empfohlen wird (im Stil von TURBO) und die über einen eigenen Satz verfügt - + ermöglicht es Ihnen, Parameter, die dem Standard entsprechen, nicht explizit anzugeben, + erfordert nicht, sich die Reihenfolge der Parameter zu merken, -multi-verbal, -großere Speicherkosten, -Sie müssen die Standardparameter kennen, + arbeitet im Lambda-Stil, + macht Standardfehler schwierig zu implementieren, -sicht etwas seltsam wegen der vielen rechten Klammern.
Nun, es gibt eine weitere vierte Option, die keine Nachteile aufweist, aber C ++ von mindestens 14 erfordert - wir lecken uns die Lippen und gehen vorbei.
Wir starten das Debuggen, führen das Programm aus und öffnen eine der beiden seriellen Schnittstellen, die von der Debugging-Karte bereitgestellt werden, im Terminalfenster, das von der Programmierumgebung bereitgestellt wird. Welcher der beiden Ports (einer debuggt, wahrscheinlich der zweite ist Benutzer, Sie können ihre Nummern im System sehen) ist im Voraus schwer zu erkennen, manchmal der jüngste, manchmal der ältere, zumindest ändert sich nichts, wenn Sie die Karte wieder anschließen, sodass Sie ihn auf die Karte schreiben können. Noch eine Unannehmlichkeit: Offene Terminals werden nicht mit dem Projekt gespeichert und beim Öffnen einer Debugging-Sitzung nicht wiederhergestellt, obwohl sie beim Beenden nicht geschlossen werden. Wir überprüfen die Funktionsweise des Programms und stellen sofort einen weiteren Nachteil fest: Das Terminal kann nicht konfiguriert werden. Beispielsweise funktioniert es im Unix-Stil mit einem Closing / r. Ich habe den Kontakt zu einem solchen Minimalismus verloren, obwohl uns niemand mit der Verwendung eines externen Terminalprogramms stört.
Pnp: Wir stellen eine weitere Funktion des Debuggens fest. Dies gilt für jede Entwicklungsumgebung. Wenn wir Aufgaben mit einem Sheduler wechseln, verlieren wir den Fokus. Haltepunkte helfen uns, dieses Problem zu lösen.
Betrachten Sie zunächst den Prozess des Erstellens einer Instanz einer seriellen Schnittstelle - hier scheint alles Standard zu sein, es wird eine Struktur verwendet, deren Feldern die erforderlichen Parameter des Objekts zugewiesen sind. Beachten Sie, dass wir bei den Profis, die in C völlig abwesend sind, die Möglichkeit haben, alle Initialisierungen „unter der Haube“ vollständig zu verbergen, aber ich habe bereits mögliche Argumente für die zweite Lösung vorgebracht. Es gibt eine Funktion zum Initialisieren der Optimierungsstruktur, und diese ist gut (paradoxerweise klingt diese Funktion für die Autoren einiger Bibliotheken nicht obligatorisch). An diesem Punkt der Geschichte enden die Flitterwochen und das gewöhnliche
(eheliche) Leben beginnt.
Eine sorgfältige Untersuchung der Quellen zeigt, dass nicht alles so gut ist. Was ist das Problem - die Initialisierungsfunktion kopiert die Standardwerte von dem Objekt, das im konstanten Bereich liegt, in unsere Kontrollstruktur, und das ist wunderbar, aber aus irgendeinem Grund:
- Das Objekt ist global, obwohl es von der einzigen Funktion zum Initialisieren der Parameter verwendet wird (zu einem Zeitpunkt kostete eine ähnliche Vorgehensweise Toyota einen angemessenen Betrag). Nun, das Hinzufügen der statischen Anweisung ist einfach.
- Das Steuerobjekt heißt, in C gibt es keine schöne Lösung für dieses Problem, oder besser gesagt, es gibt eine Lösung mit einer anonymen Kopie, und ich habe es in einem langen Post gegeben, aber viele rechte Klammern erlauben es nicht, diese Option wirklich schön zu nennen, in Plus gibt es eine Lösung von unglaublicher Schönheit, aber was von einem Wunschtraum träumen;
- Alle Felder des Objekts sind in der Bittiefe eindeutig redundant, sogar Bitfelder (Aufzählungen von zwei möglichen Werten) werden in 32-Bit-Wörtern gespeichert.
- Aufzählungsmoduskonstanten werden in Form von Definitionen definiert, was eine Überprüfung in der Kompilierungsphase unmöglich und zur Laufzeit erforderlich macht.
- Wenn Sie einen Abschnitt einer Endlosschleife an verschiedenen Stellen möglicher Fehler wiederholen, ist es viel korrekter, einen (in diesem Fall leeren) Handler zu erstellen.
- Nun, alle Operationen zum Einrichten und Starten einer Aufgabe können (und sollten) in einer Funktion oder sogar einem Makro versteckt sein.
Aber die Initialisierung des Empfangspuffers ist gut gemacht - wir verwenden vorreservierten Speicher, keine Manipulation des Heaps, die Aufrufkette ist etwas kompliziert, aber alles ist lesbar.
Pnp: Im Debug-Fenster, vor unseren Augen, dem Call-Stack, wird alles so gemacht, wie es sollte und solide - Respekt und Respekt. Das einzige, was etwas überraschend ist, ist der Versuch, dieses Fenster auszublenden, der zum Ende der Debugging-Sitzung führt.
Nun, und noch eine etwas unerwartete Entscheidung: Setzen Sie die mögliche Anzahl von Objekten in der Aufzählung für serielle Ports und für diese Debug-Karte im Stil auf 1
typedef enum CC1310_LAUNCHXL_UARTName { CC1310_LAUNCHXL_UART0 = 0, CC1310_LAUNCHXL_UARTCOUNT } CC1310_LAUNCHXL_UARTName;
Solche Lösungen sind Standard für echte Übertragungen, aber für die Beschreibung von Hardwareobjekten - und ich wusste nicht, dass dies möglich ist, obwohl es für mich selbst funktioniert. Wir haben die Initialisierung von Eisen abgeschlossen, fahren wir fort.
In einer laufenden Aufgabe beobachten wir eine klassische Endlosschleife, in der Daten von einer seriellen Schnittstelle von der Funktion gelesen werden
UART_read(uart, &input, 1);
und sofort per Funktion zurückgeschickt
UART_write(uart, &input, 1);
. Gehen wir zum ersten und sehen uns einen Versuch an, Zeichen aus dem Empfangspuffer zu lesen
return (handle->fxnTablePtr->readPollingFxn(handle, buffer, size))
(Wie hasse ich solche Dinge, aber in C ist es sonst einfach unmöglich), gehen wir tiefer und finden uns in UARTCC26XX_read wieder, und von dort aus gelangen wir zur Implementierung des Ringpuffers - einer Funktion
RingBuf_get(&object->ringBuffer, &readIn)
. Hier geht das gewöhnliche Leben in eine akute Phase.
Ich wollte nicht sagen, dass mir dieses spezielle Modul (ringbuf.c-Datei) nicht gefallen hat, es wurde einfach schrecklich geschrieben und ich persönlich hätte den Platz einer so angesehenen Gruppe von Autoren dieses Teils mit Scham rausgeschmissen (Sie können mich immer noch an ihre Stelle setzen, aber ich fürchte dass das Gehaltsniveau unserer indischen Kollegen nicht zu mir passt), aber ich weiß wahrscheinlich nicht was. Pass auf deine Hände auf:
1) Das erneute Rollen von Lese- / Schreibzeigern wird durch den Rest der Division implementiert
object->tail = (object->tail + 1) % object->length;
und es gibt keine Optimierungen des Compilers, wenn diese Operation ausgeführt wird, wie beispielsweise das Überlagern einer Bitmaske, da die Länge des Puffers keine Konstante ist. Ja, in diesem MK gibt es eine Hardware-Teilungsoperation und sie ist ziemlich schnell (ich habe darüber geschrieben), aber es dauert immer noch nie 2 Taktzyklen, wie bei der korrekten Implementierung mit ehrlichem Re-Roll (und ich habe auch darüber geschrieben).
Pnp: Ich habe kürzlich eine Beschreibung der neuen M7-Architektur in der Implementierung gesehen und kann mich an niemanden erinnern. Aus irgendeinem Grund wurde das Teilen von 32 durch 32 in 2-12 Zyklen statt in 2-7 Zyklen durchgeführt. Entweder handelt es sich um einen Übersetzungsfehler, oder ... ich weiß nicht einmal, woran ich denken soll.
2) Darüber hinaus wird dieses Codefragment an mehr als einer Stelle wiederholt - Makros und Inlines für Weicheier, Strg + C und Strg + V-Regel, das DRY-Prinzip geht durch den Wald,
3) ein vollständig redundanter Zähler von gefüllten Pufferplätzen wurde implementiert, was den folgenden Nachteil mit sich brachte:
4) kritische Abschnitte beim Lesen und Schreiben. Nun, ich kann immer noch glauben, dass die Autoren dieses Moduls meine Beiträge zu Habré nicht lesen (obwohl dieses Verhalten für Fachleute auf dem Gebiet der Firmware nicht akzeptabel ist), aber sie sollten mit dem Mustang-Buch vertraut sein, da dieses Problem im Detail untersucht wird.
5) Wie eine Kirsche auf dem Kuchen wurde außerdem ein Indikator für die maximale Puffergröße mit einem sehr verschwommenen Namen und einer vollständig fehlenden Beschreibung eingeführt (letzteres gilt allgemein für das gesamte Modul). Ich schließe nicht aus, dass diese Option für das Debuggen nützlich sein kann, aber warum sollte sie in die Version gezogen werden? Haben wir überhaupt Prozessorzyklen mit dem RAM?
6) Gleichzeitig fehlt die Pufferüberlaufverarbeitung vollständig (es gibt eine -1-Rückmeldung über diese Situation) - selbst in Arduino werden wir die Qualität dieser Verarbeitung außer Acht lassen, aber ihre Abwesenheit ist noch schlimmer. Oder waren die Autoren von der bekannten Tatsache inspiriert, dass Annahmen bezüglich einer relativ leeren Menge zutreffen, einschließlich der Tatsache, dass sie nicht leer ist?
Im Allgemeinen stimmen meine Kommentare vollständig mit der ersten Zeile des Demotivators zum Thema Codeüberprüfung "10 Codezeilen - 10 Kommentare" überein.
Das vorletzte der festgestellten Mängel lässt uns übrigens über globalere Dinge nachdenken - aber wie implementieren wir die Basisklasse überhaupt, um ihre tiefgreifende Modifikation durchführen zu können. Alle Felder sicher zu machen, ist eine zweifelhafte Idee (obwohl wahrscheinlich die einzig richtige). Einen Aufruf freundlicher Funktionen in die Erben einzufügen, ist Krücken sehr ähnlich. Wenn es in diesem speziellen Fall eine einfache Antwort auf die Frage gibt, einen Indikator für die Pufferfülle einzuführen - eine generierte Klasse mit überlappendem Schreiben und Lesen und einem zusätzlichen Zähler -, dann das Lesen zu implementieren, ohne den Puffer vorzurücken (wie in diesem Fall) oder das zuletzt platzierte Zeichen zu ersetzen (ich habe z Ringpuffer-Implementierung) Sie können nicht auf den Zugriff auf die internen Daten der übergeordneten Klasse verzichten.
Gleichzeitig gibt es keine Beschwerden über die Implementierung des tatsächlichen Lesens von der seriellen Schnittstelle - die Eingabe blockiert, wenn nicht genügend Zeichen im Empfangspuffer vorhanden sind, wird ein Semaphor gespannt und die Steuerung wird an den Sheduler übertragen - alles wird genau und korrekt implementiert. Persönlich mag ich es nicht wirklich, die Ausrüstung in einem Allzweckverfahren zu steuern, aber dies verringert die Verschachtelung von Verfahren und verringert den Index der zyklomatischen Komplexität, unabhängig davon, was dies bedeutet.
Achten wir nun auf die Übertragung der empfangenen Daten an den seriellen Kanal, da beim Erstellen des Objekts nur ein Ringpuffer vorhanden war - der empfangende. In der Tat wird der interne Puffer der Hardware zum Übertragen von Zeichen verwendet, und wenn er gefüllt ist, wird das Warten auf die Bereitschaft eingegeben (zumindest im blockierenden Betriebsmodus). Ich kann mir nicht helfen, um den Stil der entsprechenden Funktionen nicht zu kritisieren: 1) Aus irgendeinem Grund verfügt das Objekt über einen verallgemeinerten Zeiger, der sich innerhalb der Funktion ständig in einen Zeiger auf Zeichen verwandelt
*(unsigned char *)object->writeBuf);
2) Die Logik der Arbeit ist völlig undurchsichtig und leicht verwirrt. Dies alles ist jedoch nicht so wichtig, da es dem Benutzer verborgen bleibt und "die maximale Geschwindigkeit nicht beeinflusst".
Während der Recherche stoßen wir auf eine weitere Funktion - wir sehen den Quellcode einiger interner Funktionen im Debug-Modus nicht - dies ist auf eine Namensänderung für verschiedene Kompilierungsoptionen (ROM / NO_ROM) zurückzuführen. Ersetzen Sie die erforderliche Quelldatei (C: \ Jenkins \ jobs \ FWGroup-DriverLib \ workspace \ modules \ output \ cc13xx_cha_2_0_ext \ driverlib \ bin \ ccs /./../../../ driverlib / uart.c--) Ich habe versagt (aber ich habe es nicht wirklich versucht), obwohl ich die Quelle gefunden habe (natürlich in der Datei in der Datei uart.c, danke, Captain). Glücklicherweise ist dieses Fragment einfach und es ist einfach, den Assembler-Code mit dem Quellcode in C zu identifizieren (vor allem, wenn Sie die Funktionen des ITxxx-Teams kennen). Ich weiß nicht, wie ich dieses Problem für Bibliotheken mit komplexen Funktionen lösen soll. Wir werden überlegen, wann dies erforderlich ist.
Und schließlich eine kleine Bemerkung: Ich bin bereit zu glauben, dass die Hardware der seriellen Kanalimplementierung für MK CC13x0-Modelle dieselbe ist wie die für CC26x0, und das Duplizieren des Inhalts einer Datei mit dem Namen UARTCC26XX.c --- kann nicht als die richtige Lösung bezeichnet werden, sondern das Erstellen einer Zwischendefinitionsdatei mit Einbeziehung Ich würde die Quelldatei begrüßen, die Funktionen und den entsprechenden Kommentar überschreiben, da dies das Programm verständlicher machen würde, und dies sollte immer willkommen sein, vout.
Während der Testfall funktioniert, haben wir viel über die interne Struktur von Standardbibliotheken gelernt, ihre Stärken und nicht so guten Seiten festgestellt. Zum Abschluss der Überprüfung werden wir versuchen, die Antwort auf die Frage zu finden, die dem Programmierer normalerweise im Dilemma „Betriebssystem oder nicht Betriebssystem“ - Kontextwechselzeit - wichtig ist. Hier sind zwei Möglichkeiten möglich: 1) Die Betrachtung des Quellcodes ist eher eine theoretische Methode, erfordert ein gewisses Maß an Eintauchen in das Thema, das ich nicht demonstrieren möchte, und 2) ein praktisches Experiment. Natürlich liefert die zweite Methode im Gegensatz zur ersten keine absolut korrekten Ergebnisse, aber „die Wahrheit ist immer konkret“ und die erhaltenen Daten können als angemessen angesehen werden, wenn die Messungen korrekt organisiert sind.
Um die Schaltzeit abzuschätzen, müssen wir zunächst lernen, wie die Gesamtausführungszeit verschiedener Programmfragmente bewertet wird. In dieser Architektur gibt es ein Debugging-Modul, zu dem auch ein Systemzähler gehört. Informationen über dieses Modul sind leicht zugänglich, aber der Teufel versteckt sich wie immer im Detail. Versuchen wir zunächst, den erforderlichen Modus mit den Handles direkt über den Zugriff auf die Register zu konfigurieren. Wir finden schnell den Registerblock CPU_DWT und darin finden wir sowohl den CYCCNT-Zähler selbst als auch das Steuerregister dafür CTRL mit dem CYCCNTENA-Bit. Natürlich, oder wie sie sagen, ist natürlich nichts passiert und die ARM-Website hat eine Antwort auf die Frage, warum - es ist notwendig, das Debugging-Modul mit dem TRCENA-Bit im DEMCR-Register zu aktivieren. Aber das letzte Register ist nicht so einfach - im DWT-Block ist es nicht vorhanden, in anderen Blöcken ist die Suche faul - sie sind ziemlich lang, aber ich habe keine Suche nach Namen im Registerfenster gefunden (aber es wäre schön, sie zu haben). Wir gehen in das Speicherfenster, geben die Adresse des Registers ein (sie ist uns seit dem Datum bekannt) (aus irgendeinem Grund ist das hexadezimale Format der Adresse nicht standardmäßig, Sie müssen das Präfix 0x mit Stiften hinzufügen) und plötzlich sehen wir eine benannte Speicherzelle mit dem Namen CPU_CSC_DEMCR. Es ist, gelinde gesagt, lustig, warum das Unternehmen die Register im Vergleich zu den vom Lizenzgeber der Architektur vorgeschlagenen Namen umbenannt hat, wahrscheinlich war es notwendig. Und genau, im Registerblock CPU_CSC finden wir unser Register, setzen das gewünschte Bit darin, kehren zum Zähler zurück, aktivieren es und alles hat funktioniert.
Pnp: Es gibt immer noch eine Suche nach Namen, sie wird (natürlich) von der Strg-F-Kombination aufgerufen, sie existiert nur im Kontextmenü, aber in der üblichen wird sie abgebrochen, ich entschuldige mich bei den Entwicklern.
Sofort stelle ich einen weiteren Nachteil des Speicherfensters fest - das Drucken des Inhalts wird durch Angabe der benannten Zellen unterbrochen, wodurch die Ausgabe zerrissen und nicht in 16 (8.32.64, ersetzen Sie die erforderlichen) Wörter unterteilt wird. Darüber hinaus ändert sich das Ausgabeformat, wenn die Fenstergröße geändert wird. Möglicherweise kann dies alles nach Bedarf des Benutzers konfiguriert werden, aber basierend auf meiner eigenen Erfahrung (und was ich sonst noch tun sollte) erkläre ich, dass das Festlegen des Ausgabeformats des Speicheranzeigefensters nicht für intuitiv offensichtliche Lösungen gilt. Ich bin voll und ganz dafür, eine so praktische Funktion wie das Anzeigen benannter Speicherbereiche im Anzeigefenster zu aktivieren, da sonst viele Benutzer nie davon erfahren würden, aber auch diejenigen, die es bewusst deaktivieren möchten, müssen vorsichtig sein.
Übrigens würde ich die Möglichkeit, Makros (oder Skripte) für die Arbeit mit der Umgebung zu erstellen, nicht ganz aufgeben, da ich diese Registereinstellung (um die Zeitmessung zu aktivieren) jedes Mal nach dem Zurücksetzen des MK vornehmen musste, da ich die Codekorrektur durch Einfügen von Registermanipulationen für Debugging-Zwecke in Betracht ziehe nicht sehr richtig. Obwohl ich nie Makros gefunden habe, kann die Arbeit mit Registern erheblich vereinfacht werden, da einzelne (notwendige) Register in das Ausdrucksfenster aufgenommen werden können, wodurch die Arbeit mit ihnen erheblich erleichtert und beschleunigt wird.
Um zu betonen, dass sich das Gefühl des Ingenieurs für die MK-Familie nicht abgekühlt hat (ansonsten schimpfe ich mit verschiedenen Aspekten der Entwicklungsumgebung), stelle ich fest, dass der Zähler einwandfrei funktioniert - ich konnte in keinem der Debug-Modi zusätzliche Zyklen finden, aber bevor dies geschah zumindest in der MK-Serie von LuminaryMicro entwickelt werden.
Wir skizzieren also den Versuchsplan zur Bestimmung der Kontextwechselzeit - erstellen Sie eine zweite Aufgabe, die einen bestimmten internen Zähler (in einer Endlosschleife) inkrementiert, den MC für eine bestimmte Zeit startet und die Beziehung zwischen dem Systemzähler und dem Aufgabenzähler ermittelt. Starten Sie als Nächstes den MK für eine ähnliche Zeit (nicht unbedingt genau gleich) und geben Sie ungefähr einmal pro Sekunde 10 Zeichen in einem Tempo ein. Es ist zu erwarten, dass dies dazu führt, dass 10 zur Echo-Task und 10 zurück zur Zähler-Task geschaltet werden. Ja, diese Kontextwechsel werden nicht gemäß dem Sheduler-Timer, sondern je nach Ereignis ausgeführt. Dies sollte jedoch keinen Einfluss auf die Gesamtausführungszeit der untersuchten Funktion haben. Daher beginnen wir mit der Implementierung des Plans, erstellen die Zähleraufgabe und starten sie.
Hier finden wir eine Funktion von RTOS, zumindest in der Standardkonfiguration - es verdrängt sich nicht "für echt": Wenn die Prioritätsaufgabe ständig zur Ausführung bereit ist (und die Gegenaufgabe ist dies) und dem Sheduler keine Kontrolle gibt (keine Signale erwartet, nicht einschlafen, nicht durch Flags usw. blockiert), dann wird überhaupt keine einzige Aufgabe mit niedrigerer Priorität aus dem Wort ausgeführt. Dies ist nicht Linux, bei dem verschiedene Methoden verwendet werden, um sicherzustellen, dass jeder ein Quantum erhält, "damit niemand beleidigt wird". Dieses Verhalten ist zu erwarten, viele leichte RTOS verhalten sich so, aber das Problem ist tiefer, da das Management keine Aufgaben mit gleicher Priorität wie eine ständig vorbereitete erhält. Aus diesem Grund habe ich in diesem Beispiel die Echo-Task angehalten, die angehalten ist. Die Priorität ist höher als die ständig bereitgestellte Zähler-Task. Andernfalls erfasst diese alle Prozessorressourcen rechtzeitig.
Wir beginnen das Experiment, der erste Teil (der nur auf die Ausführungszeit wartet) gab die Daten zum Verhältnis der Zähler 406181 / 58015 = 7 an - es wird durchaus erwartet. Der zweite Teil (mit 10 aufeinanderfolgenden Zeichen für ~ 10 Sekunden) liefert die Ergebnisse 351234k-50167k * 7 = 63k / 20 = 3160 Zyklen, die letzte Ziffer ist die Zeit, die mit der Kontextumschaltprozedur in MK-Zyklen verbunden ist. Persönlich scheint mir dieser Wert etwas größer zu sein als erwartet, wir forschen weiter, es scheint, dass es noch einige Aktionen gibt, die die Statistik verderben.
PNP: Ein häufiger Fehler eines Experimentators besteht darin, die zuvor erwarteten Ergebnisse nicht zu bewerten und an den empfangenen Müll zu glauben (bis zu 737 Entwickler).
Es ist offensichtlich („ja, ganz offensichtlich“), dass das Ergebnis neben der eigentlichen Kontextumschaltung auch die Zeit enthält, die erforderlich ist, um die Vorgänge zum Lesen eines Zeichens aus dem Puffer und zum Ausgeben an die serielle Schnittstelle auszuführen. Weniger offensichtlich ist, dass es auch die Zeit hat, einen Interrupt beim Empfang zu verarbeiten und das Zeichen in den Empfangspuffer zu legen. Wie können wir eine Katze von Fleisch trennen - dafür haben wir einen kniffligen Trick - wir stoppen das Programm, geben 10 Zeichen ein und starten es. Wir können erwarten (wir sollten uns die Quelle ansehen), dass ein Interrupt beim Empfang nur einmal auftritt und sofort alle Zeichen vom Empfangspuffer an den Ring gesendet werden, was bedeutet, dass wir weniger Overhead sehen. Es ist auch einfach, den Zeitpunkt der Lieferung an die serielle Schnittstelle zu bestimmen - wir geben jedes zweite Zeichen aus und lösen die resultierenden 2 linearen Gleichungen mit 2 unbekannten. Und es ist möglich und noch einfacher - nichts abzuleiten, was ich getan habe.
Und hier sind die Ergebnisse solcher kniffligen Manipulationen: Wir machen die Eingabe durch das Paket und die fehlenden Ticks werden kleiner - 2282, schalten die Ausgabe aus und die Kosten fallen auf 1222 Ticks - es ist besser, obwohl ich auf 300 Ticks gehofft habe.
Mit der Zeit des Lesens kann jedoch nichts dergleichen gefunden werden, da es gleichzeitig mit der gewünschten Kontextumschaltzeit skaliert wird. Das einzige, was ich anbieten kann, ist, den internen Timer zu Beginn der Eingabe des empfangenen Zeichens auszuschalten und wieder einzuschalten, bevor Sie auf das nächste warten. Dann arbeiten zwei Zähler synchron (mit Ausnahme des Umschaltens) und es kann leicht bestimmt werden. Ein solcher Ansatz erfordert jedoch eine gründliche Implementierung von Systemprogrammen in den Texten, und dennoch bleibt die Komponente der Interrupt-Behandlung erhalten. Daher schlage ich vor, mich auf die bereits erhaltenen Daten zu beschränken, die es uns ermöglichen, fest zu behaupten, dass die Taskwechselzeit im betrachteten TI-RTOS 1222 Taktzyklen nicht überschreitet, was für eine gegebene Taktfrequenz 30 Mikrosekunden beträgt.
PNP: jedenfalls viel - ich habe Zyklen bei 100: 30 gezählt, um den Kontext zu speichern, 40, um die fertige Aufgabe zu bestimmen und 30, um den Kontext wiederherzustellen, aber wir bekommen eine Größenordnung mehr. Obwohl die Optimierung jetzt deaktiviert wurde, schalten Sie –o2 ein und sehen Sie das Ergebnis: Es hat sich nicht viel geändert - es wurde 2894 statt 3160.
Es gibt noch eine andere Idee: Wenn das Betriebssystem das Wechseln von Peer-to-Peer-Aufgaben unterstützt, können Sie zwei Aufgaben mit Zählern ausführen, auf magische Weise Daten über die Anzahl der Schalter in einer Weile abrufen und den Verlust des Systemzählers berechnen, jedoch aufgrund der Besonderheiten des Shedulers, über die ich bereits gesagt, wird dieser Ansatz nicht zum Erfolg führen. Obwohl eine andere Option möglich ist - Ping-Pong zwischen zwei Peer-to-Peer- (oder sogar Peer-to-Peer-) Aufgaben über ein Semaphor durchzuführen, ist es einfach, die Anzahl der Kontextwechsel hier zu berechnen - Sie müssen es versuchen, aber es wird morgen sein.
Die traditionelle Umfrage am Ende des Beitrags dieses Mal widmet sich nicht der Präsentationsebene (es ist jedem unvoreingenommenen Leser klar, dass er über jedes Lob hinausgeht und alle Erwartungen übertrifft), sondern dem Thema des nächsten Beitrags.