Forum: Mikrocontroller und Digitale Elektronik 16-Bit DAC an SPI Schnittstelle mit MSP430


von Albert K. (Gast)


Lesenswert?

Mit der SPI Schnittstelle des MSP kann man ja maximal 8 Bit in das 
UCB0TXBUF Register laden und dann senden.

Jetzt ist es aber so, dass ich einen 16-Bit DAC habe, bei dem ich 16 Bit 
am Stück senden muss.
Ist das irgendwie möglich mit dem MSP ohne jetzt selbst so ein riesiges 
SPI Protokoll entwickeln zu müssen?

Der DAC ist ein LTC2641 -> siehe Seite 10/11
http://pdf1.alldatasheet.com/datasheet-pdf/view/216844/LINER/LTC2641.html

ich verwende den UCB0

Momentan habe ich den DAC so angeschlossen:
   DIN     P3.1/UC1SIMO/UC1SDA
   /CS     P3.2/UC1SOMI/UC1SCL
   SCLK    P3.3/UC1CLK/UC0STE

Ist das richtig so?

von Moi (Gast)


Lesenswert?

Öhm... SPI Protokoll entwickeln?!
Schicke einfach 2 mal 8 Bit hintereinander und du hast deine 16 Bit.

von Albert K. (Gast)


Lesenswert?

1
If there are less than 16 low-to-high transitions on SCLK while /CS remains
2
low, the data will be corrupted, and must be reloaded.

Es müssen vom DAC aus 16-Bit nacheinander kommen.

von smd (Gast)


Lesenswert?

weiß nicht ob es beim MSP auch so ist, aber wahrscheinlich gibt es ein 
Flag, das Du abfragen kannst und das gesetzt wird, sobald Dein erstes 
Byte fertig gesendet ist. Dann sendest Du das zweite und fertig bist Du.

Aber mal interessehalber - wo hast Du diesen Chip gekauft / wieviel 
bezahlt?

von Albert K. (Gast)


Lesenswert?

Meinst du den LTC2641?
Das ist ein Sample von Linear.

von smd (Gast)


Lesenswert?

Der MSP agiert doch als Master, oder? Dann kannst Du ja dafür sorgen, 
daß immer 16 clocks (2 Byte) gesendet werden, siehe oben, bevor CS 
wieder auf high geht. wenn Deine SPI-SChnittstelle immer ein CS nach 
jedem Byte generieren will, nimmst Du eben ein normales Portpin als CS.

von Stefan (Gast)


Lesenswert?

>Es müssen vom DAC aus 16-Bit nacheinander kommen.

Freilich, aber da steht nix von einer Zeitbegrenzung.
Lediglich /CS darf nicht zappeln zwischen den beiden 8-bit Daten.
Um /CS musst Du Dich eh' selbst kümmern, per I/O-Port.
Also liegt es ganz in Deiner Hand!

von Christian R. (supachris)


Lesenswert?

Albert K. wrote:
>
1
> If there are less than 16 low-to-high transitions on SCLK while /CS
2
> remains
3
> low, the data will be corrupted, and must be reloaded.
4
>
>
> Es müssen vom DAC aus 16-Bit nacheinander kommen.

Nein, das heißt lediglich, dass du das CS erst wieder auf H setzen 
darfst, wenn du 16 Bit reingeschoben hast.
Ich benutze auch LT ADCs mit 16 Bit SPI am MSP430 ohne Probleme. Ist 
doch nur ein Schieberegister in Hardware, das macht nur was, wenn eine 
Flanke am SCLK ist.

von Albert K. (Gast)


Lesenswert?

Ok, ich habe es grundsätzlich hingekriegt mit den 16-Bit, aber habe 
dennoch noch zwei Probleme.

1.) Wenn ich im Debugger schritt für schritt vorgehe, erhalte ich am DAC 
ausgang einmal 2.86V und 2.97V was stimmt und super ist.
Aber wenn ich dann das Programm in normaler Geschwindigkeit laufen 
lasse, verändert sich das dramatisch.
Statt 2.86V habe ich noch 2.25V und statt 2.97V nur noch 2.80V.
Kommt da der DAC nicht nach, bzw. muss ich irgendwo noch delays 
einbauen?

