Forum: Mikrocontroller und Digitale Elektronik MSP430 I2C Abfolge


von Max (Gast)


Lesenswert?

Hallo,

auch wenn hier wohl mehr Leute mit AVRs arbeiten, hoffe ich doch, dass 
ihr mir vielleicht doch etwas helfen könntet.

Ich bastel derzeit mit einem MSP430G2553 und versuche diesen mit einer 
DS3232 über I2C zu verbinden.
Ich würde gerne das USCI-Modul verwenden. Laut Datenblatt kann ich die 
Kommunikation nun über das Pollen von Statuswerten oder Interrupts 
abwickeln, wobei ich gerne Interrupts nutzen würde.
Aber nun weiß ich nicht weiter.

Hier einmal meine Initialisierung:
1
    BCSCTL1 = CALBC1_1MHZ; // Set DCO to 1Mhz
2
    DCOCTL = CALDCO_1MHZ;
3
4
    P1SEL |= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
5
    P1SEL2|= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
6
7
    UCB0CTL1 |= UCSWRST;                      // Enable SW reset
8
    UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;     // I2C Master, synchronous mode
9
    UCB0CTL1 = UCSSEL_2 | UCSWRST;            // Use SMCLK, keep SW reset
10
    UCB0BR0 = 10;                             // fSCL = SMCLK/10 = ~100kHz
11
    UCB0BR1 = 0;
12
    UCB0I2CSA = 0x68;                         // Slave Address is 0x68
13
14
    UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
15
    IE2 |= UCB0TXIE;                          // Enable TX interrupt
16
17
    _BIS_SR(GIE);

Aber nun weiß ich nicht ganz weiter.
Als ich mal mit AVRs hantiert habe, nutzte ich die Bibliothek von Peter 
Fleury. Dieser scheint aber die Status-Flags auch eher zu pollen.

Wenn ich das Datenblatt richtig verstanden habe, dann kann ich mit 
folgender Funktion eine Startbedingung und die Adresse des Slaves 
senden:
1
void i2c_start(unsigned char device_addr)
2
{
3
  while(UCB0STAT & UCBUSY);   // wait for STOP
4
  UCB0I2CSA = device_addr;
5
  UCB0CTL1 |= UCTR | UCTXSTT;  // write-access + start  
6
}

Zum Test wollte ich gerne in den SRAM-Bereich (0x14-0xFF) der DS3232 
schreiben und das erste Register gleich wieder auslesen.

Zum schreiben bräuchte ich doch dann so etwas wie:
1
void i2c_write(unsigned char address, unsigned char data)

Nun komme ich aber gerade nicht weiter. Brauche ich die write-Funktion 
überhaupt? Ich möchte mir zunächst eine allgemeine I2C-Lib schreiben, 
die ich dann für beliebige I2C-Devices nutzen kann.
Aber wenn das ganze Interrupt-gesteuert sein soll, dann müsste ich doch 
die write-Funktion in den TX-Interrupt legen oder nicht? solange der 
Interrupt ausgelöst wird und Daten vorhanden sind, wir gesendet.
Bin gerade durch den Ablauf etwas verwirrt. Übergebe ich dann die Daten 
eher an so etwas:
1
 
2
void DS3232_write(unsigned address, unsigned char data)
3
{
4
    i2c_buffer[0] = address;
5
    i2c_buffer[1] = data;
6
    i2c_start();
7
}
8
9
#pragma vector = USCIAB0TX_VECTOR
10
__interrupt void USCIAB0TX_ISR(void)
11
{
12
    _BIC_SR(GIE);
13
14
    if(i2c_buffer)
15
        UCB0TXBUF = *(i2c_buffer++);
16
    else
17
    {
18
        i2c_stop();
19
    }
20
21
    _BIS_SR(GIE);
22
}

Aber wie ich dann das Lesen mit dem Restart hinbekomme, ist mir auch 
noch nicht so ganz klar.

