Forum: Mikrocontroller und Digitale Elektronik Atmega16 SPI Probleme


von fuelre F. (fuelre)


Lesenswert?

Hallo Zusammen

ich habe hier ein Problem mit SPI.
Ich möchte einen LTC1865 mit einem Atmega16 der bei 12MHz läuft 
auslesen.

Dazu habe ich vollgende Routine geschrieben, die ich per
1
ADC_get(0);
aufrufe
1
// SPI init
2
void init_SPI (void)
3
{
4
  DDRB = DDRB | 0b10110000;  //SCKp7 MOSIp5 CSp4 = Output
5
  DDRB = DDRB & 0b10101111;  //MISOp6 = Input
6
  PORTB = PORTB & 0b11101111; 
7
  SPCR=(1<<SPE);  //SPI starten
8
  //    Master    Polarität  CLK Phase  Taktteiler
9
  SPCR = (1<<MSTR) | (1<<CPOL) | (1<<CPHA) | (1<<SPR1) | (1<<SPR0);
10
  //    Taktteiler
11
  SPSR = (1<<SPI2X);
12
13
}
14
volatile int adc_datah, adc_datal;  //Variablen aus dem ADC
15
void ADC_get(int ADC_CH)
16
{
17
//   PORTB = PORTB & 0b11101111;
18
   _delay_ms(1000);
19
  PORTB = PORTB | 0b00010000;
20
  _delay_ms(10);
21
  uart_putc(1);
22
23
  uart_putc(2);
24
  switch(ADC_CH)
25
  {
26
    case 0:
27
      uart_putc(3);
28
      SPDR = 0x80;          //Daten anlegen für welchen Channel
29
      PORTB = PORTB & 0b11101111;
30
      uart_putc(4);
31
      //Bis hier her funktioniert es - allerdings bleibt er in der Schleife hängen
32
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
33
      uart_putc(5);
34
      adc_datah = SPDR;        //Daten aus Register und in datah speichern
35
      SPDR = 0x00;          //Pseudodaten anlegen
36
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
37
      adc_datal = SPDR;        //Daten aus Register lsen und in datal speichern
38
      uart_putc(5);
39
      break;
40
    case 1:
41
      // kann zur zeit vernachlässig werden
42
      uart_putc(3);
43
      SPDR = 0xC0;          //Daten anlegen für welchen Channel
44
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
45
      adc_datah = SPDR;        //Daten aus Register und in datah speichern
46
      SPDR = 0x00;          //Pseudodaten anlegen
47
      while (!(SPSR & !(1<<SPIF)));  //warten bis Daten da sind
48
      adc_datal = SPDR;        //Daten aus Register lesen und in datal speichern
49
      break;
50
51
    uart_putc(6);
52
  }
53
}

Die uart_putc() habe ich drinnen, dass ich erkenne wo er nicht mehr 
weiterkommt, zudem konnte ich mit einem Dragon nachweißen, dass alle 
Register nach meinem Vorgaben gesetzt werden.
Dazu habe ich auch schon im Internet gesucht und ein paar dinge gefunden 
die ich falsch gemacht habe (zb. SPE zu spät gesetzt) doch diese sind 
nun behoben.

Hat jemand eine Idee wo der Fehler liegt?

Wenn ihr noch mehr Infos braucht bitte sagen.

von Karl H. (kbuchegg)


Lesenswert?

>   SPCR=(1<<SPE);  //SPI starten
>  //    Master    Polarität  CLK Phase  Taktteiler
>   SPCR = (1<<MSTR) | (1<<CPOL) | (1<<CPHA) | (1<<SPR1) | (1<<SPR0);

Ähm.

Nach
1
   i = 5;
2
   i = 8;

hat i ganz sicher den WErt 8. Von der vorher zugewiesenen 5 ist nichts 
mehr übrig.

Genauso auch hier:
Von deinem SPE Bit ist nach der zweiten Zuweisung nichts mehr übrig.
Man könnte auch sagen: Gratuliere. Mit der 2-ten Zuweisung hast du das 
SPI gerade wieder abgeschaltet.

> die ich falsch gemacht habe (zb. SPE zu spät gesetzt)
Was heißt "zu spät gesetzt". Mach eine einzige Zuweisung an SPCR, in der 
alle Bits gesetzt sind, die du brauchst und gut ists.

von fuelre F. (fuelre)


Lesenswert?

verda.............................t