2.) Also wenn ich manuell eingebe was er im TXBUF dann an den DAC 
schicken soll, dann funktioniert es aber wenn ich wie unten eine 16-Bit 
variable einmal mit den oberen 8-Bit UND-Verknüpfe, sende und dann die 
unteren 8-Bit UND-Verknüpfe und sende, dann funktioniert es nur mit den 
unteren.

Bei den oberen also:
1
UCB0TXBUF = (dac & 0xFF00);
, schreibt er dann einfach 0x0000 in den TXBUF.

Was ist daran falsch?


Hier noch der C-Code (dac ist eine 16-Bit Unsigned variable):
1
/* init */
2
  UCB0CTL0 |= UCCKPH + UCMSB + UCMST + UCSYNC;       // 3-pin, 8-bit SPI master, MSB 1st
3
  UCB0CTL1 |= UCSSEL_2;               // SMCLK
4
  UCB0BR0 = 0x02;
5
  UCB0BR1 = 0;
6
  IE2 = UCB0TXIE;                     // Übertragungsinterrupt erlauben
7
  UCB0CTL1 &= ~UCSWRST;               // USCIB0 starten
8
9
/* code */
10
     P3OUT &= ~0x04;                   // DAC /CS enable
11
     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
12
     UCB0TXBUF = (dac & 0xFF00);               // Bit 15..8 senden
13
     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
14
     UCB0TXBUF = (dac & 0x00FF);               // Bit 7..0 senden
15
     P3OUT |=  0x04;                   // 16-Bit Übertragung fertig

von Christian R. (supachris)


Lesenswert?

Du nimmst das CS zu schnell wieder auf High. Wenn der TX-Buffer bereit 
ist, heißt das nicht, dass das Byte schon rausgeschoben ist. Das 
TX-Register ist doppelt gepuffert. Du musst warten, bis das RX-Flag im 
IFG2 gesetzt wird, erst dann ist das Byte raus. SPI sendet und empfängt 
ja gleichzeitig, also musst du warten bis der Empfang komplett ist. Dann 
musst du noch das Flag manuell zurücksetzen, oder den RX-Buffer in eine 
Dummy-Variable auslesen, da wird es zurück gesetzt.

von Stefan (Gast)


Lesenswert?

>Bei den oberen also:
1
UCB0TXBUF = (dac & 0xFF00);
>, schreibt er dann einfach 0x0000 in den TXBUF.

Nö, er schreibt 0x00 rein, ist doch nur ein 8-bit-Register,
was ja anfänglich Dein Problem war!

von Christian R. (supachris)


Lesenswert?

Jo, das außerdem noch, du musst natürlich das HighByte nach unten 
schieben:
Etwa so:
1
unsigned char dummy;
2
3
P3OUT &= ~0x04;                   // DAC /CS enable
4
while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
5
UCB0TXBUF = (dac >> 8);               // Bit 15..8 senden
6
while (!(IFG2 & UCB0RXIFG));
7
dummy = UCB0RXBUF;
8
while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
9
UCB0TXBUF = (dac);               // Bit 7..0 senden
10
while (!(IFG2 & UCB0RXIFG));
11
dummy = UCB0RXBUF;
12
P3OUT |=  0x04;                   // 16-Bit Übertragung fertig

von Albert K. (Gast)


Lesenswert?

Ok, er schneidet die oberen 8 Bit einfach ab, das war wohl ein 
überlegungsfehler von mir.
Jetzt mit einer buffervariable funktioniert es.

Aber das andere Problem habe ich noch immer:

Hier mal der aktuelle Code:
1
/* init */
2
  INT16U dac_buffer;
3
  UCB0CTL0 |= UCCKPH + UCMSB + UCMST + UCSYNC;       // 3-pin, 8-bit SPI master, MSB 1st
