Forum: Mikrocontroller und Digitale Elektronik AD-Wandler Free Running - Mega8


von Chris J. (Gast)


Lesenswert?

Wenn ich meinen AD-Wandler sagen will dass er:
-im FreeRunning-Mode laufen soll (ADEN=1) (ADCS=1) (ADFR=1)
-den Interrupt Flag setzen soll, aber > keinen Interrupt < auslösen soll
(ich will das Flag bei einem anderen Timer-Interrupt auslesen)

Dann muss ich doch nur (ADIE=1) setzen und kein Interrupt vom ADC funkt 
mir dazwischen, weil ich ja das I-bit im SREG nicht gesetzt habe.

Ist das so erstmal richtig?

Ich würd gern bei einem anderen Interrupt (der eh zur rechten Zeit 
kommt) abfragen ob die Wandlung fertig ist und dann den Wert in dieser 
Interrupt-Routine weiter verarbeiten.

Ich dachte da (bei 16MHz Takt) an einem Prescaler von 128 (9.6KHz 
Samplingrate bei 13 Takte/Umwandlung und 5kHz bei 25 Takte/Umwandlung)

von 2920 (Gast)


Lesenswert?

Sollt so gehen.

von Spess53 (Gast)


Lesenswert?

Hi

Da geht ewas durcheinander: Mit ADIE erlaubst du den ADC-Interrupt. Das 
I-Bit zeigt global einen Interrupt an. Ein ADC-Interrupt wird durch ADIF 
oder so ähnlich angezeigt.

MfG Spess

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

Wenn das I-Bit im SREG gelöscht (0) ist, wird gar kein Interrupt mehr 
ausgelöst. Wird das I-Bit wieder gesetzt, fallen alle in der 
Zwischenzeit ausgelösten Interrupts an. Den Interrupt des ADC kannst Du 
sperren, indem Du ADIE auf 0 läßt. Die Wandlung ist fertig, wenn ADIF 
gesetzt ist. Dieses Flag mußt Du nach dem Auslesen der 
ADC-Ergebnis-Register manuell auf 1 setzen, um es zu löschen und als 
Indikator für das nächste Wandlungsende zu nutzen. Im Free-Running Mode 
besteht, wenn Du den ADC-Interrupt nicht nutzt, die Gefahr, daß Du 
Wandlungen verpaßt.

von Chris J. (Gast)


Lesenswert?

Danke für die Erklärung, lass ich ADIE auf null und lösch dann einfach 
ADIF durch setzen auf eins.

Hier hab ich etwas geschrieben, ich hoffe es macht das was es soll.
1
void  adc_init(){
2
  // wähle AREF, intern 2.54V 
3
  ADMUX |= (1 << REFS1) | (1 << REFS0); 
4
   // Enable ADC, AD-Wandlung starten, FreeRunning,AD-Interrupt Enable , Divisor 128 -> 125kHz
5
  ADCSRA |= ( 1<<ADEN )|( 1<<ADSC ) | (1<<ADFR) | (1<<ADIE) |( 1<<ADPS0 )| (1<<ADPS1) |( 1<<ADPS2 ); 
6
7
  //AD-Kanal wechsel ermöglichen
8
  // ADCSRA &= ~((1 << ADFR)|(1<<ADEN)); 
9
  // ADCSRA |= (1 << ADSC); //AD-Wandlung starten
10
  // ADCSRA &= ~(1 << ADSC); //AD-Wandlung stoppen
11
}
12
13
uint16_t  getADC(void){
14
  // ohne AD-Wert Verlust
15
  //while (ADCSRA & _BV(ADSC) ){}
16
  //int adc_wert = ADC;
17
18
  // Verlust einer AD-Wertes möglich
19
  if (bit_is_set(ADCSRA,ADIF)){
20
  int adc_wert = ADC; // wert auslesen
21
22
  ADCSRA |=(1<<ADIF); // Interrupt Flag mit 1 löschen
23
  return adc-wert; // gebe (10)16-bit Wert zurück?
24
  }
25
}
26
27
// Kanal von 0 bis 7 , 14 , 15
28
void setMux(byte kanal){
29
ADMUX = (ADMUX & 0b11110000); // ich will die Referenz Spannung so lassen
30
ADMUX |= kanal; // Kanal einstellen
31
}

