Skip to content

MSP430 - UART / RS-232

Überblick

  • Universal Asynchronous Receiver Transmitter
  • Senden und Empfangen von Daten über jeweils eine Datenleitung
  • RS-232 besitzt zusätzliche Steuerleitungen (z. B. Flow-Control: RTC, CTS)

alt:"Pinbelegung RS-232", src:"www.electronics-lab.com", w:50

  • Über TXD und RXD werden Spannungssignale übertragen
    RS-232: -12 V ... 12 V
    UART: 0 ... 5 V oder 0 ... 3,3 V → abhängig von Betriebspannung der Mikrocontroller
  • Wandeln des UART-Signals in RS-232 durch IC MAX-232 (Datenblatt)
  • Bitbreite: Bit/sec = Baud
  • Typische Bitbreiten: 2400, 4800, 9600, 115200 Baud

Aufbauen einer UART-Verbindung mit dem MSP430

MSP430 Register

alt: "Registerwerte für typische Baudraten", src: "Family Guide S. 425", w:75

alt: "Register UCAxCTL0", src: "Familiy Guide S. 429", w:75

alt: "Register UCAxCTL1", src: "Familiy Guide S. 430", w:75

alt: "Register UCAxMCTL", src: "Familiy Guide S. 431", w:75

Detaillierte Beschreibung

Kalibieren des DCO

  • Der DCO des MSP430 erzeugt den Takt für die MCLK und SMCLK.
  • Dieser muss kalibriert werden, um exaktere Ausgangssignale zu liefern.
  • Texas Instruments speichert Kalibrierwerte für 1, 8, 12 und 16 MHz im Flash-Speicher des MSP430.
  • Der DCO des MSP430 ist nicht so exakt, wie der Quarzoszillator der ACLK.
  • Die Frequenz des DCO ist außerdem stark abhängig von Umgebungstemperatur und Betriebsspannung.
  • Jedoch ist das Timing besonders wichtig beim Senden und Empfangen von UART-Signalen.

Kalibieren des DCO

BCSCTL1 = CALBC1_1MHZ;
DCOCTL = CALDCO_1MHZ;

Auswahl der Pins

  • Für UART werden P1.1 und P1.2 verwendet.
  • Die PxSEL und PxSEL2 bits müssen entsprechend der Pinbelegung des MSP430G2553 gesetzt werden.
    Pinbelegung des MSP430G2553

Verbinden der Pins P1.1 und P1.2 mit der UART-Schnittstelle

P1SEL |= BIT1 + BIT2;
P1SEL2 |= BIT1 + BIT2;

USCI Software Reset

  • Das Software-Reset-Bit UCSWRST befindet sich im UCA0CTL1-Register.
  • Während der Konfiguration des USCI-Moduls muss dieses Bit gesetzt sein.
  • Nach der Konfiguration muss das Bit zurücksetzt werden, damit die Schnittstelle einwandfrei funktioniert.

USCI Software Reset

UCA0CTL1 |= UCSWRST;

// USCIA configuration
...

UCA0CTL1 &= ~UCSWRST;

Konfigurieren des USCIA

  • Wie in der UCA0-Registerbeschreibung zu sehen ist eine UART-Kommunikation der Standard (alle Bits sind null)
    • keine Paritätsbit
    • LSB zuerst
    • 8-bit Daten
    • 1 Stopbit
    • UART
    • Asynchrone Schnittstelle (keine Taktleitung)
  • Im UCA0CTL1-Register muss nur die Taktquelle konfiguriert werden.
  • Die SMCLK mit einer Taktrate von 1 MHz soll verwendet werden.

USCIA control register values

UCA0CTL0 = 0;
UCA0CTL1 |= UCSSEL_2;

Setzen der Baudrate

  • Das Setzen der Baudrate im MSP430 ist vergleichweise komplex. Es werden im MSP430 verschiedene Tricks verwendet, um ein möglichst exaktes Taktsignal zu generieren.
  • Noch komplexer ist das Timing für den Datenempfang. Das Signal wird während einer Taktperiode mehrmals gesamplet.
  • Aus diesem Grund muss die Tabelle mit Baudraten verwendet werden, um die korrekten Registerwerte zu konfigurieren.
  • Die folgenden Werte können aus der Tabelle gelesen werden, für eine 9600 Baud-Signal bei einer Taktfrequenz von 1 MHz.
UCOS16 = 1
USCBRx = 6
USCBRSx = 0
UCBRFx = 8
  • Es ergeben sich daraus die Registerwerte wie folgt.
    • UCA0BR0 und UCA0BR1.
      mit USCBRx = UCA0BR0 + 256 * UCA0BR1
    • UCA0MCTL = (UCBRFx << 4) + (UCBRSx << 1) + UCOS16

