MSP-EXP430G2 LaunchPad

Auspacken

Unter LaunchPad Wiki kann man die Entwicklungsumgebung laden.

Um das von TI zu kaufen, muß man dort einen Account anlegen. Für die Zollinformationen muß man dann angeben, für was für ein Endgerät man es einführen will, z.B. pest control (Schädlingsbekämpfung) oder militärische Munition und Zielerfassung, und kann nochmal extra anklicken, ob man es für Kernkraftwerke usw. verwenden will. Sollte man vielleicht nicht anklicken, da dann der Zoll bestimmt interessante Fragen stellt. Zahlung geht dann per Kreditkarte. Der Versand ist kostenlos, sodaß es tatsächlich nur 4,30 Dollar kostet. Das hier ist alles dabei:

Habe dann erstmal "IAR Embedded Workbench Kickstart" installiert. Alternativ gibt es auch "Code Composer Studio version 4", wer lieber eine Eclipse-basierte IDE mag. Der Quick-Start auf der Webseite listet das als Punkt 1 und 2 nacheinander auf, aber man braucht nur eine der beiden IDEs.

Im "Quick Start Guide" steht dann drin, daß man das Gerät in den USB-Port einstecken soll und die Treiber automatisch von Windows installieren lassen soll. Die Installation der IDE zuvor hat bereits den MSP430 UART Treiber installiert, sodaß der dann automatisch erkannt wird und verwendet werden kann:

Der Betrieb läuft auch an Niedrigstrom USB-Hubs, wie meiner Dell USB-Tastatur, was nützlich ist, da das mitgelieferte USB-Kabel recht kurz ist. Es läuft dann die Beispielanwendung und die rote und grüne LED blinken abwechselnd. Wenn man den Taster P1.3 klickt, dann läuft die Temperaturmessung. Finger auf den Microcontroller legen und die rote LED geht langsam an. Ein wenig Kältespray und die grüne LED geht an.

Auf dem virtuellen UART-Port kann man z.B. per Hyperterminal eine Verbindung mit 2400 Baud, 8N1, ohne Flusskontrolle, einstellen und sieht dann Großbuchstaben für die Temperatur. Ist nicht sehr genau, bei mir kamen lauter N's, was wohl 78°F sein soll, also 25,5°C. Hier war es allerdings 20°C.

Alternativ zu Hyperterminal kann man auch http://processing.org laden und die GUI-Anwendung von der Wiki-Seite ausprobieren. Wenn man allerdings noch andere virtuelle COM-Ports wie Bluetooth oder USB-COM Port Adapter hat, dann kann die Enumeration der COM-Ports schon eine Minute dauern. Das ist aber kein Fehler von Processing, sondern genauso wie die fehlende Überprüfung bei falscher COM-Port Indexeingabe nur ein Fehler des schlecht geschriebenen Processing-Programms, was die Enumeration auch noch mehrfach durchführt und auch sonst so aussieht, als wäre es von einem Programmieranfänger geschrieben. Processing ist an sich gut geeignet, um so kleine Tests mal eben zu programmieren. Hier ein leicht verbessertes Programm, aber mit festem COM-Port und Umrechnung in °C:

import processing.serial.*;

int comPort = 15;
int screenWidth = 400;
int screenHeight = 200;
int fontSize = 24;

color backColor = color(70);
int dataRead;
Serial myPort;

void setup()
{
  size(screenWidth, screenHeight);
  smooth();
  PFont fontA = loadFont("CourierNew36.vlw");
  textAlign(CENTER, CENTER);
  myPort = new Serial(this, "COM" + comPort, 2400);
  background(backColor);
  stroke(255);
  textFont(fontA, fontSize);
}

void draw()
{
  int dataRead = myPort.read();
  if(dataRead != -1){
    background(backColor);
    stroke(255);
    float gradFarenheit = dataRead;
    float gradCelsius = float(round(10 * (gradFarenheit - 32) * 5 / 9)) / 10;
    String temp = "Temperature: " + gradCelsius + " °C";
    text(temp, 0, 0, screenWidth, screenHeight);
  }
}

Und so sieht es dann aus:

Die Hardware

Ausgeliefert wird das LaunchPad mit dem MSP430G2231 im Sockel eingesteckt. Die zweite 2 nach dem G bedeutet, daß es sich um eine Version mit 2 kB Flash ist. Das G bedeutet für diesen Typ, daß es aus der "Value"-Serie ist, was eine abgespeckte Version der "F"-Varianten ist. Z.B. bieten die G-Chips keine Möglichkeit am Xin/Xout-Pin einen Quarz im MHz-Bereich anzuschließen, sondern nur Uhrenquarze im kHz-Bereich. Allerdings kann die CPU mit dem internen DCO dennoch im MHz-Bereich getaktet werden.