4
  UCB0CTL1 |= UCSSEL_2;               // SMCLK
5
  UCB0BR0 = 0x02;
6
  UCB0BR1 = 0;
7
  IE2 = UCB0TXIE + UCB0RXIE;                     // Übertragungsinterrupt erlauben
8
  UCB0CTL1 &= ~UCSWRST;               // USCIB0 starten
9
10
/* code */
11
     P3OUT &= ~0x04;                   // DAC /CS enable
12
     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
13
     dac_buffer = dac & 0xFF00;        // Bit 15..8 senden
14
     UCB0TXBUF = dac_buffer >> 8;
15
     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
16
     UCB0TXBUF = dac & 0x00FF;         // Bit 7..0 senden
17
     while (!(IFG2 & UCB0RXIFG));      // Warte bis TXBUF bereit
18
     P3OUT |=  0x04;                   // 16-Bit Übertragung fertig

Braucht es die erste while überhaupt gleich nach dem /CS enable 
schalten?

Also die DA-Werte bei Step by Step Modus:
1.53V und 3.03V

Im normalen Modus:
mit RX Flagabfrage am Schluss:
2.78V und 3.03V

mit TX Flagabfrage am Schluss, anstatt dem RX-Flag (da ich ja nur sende 
könnte er ja das empfangen auch abbrechen):
2.30V und 3.03V

Ist es so nicht richtig mit den RX/TX Flags?

von Albert K. (Gast)


Lesenswert?

@christian
warum muss ich den RXBUF in eine dummy variable speichern?

Im user guide steht ja zum TXFlag:
UCB0TXIFG is set when UCB0TXBUF is empty.

Reicht es da nicht einfach dieses abzufragen?

von Stefan (Gast)


Lesenswert?

UCB0TXBUF is empty
heißt nicht, dass die Datenübertragung beendet ist, sondern nur dass 
der TXBuffer in das interne (nicht user-zugängliche) Schieberegister 
übertragen wurde!

Wo sind eigentlich Deine ISR-Routinen für RX/TX-IRQ???

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das hat Christian doch sehr verständlich beschrieben:

> Wenn der TX-Buffer bereit ist [UCB0TXIFG also gesetzt ist],
> heißt das nicht, dass das Byte schon rausgeschoben ist.
> Das TX-Register ist doppelt gepuffert. Du musst warten,
> bis das RX-Flag im IFG2 gesetzt wird, erst dann ist das
> Byte raus.
> SPI sendet und empfängt ja gleichzeitig, also musst du warten
> bis der Empfang komplett ist.
> Dann musst du noch das Flag manuell zurücksetzen,
> oder den RX-Buffer in eine Dummy-Variable auslesen, da[durch]
> wird es zurück gesetzt.

(Anmerkungen und Hervorhebungen von mir)

von Albert K. (Gast)


Lesenswert?

Also so wie christian es gesagt hat funktioniert es jetzt.
Aber wozu brauch ich den RXBUF immer in eine dummy variable zu 
speichern?

Brauche ich wirklich alle diese while schleifen/abfragen?

@stefan
ISR zu RX/TX habe ich keine. Was hätte ich dadurch konkret für vorteile?

von Stefan (Gast)


Lesenswert?

