Skip to content

MSP430 - I²C

Übersicht

  • Inter-Integrated Circuit
  • 1982 von Phillips entwickelter serieller Datenbus
  • Hauptsächlich: Kommunikation zwischen einen Controller und Peripherie auf einer Leiterplatte
    → Verringern der Anzahl von Leiterbahnen, geringe Leitungslängen
  • technisch identischer Busstandard: SMB (System Management Bus), TWI (Two-Wire Interface)

alt: "I²C-Bus Topologie", src: "Wikipedia-Commons", w:50

  • Slaves besitzen eine einzigartige Adresse auf den Bus
    • Adresse: 7 bit-lang
    • ‌8. Bit: R/W
    • 8-bit address = ((7-bit address) << 1) + (R/W bit)
  • Master beginnt immer die Kommunikation und fragt die Adresse an
  • Erst nach Slave-Acknowledge gehts weiter
  • Daten werden übertragen

alt: "I²C Signalverlauf", src:"analog.com", w:75

Programmbeispiel - MCP23008

MCP23008 Schaltplan and Register

alt: "Testschaltung des MCP23008", w:75

alt: "Register des MCP23008", src: "MCP23008 Datenblatt, S. 9", w:75, label: "fig:mcp23008_reg"

alt: "Detaillierte Beschreibung des IODIR-Registers des MCP23008", src: "MCP23008 Datenblatt, S. 10", w:75, label: "fig:mcp23008_iodir"

MSP430 Register

alt: "Register UCBxCTL0", src: "Family Guide, S. 468", w: 75

alt: "Register UCBxCTL1", src: "Family Guide, S. 469", w: 75

Step-by-Step Instruction

Auswahl der Pins

Die seriellen Schnittstellen des MSP430G2553

  • USCIA
    - Pins: P1.1, P1.2 und P1.4
    - Unterstütze Protokolle: UART und SPI
  • USCIB
    - Pins: P1.6, P1.7 und P1.5
    - Unterstütze Protokolle: SPI und I²C:::
  • Um die Pins P1.6 and P1.7 als SCL und SDA zu verwenden, müssen die Pin-Select-Register gesetzt werden.
  • siehe Pinbelegung des MSP430G2553

Verbinden der Pins P1.6 und P1.7 mit der I²C-Schnittstelle

P1SEL |= BIT6 + BIT7;
P1SEL2 |= BIT6 + BIT7;

Konfigurieren der USCIB-Schnittstelle

  • Während der Konfiguration muss das Software-Reset-Bit gesetzt werden.
  • Einstellungen im UCBxCTL0-Register:
    • 7-bit addressing
    • single master
    • master mode
    • I²C mode
    • Synchronous mode
  • Setzen des Takt-Teilers:

    • Maximale I²C-Frequenz des MCP23008: 400 kHz, siehe Datenblatt S. 1
    • Taktquelle: SMCLK

      \[ n\ped{divider} = \frac{1\si{MHz}}{400\si{kHz}} = 2.5 \]
    • Der Teiler muss auf 3 gesetzt werden. Die resultierende Taktrate ist daher 333 kHz.
    • Im Register UCBxCTL1 muss nur die Taktquelle definiert werden.

Konfigurieren der USCIB-Schnittstelle

UCB0CTL1 |= UCSWRST;
UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;
UCB0BR0 = 3;
UCB0BR1 = 0;
UCB0CTL1 = UCSSEL_2;

Funktion i2c_write

Funktion i2c_write