Auf der Wiki-Seite gibt es einen Schaltplan für Eagle, oder auch als PDF im User's Guide enthalten. Die "Emulator" Seite, also der MSP430-Chips der zur Programmierung und Debugging des Zielchips eingesetzt wird, ist der MSP430F1612. Das ist ein recht großer MSP430, der sogar in 1.000'er Stückzahlen bei Digikey noch knapp 10 Euro kostet. Gibt auch schon Leute, die den umprogrammiert haben. Schade, daß das scheinbar nicht offiziell von TI unterstützt wird.

Hello World

Fangen wir aber erstmal mit dem bestimmungsgemäßen Betrieb an, also eine Hello-World Anwendung basteln. Auf der Wiki-Seite ist eine Anleitung für den CCS. Mit der IAR IDE geht es aber ähnlich einfach. Zunächst unter Project->Create New Project ein MSP430 C Projekt anlegen:

Es werden dann die Projektdateien angelegt, mit einer main.c, in der nur der Watchdog ausgeschaltet wird.

Unter "Project->Options->General Options" gibt es dann ein "Target"-Karteikarte, bei der man unter "Device" den MSP430G2231 auswählen kann. Schließlich muß man noch in den Options unter "Debugger" als "Driver" von "Simulator" auf "FET Debugger" umstellen. Hier eine Beispielanwendung für main.c:

#include "msp430x20x2.h"

#define RED_LED   BIT0
#define GREEN_LED BIT6
#define LED_DIR   P1DIR
#define LED_OUT   P1OUT

#define BUTTON      BIT3
#define BUTTON_OUT  P1OUT
#define BUTTON_DIR  P1DIR
#define BUTTON_IN   P1IN
#define BUTTON_REN  P1REN

void waitAndTestButton(void)
{
  int i;
  for (i = 0; i < 10000; i++) {
    if (BUTTON_IN & BUTTON) {
      LED_OUT &= ~GREEN_LED;
    } else {
      LED_OUT |= GREEN_LED;
    }
  }
}

int main(void)
{
  // Watchdog ausschalten
  WDTCTL = WDTPW + WDTHOLD;

  // LED Pins auf Ausgang setzen
  LED_DIR |= RED_LED | GREEN_LED;
  
  // Button-Eingang konfigurieren
  BUTTON_DIR &= ~BUTTON;
  BUTTON_REN |= BUTTON;

  // rote LED blinken lassen und grüne LED per Button anschaltbar machen
  while (1) {
    LED_OUT &= ~RED_LED;
    waitAndTestButton();
    LED_OUT |= RED_LED;
    waitAndTestButton();
  }
}

Ich konnte es zwar mit "Project->Download->Download active application" auf den Chip brennen, lief dann aber erst nachdem ich die Stromversorgung per USB-Stecker ziehne unterbrochen hatte und wieder eingesteckt. Per "Download and debug" geht aber zumindest der Debugger und dann per "Go" kann man die Anwendung auch ohne Ein-/Ausstecken ausprobieren. "Stop Debugging" beendet dann den Debugmodus, die Anwendung auf dem Microcontroller läuft aber weiter.

Nützlich ist noch in den Project Options unter Linker->List die Option "Generate linker listing" anzuklicken. Man bekommt dann links im Workspace unter "Output" eine neue map-Datei, in der man z.B. für das Programm oben sehen kann, daß das 66 Bytes im Flash verbraucht. Stellt man es auf "Release" um (man muß dann alle Options neu einstellen, also auch das Target-Device), dann braucht es nur noch 50 Bytes. Flashen konnte ich die aber nicht so ohne weiteres, denn es kam die Fehlermeldung "Failed to load flash loader". Man kann es aber in den Linker Options wieder auf Debug-Format umstellen, was scheinbar nichts an der Länge des zu flashenden Programms ändert, und dann nach dem oben beschriebenen Verfahren per "Download and debug" flashen.

Voltmeter Anwendung