>ISR zu RX/TX habe ich keine. Was hätte ich dadurch konkret für vorteile?
Hängt von Dir ab ;-)
Du hast aber beide IRQs aktiviert:
1
IE2 = UCB0TXIE + UCB0RXIE;
Ohne dazugehörige ISR kann Dein Programm durchaus abschmieren (bei Dir 
hat Dich Dein Compiler offensichlich gerettet, indem er die 
IRQ-Vektortabelle mit reti's gefüllt hat)

von Christian R. (supachris)


Lesenswert?

Albert K. wrote:
> Also so wie christian es gesagt hat funktioniert es jetzt.
> Aber wozu brauch ich den RXBUF immer in eine dummy variable zu
> speichern?

Um das RX-Flag zu löschen. Kann man natürlich auch selber im IFG2 
löschen.

> Brauche ich wirklich alle diese while schleifen/abfragen?

Nö, natürlich nicht, aber dann funktioniert dein Programm nicht, wie du 
ja schon bemerkt hast. Du kannst die ganze Kiste natürlich auf Interrupt 
umbauen, macht aber nur Sinn, wenn die SPI viel langsamer getaktet ist 
als der Prozessor, und du zwischen dem Senden der Bytes noch andere 
Dinge erledigen willst.

Achja, deaktivier mal die Interrupts. Sonst kann der µC wie schon 
geschrieben abschmieren. Nicht jeder Kompiler ist so gnädig.

von Albert K. (Gast)


Lesenswert?

Vielen Dank für eure Hilfe.
Ist das erste Mal seit einem Jahr, dass ich wieder was programmiere und 
mit SPI hab ich auch noch nichts gemacht, darum kann es teilweise etwas 
länger gehen bis ich etwas verstanden habe, aber jetzt habe ich es 
glaube ich kappiert.

Mein neuer code:
1
/* init */
2
  UCB0CTL0 |= UCCKPH + UCMSB + UCMST + UCSYNC;       // 3-pin, 8-bit SPI master, MSB 1st
3
  UCB0CTL1 |= UCSSEL_2;               // SMCLK
4
  UCB0BR0 = 0x02;
5
  UCB0BR1 = 0;
6
  UCB0CTL1 &= ~UCSWRST;               // USCIB0 starten
7
8
/* code */
9
     P3OUT &= ~0x04;                   // DAC /CS enable
10
//     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
11
     UCB0TXBUF = dac >> 8;             // Bit 15..8 senden
12
     while (!(IFG2 & UCB0RXIFG));      // Warte bis gesendet
13
     IFG2 &= ~UCB0RXIFG;
14
//     while (!(IFG2 & UCB0TXIFG));      // Warte bis TXBUF bereit
15
     UCB0TXBUF = dac;                  // Bit 7..0 senden
16
     while (!(IFG2 & UCB0RXIFG));      // Warte bis gesendet
17
     IFG2 &= ~UCB0RXIFG;
18
     P3OUT |=  0x04;                   // 16-Bit Übertragung fertig

Die auskommentierten schleifen braucht es doch eigentlich nicht oder? 
Funktionieren tuts ohne, weil wenn man ja das RXFlag abfragt, braucht 
man das TXFlag ja nicht auch noch, da das TXFlag ja zuerst gesetzt wird 
oder?

Der Taktprescaler ist auf 2x256=512 gestellt. Stimmt das?
Das habe ich aus dem TI Sample übernommen.

Also das ganze Senden des 16-Bit Wertes geht ja nicht allzulange denke 
ich mal. Da sollte es hoffe ich reichen, wenn ich die anderen Dinge erst 
mache wenn das mit dem DAC abgeschlossen ist.

von Christian R. (supachris)


Lesenswert?

Nein, dein SPI Takt läuft mit dem halben SMCLK. Wenn du :512 haben 
willst, musst du das UCB0BR1 auf 2 setzen. Macht aber keinen Sinn, denn 
der LTC2641 kann maximal 50MHz SPI Clock, du kannst also so schnell 
machen, wie der MSP430 kann. Meines Wissens können die neuen MSPs den 
Takt auch ungeteilt benutzen, also :1, dann hast du den vollen SMCLK als 
SPI Takt. Ist ja sinnvoll. Und die Schleifen zum TX-Busy abfragen kann 
man im konkreten Fall sicherlich weglassen, aber die 2 Takte kann man 
auch sicherheitshalber reinmachen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

> Aber wozu brauch ich den RXBUF immer in eine dummy variable zu
> speichern?

Nun, in eine Variable musst Du den nicht speichern, aber einen 
Lesezugriff auf RXBUF musst Du durchführen. Und das geht eben am 
simpelsten mit einer Variablenzuweisung.

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.