USCIA Timing-Einstellung für 9600 Baud bei 1 MHz

UCA0BR0 = 6;
UCA0BR1 = 0;
UCA0MCTL = (8 << 4) + UCOS16;

Sende eines Zeichens

  • UART-Nachrichten werden Byte für Byte übertragen.
  • Zum Senden einer Nachricht wird einfach das UCA0TXBUF register beschrieben.
  • Man muss sicherstellen, dass das Register niemals schneller befüllt wird, als das USCIA Daten versenden kann.
  • Für das Beispielprogramm soll das Zeichen 'x' versendet werden, wenn der Taster an P1.3 gedrückt wurde.
  • Daher ergibt sich die folgende ISR.

Senden eines Zeichens in der Port 1 ISR

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR() {
    if (P1IFG & BIT3) {
        P1IFG &= ~BIT3;
        UCA0TXBUF = 'x';
    }
}

Empfangen von Zeichen

  • Für den UART-Datenempfang steht ein Interrupt zur Verfügung
  • Um diesen Interrupt zu aktivieren, muss das folgende Bit gesetzt werden.

Aktivieren des RX-Interrupts

UC0IE |= UCA0RXIE;

  • Die folgende ISR wird verwendet, um die Nachricht zu dekodieren.
  • Die zugehörige Interrupt-Flag UCA0RXIFG befindet sich im Register IFG2.
  • Das empfangene Zeichen kann aus dem UCA0RXBUF-Register gelesen werden.
  • Die LEDs der RGB-LED sollen umgeschaltet werden, wenn die Zeichen r, g oder b empfangen wurden.

ISR für den Datenempfang - Umschalten der LEDs

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {
    if (IFG2 & UCA0RXIFG) {
        IFG2 &= ~UCA0RXIFG;
        char received_char = UCA0RXBUF;

        switch (received_char) {
        case 'r':
            P2OUT ^= BIT1;
            break;
        case 'g':
            P2OUT ^= BIT3;
            break;
        case 'b':
            P2OUT ^= BIT5;
            break;
        default:
            break;
        }
    }
}

Low-Power-Mode

  • Der Low-Power-Mode 4 kann in diesem Programm verwendet werden.
  • Die SMCLK wird während des Senden- und Empfangens verwendet - dieser Systemtakt ist standardmäßig deaktiviert im LPM4.
  • Jedoch kann der MSP430 automatisch die SMCLK aktivieren, wenn er zum Senden oder Empfangen benötigt wird. Der Taktgeber wird anschließend automatisch deaktiviert.

Programm

Erstes Programm für UART

#include <msp430.h>
#include <stdint.h>

