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)
- 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
Programmbeispiel - MCP23008
MCP23008 Schaltplan and Register
MSP430 Register
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
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-AdressUCB0CTL1 |= 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
- 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
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
Acknowledge
Ein zusätzlicher Widerstand in der Signalleitung sorgt für einen veränderten Pegel, wenn der Slave die SDA-Leitung auf Masse zieht.
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;
}
}