LAUNCHXL-F28379D

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Auch genannt „C2000 Delfino Launchpad“ von Texas Instruments (TI). Ein Mikrocontroller/DSP mit (je nach Sichtweise) 2 oder 4 Kernen, 200 MHz Taktfrequenz, vorrangige Anwendung Frequenzumrichter und Motorsteuerung, mit komfortablem Debug-Interface und serieller Schnittstelle 10 MBaud über USB, ebenso Stromversorgung, umstellbar auf voll isoliertes Interface. Kein Ethernet, kein WLAN, kein Bluetooth. Für etwa 50 € zu haben.

Interessant für was und wen?

Nichts für Anfänger. Für einen ausreichend komfortablen Frequenzumrichter für einen Drehstrommotor genügt bereits ein ATtiny861. Dieser hat eine 64-MHz-High-Speed-PWM und 3 PWM-Ausgänge mit programmierbarem Totzeitgenerator. Ein TMS320F28379D bietet ähnliches vielfach, dazu Hardware zur Auswertung von Vielstrich-Inkrementalgebern. Herausgeführt ist's zumindestens doppelt, sodass man damit einen Maschinensatz (Motorgenerator) mit nur einem Mikrocontroller ansteuern kann. Mit den C2000 hat TI den Unterschied zwischen DSP und Mikrocontroller weitgehend geglättet.

Fallstricke

Ein C2000 ist kein ganz gewöhnlicher Mikrocontroller, sondern hat folgende Eigenheiten:

  • Obwohl im Leistungsbereich eines ARM Cortex M4 ist die bevorzugte Verarbeitungsbreite 16 Bit. 32-Bit-Befehle laufen ohne Zeitverzug, sind aber nicht Standard: Merke: sizeof(int) *2 == sizeof(long).
  • Bytes können nicht adressiert werden: Merke: sizeof(char) == sizeof(wchar_t) == sizeof(int) == 1 (16 bit). Daher ist es nicht von Nachteil, alle Zeichenketten als Wide-Char (Unicode UTF-16 oder BMP0) abzulegen. Jammerschade dass absolut kein TI-Beispiel davon Gebrauch macht.
  • Es ist kein SMP-System, alle Kerne benutzen ein eigenes Stück Flash-Speicher als Code-Speicher. Um SMP zu simulieren muss man Kode in verschiedene Flash-Speicherbereiche duplizieren.
  • Die beiden Extra-Kerne werden von TI „CLA“ = Control Law Accelerator, Regelvorschriftbeschleuniger, genannt. Diese haben einen völlig anderen Befehlssatz als die normalen Kerne namens „CPU1“ bzw. „CPU2“ und nur begrenzten Zugriff zu Ein/Ausgaberessourcen.
  • Die Kerne können über gemeinsamen RAM miteinander kommunizieren. Das alles erfordert ein gut durchdachtes Linkerskript.
  • Gleitkomma ist eingebaut, man muss nicht mehr mit C2000-typischem Festkomma herumhampeln, aber viele TI-Beispiele wissen noch nichts davon und sind dadurch unnötig unleserlich.
  • C++ wird maximal im Standard 2003 unterstützt: Kein auto-Schlüsselwort, keine vereinfachten for-Konstrukte, keine Lambdas. override wird korrekt unterstützt aber generiert eine Warnung.
  • Auf dem Board ist USB des Mikrocontrollers nicht herausgeführt. Dafür muss man etwas basteln.
  • Von den 3 D/A-Wandlern sind nur 2 herausgeführt.

Nachfolgetyp

Der in-etwa-Nachfolger des TMS320F28379D ist der TMS320F28388. Dieser enthält einen fünften Prozessorkern, einen echten ARM Cortex M4, von TI „CM“ = „Connectivity Manager“ genannt. Dieser ist vorrangig für die Kommunikation nach außen vorgesehen, unter anderem die bekannte serielle und USB-Schnittstelle aber auch die neu hinzugekommene Ethernet- und EtherCAT-Schnittstelle. Ein preiswertes Einsteigerboard gibt es für diesen (2021) noch nicht.

Entwicklungssoftware

Standardmäßig wird man sich TI's „Code Composer Studio“ antun müssen. Diese Eclipse-basierte Entwicklungsumgebung bietet alles was die moderne Programmentwicklung ermöglicht und erleichtert. Leider ist es recht schwer zu verstehen und zu konfigurieren.