int main(void) {
    WDTCTL = WDTPW | WDTHOLD;    // stop watchdog timer

    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL = CALDCO_1MHZ;

    P2OUT &= ~(BIT1 + BIT3 + BIT5);
    P2DIR |= BIT1 + BIT3 + BIT5;

    P1DIR &= ~BIT3;
    P1OUT |= BIT3;
    P1REN |= BIT3;
    P1IES |= BIT3;
    P1IFG &= ~BIT3;
    P1IE |= BIT3;

    P1SEL |= BIT1 + BIT2;
    P1SEL2 |= BIT1 + BIT2;

    UCA0CTL1 |= UCSWRST;
    UCA0CTL0 = 0;
    UCA0CTL1 |= UCSSEL_2;

    UCA0BR0 = 6;
    UCA0BR1 = 0;
    UCA0MCTL = (8 << 4) + UCOS16;

    UCA0CTL1 &= ~UCSWRST;
    UC0IE |= UCA0RXIE;

    __enable_interrupt();

    while (1) {
        __low_power_mode_4();
    }
}

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {
    if (IFG2 & UCA0RXIFG) {
        IFG2 &= ~UCA0RXIFG;
        char received_char = UCA0RXBUF;

        switch (received_char) {
        case 'r':
            P2OUT ^= BIT1;
            break;
        case 'g':
            P2OUT ^= BIT3;
            break;
        case 'b':
            P2OUT ^= BIT5;
            break;
        default:
            break;
        }
    }
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR() {
    if (P1IFG & BIT3) {
        P1IFG &= ~BIT3;
        UCA0TXBUF = 'x';
    }
}

Herstellen der UART-Verbindung mit dem PC

alt: "Korrekte Verbinden am Launchpad", w:33

alt: "Herausfinden der COM-Port-Nummer im Windows Gerätemanager", w:33

alt: "Verwenden von PuTTY", w:33

alt: "Verwenden von HTerm", w:50

Kommunikation zwischen mehr als 2 Teilnehmern

alt: "Push-Pull- vs Open-Drain-Ausgang", src: "www.circuitdigest.com", w:50

Kommunikation zwischen mehr als 2 Teilnehmern in der Praxis eher selten.
- Verwendung mehrerer UART-Schnittstellen eines Mikrocontrollers
- Multiplexen der Datenleitungen
- Bit-Banging der UART-Schnittstelle

Senden von Zeichenketten und Zahlen

*Funktionen zum Senden von Zeichenketten und Zahlen

void uart_print(char *str)
{
    while (*str != 0)
    {
        while (!(IFG2 & UCA0TXIFG))
        {
        }
        UCA0TXBUF = *str;
        *str++;
    }
}

void uart_hex(uint8_t number)
{
    char str[3];
    for (uint8_t i = 0; i < 2; i++)
    {
        uint8_t digit = number & 0xf;
        if (digit < 10)
        {
            str[1 - i] = '0' + digit;
        }
        else
        {
            str[1 - i] = 'a' + digit - 10;
        }
        number >>= 4;
    }

    str[2] = 0;
    uart_print(str);
}

Beispiel-Implementierung eines Protokolls

Programmierung des MSP430

  • Die Daten werden durch den RX-Interrupt aufgenommen und in einem Puffer abgespeichert.
  • Nach einen Zeichenumbruch \n wird er LPM verlassen und der Puffer kann im Hauptprogramm ausgewertet werden.
  • Durch eine Nachricht in der Form l000\n / l111\n können die RGB-LEDs entsprechend der gegebenen Bit-Werte einzeln an- oder ausgeschaltet werden.
  • Dazu werden verschiedene Variablen benötigt:

Variablen für den UART-RX-Puffer

volatile char uart_rx_buf[16];
volatile uint8_t uart_rx_buf_pos;
volatile uint8_t uart_rx_buf_len;

UART-RX-ISR zum Dekodieren der Nachrichten

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {
    if (IFG2 & UCA0RXIFG) {
        IFG2 &= ~UCA0RXIFG;
        char received_char = UCA0RXBUF;
        uart_rx_buf[uart_rx_buf_pos] = received_char;
        uart_rx_buf_pos++;
        if (received_char == '\n') {
            uart_rx_buf_len = uart_rx_buf_pos;
            uart_rx_buf_pos = 0;
            isr_flags |= BIT1;
            __low_power_mode_off_on_exit();
        }
    }
}

  • Der LPM kann in diesem Programm aus verschiedenen Gründen verlassen werden.
  • Daher wird die Variable isr_flags benötigt.

Hauptprogramm mit Taster-Druck- und UART-RX-Event

while (1) {
    if (isr_flags & BIT0) {
        uart_send_string("btn\n");
        isr_flags &= ~BIT0;
    } else if (isr_flags & BIT1) {
        message_decode();
        isr_flags &= ~BIT1;
    } else {
        __low_power_mode_4();
    }
}

Funktion message_decode() zum Dekodieren der empfangenen Nachricht und setzen der RGB-LEDs

void message_decode() {
    if (uart_rx_buf[0] != 'l') {
        return;
    }

    if (uart_rx_buf_len != 5) {
        return;
    }

    if (uart_rx_buf[1] == '1') {
        P2OUT |= BIT1;
    } else {
        P2OUT &= ~BIT1;
    }

    if (uart_rx_buf[2] == '1') {
        P2OUT |= BIT3;
    } else {
        P2OUT &= ~BIT3;
    }

    if (uart_rx_buf[3] == '1') {
        P2OUT |= BIT5;
    } else {
        P2OUT &= ~BIT5;
    }
}

Gesamtes Programm

#include <msp430.h>
#include <stdint.h>

volatile char uart_rx_buf[16];
volatile uint8_t uart_rx_buf_pos;
volatile uint8_t uart_rx_buf_len;

volatile uint8_t isr_flags;

void uart_send_string(char *str);
void message_decode();

int main(void) {
    WDTCTL = WDTPW | WDTHOLD;    // stop watchdog timer

    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL = CALDCO_1MHZ;

    P2OUT &= ~(BIT1 + BIT3 + BIT5);
    P2DIR |= BIT1 + BIT3 + BIT5;

    P1DIR &= ~BIT3;
    P1OUT |= BIT3;
    P1REN |= BIT3;
    P1IES |= BIT3;
    P1IFG &= ~BIT3;
    P1IE |= BIT3;

    P1SEL |= BIT1 + BIT2;
    P1SEL2 |= BIT1 + BIT2;

    UCA0CTL1 |= UCSWRST;
    UCA0CTL0 = 0;
    UCA0CTL1 |= UCSSEL_2;

    UCA0BR0 = 6;
    UCA0BR1 = 0;
    UCA0MCTL = (8 << 4) + UCOS16;

    UCA0CTL1 &= ~UCSWRST;
    UC0IE |= UCA0RXIE;

    uart_rx_buf_pos = 0;

    __enable_interrupt();

    while (1) {
        if (isr_flags & BIT0) {
            uart_send_string("btn\n");
            isr_flags &= ~BIT0;
        } else if (isr_flags & BIT1) {
            message_decode();
            isr_flags &= ~BIT1;
        } else {
            __low_power_mode_4();
        }
    }
}

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {
    if (IFG2 & UCA0RXIFG) {
        IFG2 &= ~UCA0RXIFG;
        char received_char = UCA0RXBUF;
        uart_rx_buf[uart_rx_buf_pos] = received_char;
        uart_rx_buf_pos++;
        if (received_char == '\n') {
            uart_rx_buf_len = uart_rx_buf_pos;
            uart_rx_buf_pos = 0;
            isr_flags |= BIT1;
            __low_power_mode_off_on_exit();
        }
    }
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR() {
    if (P1IFG & BIT3) {
        P1IFG &= ~BIT3;
        isr_flags |= BIT0;
        __low_power_mode_off_on_exit();
    }
}

void uart_send_string(char *str) {
    while (*str != 0) {
        while (!(IFG2 & UCA0TXIFG)) {
        }
        UCA0TXBUF = *str;
        *str++;
    }
}

void message_decode() {
    if (uart_rx_buf[0] != 'l') {
        return;
    }

    if (uart_rx_buf_len != 5) {
        return;
    }

    if (uart_rx_buf[1] == '1') {
        P2OUT |= BIT1;
    } else {
        P2OUT &= ~BIT1;
    }

    if (uart_rx_buf[2] == '1') {
        P2OUT |= BIT3;
    } else {
        P2OUT &= ~BIT3;
    }

    if (uart_rx_buf[3] == '1') {
        P2OUT |= BIT5;
    } else {
        P2OUT &= ~BIT5;
    }
}

Programmierung des PC in Python

  • Verwendete Python-Version: 3.8, siehe https://www.python.org/
  • Benötigte Bibliothek: pyserial, siehe https://pypi.org/project/pyserial/
    Installation über PIP: python -m pip install pyserial
  • Verwenden der Funktion input() für Eingaben des Nutzers über die Befehlszeile, siehe https://docs.python.org/3/library/functions.html#input
  • Verwenden der Methode serial.read_until(expected=serial.LF) zum Einlesen von eingehenden Daten.
  • Beide Funktionen wirken blockierend! Daher wird ein zusätzlicher Thread erstellt.
  • Verwenden des threading-Moduls: https://docs.python.org/3/library/threading.html
  • Erstellen eines Events (thread-sicherer Boolean), das gesetzt wird, wenn das Programm beendet werden soll.
  • Aufrufen der Funktion serial_loop() mit den passenden Argumenten in einen neuen Thread.
  • serial.read_until() liefert bytes. Diese müssen in eine Zeichenkette dekodiert werden:
    siehe https://docs.python.org/3/library/stdtypes.html#bytes
  • Zum Versenden der Nachricht müssen bytes übergeben werden. Daher wird eine Zeichenkette enkodiert.
  • Durch if __name__ == "__main__": wird in Python typischerweise das Hauptprogramm aufgerufen.

Empfangen der Daten im Beispielprotokoll

import threading

import serial


def main():
    ser = serial.Serial("COM5", 9600, timeout=1)
    stop_event = threading.Event()

    threading.Thread(target=serial_loop, args=[ser, stop_event]).start()

    while not stop_event.is_set():
        inp = input("? ")
        if inp == "x":
            stop_event.set()
        elif inp == "r":
            serial_write(ser, "l100\n")
        elif inp == "g":
            serial_write(ser, "l010\n")
        elif inp == "b":
            serial_write(ser, "l001\n")


def serial_loop(ser, stop_event):
    while not stop_event.is_set():
        msg = ser.read_until(expected=serial.LF)
        if len(msg) > 0:
            msg = msg.decode("ascii")
            print(msg[:-1])


def serial_write(ser, msg):
    if isinstance(msg, str):
        msg = msg.encode("ascii")
    ser.write(msg)


if __name__ == "__main__":
    main()