VIELEN DANK - habe wohl den Baum vor lauter Bäumen nicht mehr gesehen.

naja jetzt komme ich per JTAG Debuging  über die while Schleife

DANKE

von fuelre F. (fuelre)


Lesenswert?

Falls mal jemand nach einer Lösung für den ADC sucht:
Ist zwar von den Timings noch nicht optimiert aber ansonsten 
funktioniert es
1
void ADC_get(int ADC_CH)
2
{
3
   PORTB = PORTB & 0b11101111;
4
   _delay_ms(10);
5
  PORTB = PORTB | 0b00010000;
6
  _delay_ms(10);
7
8
  switch(ADC_CH)
9
  {
10
    case 0:
11
      SPDR = 0x80;        //Daten anlegen für welchen Channel
12
      PORTB = PORTB & 0b11101111;
13
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
14
      adc_datah = SPDR;        //Daten aus Register und in datah speichern
15
      SPDR = 0x00;          //Pseudodaten anlegen
16
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
17
      adc_datal = SPDR;        //Daten aus Register lsen und in datal speichern
18
      PORTB = PORTB | 0b00010000;
19
      uart_putc(adc_datah);    //Ausgabe der Daten adc_datah und adc_datal
20
      uart_putc(adc_datal);
21
      break;
22
    case 1:
23
      //Hier praktisch das selbe für Channel 2
24
      break;
25
26
  }
27
}

von Falk B. (falk)


Lesenswert?

@ fuelre F. (fuelre)

>Falls mal jemand nach einer Lösung für den ADC sucht:
>Ist zwar von den Timings noch nicht optimiert aber ansonsten
>funktioniert es

Dafür muss man aber nicht identischen Code in mehrere case Zweige 
reinschreiben. Es reicht, per Switch oder Array das unterschiedliche 
Steuerwort zu dekodieren.
1
void ADC_get(int ADC_CH)
2
{
3
   PORTB = PORTB & 0b11101111;
4
   _delay_ms(10);
5
  PORTB = PORTB | 0b00010000;
6
  _delay_ms(10);
7
8
  switch(ADC_CH)
9
  {
10
    case 0:
11
      SPDR = 0x80;        //Kanal 1 auswählen
12
      break;
13
    case 1:
14
      SPDR = 0x40;        //Kanal 2 auswählen, NICHT GETESTET, nur Prinzip!
15
      break;
16
  }
17
  PORTB = PORTB & 0b11101111;
18
  while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
19
  adc_datah = SPDR;        //Daten aus Register und in datah speichern
20
  SPDR = 0x00;          //Pseudodaten anlegen
21
  while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
22
  adc_datal = SPDR;        //Daten aus Register lsen und in datal speichern
23
  PORTB = PORTB | 0b00010000;
24
  uart_putc(adc_datah);    //Ausgabe der Daten adc_datah und adc_datal
25
  uart_putc(adc_datal);
26
}

MFG
Falk

von fuelre (Gast)


Lesenswert?

Stimmt danke - soweit habe ich nicht mehr gedacht da ich keine Zeit mehr 
hatte

von Karl H. (kbuchegg)


Lesenswert?

Echt? Das funktioniert?
1
      SPDR = 0x80;        //Daten anlegen für welchen Channel
2
      PORTB = PORTB & 0b11101111;
3
      while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
4
      adc_datah = SPDR;        //Daten aus Register und in datah speichern

dann muss dein ADC ein Hellseher sein, wenn er schon vorher weiss, 
welche Daten welchen Kanales er als erstes Byte beim Byteaustausch 
rübergeben muss, in dessen Zuge du ihm erst mal die Kanalnummer 
mitteilst.

Warum blos glaube ich da nicht daran?
Aaaaah, weil du es nie mit 2 ADC-Kanälen probiert hast. Das könnte es 
erkläeren. Aber ansonsten kann das gar nicht gehen. Wenn du dem ADC die 
Kanalnummer geben musst, dann sind mindestens 3 SPI 
Byte-Austausch-Operationen notwendig, um die Kanalnummer und dann die 
darauf folgenden 2 Werte-Bytes von A nach B zu übertragen.

von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

@ Karl Heinz (kbuchegg) (Moderator)

>erkläeren. Aber ansonsten kann das gar nicht gehen. Wenn du dem ADC die
>Kanalnummer geben musst, dann sind mindestens 3 SPI
>Byte-Austausch-Operationen notwendig, um die Kanalnummer und dann die
>darauf folgenden 2 Werte-Bytes von A nach B zu übertragen.

