In diesem Aritkel soll es einen kleinen Einstieg in die C-Programmierung für den MSP430 geben. Das Assembler-Programm aus dem Artikel “Assembler-Programmierung” wird in ein C-Programm übersetzt und beschrieben.
1. Tasterabfrage bzw. Portabfrage und Ports setzen
UPDATE: Für Launchpads mit Rev.1.5 (Pull-Up an P1.3 muss aktiviert werden):
#include <msp430g2253.h>
void main(void)
{
WDTCTL=WDTPW+WDTHOLD; /* stop watchdog timer */
P1DIR|=BIT0; /* P1.0 (LED1) -> output */
P1DIR&=~BIT3; /* P1.3 (S2) -> input */
P1REN|=BIT3; /* enable P1.3 Pull-Up */
P1OUT&=~BIT0; /* clear P1.0 (LED1 off) */
while(1)
{
if(!(P1IN&BIT3)) /* test P1.3 (pressed?) */
{
P1OUT^=BIT0; /* toggle P1.0 */
while(!(P1IN&BIT3)); /* test P1.3 again (pressed?) */
}
}
}
Für Launchpads mit Rev.1.4:
#include <msp430g2231.h>
void main(void)
{
WDTCTL=WDTPW+WDTHOLD; /* stop watchdog timer */
P1DIR|=BIT0; /* P1.0 (LED1) -> output */
P1DIR&=~BIT3; /* P1.3 (S2) -> input */
P1OUT&=~BIT0; /* clear P1.0 (LED1 off) */
while(1)
{
if(!(P1IN&BIT3)) /* test P1.3 (pressed?) */
{
P1OUT^=BIT0; /* toggle P1.0 */
while(!(P1IN&BIT3)); /* test P1.3 again (pressed?) */
}
}
}
1.1 Beschreibung (Zeilenweise)
P1DIR|=BIT0; /* P1.0 (LED1) -> output */
P1DIR&=~BIT3; /* P1.3 (S2) -> input */
P1OUT&=~BIT0; /* clear P1.0 (LED1 off) */
1. Zeile | Port 1.0 wird als Ausgang definiert (das LSB (BIT0 oder 0x01) wird gesetzt). Dies geschiet durch eine Standard-C-Bitoperation (ODER “|”). In dem Code wird eine Kurzform benutzt um ein einzelnes Bit zu manipulieren, ausgeschrieben könnte es so aussehen:P1DIR = P1DIR | (0x01 << bit_nummer) “bit_nummer” steht hier für das zu manipulierende Bit. Der hintere Teil in der Klammer “shiftet” ein Bit (00000001) an Position “bit_nummer” nach links. Wäre bit_nummer=3 so würde sich folgendes ergeben: 00000001 (0x01) wird zu 00001000 (0x10) -> Das LSB wird an die 3. Stelle geschoben. Somit wird durch die “|”-Operation das 3. Bit gesetzt (wenn P1DIR den Anfangswert 0x00 hat). |
2. Zeile | P1.3 wird geleert bzw. auf 0 gesetzt, ausgeschriebener Befehl:P1DIR = P1DIR & ~(0x01<< bit_nummer) Diesmal wird die “&”-Operation und “~” (komplement) benutzt. Sei der Anfangswert von P1DIR=0x10 (00001000), also das Bit was in Zeile 1 gesetzt wurde. Dies soll jetzt wieder auf 0 gesetzt werden. Schauen wir uns die Klammer an: bit_nummer=3 -> 00001000 (das LSB wird an Stelle 3 geschoben), das Komplement daraus: 11110111. Die UND-Verknpüfung daraus ergibt 0x00 oder binär 00000000. |
3. Zeile | P1.0 (LED1) wird auf 0 gesetzt, hier muss das P1OUT-Register benutzt werden. |
if(!(P1IN&BIT3)) /* test P1.3 (pressed?) */
{
P1OUT^=BIT0; /* toggle P1.0 */
while(!(P1IN&BIT3)); /* test P1.3 again (pressed?) */
}
1. Zeile | Falls der Taster (P1.3) betätigt wurde, wird in die in if-Abfrage gesprungen. Wenn der Taster nicht gedrückt wurde, hat das P1IN-Register den Wert: 00001000, gedrückt: 00000000. Die UND-Operation zwischen BIT3 und P1IN ergibt 0, somit wird die if-Abfrage wahr. |
3. Zeile | XOR-Operation, ist die LED an und der Taster wird betätigt, wird P1.0 auf 0 gesetzt. Ist die LED aus und der Taster wird wieder betätigt, wird P1.3 auf 1 gesetzt. |
4. Zeile | Vergleiche Zeile 1. Hier wird so lange in der while-Schleife abgefragt bis der Taster wieder losgelassen wird. Durch die Endlosschleife (while(1)) wird wieder an die if-Abfrage gesprungen usw. |
2. Interrupts
Das gleiche Programm (siehe oben) kann natürlich auch mit Interrupts bzw. einem Interrupt realisiert werden. Dazu folgendes Beispiel-Programm:
#include <msp430g2231.h>
void main(void)
{
WDTCTL=WDTPW+WDTHOLD; // stop WDT
// every IO is defined as input by default
P1DIR&=~BIT3; // P1.3 (S2) input
P1IE|=BIT3; // enable P1.3 interrupt
P1IES|=BIT3; // interrupt @ high/low edge
P1IFG&=~BIT3; // clear interrupt flag
P1DIR|=BIT0; // P1.0 (LED1) output
P1OUT&=~BIT0; // LED off
_EINT();
for(;;);
}
// ISR will be terminated with RETI (return from interrupt)
#pragma INTERRUPT (ISR_Port1)
#pragma vector=PORT1_VECTOR // start address 0xFFE4 Port 1
void ISR_Port1(void)
{
P1OUT^=BIT0; // toggle LED1
P1IFG&=~BIT3; // clear interrupt flag
}
2.1 Beschreibung (Zeilenweise)
7. Zeile | Port 1.3 wird als Eingang definiert, standardmäßig sind alle Ports als Eingang definiert und die Bits im P1DIR-Register müssen nicht unbedingt auf “0” gesetzt werden. |
8. Zeile | Für P1.3 wird ein Interrupt aktiviert. Dies geschieht über das P1IE-Register (Port 1 Interrupt Enable). Wer für Port 2 einen Interrupt aktivieren will, benutzt das P2IE-Register usw. |
9. Zeile | Über das P1IES-Register (Interrupt Edge Select) wird festgelegt, bei welcher Flanke ein Interrupt ausgelöst werden soll. In diesem Beispiel wird bei einer High-Low-Flanke (“1” -> “0”) ein Interrupt ausgelöst. |
10. Zeile | Das P1IFG-Register wird geleert (bzw. das 3. Bit). Das IFG kann möglicherweise gesetzt werden, falls am P1DIR oder P1IE-Register etwas geändert wurde, so könnte direkt ein Interrupt ausgelöst werden in der Initialsierung (muss aber nicht). |
11+12. | P1.0 wird als Ausgang definiert (LED) und auf “0” gesetzt. |
13. Zeile | Mit diesem Befehl werden Interrupts global aktiviert. |
15. Zeile | Endlosschleife, hier passiert nichts. Der Rest wird in der Interrupt Service Routine bearbeitet. |
19. Zeile | Mit der #pragma-Direktive wird dem Compiler mitgeteilt, die zuständige Funktion (Interrupt Service Routine) mit einem RETI zu verlassen -> Return from Interrupt. ISRs sind immer parameterlos (keine Übergabewerte, keine Rückgabewerte). In Assembler muss “RETI” am Ende einer ISR geschrieben werden. |
20. Zeile | Mit dieser Direktive wird die Startadresse der ISR eingetragen. Falls ein Interrupt ausgelöst wird, muss der Compiler wissen wohin er überhaupt springen soll. Die Interrupt-Tabelle (Source, Address, Priority) unterscheiden sich natürlich in der MSP430-Reihe. |
22. Zeile | Die LED wird bei betätigen des Tasters, ein- oder ausgeschaltet (XOR-Verknüpfung). |
23. Zeile | Das Interrupt Flag für P1.3 wird zurücksetzt. Somit wurde die ISR abgearbeitet und kann verlassen werden. |
3. C und Assembler vermischen
Oder auch Assembler Inlining genannt. Natürlich ist es möglich in C, Assembler Code auszuführen. Dazu ein kleines Beispiel-Programm:
#include <msp430g2231.h>
// clear negative bit, zero bit and carry bit
#define SR_CLEAR_CZN asm ("\t CLRC\n\t CLRZ\n\t CLRN")
// #define SR_CLEAR_CZN asm ("\t bic.w #1,SR\n\t bic.w #2,SR\n\t bic.w #4,SR")
void main(void)
{
volatile char test_var=255;
WDTCTL=WDTPW+WDTHOLD; // stop WDT
test_var++;
SR_CLEAR_CZN; // clear SR
}
In diesem Programm werden Carry, Zero und Negative-Bit im Status-Register (SR) auf “0” gesetzt. Dies geschieht über die Direktive in Zeile 3. Wichtig hier ist: “\t” (Tabulator). Der ASM-Code muss immer eingerückt sein, da vor dem OP-Code das Label steht.