Man muss es nach C:\TI installieren! Andere Installationsorte bereiten später Probleme. Daran sieht man, dass das Produkt absolut nicht ausgereift ist.

Um damit wirklich programmieren zu können muss man „C2000Ware“ installieren. Diese (wirklich schlecht gemachte) Ansammlung von Headerdateien, Bibliotheken und Beispielen muss man erst mal verstehen:

  • Einerseits bietet TI im Verzeichnis device_support Headerdateien für die Programmierung mit dem Datenblatt. Registerblöcke sind in structs organisiert, deren Elemente komplett (.all) oder bitweise (.bit) angesprochen werden können.

Der Zwang nach dem .bit-Einschub ist leider nur ein Zugeständnis an ausgestorbene C-Compiler, die keine namenlosen structs unterstützen. Der Zwang nach .all ist notwendig in C, aber vermeidbar in C++ mit Typecast- und Zuweisungs-Operatoren. Das sieht etwa so aus:

  #include <regdef.h>
  DacaRegs.DACCTL.bit.SYNCSEL = 0;
  • Andererseits gibt es die Möglichkeit, ausgehend vom Verzeichnis driverlib, mit Funktionen statt structs zuzugreifen. Rätselhafterweise baut das eine nicht auf das andere auf. Und auch hier ausschließlich C, kein C++. Dokumentation: Fehlanzeige. Muss sich mit doxygen erstellen.

Das sieht etwa so aus:

  #include "driverlib.h"
  CPUTimerInit();
  CPUTimer_setPeriod(CPUTIMER0_BASE,SysCtl_getClock(DEVICE_OSCSRC_FREQ)/100);

Mit dem „Resource Explorer“ lädt man sich Beispielkode in seinen Arbeitsbereich. Leider werden viele Dateien aus driverlib kopiert statt Verweise zu generieren. Wie bei anderen Kodegeneratoren wird man anschließend allein gelassen, wenn es darum geht, etwas neues zu programmieren.

Zudem gibt es neben driverlib noch „höhere“ Bibliotheken, die (zumeist?) auf driverlib aufsetzen. Diese befinden sich unter libraries. Beispielsweise befindet sich usblib unter libraries/communications/usb/f2837xd/lib/usblib.lib. Diese statische Bibliothek ist offenbar eine Gabel-Bibliothek, die entweder usblib_coff.lib (COFF = älterer C-Compiler) oder usblib_eabi.lib (ELF = neuerer C-Compiler) heranzieht. Dessen Sourcecode ist davon ausgehend in ../source/ und ../include/. Wie man am o.a. Pfad erahnt, ist alles (Bibliothek, Quelltext und Headerdateien) für jeden Mikrocontroller abgelegt – und nicht identisch, sondern unterscheiden sich nur im Header um das Erstelldatum. Dankeschön für automatisch generierten Quelltext! Deshalb ist C2000Ware auch so elend groß.

Die Header enthalten eine Reihe Fehler, die eine moderne Entwicklung in C++ zur Herausforderung machen, obwohl sich der C2000 hervorragend dafür eignet. Und Eclipse besser mit C++ als mit C und endlosen #defines zurechtkommt. Auch Festkomma ist mit C++-Templates wesentlich besser und sicherer lösbar als in C, ohne extra Kode zu generieren. Man kommt nicht umhin, C2000-Systemheader ändern zu müssen.

Das MotorControl-SDK setzt auf device_support auf und unterstützt nur statische Instanzen von bspw. Regelkreisen.

Eine weitere Programmiermöglichkeit besteht in der Verwendung von Matlab/Simulink.