Als eine etwas komplexere Anwendung möchte ich ein Voltmeter bauen, was Spannungen im Bereich von 0 V bis 10 V misst und die mit zwei Stellen Genauigkeit auf dem seriellen Port ausgibt, mit Zeitstempel, jede Sekunde ein Wert. Ich habe mir dazu zunächst mal das Beispielprogramm angesehen, was vorinstalliert ist auf dem Microcontroller und für das es den Quelltext auf der Wiki-Seite gibt. Ist aber kein guter Anfang, da zum einen alles in einer Datei steht, also Steuerungslogik und hardwareabhängige Implementierungen bunt gemischt, sodaß das Verständnis schwieriger wird, und es außerdem noch schlecht programmiert ist. Z.B. ist die Anweisung "while (CCR0 != TAR) CCR0 = TAR;" unsauber, da die bei starker Compileroptmierung dazu führt, daß das in einer Endlosschleife ausgeführt wird, wie im Web nachzulesen. Ist auch gar nicht nötig, solche Tricks anzuwenden, da man einfach den Zähler stoppen und zurücksetzen kann, indem man eine 0 ins CCR0-Register schreibt, und ihn dann wieder von 0 aus bis zum Compare Wert laufen lassen kann, indem man den Compare-Wert danach ins CCR0-Register schreibt. Trickreich ist allerdings die Verwendung von OUTMOD0 und OUTMOD2, sodaß der TXD-Pin direkt vom Capture/Compare Event des Timers umgeschaltet wird, was zu sehr niedrigem Jitter führt. Nicht daß das notwendig wäre bei dieser Anwendung und ein Neuling wird sowas wohl auch nicht auf Anhieb verstehen, daher meiner Meinung nach eher nicht für ein Beispielprogramm geeignet.

Außerdem störend aufgefallen ist noch die teilweise inkonsistente Benennung der Register und Interruptvektoren. Z.B. steht im Datenblatt Timer_A2, TACCR0, CCIFG. In den Headern heisst es aber TIMERA0_VECTOR, bezeichnet aber beides denselben Vektor, 0xFFF2. Und für einige Register scheint es zwei Schreibweisen zu geben, z.B. CCR0 und TACCR0. Beides ist in den Headerdateien definiert und ist dasselbe Register, aber im Datenblatt ist nur TACCR0 zu finden. Leider verwendet das Beispielprogramm von TI auch die Schreibweise mit CCR0. Ich habe die andere Schreibweise verwendet, sodaß man es leichter in den Datenblättern wiederfindet.

Habe es also selbst von Grund auf neu programmiert, mit Trennung der Anwendungslogik und der Hardware in mehreren Dateien. Basis ist ein C++ Projekt, da das, auch ohne daß man Klassen verwendet, die Syntax teilweise vereinfacht. Hier das leicht verständliche Hauptprogramm mit der Programmlogik:

#include <stdint.h>
#include "hardware.h"
#include "utilities.h"

static uint16_t g_timestamp = 0;
static uint16_t g_crystalCount = 0;
static bool g_waitForTimestamp = false;

// wird alle 14 Ticks des 32768 Hz Uhrenquarzes aufgerufen,
// also mit ca. 2340,57 Hz
void onInterrupt()
{
  // Takte des Uhrenquarzes zählen
  g_crystalCount += 14;
  
  // grüne LED mit 1 Hz blinken lassen
  setGreenLed(g_crystalCount > 16384);
  
  // Timestamp nach einer Sekunde erhöhen
  if (g_crystalCount > 32768) {
      g_crystalCount -= 32768;
      if (g_waitForTimestamp) {
        g_waitForTimestamp = false;
        wakeup();
      }
      g_timestamp++;
  }
}

int main(void)
{
  // Hardware initialisieren
  initHardware();

  // Endlosschleife
  while (true) {
    // ADC-Wert holen
    uint16_t adc = getAdc();
    
    // in Volt umrechnen, wie es am Eingang des ADCs anliegt
    float volt = 1.5f / 1024.0f * float(adc);
    
    // Korrektur für Spannungsteiler und andere Ungenauigkeiten
    volt *= 9.1324;
    
    // bis zum nächsten Timestamp warten
    g_waitForTimestamp = true;
    lpm0Halt();
    
    // Timestamp und ungerechnete Spannung ausgeben
    printInt(&sendByte, g_timestamp);
    sendByte(' ');
    printFloat(&sendByte, volt);
    
    // Zeilenvorschub übertragen
    sendByte(13);
    sendByte(10);
  }
}

Der Faktor 9,1324 ist dabei ausgemessen: Zunächst als Faktor 10 eintragen, für eine erste Näherung des Spannungsteilers. Wenn dann x V angelegt wird (z.B. 10 V) und es wird y V in Hypertermnial angezeigt (z.B. 10,95), dann ist der einzutragende Faktor 10*x/y (in meinem Aufbau also 10*10/10,95=9,1324).

Das gesamte Projekt: voltmeter.zip

Alle hardwarespezifischen Dinge sind in hardware.cpp implementiert. So ist es relativ leicht, das Programm selbst auf einen anderen Microcontroller zu portieren oder auch die Anwendungslogik für einen anderen Anwendungsfall umzuschreiben. Ist alles gut dokumentiert, sodaß es selbsterklärend sein sollte.

