Forum: Mikrocontroller und Digitale Elektronik SPI mit ADS7825


von Nikolas B. (physikant)


Lesenswert?

Hallo,

mein Ziel ist es, den ADS7825 (16bit, 25µS conversion time, 4 Kanäle) 
von TI mit einem mega168 bei 18.432Mhz auszulesen.
Auf Seite 11 im Datenblatt ist dazu ein Diagramm, wie das mit den 
Timings aussieht.
Man muss sowohl CS low als auch R/C auf low setzen, dann beginnt ein 
Wandlungsprozess. R/C muss nach mind. 40ns und maximal 12µS wieder auf 
high zurückgesetzt werden.
Nach einer Zeit, die für mich nicht klar ersichtlich ist, sendet der ADC 
ein sync-Signal, kurz darauf (laut Datenblatt etwa 40ns) beginnt er, die 
Daten seriell zu versenden, MSB first.

CS habe ich fest mit GND verbunden.

Nun hatte ich bisher folgende Auslese-Funktion:
1
uint16_t ADCread(void)
2
{
3
  DCLK_PORT &= ~(1<<DCLK_PIN); //erste SCK-Flanke erzeugen
4
  uint16_t data = 0;
5
  STARTSIG_PORT |= (1<<STARTSIG_PIN); //Wandlung beenden
6
  DCLK_PORT |= (1<<DCLK_PIN);   
7
  DCLK_PORT &= ~(1<<DCLK_PIN); // 1 1/2 Takte warten
8
  DCLK_PORT |= (1<<DCLK_PIN);             
9
  for(int i = 15; i >= 0; i--)
10
  {
11
    DCLK_PORT &= ~(1<<DCLK_PIN); //fallende Flanke
12
    data |= ((DATASIG_PORT&(1<<DATASIG_PIN))<<i); //Daten lesen
13
    DCLK_PORT |= (1<<DCLK_PIN); //steigende Flanke
14
                    
15
  }
16
  STARTSIG_PORT &= ~(1<<STARTSIG_PIN); //Wandlung beginnen
17
  return data;            
18
}