von Chris J. (Gast)


Lesenswert?

Fehler (... lass ich ADIE auf null ...)
1
void  adc_init(){
2
  // wähle AREF, intern 2.54V 
3
  ADMUX |= (1 << REFS1) | (1 << REFS0); 
4
   // Enable ADC, AD-Wandlung starten, FreeRunning,AD-Interrupt Enable , Divisor 128 -> 125kHz
5
  ADCSRA |= ( 1<<ADEN )|( 1<<ADSC ) | (1<<ADFR) |( 1<<ADPS0 )| (1<<ADPS1) |( 1<<ADPS2 ); 
6
}
7
8
uint16_t  getADC(void){
9
  // ohne AD-Wert Verlust (max. 13 Takte warten)
10
  //while (ADCSRA & _BV(ADSC) ){}
11
  //int adc_wert = ADC;
12
13
  // Verlust eines AD-Wertes ist möglich
14
  if (bit_is_set(ADCSRA,ADIF)){
15
  int adc_wert = ADC; // wert auslesen
16
17
  ADCSRA |=(1<<ADIF); // Interrupt Flag mit 1 löschen
18
  return adc-wert; // gebe (10)16-bit Wert zurück?
19
  }
20
}
21
22
// Kanal von 0 bis 7 , 14 , 15
23
void setMux(byte kanal){
24
  //AD-Kanal wechsel ermöglichen
25
  ADCSRA &= ~((1 << ADFR)|(1<<ADEN)); 
26
  ADCSRA &= ~(1 << ADSC); //AD-Wandlung stoppen
27
28
  ADMUX = (ADMUX & 0b11110000); // ich will die Referenz Spannung so lassen
29
  ADMUX |= kanal; // Kanal einstellen
30
31
  ADCSRA |= (1 << ADSC); //AD-Wandlung starten
32
}

Hab ich irgend was vergessen?
Was macht man sonst noch normalerweise so bei einer AD-Wandlung?

von Jörg X. (Gast)


Lesenswert?

>>Hab ich irgend was vergessen?
 1) ja, du kannst ADSC nämlich nicht löschen, das passiert aber 
automatisch, wenn der ADC abgescaltet wird

 2) ja, setMux() schaltet den ADC nicht wieder ein

 3) ja, Was macht deine get_ADC Funktion, wenn der ADC noch nicht fertig 
ist? (du hast keine Warteschleife oder einen brauchbaren 
Rückgabe-'Code').

Das ganze könnte man z.B. mit dem ADC-complete interrupt lösen:
 - eine globale Var. enthält den nächsten Kanal.
 - eine Weitere enthält Flags:
    - eines für 'fertig'
    - ... (fällt mir grad nichts weiter ein ;) )

 - der ADC wird einmal gestartet (als "single conversion"!)
 - die ISR
    - liest das Ergebnis aus (in die Globale Var.)
    - stellt den neuen Kanal ein
    - startet die nächste Wandlung
 - get_adc_val:
    - fragt das flag ab, wenn das gesetzt ist:
      - setzt den nächsten kanal
      - gibt das Ergebnis zurück

hth. Jörg

von Chris J. (Gast)


Lesenswert?

@ Jörg X. Ich muss mir das noch mal genau aufzeichnen und mal deine 
Lösung umsetzen.