In utilities sind zwei Funktionen, die einen Funktionspointer als Argument bekommen, um eine Zahl auszugeben. Dadurch spart man sich einen Zwischenspeicher, aber die Zahlausgaberoutinen selbst sind dennoch portabel, da sie nicht den konkreten Namen der Routine kennen müssen, die ein Zeichen auf dem seriellen Port ausgibt. Das Konzept ist als higher order function bekannt.

Eine weitere Besonderheit sind die Low-Power Modi vom MSP430. Sobald man die aktiviert, bleibt die CPU stehen und läuft erst wieder beim nächsten Interrupt weiter. Dadurch kann man Strom sparen, wie es beim Übertragen der seriellen Daten geschieht und beim Warten auf das Ergebnis des AD-Wandlers. Beim Aufruf eines Interrupts wird dabei das Status-Register auf dem Stack gerettet, wo der Stromsparmodus vermerkt ist. Beim Verlassen der Interruptroutine wird dieses Register wiederhergestellt, sodaß die CPU dann normalerweise wieder anhält. Per speziellem Compiler-Kommando (__bic_SR_register_on_exit(CPUOFF)) kann man dieses Register auf dem Stack patchen, sodaß beim Verlassen des Interrupts dann die CPU dort weiterläuft, wo sie zuletzt anhielt. Ich habe das im Prinzip prozessorunabhängig per lpm0Halt() und wakeup() in hardware.cpp gekapselt.

Voltmeter Hardware

Damit das Programm läuft, brauchen wir aber noch etwas Zusatzhardware. Um eigene Schaltungen aufzubauen, habe ich auf eine Platine die Buchsenleisten eingelötet und auf dem LaunchPad die Stiftleisten. Am besten geht sowas, indem man die Stiftleisten zunächst in ein Steckboard einsteckt, damit die genau ausgerichtet sind und später auch in die Buchsenleisten ohne verkanten passen. Links noch ein paar Stiftleisten, die nicht angelötet werden, nur damit es stabiler aufliegt:

Man kann dann die Platine auflegen und problemlos anlöten:

Danach dann die Buchsenleisten ins LaunchPad einstecken, die Platine aufstecken und das Ganze umdrehen, zum anlöten:

Man kann das dann danach wieder abziehen, wenn man z.B. den Microcontroller tauschen will.

Schließlich noch den Uhrenquarz auflöten:

Besonders um das Quarzgehäuse an dem großen Pad anzulöten braucht man schon einen leistungsfähigen geregelten Lötkolben und eine dünne Spitze für die beiden Anschlüsse.

Auf der Platine ist folgende kleine Schaltung zum Schutz des AD-Wandler Eingangs eingebaut, die die Eingangsspannung auf 1/10 herunterteilt und zu hohe positive oder negative Spannungen per Diode kurzschließt:

Der Kondensator wirkt zusammen mit R1 bis R10 als Tiefpassfilter. Fertig aufgebaut sieht es dann so aus:

Die Widerstände und der Kondensator sind in 0805 SMD-Bauform. Passt sehr gut zwischen die Lötaugen der Platine und lässt sich noch relativ leicht von Hand einlöten.

Damit kann dann die Messung beginnen. Beispielausgabe in Hyperterminal:

1 10.00
2 9.99
3 9.99
4 10.00
5 10.01
6 10.00
7 10.00
8 10.01
9 10.03
10 10.00
11 10.00

Man kann Spannungen bis 13,6 V damit messen. Nach der Kalibrierung ist die Genauigkeit im Bereich von 0 V bis 10 V besser als 50 mV bei Raumtemperatur. Aber auch wenn man den Microcontroller gut mit Kältespray bespüht, steigt der Messwert um maximal 0,1 V an.

Mit dem Programm ist der Microcontroller allerdings auch schon fast voll im Release-Modus und im Debug-Modus kann man es gar nicht mehr compilieren, da fehlen dann 31 Byte. Das sagt die MAP-Datei für den Release-Modus:

1 994 bytes of CODE memory
59 bytes of DATA memory (+ 20 absolute )

Man könnte noch einiges sparen, da die Floating-Point Berechnungen recht viel Platz brauchen, aber allzuviel Logik kann man in den 2 k nicht hineinbekommen. Auch das nur ein Timer verfügbar ist und keine PLL, um flexibler genauere interne höhere Frequenzen zu generieren kann für manche Anwendungen ein Ausschlußgrund sein. Aufpassen muß man auch auf den Stack, der bei der Defaulteinstellung auf 50 Bytes steht. Für kleinere Anwendungen ist das aber alles kein Problem und der Microcontroller gut verwendbar. Mit den verschiedenen Stromsparmodi kann er auch längere Zeit per Batterie betrieben werden.


30. Dezember 2010, Frank Buß