Leider hat sich die als ziemlich instabil erwiesen. Jetzt hat mir hier 
in einem anderen Thread jemand gesagt, die Verbindung sei über SPI Mode1 
zu realisieren. Also hab ich das mal probiert:
1
#define STARTSIG_DDR   DDRD
2
#define STARTSIG_PORT   PORTD
3
#define STARTSIG_PIN    PD5
4
#define DDRMISO DDRB    
5
#define SEG_MISO PB4    
6
#define DDRSCK DDRB     
7
#define SEG_SCK PB5      
8
9
10
void ADC_init()
11
{
12
 SPCR = (0<<SPIE)|(1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(1<<CPHA)|(0<<SPR1)|(0<<SPR0);
13
 SPSR = (1<<SPI2X);
14
 // SPIE 0 = Interrupt für fertig gesendete Daten deaktiviert
15
 // SPE  1 = SPI aktiviert
16
 // DORD 0 = Most significant bit (MSB) zuerst übermittelt
17
 // MSTR 1 = Der ATmega ist Master der SPI-Übertragung
18
 // CPOL 0 = Im Grundzustand ist das Signal auf GND
19
 // CPHA 1 = Bei der steigenden Flanke wird geschrieben, bei der fallenden gelesen
20
 // SPR1 0 & SPR0 0 = Übertragungstakt ist 1/4 des Prozessortaktes
21
 // SPI2X 1 = Doppelte Geschwindigkeit
22
 //DDRSS  |= (1 << SEG_SS);
23
 STARTSIG_PORT  |= (1 << STARTSIG_PIN);   
24
 DDRMISO  |= (1 << SEG_MISO);   
25
 DDRSCK  |= (1 << SEG_SCK);   
26
}
27
28
uint16_t ADCread(void) //Empfaengt Daten ueber SPI
29
{
30
 STARTSIG_PORT &= ~(1 << STARTSIG_PIN);       // Aufheben des Start-Conversion-Bits
31
 _delay_us(1);
32
 STARTSIG_PORT |= (1 << STARTSIG_PIN);        // Setzen des Start-Conversion-Bits
33
 while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung 
34
 return SPDR;
35
}

Hier hängt er jedoch schlicht und ergreifend in der while-Schleife fest. 
Was mache ich falsch?
Danke im Vorraus!

Grüße
  Nikolas

von Krapao (Gast)


Lesenswert?

> Nun hatte ich bisher folgende Auslese-Funktion:
Beitrag "Re: schnelle Kommunikation über UART"

Hast du einen Logikanalysator um das tatsächliche Timing zu 
protokollieren? Das Biest soll bei SPI nicht unkritisch sein 
(http://reviews.ti.com/8594/19090/reviews.htm?sort=rating&dir=asc).

von Krapao (Gast)


Lesenswert?

>  //DDRSS  |= (1 << SEG_SS);

Auskommentieren ist IMHO falsch.

AVR151: Setup And Use of The SPI
http://www.atmel.com/Images/doc2585.pdf

"2.9.1 Example 1 - SPI communication controlled by polling:
2.9.1.1 Master Side:
If no interrupts are used there is just the SPI module and its pins to 
configure. Important in this example is the setting of the SS pin as 
output pin. This has to be done before the SPI is enabled in master 
mode. Enabling the SPI while the SS pin is still configured as an input 
pin would cause the SPI to switch to slave mode immediately if a low 
level is applied to this pin. This pin is always configured as an input 
pin in slave mode"

> STARTSIG_PORT |= (1 << STARTSIG_PIN); // Setzen des Start-Conversion-Bits
> while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung

IMHO fehlt dazwischen etwas: Die Erzeugung der Clock durch den AVR 
(Master)

"The master however generates the clock signal only while sending data.
That means that the master has to send data to the slave to read data 
from the slave."

Deswegen sollte da ein Dummysenden vor das while()

1
SPDR = 0xAA;

Der ADS7825 sendet 16-Bits während deine ADCread(void) nur einmal das 
8-Bit Register SPDR ausliest. Die SPI Sequenz würde ich daher zweimal 
nacheinander ausführen

1
 uint16_t adcdata;
2
 SPDR = 0xAA;
3
 while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung 
4
 adcdata = SPDR<<8; // ADC7825 sendet MSB zuerst
5
 SPDR = 0xAA;
6
 while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung 
7
 adcdata |= SPDR;
8
 return adcdata;

von Nikolas B. (physikant)


Lesenswert?

Nein, hab ich nicht ...
Wenn alle Stricke reißen muss ich wohl bei meiner eigenen Kommunikation 
bleiben und damit leben, dass sie manchmal versagt.

EDIT: habe deine Korrekturen berücksichtigt. Leider hängt er noch immer 
in der Schleife.

Grüße
   Nikolas

von Krapao (Gast)


Lesenswert?

Ohne LA oder Oszi ist das schwer zu debuggen.

Der SPI Modus bei externer Clock sieht mir in FIGURE 5 aus dem DB nach 
CPOL 1 und CPHA 1 aus.

Externe Clock ist hervorgehoben, weil ich mangels Schaltplan und 
Codebeschreibung nicht weiss, ob du mit interner oder externer Clock 
arbeitest.

Der Vorschlag oben bezog sich vorschnell auf die Annahme, dass du mit 
externer SPI Clock (Master liefert Clock) arbeitest und da muss das SPDR 
= ... her.

Es wäre IMHO hilfreich, wenn du mit einem Schaltplan an gibst, welche 
der essentiellen Leitungen nCS und R/C des ADC du mit welcher Leitung 
des AVR bedienst.

von Nikolas B. (physikant)


Angehängte Dateien:

Lesenswert?

Naja nen Logicanalyzer kann ich mir nicht leisten, ein Oszi hab ich 
zwar, aber ohne single shot wird da wohl wenig zu machen sein.
Da mein Schaltplan mal wieder furchtbar unlesbar geworden ist, hier die 
relevante Beschaltung:
Par/Ser , CS, CONTC und PWRD sind auf GND.
A0 und A1 sind an PC2 und PC3.
D1 (DATA) ist an PB4(MISO).
D2 (CLK) ist an PB5(CLK).
R/C ist an PD5.
D0(TAG) ist auf GND.
D4 (EXTCLK) ist auf HIGH.

Momentan sieht mein Code so aus:
1
#define STARTSIG_DDR   DDRD
2
#define STARTSIG_PORT   PORTD
3
#define STARTSIG_PIN    PD5
4
#define DDRMISO DDRB    
5
#define SEG_MISO PB4    
6
#define DDRSCK DDRB     
7
#define SEG_SCK PB5  
8
void ADC_init() 
9
{
10
 SPCR = (0<<SPIE)|(1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(1<<CPHA)|(0<<SPR1)|(0<<SPR0);
11
 SPSR = (1<<SPI2X);
12
 // SPIE 0 = Interrupt für fertig gesendete Daten deaktiviert
13
 // SPE  1 = SPI aktiviert
14
 // DORD 0 = Most significant bit (MSB) zuerst übermittelt
15
 // MSTR 1 = Der ATmega ist Master der SPI-Übertragung
16
 // CPOL 0 = Im Grundzustand ist das Signal auf GND
17
 // CPHA 1 = Bei der steigenden Flanke wird geschrieben, bei der fallenden gelesen
18
 // SPR1 0 & SPR0 0 = Übertragungstakt ist 1/4 des Prozessortaktes
19
 // SPI2X 1 = Doppelte Geschwindigkeit
20
 STARTSIG_PORT  |= (1 << STARTSIG_PIN);   
21
 DDRMISO  |= (1 << SEG_MISO);   
22
 DDRSCK  |= (1 << SEG_SCK);   
23
}
24
25
uint16_t ADCread(void) //Empfaengt Daten ueber SPI
26
{
27
 uint16_t daten = 0;
28
 STARTSIG_PORT &= ~(1 << STARTSIG_PIN);       // Aufheben des Start-Conversion-Bits
29
 _delay_us(1);
30
 STARTSIG_PORT |= (1 << STARTSIG_PIN);        // Setzen des Start-Conversion-Bits
31
 SPDR = 0xAA;
32
 while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung 
33
 daten = SPDR<<8;
34
 SPDR = 0xAA;
35
 while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung 
36
 daten |= SPDR;
37
 return daten;
38
}
Allerdings habe ich schon CPOL 1 getestet, es macht keinen Unterschied.
An meiner Übertragung zum PC (mittels UART) liegts nicht, die läuft mit 
384000 fehlerfrei, z.B. wenn ich konstante Werte sende.
Auch an der grundsätzlichen Beschaltung kanns eigentlich nicht liegen, 
da ja der zuvor gepostete Code funktionierte. Allerdings verschob sich 
manchmal die Stelle, an der gelesen wird, sprich ein Datenpaket beginnt 
mitten im Byte und endet dann zur Hälfte im nächsten Datenpunkt. Da ich 
keine Lösung gefunden habe, das zu beheben, wollte ich mich dann doch 
mal um SPI kümmern.
Danke auf jeden Fall soweit!

Grüße
   Nikolas

-

von Mar V. (marvol)


Lesenswert?

Hallo.

Spontan sehe ich, dass die Delay vor eine Conversion 1,4 us beträgt.

Aber grundsätzlich würde ich mal nicht den SPI benutzen, sondern die 
Bits separat ansteuern (Software SPI), dann kannst Du das Timing 
Diagramm genau nach programmieren.

Gruß
Marvol

von Mar V. (marvol)


Lesenswert?

Hallo.

Ich sehe gerade, dass Dein Programmer natürlich auch auf SPI liegt, 
damit die ADC Kommunikation sauber läuft, musst Du Programmer abziehen.

Gruß
Marvol

von Nikolas B. (physikant)


Lesenswert?

Der Programmer ändert garnix, ob er nun dran ist oder nicht, gerade 
getestet.

SoftwareSPI habe ich doch implementiert, siehe meinen ersten Post, nur 
richtig funktionieren tuts nicht.
Grüße
  Nikolas

von eProfi (Gast)


Lesenswert?

for(int i = 15; i >= 0; i--)
  {
    DCLK_PORT &= ~(1<<DCLK_PIN); //fallende Flanke
    data |= ((DATASIG_PORT&(1<<DATASIG_PIN))<<i); //Daten lesen
    DCLK_PORT |= (1<<DCLK_PIN); //steigende Flanke

  }
Das klappt aber nur richtig, wenn DATASIG_PIN 0 ist, sonst landen die 
oberen Bits im Nirwana.

Besser:
    mask=0x8000;do{
      DCLK_PORT &= ~(1<<DCLK_PIN); //fallende Flanke
      if((DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
      DCLK_PORT |=  (1<<DCLK_PIN); //steigende Flanke
      }while((mask>>=1));

von eProfi (Gast)


Lesenswert?

if((DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
-->
      if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;

von Nikolas B. (physikant)


Lesenswert?

Sie haben natürlich recht. Zufällig ist DATASIG_PIN tatsächlich 0 und es 
funktioniert.
Ich habe ihren Code eingebaut und er löst auch ein weiteres Problem. Ich 
bin mir nicht sicher, warum er das tut, aber er tuts ;)
Grundsätzlich verstehe ich die Idee hinter dem Code, durch eine Maske 
eine 1 durchzuschieben und bei Bedarf mit dem Ergebnis zu "odern".
Lediglich den code (mask>>=1) verstehe ich nicht, er schiebt wohl die 1 
durch, bis es nicht mehr geht, aber so richtig klar ist mir nicht, was 
der Operator >>= macht.
Danke auf jeden Fall! Hardware-SPI kann mir gestohlen bleiben!

von eProfi (Gast)


Lesenswert?

Freut mich, dass es bei Dir klappt.

while((mask>>=1));

ist eine Kurzschreibweise für
while((mask=mask>>1)!=0);

was weiter ausgeschrieben
mask=mask>>1; while(mask!=0);
bedeutet.
Der Clou ist, dass die Variable zwei Aufgaben erfüllt: Maske und 
Bitzähler.

Die doppelten Klammern setze ich, weil bei manchen Compilern dann die 
Warnung "Assignment in Condition" nicht kommt.

Wie äußerte sich das erste Problem?

Welches war das zweite Problem?
Die Codegröße? ;-)  Laufzeit?
((DATASIG_PORT&(1<<DATASIG_PIN))<<i);  erzeugt aufwendigen Code, da die 
Anzahl der Schiebungen von i abhängig sein muss (außer die CPU hätte 
einen Barrel-Shifter).

von Nikolas B. (physikant)


Lesenswert?

Ok, faszinierende Abkürzung! Wieder was gelernt :)

Das erste Problem, dass er nur auf einem Pin mit Nummer 0 funktioniert, 
war ja im Prinzip nicht relevant.
Das zweite Problem war etwas kurioser.
Ich habe eine UART-Verbindung zum PC hergestellt und in main hatte ich 
das hier:
while(1)
{
send_UART(ADCread());
}
(denke das Prinzip ist klar).
Jedenfalls ging das mit meinem Code prinzipiell, aber nur wenn ich nach 
jedem senden ein delay von 17µS eingefügt habe in die Schleife. Sonst 
wurde das Paket in sich verschoben, aus:
11111111 00000000
11111111 00000000
wurde:
00001111 11110000
00001111 11110000
wobei die Verschiebung nicht fest war und auch mal 3 oder nur 2 Bits 
betrug.
Das ist gleich doppelt problematisch gewesen:
1) drückt es meine maximale Frequenz von 19kHz auf 13kHz.
2) waren die 17µS ein instabiler Wert; um mit anderen PCs (!) als meinem 
üblichen zu kommunizieren waren andere delays notwendig.

Als ich mich dann wiederfand, eine PC-seitige Routine zu schreiben, die 
automatisch das minimale Delay bestimmt und die auch nicht so wirklich 
funktionieren wollte, dachte ich mir: es heisst zwar never touch a 
running system, aber das ist mir doch zu sehr ein Krampf, das muss 
besser gehen :)

Danke nochnmal!
  Nikolas

von Nikolas B. (physikant)


Lesenswert?

Hallo,

nachdem ich in der Entwicklung des Projektes, das den ADS7825 verwendet, 
weiter gekommen bin, haben sich neue Probleme aufgeworfen, die leider 
wohl am bisherigen Code liegen.
Kurzer Überblick, was im Thread bisher zustande kam:

- Hardware-SPI zum Auslesen des ADS7825 ist, trotz Beachtung diverser 
Hürden, daran gescheitert, dass der Übertragungsvorgang nie als 
abgeschlossen bestätigt wird

- als Stream funktioniert folgender Code:
1
uint16_t ADC_read(void)
2
{
3
  DCLK_PORT &= ~(1<<DCLK_PIN);
4
  uint16_t data = 0;
5
  uint16_t mask = 0x8000;
6
  RC_PORT |= (1<<RC_PIN);
7
  DCLK_PORT |= (1<<DCLK_PIN);  
8
  DCLK_PORT &= ~(1<<DCLK_PIN);
9
  DCLK_PORT |= (1<<DCLK_PIN);
10
  do{
11
      DCLK_PORT &= ~(1<<DCLK_PIN);
12
      if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
13
      DCLK_PORT |=  (1<<DCLK_PIN);
14
      }while((mask>>=1));
15
  RC_PORT &= ~(1<<RC_PIN);
16
  return data;            
17
}
Dabei dauert eine Wandlung + Übertragung mit 384kbaud an den PC 52µs.
Allerdings  habe ich nun festgestellt, dass eine Änderung des 
ADC-Channels erst 4 Wandlungen später Effekt zeigt, eine Änderung der zu 
messenden Spannung 3 Wandlungen später. Das ist leider in meiner 
zeitkritischen Anwendung ungeeignet.
Als Ursache sehe ich klar den überhaupt nicht Datenblatt-gemäßen 
Auslesevorgang. Wie man sieht wird R/C am Ende des Auslesens LOW gesetzt 
(und damit eine Wandlung gestartet), und erst wieder HIGH genommen, wenn 
ein neuer Auslesevorgang begonnen wird. Die Zeit, die R/C LOW ist hängt 
also massiv vom Rest des µC-Codes ab. Das Datenblatt sagt dazu:
"CS and/or R/C must go HIGH before BUSY goes HIGH or
a new conversion will be initiated without sufficient time to
acquire a new signal."
Ich habe also den Code nochmal genau an Seite 11 des Datenblattes 
angepasst, und folgendes erzeugt:
1
uint16_t ADC_read(void)
2
{
3
  uint16_t data = 0;
4
  uint16_t mask = 0x8000;
5
  RC_PORT &= ~(1<<RC_PIN);
6
  CS_PORT &= ~(1<<CS_PIN);
7
  _delay_us(1);
8
  RC_PORT |= (1<<RC_PIN);
9
  DCLK_PORT |= (1<<DCLK_PIN);
10
  while(!(SYNC_PORT & (1<<SYNC_PIN)));
11
   do{
12
      DCLK_PORT &= ~(1<<DCLK_PIN);
13
      if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
14
      DCLK_PORT |=  (1<<DCLK_PIN);
15
      }while((mask>>=1));
16
  CS_PORT &= ~(1<<CS_PIN);
17
}
Ergebnis ist, dass die erste Wandlung 0 ergibt, die zweite Wandlung 
1000, die dritte ebenfalls und die vierte nicht fertig wird, da er in 
der Warteschleife für SYNC hängenbleibt.

Leider besitze ich (wie zuvor geschrieben) keinen Logicanalyzer. Für das 
Setup und die Rahmenbedingungen, siehe meine vorherigen Postings, 
unterschied ist jetzt nur, dass ich CS nicht immer auf GND habe, sondern 
angeschlossen, genauso wie SYNC.

Jemand eine Idee, warum das Teil nicht so läuft, wie es soll?

Vielen Dank schonmal,
  Nikolas

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.