Erstmal die beiden angesprochenen Methoden:
1
// globale Variablen
2
adc_wert = 0; // mit null initialisiert
3
4
void setMux(byte kanal){
5
  //AD-Kanal wechsel ermöglichen
6
  ADCSRA &= ~((1 << ADFR)|(1<<ADEN)); 
7
  ADCSRA &= ~(1 << ADSC); //AD-Wandlung stoppen
8
9
  ADMUX = (ADMUX & 0b11110000); // ich will die Referenz Spannung so lassen
10
  ADMUX |= kanal; // Kanal einstellen
11
  
12
  ADCSRA |= (1 << ADFR) | (1<<ADEN); // FreeRunning Mode und ADC Enabled
13
  ADCSRA |= (1 << ADSC); //AD-Wandlung starten
14
  get_ADC(); // eine Wandlung verwerfen
15
}
16
17
uint16_t  getADC(void){
18
  // Verlust eines AD-Wertes ist möglich
19
  if (bit_is_set(ADCSRA,ADIF)){
20
  adc_wert = ADC; // wert auslesen
21
  ADCSRA |=(1<<ADIF); // Interrupt Flag mit 1 löschen
22
  return adc-wert; // gibt neuen (8 bis 10)16-bit Wert zurück
23
  }
24
return adc_wert; // gibt den alten Wert zurück
25
}
26
27
uint16_t  get_ADC(void){
28
  // ohne AD-Wert Verlust (max. 13 Takte warten)
29
  while ( !bit_is_set(ADCSRA,ADIF) ){}
30
  adc_wert = ADC; // auslesen
31
  ADCSRA |=(1<<ADIF); // Interrupt Flag mit 1 löschen
32
  return adc-wert; // gebe (8 bis 10)16-bit Wert zurück
33
}
Wird mir der Compiler das get_ADC() weg optimieren (Optimization flag 
-0s), weil ich damit nichts mache?

von Jörg X. (Gast)


Angehängte Dateien:

Lesenswert?

>> Wird mir der Compiler das get_ADC() weg optimieren [...]
ein kurzer Test sagt: Nein (Test im Anhang)
 - ist aber auch nicht so tragisch: Wandlung starten und dann abwarten 
geht kaum schneller (Eine Wandlung dauert min. 13,5 * 128(-> Prescaler) 
Takte ), da kommt es doch auf 6-10 unnötige (ASM-)Anweisungen (Aufruf, 
Einlesen etc.) nicht wirklich an, oder?

hth. Jörg

von Chris J. (Gast)


Lesenswert?

Hab mir jetzt die neue AVR Studio Version installiert und festgestellt 
dass die Speicher- Nutzung/Belegung angezeigt wird.

Ist das erst seit 4.13 Build 528 so ?

AVR Memory Usage
----------------
Device: atmega8

Program:     444 bytes (5.4% Full)
(.text + .data + .bootloader)

Data:          5 bytes (0.5% Full)
(.data + .bss + .noinit)

Build succeeded with 0 Warnings...


Also morgen kann ich jedenfalls testen :-)

von Chris J. (Gast)


Lesenswert?

So, hab es jetzt alles aufgebaut.

1.) Läuft aber noch nicht :-(

Über "PORTD" lass ich mir den ADC-Wert ausgeben (mit Leuchtdioden)
An ADC7 hab ich mein Poti.

Ich kann drehen wie ich will, das Signal scheint für mein Programm immer 
High, 100%, 5V zu sein.
An ADC7 liegen jetzt 0.63V an (mit Multimeter gemessen) und während der 
Simulation in AVR Studio war auch Kanal 7 ausgewählt.

2.) (merkwürdiges Problem)
ADSC wird bei der Simulation immer gelöscht und zwar in get_ADC_8() 
nachdem ich ADIF lösche.

Als Fehlerumgehung hab ich ADSC einfach in get_ADC_8() nochmal gesetzt, 
eigentlich läuft mein AD-Wandler doch im FreeRunning Modus ... da müsste 
ich dieses bit eigentlich nicht immer wieder neu setzen.