void i2c_write(uint8_t address, const uint8_t* data, uint8_t len) {
    // Warten, bis Bus frei
    while (UCB0CTL1 & UCTXSTP) {
    }

    UCB0I2CSA = address; // Slave Adresse setzen
    UCB0CTL1 |= UCTR + UCTXSTT;      // W-Mode, Start der Übertragung

    // Senden der Datenbytes
    for (uint8_t i = 0; i < len; i++) {
        while (!(IFG2 & UCB0TXIFG)) {
        }

        UCB0TXBUF = data[i];
    }

    // Senden der Stoppsequenz
    while (!(IFG2 & UCB0TXIFG)) {
    }
    UCB0CTL1 |= UCTXSTP;
}

  • Flag-Polling wird anstelle von Interrupts verwendet.
  • Es wird dadurch sichergestellt, dass es zu keine Kollisionen kommt.
  • Da Wartezeiten nicht übermäßig lang sind, ist der zusätzliche Strombedarf nicht zu hoch:

    \[ T\ped{byte} = \frac{1\si{Startbit} + 8\si{Databit} + 1\si{ACKbit}}{333\si{kHz}} = 30\si{\mu s} \]
  • while (UCB0CTL1 & UCTXSTP)
    Sicherstellen dass die Schnittstelle frei ist, da das Stop-Bit der letzten Kommunikation versendet wurde.
  • UCB0I2CSA = address
    Setzen der Slave-Adress
  • UCB0CTL1 |= UCTR + UCTXSTT
    Start der Kommunikation. UCTR setzt das R/W bit.
  • Verwenden einer Schleife um alle Datenbytes zu versenden.
  • while (!(IFG2 & UCB0TXIFG))
    Warte bis das letzte Byte versendet wurde.
  • UCB0CTL1 |= UCTXSTP;
    Senden der Stop-Sequenz.

Hauptprogramm

  • Um LEDs, die am MCP23008 angeschlossen sind, anzusteuern, müssen zwei Register gesetzt werden.
  • Alle Register des MCP23008 sind im entsprechenden Datenblatt zu sehen.
  • Das erste Register ist IODIR, welches die selbe Funktion erfüllt, wie das PxDIR-Register des MSP430.
  • Jedoch ist die Bedeutung der Bits invertiert.
  • Um alle Pins auf Ausgang zu schalten, muss IODIR auf 0 gesetzt werden.

Setzen aller Bits des IODIR-Register auf 0

const uint8_t data_iodir[] = { 0, 0 };
i2c_write(0x20, data_iodir, 2);

  • Wenn die Funktion i2c_write aufgerufen wird, muss ein Datenfeld vorbereitet werden.
  • Im Fall des MCP23008 müssen mindestens 2 Bytes übertragen werden: Register-Adresse und Register-Wert.
  • Um die entsprechenden LEDs zu aktivieren muss das GPIO-Register gesetzt werden.
  • Diese besitzt die Adresse 9

Setzen des Wertes des GPIO-Register auf 0xA5

const uint8_t data_gpio1[] = { 9, 0xA5 };
i2c_write(0x20, data_gpio1, 2);

Programm

Blinken der LEDs am MCP23008

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

void i2c_write(uint8_t address, const uint8_t* data, uint8_t len);

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

    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL = CALDCO_1MHZ;

    P1SEL |= BIT6 + BIT7;
    P1SEL2 |= BIT6 + BIT7;

    UCB0CTL1 |= UCSWRST;
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;
    UCB0BR0 = 3;
    UCB0BR1 = 0;
    UCB0CTL1 = UCSSEL_2;

    __enable_interrupt();

    const uint8_t data_iodir[] = { 0, 0 };
    i2c_write(0x20, data_iodir, 2);

    while (1) {
        const uint8_t data_gpio1[] = { 9, 0xA5 };
        i2c_write(0x20, data_gpio1, 2);
        __delay_cycles(500000);

        const uint8_t data_gpio2[] = { 9, 0x5A };
        i2c_write(0x20, data_gpio2, 2);
        __delay_cycles(500000);
    }
}

void i2c_write(uint8_t address, const uint8_t* data, uint8_t len) {
    // Warten, bis Bus frei
    while (UCB0CTL1 & UCTXSTP) {
    }

    UCB0I2CSA = address; // Slave Adresse setzen
    UCB0CTL1 |= UCTR + UCTXSTT;      // W-Mode, Start der Übertragung

    // Senden der Datenbytes
    for (uint8_t i = 0; i < len; i++) {
        while (!(IFG2 & UCB0TXIFG)) {
        }

        UCB0TXBUF = data[i];
    }

    // Senden der Stoppsequenz
    while (!(IFG2 & UCB0TXIFG)) {
    }
    UCB0CTL1 |= UCTXSTP;
}