Nein, es sind 2, weil der ADC etwas anders funktioniert. Man sendet nur 
2 Steuerbits im 1. Byte, der Rest ist don't care. Danach noch ein Dummy 
Byte. Parallel dazu wird das Ergebnis der VORHERIGEN Messung übertragen. 
Ob der Code jetzt aber wirklich gut ist hab ich nicht geprüft ;-)

von Karl H. (kbuchegg)


Lesenswert?

Falk Brunner schrieb:
> @ Karl Heinz (kbuchegg) (Moderator)
>
>>erkläeren. Aber ansonsten kann das gar nicht gehen. Wenn du dem ADC die
>>Kanalnummer geben musst, dann sind mindestens 3 SPI
>>Byte-Austausch-Operationen notwendig, um die Kanalnummer und dann die
>>darauf folgenden 2 Werte-Bytes von A nach B zu übertragen.
>
> Nein, es sind 2, weil der ADC etwas anders funktioniert. Man sendet nur
> 2 Steuerbits im 1. Byte, der Rest ist don't care. Danach noch ein Dummy
> Byte. Parallel dazu wird das Ergebnis der VORHERIGEN Messung übertragen.

OK. Das heist aber doch, dass sich dieses Steuerbyte dann erst auf die 
nächste Messung auswirkt.

Solange er nur mit 1 Kanal misst, ist das ja ok.
Aber sobald er abwechselnd auf beiden Kanälen messen will, muss man das 
berücksichtigen. Ich wollts nur noch mal explizit herausgestrichen 
haben. Denn ohne dieses Wissen, kann ein Aufruf von
1
  void ADC_get(int ADC_CH)
2
...
3
4
5
  channel0 = ADC_get( 0 );
6
  channel1 = ADC_get( 1 );

zu bösen Überraschungen bei den Werten führen. Die 0 in
1
  channel0 = ADC_get( 0 );
bezieht sich nicht auf das Ergebnis, das man aus der Funktion 
rauskriegt, sondern darauf, welches Ergebnis man beim nächsten Aufruf 
von ADC_get kriegt.

von Christian W. (Gast)


Lesenswert?

Karl Heinz schrieb:
> Solange er nur mit 1 Kanal misst, ist das ja ok.
> Aber sobald er abwechselnd auf beiden Kanälen messen will, muss man das
> berücksichtigen. Ich wollts nur noch mal explizit herausgestrichen
> haben. Denn ohne dieses Wissen, kann ein Aufruf von  void ADC_get(int
> ADC_CH)
> ...
>
>   channel0 = ADC_get( 0 );
>   channel1 = ADC_get( 1 );
>
> zu bösen Überraschungen bei den Werten führen. Die 0 in  channel0 =
> ADC_get( 0 );
> bezieht sich nicht auf das Ergebnis, das man aus der Funktion
> rauskriegt, sondern darauf, welches Ergebnis man beim nächsten Aufruf
> von ADC_get kriegt.

Wie kann man denn das Problem softwaretechnisch lösen, dass man am Ende 
über zwei Kanäle problemlos messen kann? Wie sieht der Aufruf bzw. wie 
ist er strukturiert? Arbeite ich nur mit einem Kanal, läuft es. Sobald 
ich wie beschrieben beide Kanäle nacheinander auswähle, kommt es zur 
fehlerhaften Ausgabe.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Christian W. schrieb:
> Wie kann man denn das Problem softwaretechnisch lösen, dass man am Ende
> über zwei Kanäle problemlos messen kann? Wie sieht der Aufruf bzw. wie

 a) Ein Dummy Aufruf genügt doch, oder ?
 b) Bei 250Ks sind es 4uS, solange kann dein Programm warten ?

von fuelre (Gast)


Lesenswert?

Hallo Christian

ich habe das gelöst indem ich in der Funktion abfrage welcher Channel 
als letztes mal abgerufen wurde. Falls nun der gewünschte channel nicht 
als letztes dran war, wird die konversation neu gestartet und solange 
gewartet. Wenn schon der richtige channel abgefragt wurde gibt die 
Funktion mir sofort den Wert zurück.

mfg fuelre

von Christian W. (Gast)


Lesenswert?

Hallo,

danke erstmal für die schnelle antwort. Also die ADC-Funktion mit einer 
if-Anweisung erweitern?

Hättest du vielleicht ein Codebeispiel dazu?