1
main:
2
-----
3
4
main(){
5
  uint8_t adc_wert;
6
7
  set_ADC_8_Kanal(7); // Teste ADC auf Kanal 7 (Pin 22)
8
  init_ADC();
9
10
  // Port C
11
  // 7 = intern | 6=reset | 5,4 = 8bit | 3,2,1,0 = 10bit
12
  DDRC = 0b00000000; // Port C für analoge Eingänge
13
  PORTC= 0b00000000; // (ohne Pull-Up's da ADC aktiviert)
14
15
  // ADC6+ADC7 (TQFP Pin19+22)
16
  // nutze ADC6 und ADC7
17
18
  // Port D
19
  // 7,6,5,4,3,2 = ?????? | 1 = TXD, 0 = RXD (U(S)ART)
20
  DDRD = 0b11111111; // alles Ausgänge
21
22
  for (;;)
23
  {
24
    adc_wert = get_ADC_8(); 
25
    ad = (adc_wert /32 ); // unsigned Int
26
27
    switch(ad){
28
    case 0:  PORTD = 0b00000001; break;
29
    case 1:  PORTD = 0b00000010; break;
30
    case 2:  PORTD = 0b00000100; break;
31
    case 3:  PORTD = 0b00001000; break;
32
    case 4:  PORTD = 0b00010000; break;
33
    case 5:  PORTD = 0b00100000; break;
34
    case 6:  PORTD = 0b01000000; break;
35
    case 7:  PORTD = 0b10000000; break;
36
    }
37
  }
38
}
39
40
41
adc.c
42
-----
43
44
volatile uint8_t  adc_wert_8  = 0; // mit null initialisiert
45
46
void  init_ADC(){
47
  // wähle AREF, intern 2.54V
48
  ADMUX |= (1 << REFS1) | (1 << REFS0);
49
   // Enable ADC, AD-Wandlung starten, FreeRunning,AD-Interrupt Enable , Divisor 128 -> 125kHz
50
  ADCSRA |= ( 1<<ADEN )|( 1<<ADSC ) | (1<<ADFR) | (1<<ADPS0 ) | (1<<ADPS1) |( 1<<ADPS2 );
51
}
52
53
uint8_t  get_ADC_8(void){
54
55
  // Verlust eines AD-Wertes ist möglich
56
  if ( CHECKBIT(ADCSRA,ADIF) ){
57
  adc_wert_8 = ADCL; // LOW - wert auslesen
58
59
  ADCSRA |=(1<<ADIF); // Interrupt Flag mit 1 löschen
60
61
  // ADCSRA |= (1 << ADIF); // Interrupt Flag mit 1 löschen
62
63
  // Zur behebung des "ADSC Fehlers 
64
  //  ADSC wird nach einer weile immer gelöscht :-( "
65
  ADCSRA |= (1<<ADEN) | (1 << ADSC);
66
67
  return adc_wert_8; // gibt neuen (8 bis 10)16-bit Wert zurück
68
  }
69
70
return adc_wert_8; // gibt den alten Wert zurück
71
}
72
73
// Kanal von 0 bis 7
74
void set_ADC_8_Kanal(uint8_t kanal){
75
  //AD-Kanal wechsel ermöglichen
76
  ADCSRA &= ~(1 << ADFR); // stop free runing
77
  ADCSRA &= ~(1 << ADEN); // ADC disable
78
  ADCSRA &= ~(1 << ADSC); //AD-Wandlung stoppen
79
80
  ADMUX = (ADMUX & 0b11110000); // ich will die Referenz Spannung so lassen
81
  ADMUX |= kanal; // Kanal einstellen
82
83
  ADCSRA |= (1 << ADFR) | (1<<ADEN); // FreeRunning Mode und ADC Enabled
84
  ADCSRA |= (1 << ADSC); //AD-Wandlung starten
85
  get_ADC_8(); // eine AD-Wandlung verwerfen
86
}

In der Simulation beim AVR Studio geht das alles ... weiß echt nicht 
weiter  ... heul

von Johannes M. (johnny-m)


Lesenswert?

Es muss grundsätzlich immer das ADCH ausgelesen werden! Steht auch im 
Datenblatt. Abgesehen davon, dass es sinnfrei ist, nur das Low-Byte 
auszuwerten, wird das Ergebnisregister erst wieder freigegeben, wenn 
ADCH gelesen wurde. Wird nur ADCL gelesen, dann kann der ADC keinen 
neuen Wert im Ergebnisregister ablegen. Ist klar, dass Du immer den 
selben Wert ausliest. Wenn Du nur 8 Bit Auflösung brauchst, dann setze 
ADLAR und lies nur das High-Byte aus. Dann gibts auch keine Probleme.