Analyse des Signalverlaufs

Pullup-Widerstände

alt: "I²C-Signalverlauf ohne Pullup-Widerstände", w:75
alt: "I²C-Signalverlauf mit Pullup-Widerstände", w:75

Acknowledge

Ein zusätzlicher Widerstand in der Signalleitung sorgt für einen veränderten Pegel, wenn der Slave die SDA-Leitung auf Masse zieht.
alt: "I²C-Signalverlauf mit Slave-Acknowledge", w: 75

Auslesen von Registern

  • Zum Lesen von Registern wird bei I²C-Slaves häufig das folgende Verfahren verwendet.
  • Durch einen Schreib-Befehl wird die Register-Adresse an den Slave übermittelt.
  • Anschließend werden in einem Lesen-Befehl die Daten übertragen.
  • Programmbeispiel: An den Pins 0-3 werden LEDs angeschlossen, die leuchten, wenn Taster an den Pins 4-7 gedrückt sind.

Beispiel eines Lesevorgangs über I²C

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

void i2c_write(uint8_t address, const uint8_t* data, uint8_t len);
void i2c_read(uint8_t address, uint8_t* data, uint8_t len);

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

    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL = CALDCO_1MHZ;

    P1SEL |= BIT6 + BIT7;
    P1SEL2 |= BIT6 + BIT7;

    UCB0CTL1 |= UCSWRST;
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;
    UCB0BR0 = 3;
    UCB0BR1 = 0;
    UCB0CTL1 = UCSSEL_2;

    __enable_interrupt();

    const uint8_t data_iodir[] = { 0, 0xF0 };
    i2c_write(0x20, data_iodir, 2);

    const uint8_t data_gppu[] = { 6, 0xF0 };
    i2c_write(0x20, data_gppu, 2);

    while (1) {
        const uint8_t data_gpio_req = 9;
        i2c_write(0x20, &data_gpio_req, 1);

        uint8_t data_gpio_res;
        i2c_read(0x20, &data_gpio_res, 1);

        data_gpio_res = (data_gpio_res >> 4) ^ 0x0F;
        uint8_t data_olat[] = { 10, data_gpio_res };
        i2c_write(0x20, data_olat, 2);

        __delay_cycles(1000);
    }
}

void i2c_write(uint8_t address, const uint8_t* data, uint8_t len) {
    // Warten, bis Bus frei
    while (UCB0CTL1 & UCTXSTP) {
    }

    UCB0I2CSA = address; // Slave Adresse setzen
    UCB0CTL1 |= UCTR + UCTXSTT;      // W-Mode, Start der Übertragung

    // Senden der Datenbytes
    for (uint8_t i = 0; i < len; i++) {
        while (!(IFG2 & UCB0TXIFG)) {
        }

        UCB0TXBUF = data[i];
    }

    // Senden der Stoppsequenz
    while (!(IFG2 & UCB0TXIFG)) {
    }
    UCB0CTL1 |= UCTXSTP;
}

void i2c_read(uint8_t address, uint8_t* data, uint8_t len) {
    // Warten, bis Bus frei
    while (UCB0CTL1 & UCTXSTP) {
    }

    UCB0I2CSA = address; // Slave Adresse setzen
    UCB0CTL1 &= ~UCTR;  // R-Mode
    UCB0CTL1 |= UCTXSTT; // Start der Übertragung

    // Warte, bis Startsequenz gesendet
    while (UCB0CTL1 & UCTXSTT) {
    }

    // Empfangen der Datenbytes
    for (uint8_t i = 0; i < len; i++) {
        if (i == len - 1) {
            // Setzen der Stop-Bits, während des Empfangen des letzten Bytes
            UCB0CTL1 |= UCTXSTP;
        }

        while (!(IFG2 & UCB0RXIFG)) {
        }

        data[i] = UCB0RXBUF;
    }
}