Schöne Grüße.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Christian W. schrieb:
> Hättest du vielleicht ein Codebeispiel dazu?

 Wozu ?
 Du hast es jetzt fast soweit.
 Einfach eine variable z.B. 'LastChan' oder so verwenden.
 Zuerst ein Dummy Aufruf mit Kanal 0, LastChan steht auf 0.

 Wenn du abwechselnd die Kanäle aufrufst, brauchst du nichts mehr
 zu machen, in LastChan steht von welchen Kanal die Werte sind. Nur
 halt die LastChan fleissig toggeln.

 Wenn nicht, wirst du die 4us warten müssen, falls LastChan ungleich...

von fuelre (Gast)


Lesenswert?

Hallo Christian

ich habe gerade das Projekt geöffnet und musste feststellen, dass ich 
den Teil nochmal anderst gelöst habe, da sich die Aufgabe geändert hat.

Hier musste ich die Channel abwechselnd abfragen, daher musste ich immer 
ein "dummy Aufruf" machen.
Danach habe ich noch mit 10 Werten gemittelt.
Ich glaube auch, dass ich die wartezeiten nicht ausgereizt habe, da dies 
bei mir nicht wichtig war.

Falls du doch noch interesse am Code hast:
1
#define ADC0 0
2
#define ADC1 1                            
3
#define set_CS_ADC PORTB = PORTB & 0b11110111;        
4
#define cls_CS_ADC PORTB = PORTB | 0b00001000;        
5
6
unsigned int ADC_get(char ADC_CH)
7
{
8
  unsigned char adc_datah, adc_datal;
9
  unsigned long temp_adc=0;
10
  switch(ADC_CH)
11
  {  
12
    case 1:  
13
    {
14
      set_CS_ADC
15
      SPDR = 0x80;
16
      while(!(SPSR & (1<<SPIF)));
17
      SPDR = 0x00;
18
      while(!(SPSR & (1<<SPIF)));  
19
      cls_CS_ADC
20
      
21
      for(char i =0;i<10;i++)
22
      {
23
        _delay_ms(10);
24
        set_CS_ADC
25
        SPDR = 0x80;
26
        while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
27
        adc_datah = SPDR;        //Daten aus Register und in datah speichern
28
        SPDR = 0x00;          //Pseudodaten anlegen
29
        while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
30
        adc_datal = SPDR;        //Daten aus Register lsen und in datal speichern
31
        cls_CS_ADC
32
        temp_adc = temp_adc + ((adc_datah*256)+adc_datal);
33
      }
34
      break;
35
    }    
36
    case 2:
37
    {  
38
      set_CS_ADC
39
      SPDR = 0xC0;
40
      while(!(SPSR & (1<<SPIF)));
41
      SPDR = 0x00;
42
      while(!(SPSR & (1<<SPIF)));
43
      cls_CS_ADC
44
      
45
      for(char i = 0; i<10; i++)
46
      {
47
        _delay_ms(10);
48
        set_CS_ADC
49
        SPDR = 0xC0;
50
        while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
51
        adc_datah = SPDR;        //Daten aus Register und in datah speichern
52
        SPDR = 0x00;          //Pseudodaten anlegen
53
        while(!(SPSR & (1<<SPIF)));    //warten bis Daten da sind
54
        adc_datal = SPDR;        //Daten aus Register lsen und in datal speichern
55
        cls_CS_ADC  
56
        temp_adc = temp_adc + ((adc_datah*256)+adc_datal);
57
      }
58
      break;  
59
    }
60
    default:
61
    {  
62
      break;
63
    }
64
  }

von Christian W. (Gast)


Lesenswert?

Okay also raten mir beide zu der Dummylösung :-)

Ich danke euch erstmal für die Hilfe. Ich werde mich nun mit dem 
Beispielcode beschäftigen und ihn für meine Aufgabe anpassen.

Bei Fragen melde ich mich einfach nochmal.

Gruß.

von fuelre (Gast)


Lesenswert?

Mir ist gerade aufgefallen, dass das return am Ende fehlt - ist aber 
eig. einfach - ADC_temp auf unsigned int casten und durch 10 teilen

von Thorsten (Gast)


Lesenswert?

Mit der Variante kann also der Aufruf der Funktion in der Main wie zu 
Beginn beschrieben über

channel0 = ADC_get( 0 );
channel1 = ADC_get( 1 );

nacheinander erfolgen oder muss man dort auch noch was beachten?

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.