Deine switch-case Abfrage macht m.E. auch nicht viel Sinn. Was soll die 
Division durch 32 und das Abfragen auf 0...7 ?

Dass das ADSC gelöscht wird, hängt möglicherweise damit zusammen, dass 
der ADC im Simulator nur teilweise unterstützt wird. In Hardware sollte 
das nicht passieren, wenn der Free Running Mode korrekt aktiviert wurde.

von Chris J. (Gast)


Lesenswert?

@ johnny-m

Danke, hab mir das Datenblatt durchgelesen und viel Zeit dafür 
aufgewendet.
War aber wohl noch nicht genug.

Dann setze ich ADLAR ... hab mich schon gefragt wozu dieses bit gut ist.

Zu der Division durch 32, das ist quasi ein bit-shift nach links.

z.B.: 10110011 >> 5 = 00000101
      ---                  ---
Das sind dann Zahlen von 0 bis 7 (3 Bit)
dann brauch ich keine 8 if(zahl<x && zahl>y) Abfragen ...

Ich will nur eine LED an PORTD leuchten lassen, je nach Wert. Bei 0 soll 
PinD0 high sein und bei 255 soll PinD7 high sein.

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

>Dann setze ich ADLAR ...

Auch dann mußt Du ADCH einlesen. ADCL muß dann nicht gelesen werden.

von Jörg X. (Gast)


Angehängte Dateien:

Lesenswert?

Probier's wirklich mal mit dem Interrupt, statt Free-Runinng - 
vereinfacht, z.B. den Kanalwechsel.

hth. Jörg

von Johannes M. (johnny-m)


Lesenswert?

Chris J. wrote:
> Zu der Division durch 32, das ist quasi ein bit-shift nach links.
>
> z.B.: 10110011 >> 5 = 00000101
>       ---                  ---
> Das sind dann Zahlen von 0 bis 7 (3 Bit)
> dann brauch ich keine 8 if(zahl<x && zahl>y) Abfragen ...
Jau, ist mir mittlerweile auch aufgegangen... Hatte es nur überflogen.

Aber:
Wie Travel Rec. anmerkte: ADCH muss immer gelesen werden, unabhängig 
von irgendwelchen anderen Einstellungen.

von Chris J. (Gast)


Lesenswert?

@ johnny-m
Hab's jetzt so gemacht und hat funktioniert.

> Dass das ADSC gelöscht wird
Das hat in der Simulation nicht funktionier und der Mega8 hat sich wie 
die Simulation verhalten und ADSC intern auch gelöscht.
Also wird ADSC bei fehlerhaften auslesen der ADCL/H Register gelöscht.

@ Travel Rec.
Ich weiß jetzt wie es richtig geht :-)

@ Jörg X.
Genau das werde ich jetzt machen.
Muss dazu nur noch meine Testplatine umlöten und zusätzlich 3 Taster 
anlöten.

Ich hab jetzt aber noch etwas festgestellt, wenn ich den Poti drehe und 
die Leuchtdioden beobachte hab ich nicht das Verhalten welches ich mir 
vorgestellt habe.
z.B.: Die LED an Pin3 ist an, jetzt dreh ich weiter und die LED an Pin3 
wird sozusagen gedimmt und die LED an Pin4 wird heller.
Wie kann das sein, ich dachte das ist eine rein logische Angelegenheit.

Kann es sein dass die Versorgungsspannung/Referenzspannung etwas 
schwankt und zeitweise der Pin3 und der Pin4 high sind? Sozusagen 
Pulsweitenmoduliert?

von Chris J. (Gast)


Lesenswert?

Hab's einfach mal getestet, es sind bei mir 2.05kHz die an den beiden 
Pin's dann anliegen. (Also sie schalten voll durch!)

Meine Abblockkondensatoren:
VCC  - 330µF (Versorgung von USB(5V, GND) )
AVCC - 330µF + 470nF(mit Spule zu VCC und kleine SMD-Spule nach GND)
... ob da was schwingt?

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.