Könnt ihr mir vielleicht einen kleinen Tipp geben, wie ich das aufbauen 
könnte?


Viele Grüße

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Du solltest Dir neben dem "MSP430x2xx Family User's Guide" auch die 
aktuellen Codebeispiele von TI ansehen.

www.ti.com/lit/zip/slac463

Möchte man beim 'G2553 gleichzeitig die UART verwenden, so wird die 
Angelegenheit etwas komplizierter, weil die USCI_B, die für I2C 
zuständig ist, und die USCI_A, die für den UART-Betrieb zuständig ist, 
sich die Interruptvektoren teilen.

Aber auch dafür gibt es Beispielcode, den findet man in der 
arduino-artigen Programmierumgebung "energia".

http://energia.nu/download/

Nein, das bedeutet nicht, daß man jetzt "arduino-like" programmieren 
muss, die relevanten Codeteile sind in reinem C geschrieben und lassen 
sich mit vertretbarem Aufwand auch mit anderen Compilern als gcc 
verwenden (für IAR müssen die Interruptvektordeklarationen angepasst 
werden und "boolean" durch "bool" ersetzt werden).

Die relevanten Dateien finden sich in der energia-Verzeichnisstruktur* 
unter "hardware/msp430/cores/msp430/"
und sind

usci_isr_handler.c/usci_isr_handler.h
twi.c/twi.h
hardware_serial.cpp/hardware_serial.h

Der I2C-Teil lässt sich recht leicht aus dem "energia"-Gerüst lösen 
(eingebundene Headerdateien anpassen, statt "energia.h" "msp430.h" 
einbinden, "stdint.h" und "stdbool.h"), und mit etwas Geschick sollte 
gleiches auch mit dem Code für die UART möglich sein.

Der Code ist so flexibel geschrieben, daß er nicht nur auf 'G2553 
verwendbar ist, sondern auch auf Controllern, bei denen USCI_A und 
USCI_B getrennte Interruptvektoren verwenden, und auch auf Controllern 
mit EUSCI genutzt werden kann. Das wird mit bedingter Compilation 
gemacht; die da verwendeten Symbole werden von den zum jeweiligen 
Controller gehörenden Headerdateien korrekt gesetzt, sowohl in den des 
mit "energia" mitgelieferten gcc als auch denen der "Embedded Workbench" 
von IAR.


*) Ich habe hier gerade einen Mac, daher wird es wenig bringen, den 
absoluten Pfad anzugeben, aber eine Suche nach der Datei "twi.c" wird 
den richtigen Ort auch für die anderen Dateien ergeben.

von Max (Gast)


Lesenswert?

Hallo,

erstmal vielen Dank für die Tipps! Über den Userguide hänge ich jetzt 
schon ziemlich lange und bin dann irgendwann nicht mehr schlauer 
geworden.
Nach einer ersten Durchsicht des Energia-Codes kehrt dann doch 
Ernüchterung ein. Ich hätte nicht gedacht, dass hinter dem 
Interrupt-basierten Code doch soviel steckt. Über das Pollen, wäre es 
denkbar einfacher. Hätte ich dadurch große Nachteile? Das der Code dann 
an gewissen stellen steht und nichts tut ist mir klar. Aber gibt es 
sonst noch nennenswerte Nachteile?

Viele Grüße

von Max (Gast)


Lesenswert?

Ich nochmal.

Da ich mich jetzt langsam ranwagen möchte, dachte ich mir, dass ich es 
erstmal mit Warteschleifen zum Laufen bekomme und dann mit Interrupts.