Tipps für die Verwendung von C

  • #defines von Konstanten sollten durch enums ersetzt werden.
  • Header-Abhängigkeiten sollten genau festgelegt werden, weniger ist mehr. Erfordert Restrukturierung der C2000Ware.
  • Bedingte Compilierung weitestgehend vermeiden: #if konstante durch if (konstante) ersetzen aktiviert die Syntaxprüfung im totgelegten Abschnitt.
  • Alles Strukturen die zum Byte-Array werden müssen (typisch: USB-Deskriptoren) als Byte-Array (oder Struktur aus Bytes) und nicht als Struktur ablegen.
  • Dem Volk aufs Maul schauen und den Datentyp „byte“ deklarieren (wie bei Arduino geschehen).
   #define elemof(x) (sizeof(x)/sizeof(*(x)))
   typedef unsigned char byte;
  • Eine sinnvolle Stapelung von Funktionen, Headern und C-Quelltexten (in Schichten und Teilaufgaben) ist Voraussetzung für ein wartbares Software-Projekt. Weder sollten C-Dateien und Header zu klein (Projekt-Übersicht) noch zu groß (fehlende Modularisierung) sein.
  • Ob man für ein Projekt eine, mehrere oder für jede C-Datei einen eigene Header-Datei anlegt ist eine Glaubensfrage, hängt von der Komplexität ab. Hat man im Auge, gewisse Teile in anderen Projekten verwenden zu können, bietet sich eine Aufteilung der Header an. Bei GUI-lastigen Programmen mit sehr vielen kreuzweisen Abhängigkeiten lohnt sich der Aufwand für mehrere Header meistens nicht.
  • Es ist Aufgabe des Compilers, lange switch-Ketten in Sprungtabellen aufzulösen oder auch nicht. So etwas „zu Fuß“ zu tun erinnert an Assemblerprogrammierung. Auch das Ersetzen von switch durch if-Ketten für bessere Optimierung ist Schnee von gestern, vor allem für diesen schnellen Prozessor.
  • Unicode-Zeichenketten (L"Beispiel") nehmen genauso viel Platz ein wie ASCII-Zeichenketten ("Beispiel"). Wide-Char (wchar_t) ist praktisch dasselbe wie Unicode, aber korrekterweise UTF-16.
  • Die Lieblingsdatengröße des alten C2000 ist int, gefolgt von int32_t. Beim aktuellen C28x und beim ARM umgekehrt. Daher bietet es sich an, Variablen und Daten in dieser Größe zu portionieren: Funktionsparameter, Rückgabetypen, Schleifenzähler, Bit-Eimer und -Masken, switch-Ausdrücke …

Tipps für die Verwendung von C++

  • Die Tipps für C, siehe oben. Zusätzlich lassen sich Konstanten mit const int bzw. const float anlegen.
  • Grundsätzlich würde ich kein C mehr verwenden, da C++ viele Vorteile bietet. Von Nachteil ist, dass man unbemerkt Funktionen gleichen Namens durch unterschiedliche Signaturen diversifizieren kann und dadurch Compiler-Fehlermeldungen schwieriger zu interpretieren sind. Das muss nicht heißen, auf printf zu verzichten! Mit cout-Beispielen hat man Umsteiger schon genug abgeschreckt.
  • Man verwende Referenzen statt Zeiger immer dann, wenn man nicht vorhat, den Zeiger zu inkrementieren. Dann kann man auch einigermaßen sicher sein, keinen NULL-Zeiger zu bekommen. Leider geht das mit dem Verlust der C-Kompatibilität einher.
  • (Nicht-virtuelle) Methoden in structs machen den Kode besser leserlich ohne dass dabei mehr Kode entsteht. Beispiel:
   struct tUSBRequest{  // Als Byte-Array sowohl für C2000 als auch für ARM geeignet
     uint8_t bmRequestType,bRequest,wValueL,wValueH,wIndexL,wIndexH,wLengthL,wLengthH;
     uint16_t wValue() const {return wValueH<<8|wValueL;}  // immer Little-Endian
     uint16_t wIndex() const {return wIndexH<<8|wIndexL;}
     uint16_t wLength() const {return wLengthH<<8|wLengthL;}
   };
  • Auf class zugunsten von struct verzichten, das erspart für's erste Member und Methoden public machen zu müssen. Gleichzeitig erspart C++ eine Menge typedefs, weil enums und structs auch ohne Präfix enum bzw. struct ansprechbar sind, so wie im o.a. Beispiel zu sehen.
  • Statische Konstruktoren funktionieren. Man beachte, dass man Klassen mit virtuellen Methoden nicht mit memset(this,0,sizeof*this) initialisieren darf, weil das den Methodenzeiger killt.
  • Templates funktionieren. Mit Bedacht einsetzen. In der Regel schließen sich Templates und virtuelle Methoden aus, man entscheide sich für eins von beiden: Templates für kleine Helferlein und virtuelle Methoden für größere Batzen.
  • Operatorenüberladung und Cast-Operatoren funktionieren. Mit Bedacht einsetzen.
  • Virtuelle Klassenmethoden sind eine Alles-Oder-Nichts-Entscheidung: Hat man eine, kann man auch mehrere anlegen. Sind auf jeden Fall besser als der Callback-Spaghetti aus C oder Methodenzeiger.
  • Inplace-new funktioniert nicht. Statt Konstruktoren init-Methode(n) benutzen wenn man auf dynamische Speicherverwaltung verzichten will oder muss.
  • Auf Mehrfachvererbung und Exceptions verzichtet man besser.
  • Eine gute Klassenhierarchie ist A und O für ein wartbares C++-Softwareprojekt: Weder sollte ein umfangreiches Programm aus nur einer Klasse bestehen (sog. Gottobjekt), noch sollte man es mit Vererbungshierarchien und Abhängigkeiten übertreiben.

