Tux trifft MSP430-Launchpad
Aus BraLUG-Wiki
(→MSP430G2553) |
|||
(90 dazwischenliegende Versionen von einem Benutzer werden nicht angezeigt) | |||
Zeile 7: | Zeile 7: | ||
=Warum ein MSP430-Launchpad?= | =Warum ein MSP430-Launchpad?= | ||
+ | Bisher waren die [http://www.atmel.com/products/microcontrollers/avr/default.aspx AVRs] von [http://www.atmel.com/ Atmel] die Favoriten für meine Mikrocontroller-Projekte. Für das Projekt [[Scopeclock|"Scopeclock"]] scheint allerdings die Performance dieser MCUs nicht mehr auszureichen. Bei der Suche nach Alternativen ist mir das [[Tux fliegt zu den Sternen|Stellaris-Launchpad]] in die Finger geraten. Wenn wir dann aber schon bei Mikrocontrollern der Firma [http://www.ti.com/ TI] sind, können wir uns zuerst auch mal mit dem [http://www.ti.com/ww/en/launchpad/msp430_head.html?247SEM MSP430-Launchpad] beschäftigen. | ||
+ | |||
+ | In der Folge soll ein wenig die Hardware des MSP430-Launchpads an Hand von einigen Software-Experimenten vorgestellt werden, | ||
+ | |||
+ | ...have fun! | ||
=Hardware= | =Hardware= | ||
==MSP430== | ==MSP430== | ||
==MSP430-Launchpad== | ==MSP430-Launchpad== | ||
+ | Das [http://www.ti.com/lit/ug/slau318d/slau318d.pdf MSP-EXP430G2 LaunchPad] vom TI bietet ideale Moglichkeiten für den Einstieg in die MSP430-Welt. Kauft man sich dieses Board für wenig Geld (je nach dem wo, weit unter 10€), findet man folgendes in der kleinen Schachtel: | ||
+ | * das Launchpad-Board selbst | ||
+ | * ein Mini-USB-B Kabel | ||
+ | * zwei MSP430-MCUs (bei Launchpad Rev. 1.5: [[Tux trifft MSP430-Launchpad#MSP430G2553|MSP430G2553]], [[Tux trifft MSP430-Launchpad#MSP430G2452|MSP430G2452]]) | ||
+ | * einen 32,768kHz Uhrenquarz (damit kann man das Board entsprechend nachbestücken, siehe Dokumentation) | ||
+ | * zwei 10-polige Stiftleisen, eine kleine Anleitung und ein paar Sticker | ||
+ | |||
+ | |||
+ | Das Launchpad-Board ist mit allem ausgerüstet, was man für die ersten Experimente benötigt: | ||
+ | * ein 20-pin DIP Sockel, in den alle [http://processors.wiki.ti.com/index.php/MSP-EXP430G2_Compatible_MSP430_Devices MSP430G2xxx mit 14 oder 20 Pins] passen sollten (sämtliche Pins sind auf Stiftleisten herausgeführt) | ||
+ | * zwei "frei programmierbare" LEDs | ||
+ | * ein "frei programmierbarer" Taster | ||
+ | * ein Reset-Taster | ||
+ | * ein Programmier- und Debugging-Interface (cool, innerhalb der Atmel-Welt muss man sich soetwas für viel Geld dazukaufen!) | ||
+ | * ein paar Jumper zur Hardware-Konfiguration des Boards (sind beschriftet bzw. auch in der entsprechenden Dokumentation beschrieben) | ||
+ | * ein USB-Anschluß | ||
+ | |||
+ | |||
+ | Auf dem mitgelieferten MSP430G2553 ist ein [http://www.ti.com/lit/zip/slac435 Beispielprogramm] vorinstalliert (blinkende LEDs, Abfrage des internen Temperatursensors usw.), aber wir wollen ja selbst ein paar Programme schreiben... Wenn man mal die "Windowslastigkeit" aus acht läßt, vermittelt dieser kleine [http://software-dl.ti.com/trainingTTO/trainingTTO_public_sw/MSP430_LaunchPad_Workshop/LaunchPad.pdf Workshop] einige Grundlagen zur Programmierung von MSP430-MCUs. | ||
+ | |||
+ | |||
+ | Das Lauchpad-Board ist mit sogenannten [http://processors.wiki.ti.com/index.php/BoosterPacks BoosterPacks] aufrüstbar. Das Konzept ist mit den [http://arduino.cc/en/Main/ArduinoShields Arduino-Shields] vergleichbar. | ||
+ | |||
+ | |||
+ | ===MSP430G2452=== | ||
+ | [[Bild:Msp430g2452.png|thumb|250px|Pin-Belegung MSP430G2452]] | ||
+ | [http://www.ti.com/lit/ds/symlink/msp430g2452.pdf Datenblatt MSP430G2452] | ||
+ | |||
+ | Wichtigste Hardwareeigenschaften: | ||
+ | * 16-Bit RISC CPU | ||
+ | * Versorgungsspannung: 1,8V ... 3,6V | ||
+ | * [[Mikrocontroller stromsparend programmieren|Ultra-low Power Design]] | ||
+ | * CPU-Takt bis 16MHz intern konfigurierbar | ||
+ | * 8kByte Flash | ||
+ | * 256Byte RAM | ||
+ | * 16 I/O-Ports | ||
+ | * 1 16-Bit-Timer, | ||
+ | * WatchDog-Timer | ||
+ | * 1x USI (I2C/SPI) | ||
+ | * 8 Kanäle 10-bit ADC | ||
+ | |||
+ | ===MSP430G2553=== | ||
+ | [[Bild:Msp430g2553.png|thumb|250px|Pin-Belegung MSP430G2553]] | ||
+ | [http://www.ti.com/lit/ds/symlink/msp430g2553.pdf Datenblatt MSP430G2553] | ||
+ | |||
+ | Wichtigste Hardwareeigenschaften: | ||
+ | * 16-Bit RISC CPU | ||
+ | * Versorgungsspannung: 1,8V ... 3,6V | ||
+ | * [[Mikrocontroller stromsparend programmieren|Ultra-low Power Design]] | ||
+ | * CPU-Takt bis 16MHz intern konfigurierbar | ||
+ | * 16kByte Flash | ||
+ | * 512Byte RAM | ||
+ | * 16 I/O-Ports | ||
+ | * 2 16-Bit-Timer | ||
+ | * WatchDog-Timer | ||
+ | * 1x USCI (I2C/SPI/UART/IrDA) | ||
+ | * 8 Kanäle 10-bit ADC | ||
=Toolchain= | =Toolchain= | ||
+ | |||
+ | Von TI werden für die MSP430-Serie (und damit auch für das MSP430-Launchpad) diverse Entwicklungsumgebungen empfohlen und angeboten. Diese sind allerdings ausschließlich nur für Windows-Betriebssysteme verfügbar und in den kostenlosen Versionen im Funktionsumfang teilweise stark beschränkt... | ||
+ | |||
+ | Wir wollen uns natürlich auf die Linux-Plattform konzentrieren! Dort sind leistungsfähige (und 100% freie) Alternativen verfügbar. | ||
+ | |||
+ | ==Die üblichen Kommandozeilen-Tools...== | ||
+ | Folgende Pakete sind Bestandteil vieler Debian-Derivate (z.B. auch Ubuntu) und bilden die notwendige Entwicklungsumgebung für MSP430-MCUs unter Linux: | ||
+ | * binutils-msp430 | ||
+ | * gcc-msp430 | ||
+ | * gdb-msp430 | ||
+ | * msp430-libc | ||
+ | * msp430mcu | ||
+ | * mspdebug | ||
+ | |||
+ | Dazu kommt natürlich noch ein geeigneter Texteditor zum Erstellen der Quelltext-Dateien. | ||
+ | |||
+ | ==Energia== | ||
+ | [[Bild:Energia.png|thumb|200px|Energia-Oberfläche]] | ||
+ | Wer von der Arduino-Fraktion kommt bzw. Mikrocontroller-Einsteiger ist, wird vielleicht Gefallen an [http://energia.nu/ Energia], als "Arduino-IDE-ähnliche" Toolchain finden. Die Programmierung erfolgt mit einer C/C++-ähnlichen Sprache. Für Einsteiger vorteilhaft ist, dass hardwarenahe Programmierung in Standard-Funktionen des Sprachumfangs gekapselt sind. Zum Lernen ganz gut, aber irgendwann sollte man native auf C-(bzw. ASM-)Programme umschwenken...! | ||
+ | |||
+ | Folgende Boards werden durch Energia in der derzeitigen Stable-Version unterstützt: | ||
+ | * [http://energia.nu/Guide_MSP430LaunchPad.html MSP430 LaunchPad (mit MSP430G2231, MSP430G2452 und MSP430G2553)] | ||
+ | * [http://energia.nu/Guide_MSP430FraunchPad.html MSP430 FraunchPad] | ||
+ | * [http://energia.nu/Guide_StellarisLaunchPad.html Stellaris LaunchPad] | ||
+ | |||
+ | ''(Anmerkung zum Stellaris LaunchPad: für einen erfolgreichen Upload der Programme in die MCU via Energia muss (zumindestens bis Version 0101E0009) das Tool lm4flash im entsprechenden Energia-Verzeichnis mit einem, aus den [https://github.com/utzig/lm4tools Originalquellen] erzeugtes, Binary ausgetauscht werden)'' | ||
+ | |||
+ | Ein [[Tux_trifft_MSP430-Launchpad#.22Hello_World.22|blinkendes "Hello World"]] würde mit Energia ungefähr so aussehen: | ||
+ | <pre> | ||
+ | // | ||
+ | // "Hello World" --> rote LED blinkt im Sekundentakt | ||
+ | // | ||
+ | |||
+ | // Hardware initialisieren | ||
+ | void setup() { | ||
+ | pinMode(RED_LED, OUTPUT); // LED-Pin alös Output definieren | ||
+ | } | ||
+ | |||
+ | // "Hauptendlosschleife" (hier springt MCU nach Initialisierung rein) | ||
+ | void loop() { | ||
+ | digitalWrite(RED_LED, HIGH); // LED an | ||
+ | delay(1000); // eine Sekunde warten | ||
+ | digitalWrite(RED_LED, LOW); // LED aus | ||
+ | delay(1000); // eine Sekunde warten | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Es gibt ein gutes [http://forum.43oh.com/forum/28-energia/ Forum], welches sich mit Energia beschäftigt. Hier sind auch diverse [http://forum.43oh.com/forum/30-energia-libraries/ Erweiterungs-Bibliotheken] zu finden. | ||
="Hello World"= | ="Hello World"= | ||
+ | |||
+ | In der Folge soll kurz beschrieben werden, wie man ein (einfaches) Programm für einen MSP430-Launchpad übersetzt und auf die MCU überträgt. Weiterhin wird kurz aufgezeigt, wie man mit dem Debugger direkt auf der MCU arbeiten kann. | ||
+ | |||
==Das Programm== | ==Das Programm== | ||
− | == | + | |
− | == | + | Das erste Programm ist immer ein "Hello World". Bei einem Mikrocontroller bietet es sich dazu an, ein paar Ausgänge zyklisch ein- und auszuschalten. Hier der entsprechende C-Quelltext, mit dem die beiden, auf dem Launchpad vorhandenen LEDs als Wechselblinker zyklisch angesteuert werden: |
+ | |||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED_RED BIT0 // rote LED an PIN0 | ||
+ | #define LED_GREEN BIT6 // gruene Led an PIN6 | ||
+ | |||
+ | //*************************************** | ||
+ | void delay_ms(unsigned int ms){ | ||
+ | while(ms--){ | ||
+ | __delay_cycles(1000); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void){ | ||
+ | WDTCTL = WDTPW + WDTHOLD; // watchdog ausschalten | ||
+ | P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge | ||
+ | P1OUT = LED_GREEN; // gruene LED ein | ||
+ | while(1) { // Enlosschleife | ||
+ | P1OUT ^= LED_RED + LED_GREEN; // LEDs toggle | ||
+ | delay_ms(500); // 500ms Pause | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==Programm übersetzen und auf MCU übertragen== | ||
+ | |||
+ | C-Quelltext übersetzen, wobei der Code für einen MSP430G2452 erzeugt wird: | ||
+ | <pre> | ||
+ | msp430-gcc -mmcu=msp430g2452 -o blink.elf blink.c | ||
+ | </pre> | ||
+ | |||
+ | Die Übertragung auf den Mikrocontroller erfolgt mit Hilfe des Kommandozeilen-Tools mspdebug: | ||
+ | <pre> | ||
+ | > mspdebug rf2500 | ||
+ | ... | ||
+ | |||
+ | (msdebug) prog blink.elf | ||
+ | Erasing... | ||
+ | Programming... | ||
+ | Writing 186 bytes to e000 [section: .text]... | ||
+ | Writing 32 bytes to ffe0 [section: .vectors]... | ||
+ | Done, 218 bytes written | ||
+ | </pre> | ||
+ | |||
+ | Innerhalb der mspdebug-Shell kann das Programm mit dem Befehl run gestartet werden: | ||
+ | <pre> | ||
+ | (mspdebug) run | ||
+ | Running. Press Ctrl+C to interrupt... | ||
+ | </pre> | ||
+ | |||
+ | Natürlich läuft das Programm auch ohne mspdebug. Dazu beendet man das Tool mit dem Befehl exit. | ||
+ | |||
+ | |||
+ | Da man obige Befehlsfolgen wahrscheinlich nicht immer wieder neu eingeben möchte, hier ein entsprechendes Makefile, in dem alle notwendigen Aktionen zusammengefasst sind: | ||
+ | <pre> | ||
+ | PROJ=blink | ||
+ | |||
+ | CC=msp430-gcc | ||
+ | MCU=msp430g2452 | ||
+ | CFLAGS=-Os -g -Wall -mmcu=$(MCU) | ||
+ | LDFLAGS=-g -mmcu=$(MCU) | ||
+ | |||
+ | OBJS=$(PROJ).o | ||
+ | |||
+ | all:$(OBJS) | ||
+ | $(CC) $(LDFLAGS) -o $(PROJ).elf $(OBJS) | ||
+ | msp430-size $(PROJ).elf | ||
+ | |||
+ | clean: | ||
+ | rm -fr $(PROJ).elf $(OBJS) | ||
+ | |||
+ | flash: | ||
+ | mspdebug rf2500 'erase' 'load $(PROJ).elf' 'exit' | ||
+ | </pre> | ||
+ | |||
==Debuggen== | ==Debuggen== | ||
+ | ===Debugproxy starten=== | ||
+ | Debugproxy, über man sich in der Folge mit dem Target verbindet, auf Port 2000 starten: | ||
+ | <pre> | ||
+ | > mspdebug rf2500 | ||
+ | ... | ||
− | = | + | (mspdebug) gdb |
+ | Bound to port 2000. Now waiting for connection... | ||
+ | </pre> | ||
+ | |||
+ | ===Kommandozeilen-Debugger msp430-gdb=== | ||
+ | Kommadozeilen-Debugger starten und remote mit dem Debugproxy verbinden: | ||
+ | <pre> | ||
+ | > msp430-gdb blink.elf | ||
+ | ... | ||
+ | (gdb) target remote localhost:2000 | ||
+ | ... | ||
+ | (gdb) | ||
+ | </pre> | ||
+ | ''(Anmerkung: Inhalt von z.B. P1OUT ausgeben → print/x __P1OUT)'' | ||
+ | |||
+ | ===Grafisches Debugger-Frontend=== | ||
+ | Ich habe neulich [https://projects.gnome.org/nemiver/ Nemiver] als einfaches und schlankes grafisches Debugger-Frontend gefunden. Um damit ein MSP430-Programm über den Debugproxy schrittweise abarbeiten zu können muß folgendes getan werden: | ||
+ | |||
+ | * zu analysierendes [[Tux_trifft_MSP430-Launchpad#Programm_.C3.BCbersetzen_und_auf_MCU_.C3.BCbertragen|Programm (mit Debug-Informationen) übersetzen]] | ||
+ | * [[Tux_trifft_MSP430-Launchpad#Debugproxy_starten|Debugproxy starten]] | ||
+ | * Nemiver starten | ||
+ | ** Menüpunkt "Bearbeitung" → "Einstellungen" → Lasche "Debugger": die ausführbare Datei des msp430gdb als "GDB Binary" angeben | ||
+ | ** Menüpunkt "Datei" → "Mit entferneten Ziel verbinden": ausführbare Datei, den Ort der MSP430-Libs sowie localhost als TCP/IP-Adresse und Port 2000 angeben → "OK" | ||
+ | ** ...Breakpoints setzen und los geht es! (Eventuell am Anfang gemeldete Probleme beim Disassemblieren von crt0.S ignorieren und mit dem Button "Continue or start" bis zum ersten Brekpoint "vorarbeiten".) | ||
+ | ** man sollte sich davor hüten delays (z.B. __delay_cycles()) u.ä. im Schrittbetrieb abzuarbeiten: besser einen Breakpoint danach setzen und mit "Continue" bis dorthin das Programm durchlaufen lassen | ||
+ | ** die weitere Bedienung von Nemiver ist dem entsprechenden Manual zu entnehmen, ist aber auch einigermaßen intuitiv | ||
+ | |||
+ | |||
+ | Andere Frontend (z.B. ddd, KDbg) sind natürlich ebenfalls geeignet, wenn sie sich remote mit einem Target verbinden können und die Auswahl eines alternativen Debuggers (hier msp430-gdb) zulassen. Einfach mal das entsprechende Manual lesen...! | ||
+ | |||
+ | =MSP430-Hardware mit Software erforschen= | ||
+ | ==Port-Input== | ||
+ | ===Taste betätigt?=== | ||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED BIT0 | ||
+ | #define BUTTON BIT3 | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void) | ||
+ | { | ||
+ | WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten | ||
+ | P1DIR = LED; // LED-Pin als Ausgang | ||
+ | P1DIR &= ~BIT3; // Button als Eingang | ||
+ | P1REN |= BUTTON; // Pull-Up-Widerstand einschalten | ||
+ | P1OUT &= ~LED; // LED ausschalten | ||
+ | while (1) { // Endlosschleife | ||
+ | if ((P1IN & BUTTON) == 0) { // Taste gedrueckt? | ||
+ | P1OUT |= LED; // ja --> LED an | ||
+ | } else { | ||
+ | P1OUT &= ~LED; // nein --> LED aus | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ===Ein-/Aus-Schalter mit Entprellung=== | ||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED BIT0 | ||
+ | #define BUTTON BIT3 | ||
+ | #define DEBOUNCE_MS 50 | ||
+ | |||
+ | //*************************************** | ||
+ | void delay_ms(unsigned int ms){ | ||
+ | while(ms--){ | ||
+ | __delay_cycles(1000); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | char button_pressed(void) { | ||
+ | if ((P1IN & BUTTON) == 0) { | ||
+ | delay_ms(DEBOUNCE_MS); | ||
+ | if ((P1IN & BUTTON) == 0) return 1; | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void) | ||
+ | { | ||
+ | char toggle = 0; | ||
+ | WDTCTL = WDTPW + WDTHOLD; | ||
+ | P1DIR = LED; | ||
+ | P1DIR &= ~BIT3; | ||
+ | P1REN |= BUTTON; | ||
+ | P1OUT &= ~LED; | ||
+ | while (1) { | ||
+ | if (button_pressed()) { | ||
+ | if (!toggle) { | ||
+ | P1OUT ^= LED; | ||
+ | toggle = 1; | ||
+ | } | ||
+ | } else { | ||
+ | toggle = 0; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==Watchdog-/Port-Interrupt== | ||
+ | ===Ein-/Aus-Schalter über Port- und Watchdog-Interrupt=== | ||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED BIT0 | ||
+ | #define BUTTON BIT3 | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void) | ||
+ | { | ||
+ | WDTCTL = WDTPW+WDTHOLD; // WatchDog ausschalten | ||
+ | P1DIR = LED; // Ausgang | ||
+ | P1DIR &= ~BUTTON; // Eingang | ||
+ | P1REN |= BUTTON; // Pull-Up ein | ||
+ | P1IES |= BUTTON; // Interrupt Port 1 bei Hight --> Low | ||
+ | P1IFG &= ~BUTTON; // Interrupt-Flag fuer Button-Pin loeschen | ||
+ | P1IE |= BUTTON; // Interrupt fuer Button-Pin einschalten | ||
+ | P1OUT &= ~LED; // LED aus | ||
+ | |||
+ | _BIS_SR(LPM0_bits+GIE); // Low Power Mode 0 und Interrupts generell ein | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | // Interrupt-Routine Port 1 | ||
+ | //*************************************** | ||
+ | #pragma vector=PORT1_VECTOR | ||
+ | __interrupt void PORT1_ISR(void) | ||
+ | { | ||
+ | static unsigned char toggle = 0; | ||
+ | |||
+ | P1IFG &= ~BUTTON; // Interrupt-Flag fuer Button-Pin loeschen | ||
+ | P1IE &= ~BUTTON; // Interrupt fuer Button-Pin ausschalten | ||
+ | WDTCTL = WDT_MDLY_32; // WatchDog-Timer 32ms starten | ||
+ | IFG1 &= ~WDTIFG; // Interrupt-Flag fuer WatchDog loeschen | ||
+ | IE1 |= WDTIE; // WatchDog-Interrupt einschalten | ||
+ | if (!toggle) { | ||
+ | P1OUT ^= LED; // LED umschalten | ||
+ | toggle = 1; | ||
+ | P1IES &= ~BUTTON; // Interrupt Port 1 bei Low --> Hight | ||
+ | } else { | ||
+ | toggle = 0; | ||
+ | P1IES |= BUTTON; // Interrupt Port 1 bei Hight --> Low | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | // Interrupt-Routine Watchdog | ||
+ | //*************************************** | ||
+ | #pragma vector=WDT_VECTOR | ||
+ | __interrupt void WDT_ISR(void) | ||
+ | { | ||
+ | IE1 &= ~WDTIE; // WatchDog-Interrupt ausschalten | ||
+ | IFG1 &= ~WDTIFG; // Interrupt-Flag fuer WatchDog loeschen | ||
+ | WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten | ||
+ | P1IE |= BUTTON; // Interrupt fuer Button-Pin einschalten | ||
+ | } | ||
+ | </pre> | ||
+ | ''Anmerkung: Bei meinem Lauchpad scheint der Taster ab und zu länger wie 32ms zu prellen und damit verschluckt sich die Logik machmal. Eigentlich ist es also sinnvoller, die Entprellung über einen Timer zu realisieren, den man an die spezifischen Hardwaregegebenheiten anpassen kann.'' | ||
+ | |||
+ | ==Timer== | ||
+ | ===Wechselblinker mit Timer=== | ||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED_RED BIT0 // rote LED an PIN0 | ||
+ | #define LED_GREEN BIT6 // gruene Led an PIN6 | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void) | ||
+ | { | ||
+ | WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten | ||
+ | P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge | ||
+ | P1OUT = LED_GREEN; // gruene LED ein | ||
+ | TACTL = TASSEL_2 + MC_2; // Timer A: SMCLCK, Continuous-Mode | ||
+ | CCTL0 = CCIE; // Capture-/Compare-Interrupt ein | ||
+ | _BIS_SR(LPM0_bits+GIE); // Low Power Mode 0 und Interrupts generell ein | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | // Interrupt-Routine TimerA0 | ||
+ | //*************************************** | ||
+ | #pragma vector=TIMER0_A0_VECTOR | ||
+ | __interrupt void Timer_A(void) | ||
+ | { | ||
+ | static unsigned char counter = 0; | ||
+ | counter++; | ||
+ | if (!(counter%4)) // Takt noch ein wenig teilen... | ||
+ | P1OUT ^= LED_RED + LED_GREEN; // LEDs toggle | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ===PWM (LED dimmen)=== | ||
+ | <pre> | ||
+ | #include <msp430.h> | ||
+ | |||
+ | #define LED BIT6; | ||
+ | |||
+ | //*************************************** | ||
+ | int main(void) | ||
+ | { | ||
+ | WDTCTL = WDT_MDLY_32; // Watchdog 32ms | ||
+ | IE1 |= WDTIE; // Watchdog-Interrupt ein | ||
+ | P1DIR |= LED; // LED als Ausgang | ||
+ | P1SEL |= LED; // LED angesteuert via PWM (Timer A0) | ||
+ | TA0CCR0 = 1000; // PWM-Periode | ||
+ | TA0CCR1 = 1; // PWM-Duty-Cycle (initial) | ||
+ | TA0CCTL1 = OUTMOD_7; // PWM-Output-Mode (siehe Datenblatt...) | ||
+ | TA0CTL = TASSEL_2 + MC_1; // Timer A: SMCLK (1MHz) und CCR0 aufwaerts | ||
+ | _BIS_SR(LPM0_bits + GIE); // Low Power Mode 0 und Interrupte ein | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | //*************************************** | ||
+ | // Interrupt-Routine WatchDog | ||
+ | //*************************************** | ||
+ | #pragma vector=WDT_VECTOR | ||
+ | __interrupt void watchdog_timer(void) | ||
+ | { | ||
+ | static int direction = 1; | ||
+ | TA0CCR1 += direction*20; // PWM-Duty-Cycle neu setzen | ||
+ | if( TA0CCR1 > 980 || TA0CCR1 < 20 ) // Richtung umschalten | ||
+ | direction = -direction; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==Serielle Schnittstelle (Uart)== | ||
+ | Um mit dem Controller kommunizieren zu können, macht sich für die Ein- und Ausgabe von Daten eine serielle Schnittstelle ganz gut. Das MSP430-Lauchpad emuliert diese über USB an /dev/ttyACMx (siehe dmesg) mit, hardwarebedingt, maximal 9600 Baud. | ||
+ | |||
+ | Je nach eingesetzter MCU auf dem Launchpad (Rev. 1.5) kann die Art und Weise der Implementierung der seriellen Schnittstelle z.B. aus diesem [http://bralug.de/wiki-common/images/f/f4/Msp430_uart.tar.gz Software-Archive] gewählt werden: | ||
+ | * MSP430G2452 → nur Software-Uart möglich | ||
+ | * MSP430G2553 → Software- oder Hardware-Uart | ||
+ | |||
+ | In der Folge sind ein paar einfache Beispiel für die serielle Schnittstelle in Hardware aufgeführt. | ||
+ | |||
+ | ===Hardware-Uart=== | ||
+ | RXD-/TXD-Jumper auf Launchpad entsprechend setzen (siehe Dokumentation...)! | ||
+ | |||
+ | ====Einfaches serielles Echo (mit Energia-IDE)==== | ||
+ | <pre> | ||
+ | uint8_t rx; | ||
+ | |||
+ | void setup() | ||
+ | { | ||
+ | Serial.begin(9600); // serielle Schnittstelle 9600Baud | ||
+ | Serial.println("*****"); | ||
+ | } | ||
+ | |||
+ | void loop () { | ||
+ | if (Serial.available()) { // Zeichen an serieller Schnittstelle? | ||
+ | rx=Serial.read(); // Zeichen lesen | ||
+ | if (rx==13) { // CR --> NewLine ausgeben | ||
+ | Serial.println(""); | ||
+ | } | ||
+ | Serial.write(rx); // Zeichen selbst ausgeben | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ====Serielle Schnittstelle mit Empfangspuffer==== | ||
+ | <pre> | ||
+ | #include "msp430.h" | ||
+ | #include <string.h> | ||
+ | |||
+ | #define LED_RED BIT0 | ||
+ | #define LED_GREEN BIT6 | ||
+ | |||
+ | #define UART_RXBUFLEN 16 // Groesse RX-Puffer | ||
+ | |||
+ | volatile uint8_t uart_rxbuf[UART_RXBUFLEN]; // RX-Puffer | ||
+ | volatile uint8_t uart_rxsize = 0; // momentane Groesse RX-Puffer | ||
+ | |||
+ | // *************************** | ||
+ | void uart_init(void) { | ||
+ | BCSCTL1 = CALBC1_1MHZ; // Set DCO | ||
+ | DCOCTL = CALDCO_1MHZ; | ||
+ | P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD | ||
+ | P1SEL2 = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD | ||
+ | UCA0CTL1 |= UCSSEL_2; // SMCLK | ||
+ | UCA0BR0 = 104; // 1MHz 9600 | ||
+ | UCA0BR1 = 0; // 1MHz 9600 | ||
+ | UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 | ||
+ | UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine** | ||
+ | IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt | ||
+ | } | ||
+ | |||
+ | // *************************** | ||
+ | void uart_putc(uint8_t c) { | ||
+ | while (!(IFG2 & UCA0TXIFG)); // USCI_A0 TX bereit? | ||
+ | UCA0TXBUF = c; // raus damit...! | ||
+ | } | ||
+ | |||
+ | // *************************** | ||
+ | void uart_puts(const char *s) { | ||
+ | while(*s) uart_putc(*s++); | ||
+ | } | ||
+ | |||
+ | // *************************** | ||
+ | uint8_t uart_getc(void) { | ||
+ | static uint8_t uart_rxstart = 0; | ||
+ | uint8_t c; | ||
+ | while (uart_rxsize == 0); // warten bis was im RX-Puffer steht... | ||
+ | c = uart_rxbuf[uart_rxstart++]; // Zeichen zurueckgeben ... | ||
+ | if (uart_rxstart == UART_RXBUFLEN) uart_rxstart = 0; // ... und Puffer-"Zeiger" | ||
+ | uart_rxsize--; // verwalten | ||
+ | return c; | ||
+ | } | ||
+ | |||
+ | |||
+ | // *************************** | ||
+ | #pragma vector=USCIAB0RX_VECTOR | ||
+ | __interrupt void USCI0RX_ISR(void) { // RX-Interrupt | ||
+ | static uint8_t uart_rxstop = 0; | ||
+ | uint8_t c; | ||
+ | P1OUT ^= LED_RED; // Debug... | ||
+ | c = UCA0RXBUF; // empfangenes Zeichen einlesen... | ||
+ | if (uart_rxsize < UART_RXBUFLEN) { // ...und in RX-Puffer schreiben | ||
+ | uart_rxbuf[uart_rxstop++] = c; | ||
+ | if (uart_rxstop >= UART_RXBUFLEN) uart_rxstop = 0; | ||
+ | uart_rxsize++; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // *************************** | ||
+ | int main(void) { | ||
+ | uint8_t c; | ||
+ | WDTCTL = WDTPW + WDTHOLD; | ||
+ | P1DIR = LED_RED | LED_GREEN; // rote und gruene LED als Ausgang | ||
+ | uart_init(); // serielle Schnittstelle initialisieren | ||
+ | __bis_SR_register(GIE); // Interrupts einschalten | ||
+ | uart_puts("UART ready!\n\r"); | ||
+ | while (1) { // Hauptschleife | ||
+ | c = uart_getc(); | ||
+ | uart_putc(c); // Echo | ||
+ | if (c == 'G') P1OUT |= LED_GREEN; | ||
+ | if (c == 'g') P1OUT &= ~LED_GREEN; | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ===Software-Uart=== | ||
+ | Wenn die MCU keine serielle Schnittstelle mit ihrer Hardware unterstützt, braucht man etwas, was das Ganze in Software emuliert. Eine Bibliothek mit Beispiel ist ebenfalls in diesem [http://bralug.de/wiki-common/images/f/f4/Msp430_uart.tar.gz Software-Archiv] zu finden. | ||
+ | |||
+ | RXD-/TXD-Jumper auf Launchpad entsprechend setzen (siehe Dokumentation...)! | ||
+ | |||
+ | ==ADC== | ||
+ | ===Interner Temperatursensor=== | ||
+ | <pre> | ||
+ | #include <stdio.h> | ||
+ | #include "msp430.h" | ||
+ | #include "uart.h" | ||
+ | |||
+ | #define LED_RED BIT0 // rote LED an PIN0 | ||
+ | #define LED_GREEN BIT6 // gruene Led an PIN6 | ||
+ | |||
+ | uint16_t temp, temp_old; | ||
+ | char buffer[6]; | ||
+ | |||
+ | // *************************** | ||
+ | // *************************** | ||
+ | // *************************** | ||
+ | int main(void) { | ||
+ | WDTCTL = WDTPW + WDTHOLD; // Watchdog ausschalten | ||
+ | BCSCTL1 = CALBC1_1MHZ; // MCU-Taktrate | ||
+ | DCOCTL = CALDCO_1MHZ; | ||
+ | P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge | ||
+ | uart_init(9600); // serielle Schnittstelle initialisieren | ||
+ | // ADC konfigurieren | ||
+ | ADC10CTL1 = INCH_10 + ADC10DIV_3; // Temp.Sensor ADC10CLK/4 | ||
+ | ADC10CTL0 = SREF_1 + ADC10SHT_3 + REFON + ADC10ON + ADC10IE; // Referenz etc. | ||
+ | __delay_cycles(1000); // etwas Warten damit sich Referenz einstellt | ||
+ | while (1) { | ||
+ | ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start | ||
+ | __bis_SR_register(CPUOFF + GIE); // LPM0 mit Interrupts einschalten | ||
+ | temp = ADC10MEM; | ||
+ | P1OUT &= ~LED_GREEN; // LEDs je nach Temp.diff setzen | ||
+ | P1OUT &= ~LED_RED; | ||
+ | if (temp<temp_old) { | ||
+ | P1OUT |= LED_GREEN; | ||
+ | } else if (temp>temp_old) | ||
+ | P1OUT |= LED_RED; | ||
+ | uart_printf("%i°C (ADC=%i)\n\r", (((temp - 630) * 761) / 1024), temp); // Formel aus LP-Demo | ||
+ | temp_old = temp; | ||
+ | __delay_cycles(1000000); // 1s warten | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | // *************************** | ||
+ | // ADC10 interrupt service routine | ||
+ | #pragma vector=ADC10_VECTOR | ||
+ | __interrupt void ADC10_ISR (void){ // feuert, wenn ADC-Wandlung fertig | ||
+ | __bic_SR_register_on_exit(CPUOFF); // MCU in aktiven Mode schalten | ||
+ | } | ||
+ | </pre> | ||
+ | [http://bralug.de/wiki-common/images/2/2a/Msp430_adc_temp.tar.gz Vollständiger Quelltext mit verwendeter Uart-Lib und Makefile...] | ||
+ | |||
+ | ===Interner Temperatursensor (mit Energia-IDE)=== | ||
+ | <pre> | ||
+ | #define Vref 1500 // Vref = 1500mV | ||
+ | #define COUNT 16 // Anzahl Teilmessungen | ||
+ | #define LED_DOWN GREEN_LED // Temperatur konstant/sinkt | ||
+ | #define LED_UP RED_LED // Temperatur steigt | ||
+ | |||
+ | uint32_t milli_volt; | ||
+ | uint32_t temperature_cal = 0; | ||
+ | uint32_t temperature = 0; | ||
+ | uint8_t i = 0; | ||
+ | |||
+ | void setup() | ||
+ | { | ||
+ | pinMode(LED_DOWN, OUTPUT); | ||
+ | pinMode(LED_UP, OUTPUT); | ||
+ | analogReference(INTERNAL1V5); // ADC-Referenz 1,5V | ||
+ | analogRead(TEMPSENSOR); // eine Dummy-Messung | ||
+ | Serial.begin(9600); // serielle Schnittstelle 9600Baud | ||
+ | Serial.println("*****"); | ||
+ | } | ||
+ | |||
+ | void loop () { | ||
+ | if (i < COUNT) { | ||
+ | // ...weiter messen, um spaeter Mittelwert berechnen zu koennen | ||
+ | milli_volt = (Vref*(uint32_t)analogRead(TEMPSENSOR))/1024; // Berechnung Spannungswert | ||
+ | temperature += (((milli_volt - 986)*1000)/355) ; // Datenblatt: V=0,00355*T+0,986 | ||
+ | i++; | ||
+ | delay(50); | ||
+ | } else { | ||
+ | // ...Mittelwert berechnen und Folgeaktionen | ||
+ | temperature = temperature/COUNT; | ||
+ | Serial.print(temperature/10); | ||
+ | Serial.print("."); | ||
+ | Serial.print(temperature-((temperature/10)*10)); | ||
+ | Serial.println("°C"); | ||
+ | digitalWrite(LED_DOWN, LOW); | ||
+ | digitalWrite(LED_UP, LOW); | ||
+ | if (temperature > temperature_cal) { // LEDs schalten... | ||
+ | digitalWrite(LED_UP, HIGH); | ||
+ | } else { | ||
+ | digitalWrite(LED_DOWN, HIGH); | ||
+ | } | ||
+ | temperature_cal = temperature; | ||
+ | temperature = 0; | ||
+ | i = 0; | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | =Tipps und Tricks= | ||
+ | ==Zugriff auf /dev/ttyACMx als Nicht-root-Benutzer== | ||
+ | Über /dev/ttyACMx (siehe dsmeg-Meldungen nach Einstöpseln des Launchpads) erfolgt die Kommunikation zwischen Linux-PC und Launchpad. Gibt es keine entsprechende udev-Regel, kann nur root auf diese Schnittstelle zugreifen. Also macht sich eine entsprechende Konfiguration für Nicht-Root-User ganz sinnvoll: | ||
+ | |||
+ | Erzeugung der Datei /etc/udev/rules.d/60-tilaunchpad.rules mit folgendem Inhalt: | ||
+ | <pre> | ||
+ | # Zugriff auf TIs MSP430-Launchpad regeln | ||
+ | |||
+ | SUBSYSTEM=="usb",ATTR{idVendor}=="0451",ATTR{idProduct}=="f432",GROUP="dialout",MODE="666" | ||
+ | </pre> | ||
+ | Statt "dialout" kann auch eine andere Gruppe benutzt werden, der jeweilige Benutzer muss Mitglied dieser Gruppe sein. | ||
+ | |||
+ | Nach dem Restart von udev (Befehl: /etc/init.d/udev restart) kann man auch als Nicht-root zugreifen. | ||
+ | |||
+ | ==Serielle Schnittstelle== | ||
+ | Das MSP430-Launchpad verhält sich (unter Linux) etwas merkwürdig an der seriellen Schnittstelle. Dieser [http://forum.43oh.com/topic/3999-solved-serial-not-working-despite-changing-the-jumpers/ Hinweis/Tipp] schafft, zu mindestens bei mir, Abhilfe...! | ||
=Weiterführende Links= | =Weiterführende Links= | ||
+ | |||
+ | Datenblätter: | ||
+ | * [http://www.ti.com/lit/ug/slau318d/slau318d.pdf MSP-EXP430G2 Launchpad User's Guide] | ||
+ | * [http://www.ti.com/lit/ds/symlink/msp430g2452.pdf MSP430G2x52] | ||
+ | * [http://www.ti.com/lit/ds/symlink/msp430g2453.pdf MSP430G2x53] | ||
+ | |||
+ | Toolchain & IDEs: | ||
+ | * [http://wiki.ubuntuusers.de/MSP430-Toolchain http://wiki.ubuntuusers.de/MSP430-Toolchain] | ||
+ | * [http://recursive-labs.com/static/courses/rl100/samples/mspstart.pdf http://recursive-labs.com/static/courses/rl100/samples/mspstart.pdf] | ||
+ | * [http://www.itopen.it/2013/03/01/msp430-energia-on-linux/ http://www.itopen.it/2013/03/01/msp430-energia-on-linux/] | ||
+ | * [http://launchpadlinux.blogspot.de/2012/10/making-thinks-easier-with-make-and-geany.html http://launchpadlinux.blogspot.de/2012/10/making-thinks-easier-with-make-and-geany.html] | ||
+ | |||
+ | mspdebug: | ||
+ | * [http://manpages.ubuntu.com/manpages/precise/man1/mspdebug.1.html http://manpages.ubuntu.com/manpages/precise/man1/mspdebug.1.html] | ||
+ | |||
+ | MSP430-Basics: | ||
+ | * [http://processors.wiki.ti.com/index.php/Category:MSP430 http://processors.wiki.ti.com/index.php/Category:MSP430] | ||
+ | * [http://dbindner.freeshell.org/msp430/ http://dbindner.freeshell.org/msp430/] | ||
+ | * [http://www.mikrocontroller.net/articles/MSP430_Codebeispiele http://www.mikrocontroller.net/articles/MSP430_Codebeispiele] | ||
+ | * [http://homepages.ius.edu/RWISMAN/C335/HTML/msp430Timer.HTM http://homepages.ius.edu/RWISMAN/C335/HTML/msp430Timer.HTM] | ||
+ | * [http://forum.43oh.com/forum/18-msp430-technical-forums/ http://forum.43oh.com/forum/18-msp430-technical-forums/] | ||
+ | |||
+ | GDB: | ||
+ | * [http://www.cs.umd.edu/~srhuang/teaching/cmsc212/gdb-tutorial-handout.pdf http://www.cs.umd.edu/~srhuang/teaching/cmsc212/gdb-tutorial-handout.pdf] | ||
+ | * [http://www.mail-archive.com/mspgcc-users@lists.sourceforge.net/msg10262.html http://www.mail-archive.com/mspgcc-users@lists.sourceforge.net/msg10262.html] | ||
=Kontakt= | =Kontakt= | ||
[[Benutzer:bergeruw|Uwe]] | [[Benutzer:bergeruw|Uwe]] |
Aktuelle Version vom 3. Januar 2014, 19:51 Uhr
Derzeit noch Baustelle...!
Inhaltsverzeichnis |
[Bearbeiten] Warum ein MSP430-Launchpad?
Bisher waren die AVRs von Atmel die Favoriten für meine Mikrocontroller-Projekte. Für das Projekt "Scopeclock" scheint allerdings die Performance dieser MCUs nicht mehr auszureichen. Bei der Suche nach Alternativen ist mir das Stellaris-Launchpad in die Finger geraten. Wenn wir dann aber schon bei Mikrocontrollern der Firma TI sind, können wir uns zuerst auch mal mit dem MSP430-Launchpad beschäftigen.
In der Folge soll ein wenig die Hardware des MSP430-Launchpads an Hand von einigen Software-Experimenten vorgestellt werden,
...have fun!
[Bearbeiten] Hardware
[Bearbeiten] MSP430
[Bearbeiten] MSP430-Launchpad
Das MSP-EXP430G2 LaunchPad vom TI bietet ideale Moglichkeiten für den Einstieg in die MSP430-Welt. Kauft man sich dieses Board für wenig Geld (je nach dem wo, weit unter 10€), findet man folgendes in der kleinen Schachtel:
- das Launchpad-Board selbst
- ein Mini-USB-B Kabel
- zwei MSP430-MCUs (bei Launchpad Rev. 1.5: MSP430G2553, MSP430G2452)
- einen 32,768kHz Uhrenquarz (damit kann man das Board entsprechend nachbestücken, siehe Dokumentation)
- zwei 10-polige Stiftleisen, eine kleine Anleitung und ein paar Sticker
Das Launchpad-Board ist mit allem ausgerüstet, was man für die ersten Experimente benötigt:
- ein 20-pin DIP Sockel, in den alle MSP430G2xxx mit 14 oder 20 Pins passen sollten (sämtliche Pins sind auf Stiftleisten herausgeführt)
- zwei "frei programmierbare" LEDs
- ein "frei programmierbarer" Taster
- ein Reset-Taster
- ein Programmier- und Debugging-Interface (cool, innerhalb der Atmel-Welt muss man sich soetwas für viel Geld dazukaufen!)
- ein paar Jumper zur Hardware-Konfiguration des Boards (sind beschriftet bzw. auch in der entsprechenden Dokumentation beschrieben)
- ein USB-Anschluß
Auf dem mitgelieferten MSP430G2553 ist ein Beispielprogramm vorinstalliert (blinkende LEDs, Abfrage des internen Temperatursensors usw.), aber wir wollen ja selbst ein paar Programme schreiben... Wenn man mal die "Windowslastigkeit" aus acht läßt, vermittelt dieser kleine Workshop einige Grundlagen zur Programmierung von MSP430-MCUs.
Das Lauchpad-Board ist mit sogenannten BoosterPacks aufrüstbar. Das Konzept ist mit den Arduino-Shields vergleichbar.
[Bearbeiten] MSP430G2452
Wichtigste Hardwareeigenschaften:
- 16-Bit RISC CPU
- Versorgungsspannung: 1,8V ... 3,6V
- Ultra-low Power Design
- CPU-Takt bis 16MHz intern konfigurierbar
- 8kByte Flash
- 256Byte RAM
- 16 I/O-Ports
- 1 16-Bit-Timer,
- WatchDog-Timer
- 1x USI (I2C/SPI)
- 8 Kanäle 10-bit ADC
[Bearbeiten] MSP430G2553
Wichtigste Hardwareeigenschaften:
- 16-Bit RISC CPU
- Versorgungsspannung: 1,8V ... 3,6V
- Ultra-low Power Design
- CPU-Takt bis 16MHz intern konfigurierbar
- 16kByte Flash
- 512Byte RAM
- 16 I/O-Ports
- 2 16-Bit-Timer
- WatchDog-Timer
- 1x USCI (I2C/SPI/UART/IrDA)
- 8 Kanäle 10-bit ADC
[Bearbeiten] Toolchain
Von TI werden für die MSP430-Serie (und damit auch für das MSP430-Launchpad) diverse Entwicklungsumgebungen empfohlen und angeboten. Diese sind allerdings ausschließlich nur für Windows-Betriebssysteme verfügbar und in den kostenlosen Versionen im Funktionsumfang teilweise stark beschränkt...
Wir wollen uns natürlich auf die Linux-Plattform konzentrieren! Dort sind leistungsfähige (und 100% freie) Alternativen verfügbar.
[Bearbeiten] Die üblichen Kommandozeilen-Tools...
Folgende Pakete sind Bestandteil vieler Debian-Derivate (z.B. auch Ubuntu) und bilden die notwendige Entwicklungsumgebung für MSP430-MCUs unter Linux:
- binutils-msp430
- gcc-msp430
- gdb-msp430
- msp430-libc
- msp430mcu
- mspdebug
Dazu kommt natürlich noch ein geeigneter Texteditor zum Erstellen der Quelltext-Dateien.
[Bearbeiten] Energia
Wer von der Arduino-Fraktion kommt bzw. Mikrocontroller-Einsteiger ist, wird vielleicht Gefallen an Energia, als "Arduino-IDE-ähnliche" Toolchain finden. Die Programmierung erfolgt mit einer C/C++-ähnlichen Sprache. Für Einsteiger vorteilhaft ist, dass hardwarenahe Programmierung in Standard-Funktionen des Sprachumfangs gekapselt sind. Zum Lernen ganz gut, aber irgendwann sollte man native auf C-(bzw. ASM-)Programme umschwenken...!
Folgende Boards werden durch Energia in der derzeitigen Stable-Version unterstützt:
- MSP430 LaunchPad (mit MSP430G2231, MSP430G2452 und MSP430G2553)
- MSP430 FraunchPad
- Stellaris LaunchPad
(Anmerkung zum Stellaris LaunchPad: für einen erfolgreichen Upload der Programme in die MCU via Energia muss (zumindestens bis Version 0101E0009) das Tool lm4flash im entsprechenden Energia-Verzeichnis mit einem, aus den Originalquellen erzeugtes, Binary ausgetauscht werden)
Ein blinkendes "Hello World" würde mit Energia ungefähr so aussehen:
// // "Hello World" --> rote LED blinkt im Sekundentakt // // Hardware initialisieren void setup() { pinMode(RED_LED, OUTPUT); // LED-Pin alös Output definieren } // "Hauptendlosschleife" (hier springt MCU nach Initialisierung rein) void loop() { digitalWrite(RED_LED, HIGH); // LED an delay(1000); // eine Sekunde warten digitalWrite(RED_LED, LOW); // LED aus delay(1000); // eine Sekunde warten }
Es gibt ein gutes Forum, welches sich mit Energia beschäftigt. Hier sind auch diverse Erweiterungs-Bibliotheken zu finden.
[Bearbeiten] "Hello World"
In der Folge soll kurz beschrieben werden, wie man ein (einfaches) Programm für einen MSP430-Launchpad übersetzt und auf die MCU überträgt. Weiterhin wird kurz aufgezeigt, wie man mit dem Debugger direkt auf der MCU arbeiten kann.
[Bearbeiten] Das Programm
Das erste Programm ist immer ein "Hello World". Bei einem Mikrocontroller bietet es sich dazu an, ein paar Ausgänge zyklisch ein- und auszuschalten. Hier der entsprechende C-Quelltext, mit dem die beiden, auf dem Launchpad vorhandenen LEDs als Wechselblinker zyklisch angesteuert werden:
#include <msp430.h> #define LED_RED BIT0 // rote LED an PIN0 #define LED_GREEN BIT6 // gruene Led an PIN6 //*************************************** void delay_ms(unsigned int ms){ while(ms--){ __delay_cycles(1000); } } //*************************************** int main(void){ WDTCTL = WDTPW + WDTHOLD; // watchdog ausschalten P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge P1OUT = LED_GREEN; // gruene LED ein while(1) { // Enlosschleife P1OUT ^= LED_RED + LED_GREEN; // LEDs toggle delay_ms(500); // 500ms Pause } }
[Bearbeiten] Programm übersetzen und auf MCU übertragen
C-Quelltext übersetzen, wobei der Code für einen MSP430G2452 erzeugt wird:
msp430-gcc -mmcu=msp430g2452 -o blink.elf blink.c
Die Übertragung auf den Mikrocontroller erfolgt mit Hilfe des Kommandozeilen-Tools mspdebug:
> mspdebug rf2500 ... (msdebug) prog blink.elf Erasing... Programming... Writing 186 bytes to e000 [section: .text]... Writing 32 bytes to ffe0 [section: .vectors]... Done, 218 bytes written
Innerhalb der mspdebug-Shell kann das Programm mit dem Befehl run gestartet werden:
(mspdebug) run Running. Press Ctrl+C to interrupt...
Natürlich läuft das Programm auch ohne mspdebug. Dazu beendet man das Tool mit dem Befehl exit.
Da man obige Befehlsfolgen wahrscheinlich nicht immer wieder neu eingeben möchte, hier ein entsprechendes Makefile, in dem alle notwendigen Aktionen zusammengefasst sind:
PROJ=blink CC=msp430-gcc MCU=msp430g2452 CFLAGS=-Os -g -Wall -mmcu=$(MCU) LDFLAGS=-g -mmcu=$(MCU) OBJS=$(PROJ).o all:$(OBJS) $(CC) $(LDFLAGS) -o $(PROJ).elf $(OBJS) msp430-size $(PROJ).elf clean: rm -fr $(PROJ).elf $(OBJS) flash: mspdebug rf2500 'erase' 'load $(PROJ).elf' 'exit'
[Bearbeiten] Debuggen
[Bearbeiten] Debugproxy starten
Debugproxy, über man sich in der Folge mit dem Target verbindet, auf Port 2000 starten:
> mspdebug rf2500 ... (mspdebug) gdb Bound to port 2000. Now waiting for connection...
[Bearbeiten] Kommandozeilen-Debugger msp430-gdb
Kommadozeilen-Debugger starten und remote mit dem Debugproxy verbinden:
> msp430-gdb blink.elf ... (gdb) target remote localhost:2000 ... (gdb)
(Anmerkung: Inhalt von z.B. P1OUT ausgeben → print/x __P1OUT)
[Bearbeiten] Grafisches Debugger-Frontend
Ich habe neulich Nemiver als einfaches und schlankes grafisches Debugger-Frontend gefunden. Um damit ein MSP430-Programm über den Debugproxy schrittweise abarbeiten zu können muß folgendes getan werden:
- zu analysierendes Programm (mit Debug-Informationen) übersetzen
- Debugproxy starten
- Nemiver starten
- Menüpunkt "Bearbeitung" → "Einstellungen" → Lasche "Debugger": die ausführbare Datei des msp430gdb als "GDB Binary" angeben
- Menüpunkt "Datei" → "Mit entferneten Ziel verbinden": ausführbare Datei, den Ort der MSP430-Libs sowie localhost als TCP/IP-Adresse und Port 2000 angeben → "OK"
- ...Breakpoints setzen und los geht es! (Eventuell am Anfang gemeldete Probleme beim Disassemblieren von crt0.S ignorieren und mit dem Button "Continue or start" bis zum ersten Brekpoint "vorarbeiten".)
- man sollte sich davor hüten delays (z.B. __delay_cycles()) u.ä. im Schrittbetrieb abzuarbeiten: besser einen Breakpoint danach setzen und mit "Continue" bis dorthin das Programm durchlaufen lassen
- die weitere Bedienung von Nemiver ist dem entsprechenden Manual zu entnehmen, ist aber auch einigermaßen intuitiv
Andere Frontend (z.B. ddd, KDbg) sind natürlich ebenfalls geeignet, wenn sie sich remote mit einem Target verbinden können und die Auswahl eines alternativen Debuggers (hier msp430-gdb) zulassen. Einfach mal das entsprechende Manual lesen...!
[Bearbeiten] MSP430-Hardware mit Software erforschen
[Bearbeiten] Port-Input
[Bearbeiten] Taste betätigt?
#include <msp430.h> #define LED BIT0 #define BUTTON BIT3 //*************************************** int main(void) { WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten P1DIR = LED; // LED-Pin als Ausgang P1DIR &= ~BIT3; // Button als Eingang P1REN |= BUTTON; // Pull-Up-Widerstand einschalten P1OUT &= ~LED; // LED ausschalten while (1) { // Endlosschleife if ((P1IN & BUTTON) == 0) { // Taste gedrueckt? P1OUT |= LED; // ja --> LED an } else { P1OUT &= ~LED; // nein --> LED aus } } }
[Bearbeiten] Ein-/Aus-Schalter mit Entprellung
#include <msp430.h> #define LED BIT0 #define BUTTON BIT3 #define DEBOUNCE_MS 50 //*************************************** void delay_ms(unsigned int ms){ while(ms--){ __delay_cycles(1000); } } //*************************************** char button_pressed(void) { if ((P1IN & BUTTON) == 0) { delay_ms(DEBOUNCE_MS); if ((P1IN & BUTTON) == 0) return 1; } return 0; } //*************************************** int main(void) { char toggle = 0; WDTCTL = WDTPW + WDTHOLD; P1DIR = LED; P1DIR &= ~BIT3; P1REN |= BUTTON; P1OUT &= ~LED; while (1) { if (button_pressed()) { if (!toggle) { P1OUT ^= LED; toggle = 1; } } else { toggle = 0; } } }
[Bearbeiten] Watchdog-/Port-Interrupt
[Bearbeiten] Ein-/Aus-Schalter über Port- und Watchdog-Interrupt
#include <msp430.h> #define LED BIT0 #define BUTTON BIT3 //*************************************** int main(void) { WDTCTL = WDTPW+WDTHOLD; // WatchDog ausschalten P1DIR = LED; // Ausgang P1DIR &= ~BUTTON; // Eingang P1REN |= BUTTON; // Pull-Up ein P1IES |= BUTTON; // Interrupt Port 1 bei Hight --> Low P1IFG &= ~BUTTON; // Interrupt-Flag fuer Button-Pin loeschen P1IE |= BUTTON; // Interrupt fuer Button-Pin einschalten P1OUT &= ~LED; // LED aus _BIS_SR(LPM0_bits+GIE); // Low Power Mode 0 und Interrupts generell ein return 0; } //*************************************** // Interrupt-Routine Port 1 //*************************************** #pragma vector=PORT1_VECTOR __interrupt void PORT1_ISR(void) { static unsigned char toggle = 0; P1IFG &= ~BUTTON; // Interrupt-Flag fuer Button-Pin loeschen P1IE &= ~BUTTON; // Interrupt fuer Button-Pin ausschalten WDTCTL = WDT_MDLY_32; // WatchDog-Timer 32ms starten IFG1 &= ~WDTIFG; // Interrupt-Flag fuer WatchDog loeschen IE1 |= WDTIE; // WatchDog-Interrupt einschalten if (!toggle) { P1OUT ^= LED; // LED umschalten toggle = 1; P1IES &= ~BUTTON; // Interrupt Port 1 bei Low --> Hight } else { toggle = 0; P1IES |= BUTTON; // Interrupt Port 1 bei Hight --> Low } } //*************************************** // Interrupt-Routine Watchdog //*************************************** #pragma vector=WDT_VECTOR __interrupt void WDT_ISR(void) { IE1 &= ~WDTIE; // WatchDog-Interrupt ausschalten IFG1 &= ~WDTIFG; // Interrupt-Flag fuer WatchDog loeschen WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten P1IE |= BUTTON; // Interrupt fuer Button-Pin einschalten }
Anmerkung: Bei meinem Lauchpad scheint der Taster ab und zu länger wie 32ms zu prellen und damit verschluckt sich die Logik machmal. Eigentlich ist es also sinnvoller, die Entprellung über einen Timer zu realisieren, den man an die spezifischen Hardwaregegebenheiten anpassen kann.
[Bearbeiten] Timer
[Bearbeiten] Wechselblinker mit Timer
#include <msp430.h> #define LED_RED BIT0 // rote LED an PIN0 #define LED_GREEN BIT6 // gruene Led an PIN6 //*************************************** int main(void) { WDTCTL = WDTPW + WDTHOLD; // WatchDog ausschalten P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge P1OUT = LED_GREEN; // gruene LED ein TACTL = TASSEL_2 + MC_2; // Timer A: SMCLCK, Continuous-Mode CCTL0 = CCIE; // Capture-/Compare-Interrupt ein _BIS_SR(LPM0_bits+GIE); // Low Power Mode 0 und Interrupts generell ein return 0; } //*************************************** // Interrupt-Routine TimerA0 //*************************************** #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A(void) { static unsigned char counter = 0; counter++; if (!(counter%4)) // Takt noch ein wenig teilen... P1OUT ^= LED_RED + LED_GREEN; // LEDs toggle }
[Bearbeiten] PWM (LED dimmen)
#include <msp430.h> #define LED BIT6; //*************************************** int main(void) { WDTCTL = WDT_MDLY_32; // Watchdog 32ms IE1 |= WDTIE; // Watchdog-Interrupt ein P1DIR |= LED; // LED als Ausgang P1SEL |= LED; // LED angesteuert via PWM (Timer A0) TA0CCR0 = 1000; // PWM-Periode TA0CCR1 = 1; // PWM-Duty-Cycle (initial) TA0CCTL1 = OUTMOD_7; // PWM-Output-Mode (siehe Datenblatt...) TA0CTL = TASSEL_2 + MC_1; // Timer A: SMCLK (1MHz) und CCR0 aufwaerts _BIS_SR(LPM0_bits + GIE); // Low Power Mode 0 und Interrupte ein return 0; } //*************************************** // Interrupt-Routine WatchDog //*************************************** #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { static int direction = 1; TA0CCR1 += direction*20; // PWM-Duty-Cycle neu setzen if( TA0CCR1 > 980 || TA0CCR1 < 20 ) // Richtung umschalten direction = -direction; }
[Bearbeiten] Serielle Schnittstelle (Uart)
Um mit dem Controller kommunizieren zu können, macht sich für die Ein- und Ausgabe von Daten eine serielle Schnittstelle ganz gut. Das MSP430-Lauchpad emuliert diese über USB an /dev/ttyACMx (siehe dmesg) mit, hardwarebedingt, maximal 9600 Baud.
Je nach eingesetzter MCU auf dem Launchpad (Rev. 1.5) kann die Art und Weise der Implementierung der seriellen Schnittstelle z.B. aus diesem Software-Archive gewählt werden:
- MSP430G2452 → nur Software-Uart möglich
- MSP430G2553 → Software- oder Hardware-Uart
In der Folge sind ein paar einfache Beispiel für die serielle Schnittstelle in Hardware aufgeführt.
[Bearbeiten] Hardware-Uart
RXD-/TXD-Jumper auf Launchpad entsprechend setzen (siehe Dokumentation...)!
[Bearbeiten] Einfaches serielles Echo (mit Energia-IDE)
uint8_t rx; void setup() { Serial.begin(9600); // serielle Schnittstelle 9600Baud Serial.println("*****"); } void loop () { if (Serial.available()) { // Zeichen an serieller Schnittstelle? rx=Serial.read(); // Zeichen lesen if (rx==13) { // CR --> NewLine ausgeben Serial.println(""); } Serial.write(rx); // Zeichen selbst ausgeben } }
[Bearbeiten] Serielle Schnittstelle mit Empfangspuffer
#include "msp430.h" #include <string.h> #define LED_RED BIT0 #define LED_GREEN BIT6 #define UART_RXBUFLEN 16 // Groesse RX-Puffer volatile uint8_t uart_rxbuf[UART_RXBUFLEN]; // RX-Puffer volatile uint8_t uart_rxsize = 0; // momentane Groesse RX-Puffer // *************************** void uart_init(void) { BCSCTL1 = CALBC1_1MHZ; // Set DCO DCOCTL = CALDCO_1MHZ; P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD P1SEL2 = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD UCA0CTL1 |= UCSSEL_2; // SMCLK UCA0BR0 = 104; // 1MHz 9600 UCA0BR1 = 0; // 1MHz 9600 UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1 UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine** IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt } // *************************** void uart_putc(uint8_t c) { while (!(IFG2 & UCA0TXIFG)); // USCI_A0 TX bereit? UCA0TXBUF = c; // raus damit...! } // *************************** void uart_puts(const char *s) { while(*s) uart_putc(*s++); } // *************************** uint8_t uart_getc(void) { static uint8_t uart_rxstart = 0; uint8_t c; while (uart_rxsize == 0); // warten bis was im RX-Puffer steht... c = uart_rxbuf[uart_rxstart++]; // Zeichen zurueckgeben ... if (uart_rxstart == UART_RXBUFLEN) uart_rxstart = 0; // ... und Puffer-"Zeiger" uart_rxsize--; // verwalten return c; } // *************************** #pragma vector=USCIAB0RX_VECTOR __interrupt void USCI0RX_ISR(void) { // RX-Interrupt static uint8_t uart_rxstop = 0; uint8_t c; P1OUT ^= LED_RED; // Debug... c = UCA0RXBUF; // empfangenes Zeichen einlesen... if (uart_rxsize < UART_RXBUFLEN) { // ...und in RX-Puffer schreiben uart_rxbuf[uart_rxstop++] = c; if (uart_rxstop >= UART_RXBUFLEN) uart_rxstop = 0; uart_rxsize++; } } // *************************** int main(void) { uint8_t c; WDTCTL = WDTPW + WDTHOLD; P1DIR = LED_RED | LED_GREEN; // rote und gruene LED als Ausgang uart_init(); // serielle Schnittstelle initialisieren __bis_SR_register(GIE); // Interrupts einschalten uart_puts("UART ready!\n\r"); while (1) { // Hauptschleife c = uart_getc(); uart_putc(c); // Echo if (c == 'G') P1OUT |= LED_GREEN; if (c == 'g') P1OUT &= ~LED_GREEN; } return 0; }
[Bearbeiten] Software-Uart
Wenn die MCU keine serielle Schnittstelle mit ihrer Hardware unterstützt, braucht man etwas, was das Ganze in Software emuliert. Eine Bibliothek mit Beispiel ist ebenfalls in diesem Software-Archiv zu finden.
RXD-/TXD-Jumper auf Launchpad entsprechend setzen (siehe Dokumentation...)!
[Bearbeiten] ADC
[Bearbeiten] Interner Temperatursensor
#include <stdio.h> #include "msp430.h" #include "uart.h" #define LED_RED BIT0 // rote LED an PIN0 #define LED_GREEN BIT6 // gruene Led an PIN6 uint16_t temp, temp_old; char buffer[6]; // *************************** // *************************** // *************************** int main(void) { WDTCTL = WDTPW + WDTHOLD; // Watchdog ausschalten BCSCTL1 = CALBC1_1MHZ; // MCU-Taktrate DCOCTL = CALDCO_1MHZ; P1DIR = LED_RED | LED_GREEN; // LED-Pins als Ausgaenge uart_init(9600); // serielle Schnittstelle initialisieren // ADC konfigurieren ADC10CTL1 = INCH_10 + ADC10DIV_3; // Temp.Sensor ADC10CLK/4 ADC10CTL0 = SREF_1 + ADC10SHT_3 + REFON + ADC10ON + ADC10IE; // Referenz etc. __delay_cycles(1000); // etwas Warten damit sich Referenz einstellt while (1) { ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start __bis_SR_register(CPUOFF + GIE); // LPM0 mit Interrupts einschalten temp = ADC10MEM; P1OUT &= ~LED_GREEN; // LEDs je nach Temp.diff setzen P1OUT &= ~LED_RED; if (temp<temp_old) { P1OUT |= LED_GREEN; } else if (temp>temp_old) P1OUT |= LED_RED; uart_printf("%i°C (ADC=%i)\n\r", (((temp - 630) * 761) / 1024), temp); // Formel aus LP-Demo temp_old = temp; __delay_cycles(1000000); // 1s warten } return 0; } // *************************** // ADC10 interrupt service routine #pragma vector=ADC10_VECTOR __interrupt void ADC10_ISR (void){ // feuert, wenn ADC-Wandlung fertig __bic_SR_register_on_exit(CPUOFF); // MCU in aktiven Mode schalten }
Vollständiger Quelltext mit verwendeter Uart-Lib und Makefile...
[Bearbeiten] Interner Temperatursensor (mit Energia-IDE)
#define Vref 1500 // Vref = 1500mV #define COUNT 16 // Anzahl Teilmessungen #define LED_DOWN GREEN_LED // Temperatur konstant/sinkt #define LED_UP RED_LED // Temperatur steigt uint32_t milli_volt; uint32_t temperature_cal = 0; uint32_t temperature = 0; uint8_t i = 0; void setup() { pinMode(LED_DOWN, OUTPUT); pinMode(LED_UP, OUTPUT); analogReference(INTERNAL1V5); // ADC-Referenz 1,5V analogRead(TEMPSENSOR); // eine Dummy-Messung Serial.begin(9600); // serielle Schnittstelle 9600Baud Serial.println("*****"); } void loop () { if (i < COUNT) { // ...weiter messen, um spaeter Mittelwert berechnen zu koennen milli_volt = (Vref*(uint32_t)analogRead(TEMPSENSOR))/1024; // Berechnung Spannungswert temperature += (((milli_volt - 986)*1000)/355) ; // Datenblatt: V=0,00355*T+0,986 i++; delay(50); } else { // ...Mittelwert berechnen und Folgeaktionen temperature = temperature/COUNT; Serial.print(temperature/10); Serial.print("."); Serial.print(temperature-((temperature/10)*10)); Serial.println("°C"); digitalWrite(LED_DOWN, LOW); digitalWrite(LED_UP, LOW); if (temperature > temperature_cal) { // LEDs schalten... digitalWrite(LED_UP, HIGH); } else { digitalWrite(LED_DOWN, HIGH); } temperature_cal = temperature; temperature = 0; i = 0; } }
[Bearbeiten] Tipps und Tricks
[Bearbeiten] Zugriff auf /dev/ttyACMx als Nicht-root-Benutzer
Über /dev/ttyACMx (siehe dsmeg-Meldungen nach Einstöpseln des Launchpads) erfolgt die Kommunikation zwischen Linux-PC und Launchpad. Gibt es keine entsprechende udev-Regel, kann nur root auf diese Schnittstelle zugreifen. Also macht sich eine entsprechende Konfiguration für Nicht-Root-User ganz sinnvoll:
Erzeugung der Datei /etc/udev/rules.d/60-tilaunchpad.rules mit folgendem Inhalt:
# Zugriff auf TIs MSP430-Launchpad regeln SUBSYSTEM=="usb",ATTR{idVendor}=="0451",ATTR{idProduct}=="f432",GROUP="dialout",MODE="666"
Statt "dialout" kann auch eine andere Gruppe benutzt werden, der jeweilige Benutzer muss Mitglied dieser Gruppe sein.
Nach dem Restart von udev (Befehl: /etc/init.d/udev restart) kann man auch als Nicht-root zugreifen.
[Bearbeiten] Serielle Schnittstelle
Das MSP430-Launchpad verhält sich (unter Linux) etwas merkwürdig an der seriellen Schnittstelle. Dieser Hinweis/Tipp schafft, zu mindestens bei mir, Abhilfe...!
[Bearbeiten] Weiterführende Links
Datenblätter:
Toolchain & IDEs:
- http://wiki.ubuntuusers.de/MSP430-Toolchain
- http://recursive-labs.com/static/courses/rl100/samples/mspstart.pdf
- http://www.itopen.it/2013/03/01/msp430-energia-on-linux/
- http://launchpadlinux.blogspot.de/2012/10/making-thinks-easier-with-make-and-geany.html
mspdebug:
MSP430-Basics:
- http://processors.wiki.ti.com/index.php/Category:MSP430
- http://dbindner.freeshell.org/msp430/
- http://www.mikrocontroller.net/articles/MSP430_Codebeispiele
- http://homepages.ius.edu/RWISMAN/C335/HTML/msp430Timer.HTM
- http://forum.43oh.com/forum/18-msp430-technical-forums/
GDB:
- http://www.cs.umd.edu/~srhuang/teaching/cmsc212/gdb-tutorial-handout.pdf
- http://www.mail-archive.com/mspgcc-users@lists.sourceforge.net/msg10262.html