Nun habe ich folgendes:
1
#define WRITE  0x01
2
#define DS3232  0x68
3
4
unsigned char rx_data = 0;
5
6
void i2c_txInit(unsigned char device_addr)
7
{
8
  while (UCB0CTL1 & UCTXSTP);               // Ensure stop condition got sent// Disable RX interrupt
9
  UCB0CTL1 |= UCSWRST;                      // Enable SW reset
10
  UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
11
  UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
12
  UCB0BR0 = 10;                             // fSCL = SMCLK/10 = ~100kHz
13
  UCB0BR1 = 0;
14
  UCB0I2CSA = device_addr;                         // Slave Address is 048h
15
  UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
16
  IE2 |= UCB0TXIE;                          // Enable TX interrupt
17
}
18
19
void i2c_rxInit(unsigned char device_addr)
20
{
21
  IE2 &= ~UCB0TXIE;
22
  UCB0CTL1 |= UCSWRST;                      // Enable SW reset
23
  UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
24
  UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
25
  UCB0BR0 = 10;                             // fSCL = SMCLK/10 = ~100kHz
26
  UCB0BR1 = 0;
27
  UCB0I2CSA = device_addr;                         // Slave Address is 048h
28
  UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
29
  IE2 |= UCB0RXIE;                          // Enable RX interrupt
30
}
31
32
void i2c_start(unsigned char device_addr, unsigned char RW)
33
{
34
  while(UCB0STAT & UCBUSY);   // wait for STOP
35
  if(RW == READ)
36
  {
37
    i2c_rxInit(device_addr);
38
    UCB0CTL1 &= ~UCTR;  // read-access
39
    UCB0CTL1 |= UCTXSTT;
40
  }
41
  else
42
  {
43
    i2c_txInit(device_addr);
44
    UCB0CTL1 |= UCTR;  // write-access
45
    UCB0CTL1 |= UCTXSTT;
46
  }
47
}
48
49
void i2c_stop()
50
{
51
  while(UCB0CTL1 & UCTXSTP);
52
  UCB0CTL1 |= UCTXSTP;
53
}
54
55
void i2c_restart(unsigned char device_addr, unsigned char RW)
56
{
57
  while(UCB0CTL1 & UCTXSTP);
58
  i2c_start(device_addr, RW);
59
}
60
61
void i2c_write(unsigned char data)
62
{
63
  while (!(IFG2 & UCB0TXIFG));
64
  UCB0TXBUF = data;
65
}
66
67
void i2c_read(unsigned char *data)
68
{
69
  while(!(IFG2 & UCB0RXIFG));
70
  rx_data = UCB0TXBUF;
71
  UCB0CTL1 |= UCTXNACK;
72
}
73
74
int main(void)
75
{
76
    WDTCTL = WDTPW | WDTHOLD;  // Stop watchdog timer
77
78
    BCSCTL1 = CALBC1_1MHZ; // Set DCO to 1Mhz
79
    DCOCTL = CALDCO_1MHZ;
80
81
    P1DIR |= 0x01;
82
83
    P1SEL |= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
84
    P1SEL2|= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
85
86
    i2c_start(DS3232, WRITE);
87
    i2c_write(0x15);
88
    i2c_write(0xAB);
89
    i2c_stop();
90
91
    i2c_start(DS3232, WRITE);
92
    i2c_write(0x15);
93
    i2c_restart(DS3232, READ);
94
    i2c_read(&rx_data);
95
    i2c_stop();
96
97
    for(;;)
98
    {
99
      volatile unsigned int i;  // volatile to prevent optimization
100
101
      P1OUT ^= 0x01;        // Toggle P1.0 using exclusive-OR
102
103
      i = 10000;          // SW Delay
104
      do i--;
105
      while(i != 0);
106
    }
107
  
108
  return 0;
109
}

Da ich kein Oszi habe, kann ich leider nichts genaues dazu sagen, ob 
alles richtig funktioniert.
Nur beim Auslesen bin ich mir sicher, dass etwas nicht stimmen kann.
Sollte die Schreibfunktion funktioniert haben, müsste ich ja 0xAB 
auslesen; bekomme aber immer 0x15, also die Adresse.


Hoffe, ihr könnt mir bitte nochmal einen Anstoß geben.

(Die For-Schleife ist nur zum schauen, ob er bis hier hin kommt ;)

Viele Grüße

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.