Linkerskript

Wie auch gcc (bekannt aus WinAVR, Arduino, MSP430) verwendet der Linker (Compiler+Linker-Pfad: c:/ti/ccs1010/ccs/tools/compiler/ti-cgt-c2000_20.2.1.LTS/bin/cl2000.exe) ein Linkerskript. Es hat hierbei die Endung .cmd, das beißt sich mit Windows-Batch-Dateien. Im Gegensatz zu anderen Mikrocontrollern verlangt C2000 die aktive Arbeit mit Linkerskripten, während man beim Arduino kaum etwas davon sieht.

  • Dem Linker können mehrere Linkerskripte übergeben werden. Davon macht device_support Gebrauch, denn in einem steht die recht umfangreiche Liste aus Peripherieblöcken und deren Adressbereich. Dies wird so gelöst, dass im Quellkode von device_support jeder struct typ Variable eines Peripherieblocks eine benannte „Section“ zugeordnet wird, die erst vom Linker aufgelöst wird. Dieses zweistufige Vorgehen erfordert (seitens TI) das Anpassen an zwei Stellen beim Wechsel des Mikrocontrollers, aber das macht TI anscheinend gerne. driverlib hingegen kodiert die Adressen auf Compiler-Seite und braucht kein extra Linkerskript (wie auch bei Arduino und MSP430).
  • Die Entwicklungsumgebung stellt zwei Linkerskripte im Projekt bereit, eine für Kode im RAM (zum Debuggen) und eine für Kode im Flash (für das Endprodukt, auch debugbar). Diese machen besonders Probleme beim Umzug (Update) der Entwicklungsumgebung und damit des Compilers, man muss Section-Namen anpassen. Auch diese Linkerskripte weisen den verschiedenen „Sections“ des Programms verschiedene Adressbereiche zu.
  • Die Angaben PAGE 0 (Flash) und PAGE 1 (RAM) sind ein Relikt aus Harvard-Zeiten, als RAM und Flash noch überlappende Adressbereiche hatten. Kann man getrost weglassen, und sollte man auch, denn das erschwert nur das Verständnis. C2000-Mikrocontroller sind schon seit langer Zeit von-neumannisiert. Alle Adressen sind Word-Adressen, adressieren 16 bit.
  • Die durchnummerierten RAM- und Flash-Bereiche kann (und sollte) man getrost zu einem zusammenfassen, um Verständnis und Wartbarkeit zu verbessern. Etwaige Bug-Workaraounds (Weglassen der letzten 8 Words eines Speicherblocks) lässt man dabei bestehen.

Anschlüsse

  • 2 Paare zweireihiger Langpin-Buchsenleisten sind dafür gemacht, bis zu 2 Stück TI's BoosterPack anzubauen, darunter oder darüber. Das sind Treiberplatinen für Frequenzumrichter, die spannungs- und stromproportionale Analogwerte dem A/D-Wandler zuführen können.
  • 2 Quadraturenkoder-Eingänge für entsprechende Enkoder.
  • 1 CAN-Anschluss
  • J9 (hochdichter Board-zu-Board-Konnektor) auf der Unterseite.
  • Nicht bestückte SMA/SMB-Buchsen als differenzieller Analogeingang.

Siehe auch