Forum: Mikrocontroller und Digitale Elektronik Attiny ADC Zuverlässigkeit


von Marius D. (lonestarr102)


Angehängte Dateien:

Lesenswert?

Ich nutze ein Attiny84A und ein analogen Temperatursensor STLM20.

Ich lasse mir die Spannung durch den internen ADC in einen Digitalwert 
umwandeln, um ihn dann in eine Spannung umzurechnen.

Mein problem ist, dass die gemessenen Temperaturen stimmen, allerdings 
manchmal komplett unrealistische Werte ausspuckt. Als Beispiel bei 30°C 
Umgebungstemperatur spuckt er immer 30°C aus, aber ab und zu auch mal 
15Ĉ oder 48°C.
Woran könnte das liegen? Ist die ADC nicht immer zuverlässig? Ich habe 
zur besseren Ansicht die Messergebnisse im Bildanhang grafisch 
ausgewertet.

ADC Prozeduren
1
#include <avr/io.h> 
2
3
void ADC_Init(void)
4
{
5
6
  ADMUX = 0b00000001; 
7
 
8
  ADCSRA = (1<<ADPS2) | (1<<ADPS0);     // frequency divider
9
  ADCSRA |= (1<<ADEN);                  // activate ADC 
10
 
11
  (void) ADC;
12
}
13
 
14
15
uint16_t ADC_Read( uint8_t channel )
16
{
17
  // select channel
18
  ADMUX = (ADMUX & ~(0x3F)) | (channel & 0x3F);
19
  ADCSRA |= ( 1 << ADSC);            // single conversion
20
  while (ADCSRA & ( 1 << ADSC) ) {   // wait for conversion
21
  }
22
  return ADC;                    
23
}

Aufruf in Programmhauptschleife
1
int16_t volatile adcval;
2
3
  for(;;)
4
    {
5
      adcval =  1273-(ADC_Read(1) *22/10);
6
    }

von Planlos (Gast)


Lesenswert?

Marius D. schrieb:
> int16_t volatile adcval;

ist 16 Bit (=2 Bytes) lang. der AVR ist ein 8-bitter.

Du musst zugriffe auf 16-Bit Variablen, die du auch im Interrupt (worauf 
dein Volatile hindeutet) verwendest, so sperren, dass immer beide Bytes 
auf einmal geschrieben werden.

von Planlos (Gast)


Lesenswert?

Analog zum AVR-GCC-Tutorial:
1
#include <util/atomic.h>
2
3
4
5
6
    int16_t /* not volatile */ tempADCval = 1273  - (ADC_read.....
7
    ATOMIC_BLOCK(ATOMIC_FORCEON) {
8
        adcval = tempADCval;
9
    }

oder so ähnlich.

von Sultan (Gast)


Lesenswert?

1
// Q & D
2
3
for(;;)
4
{
5
    cli();
6
    adcval =  1273-(ADC_Read(1) *22/10);
7
    sei();
8
}

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Wenn der ADC durchlaufen soll, mache ich eine ADC_Complete ISR, die nach 
dem erstmaligen Starten das Abfragen und Neustarten des ADC im 
Hintergrund erledigt.
1
// ADC
2
#define ROOF_ADC 2   // 0010
3
#define ROOM_ADC 3   // 0011
4
5
// globals
6
volatile uint16_t roomtemp,rooftemp;
7
8
/* Once again the multi channel ADC Readout and Restart Routine.
9
 * it examines the MUX setting, stores the corresponding value
10
 * and then switches to the next channel
11
 * The ISR ends with restarting the ADC
12
 * a little bit of averaging is done,too.
13
 * note that this routine will never fire if you miss to start the ADC 
14
 * once after initializing with the ADIE flag set.
15
 */
16
ISR(ADC_vect) {
17
uint8_t mux = ADMUX;
18
switch (mux & 0x07) {
19
  case ROOM_ADC  :  roomtemp = (roomtemp + ADC) >> 1;
20
                     ADMUX = (mux & 0xF0) | ROOF_ADC;
21
                     break;  
22
  case ROOF_ADC  :  rooftemp = (rooftemp + ADC) >> 1;
23
                     ADMUX = (mux & 0xF0) | ROOM_ADC;
24
                     break;  
25
  default        :  ADMUX = (mux & 0xF0) | ROOM_ADC;
26
                     break;  
27
     }
28
ADCSRA = (1<<ADEN)|(1<<ADSC)|(0<<ADATE)|(1<<ADIF)|(1<<ADIE)|(1<<ADPS2);
29
}
Einmal beim ADC Init starten. Da das ganze in der ISR passiert, ist mit 
Atomic Problemen hier nicht zu rechnen. Beachte, das ich immer ein wenig 
runde durchs Aufaddieren und Schieben.

: Bearbeitet durch User
von Markus W. (Firma: guloshop.de) (m-w)


Lesenswert?

Matthias S. schrieb:
> Da das ganze in der ISR passiert, ist mit
> Atomic Problemen hier nicht zu rechnen. Beachte, das ich immer ein wenig
> runde durchs Aufaddieren und Schieben.

Ich bin mir da nicht so sicher, denn der Zugriff auf das ADC-Ergebnis 
passiert gepuffert und unabhängig vom i-Flag.

Irgendwo im Datenblatt steht, wie genau der Zugriff auf solche 
16-Bit-Werte ablaufen sollte. Vermutlich kümmert sich der C-Compiler 
aber selber um solche Feinheiten.

Aber wie gesagt -- bin nicht ganz sicher. :-)

von Stefan R. (1994rstefan)


Lesenswert?

Beim lesen von ADCL wird ADCH automatisch gesperrt bis auch ADCH gelesen 
wurde. Beim Zugriff auf ADC macht der AVR-GCC das automatisch richtig.

am 16-Byte zugriff liegt es also eher nicht.

von Michael B. (laberkopp)


Lesenswert?

Marius D. schrieb:
> ADMUX = 0b00000001;

?!? Refereenzspannung von AREF, interne VREF ist abgeschasltet. Welche 
Refernzspannung hast du denn an AREF gehängt ?

http://www2.st.com/content/ccc/resource/technical/document/datasheet/61/83/f6/8b/2f/2a/47/35/CD00119601.pdf/files/CD00119601.pdf/jcr:content/translations/en.CD00119601.pdf

Welchen Rfilter und Cfilter hast du vorgesehen ?

von blechiger Dampftroll (Gast)


Lesenswert?

Ich wuerde den Sensor mal durch einen Spannungsteiler, resp Referenz 
ersetzen und die Messung nochmals machen. Es kann immer noch Hardware, 
zB als EMV sein

von Stefan F. (Gast)


Lesenswert?

Vielleicht ist die Quelle zu hochohmig.

Wie immer fehlt der Schaltplan.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Markus W. schrieb:
> Ich bin mir da nicht so sicher, denn der Zugriff auf das ADC-Ergebnis
> passiert gepuffert und unabhängig vom i-Flag.

Doch, da kann man sich sicher sein. Das Atomic Problem entsteht ja, weil 
eine etwaige Unterbrechung durch ISRn zwischen dem Lesen/Schreiben von 
High- und Lowbyte zu Fehlern führt. Das kann in der AVR Architektur 
nicht passieren, wenn man die ISR nicht explizit als ISR_NOBLOCK 
deklariert, denn eine ISR kann nicht durch eine andere unterbrochen 
werden.

Der ADC setzt ADIF erst, wenn er mit der Wandlung fertig ist. Ob 
gepuffert oder nicht ist in dem Fall also ohne Bedeutung.
Die Routine oben funktioniert immer korrekt, solange die Hardware ok ist 
(und ist eine meiner Standardfunktionen seit Jahren).

: Bearbeitet durch User
von Planlos (Gast)


Lesenswert?

Stefan R. schrieb:
> am 16-Byte zugriff liegt es also eher nicht.

Es liegt nicht am 16-Bit LESE-Zugriff auf ADC, sondern an konkurierenden 
16-Bit-Lese&Sschreib-Zugriffen auf
1
int16_t volatile adcval;

Diese Variable wird im nicht-sichtbaren Codeteil (vermutlich Timer-ISR) 
ebenfalls gelesen, dann wohl per UART o.Ä. weiterversendet.

Der Schreibzugriff auf die 16 Bit ist nicht Atomar.

Also Beispiel:

adcval startet z.B. mit 0x0100;

Der Wert sinkt ein bischen. adcval würde um eins verringert werden.
also auf 0x00FF. Geschrieben wird in zwei Etappen:

High-Byte auf 0x00:
adcval = 0x0000
<ISR sendet adcval raus. ist um 255 Zähler falsch>
Low-Byte auf 0xFF:
adcval = 0x00FF.
=> Alles wieder richtig.

Beim umgekehrten Sprung 0x00FF -> 0x0100 dasselbe Spiel, nur dass hier 
0x01FF als Zwischenwert rauskommt.

Deshalb: High- und Lowbyte von "adcval" gleichzeitig, ohne 
ISR-Unterbrechung dazwischen, schreiben.

dazu gibt's cli()/sei() oder ATOMIC_BLOCK.

von Stefan R. (1994rstefan)


Lesenswert?

Ist mir nach dem Absenden auch aufgefallen, konnte meinen Beitrag aber 
nicht mehr editieren da bereits eine Antwort gekommen ist.

Das Problem dürfte aber trotzdem nicht daher komme. Zwischen 0x00FF und 
0x01FF würden nämlich über 700°C liegen.


Was mir zu dem Problem noch eingefalle  ist - wird immer nur der gleiche 
ADC Kanal ausgelesen?
Wenn man zwischen den ADC Kanälen wechselt und sofort eine Messung 
durchführen versucht und an den beiden Kanälen unterschiedliche 
Spannungen anliegen kann es passieren, dass der ADC in dem momment 
misst, an dem sich dir Spannung gerade ändert. Dadurch kommt dann 
natürlich ein falsches Ergebnis zu stande.

von neuer PIC Freund (Gast)


Lesenswert?

Probier mal float
1
 adcval = 159.65 - ADC_Read(..) * 0.4177

Irgendwie versteh ich deine 22/10 nicht. Und die lineare Interpolation 
ist eh nicht so toll.

von Ingo Less (Gast)


Lesenswert?

Wird evtl. deine While Schleife, in der du auf den ADC wartes 
wegoptimiert? Mach da mal zur Sicherheit n asm volatile ("nop"); rein.

von klinke (Gast)


Lesenswert?

Ich kann nur jeden auffordern noch schnell einen Tip abzugeben bevor der 
TO weitere sachdienliche Indizien liefert. Danach werden keine Wetten 
mehr angenommen.

von M. K. (sylaina)


Lesenswert?

Marius D. schrieb:
> uint16_t ADC_Read( uint8_t channel )
> {
>   // select channel
>   ADMUX = (ADMUX & ~(0x3F)) | (channel & 0x3F);
>   ADCSRA |= ( 1 << ADSC);            // single conversion
>   while (ADCSRA & ( 1 << ADSC) ) {   // wait for conversion
>   }
>   return ADC;
> }

Meiner Erfahrung am Atmega328 ist, dass ADMUX nicht immer sicher 
umgestellt wird wenn ADEN auf 1 ist. Bei mir half dagegen ADEN zu 
löschen, dann ADMUX einzustellen und schließlich ADEN wieder 
einzuschalten:

uint16_t ADC_Read( uint8_t channel )
{
  ADCSRA &= ~(1 << ADEN);
  // select channel
  ADMUX = (ADMUX & ~(0x3F)) | (channel & 0x3F);
  ADCSRA |= (1 << ADEN);
  ADCSRA |= ( 1 << ADSC);            // single conversion
  while (ADCSRA & ( 1 << ADSC) ) {   // wait for conversion
  }
  return ADC;
}

von Peter D. (peda)


Lesenswert?

M. K. schrieb:
> Meiner Erfahrung am Atmega328 ist, dass ADMUX nicht immer sicher
> umgestellt wird wenn ADEN auf 1 ist.

Das ist Quatsch.
Der MUX wird gelatcht, während eine Wandlung läuft.

von M. K. (sylaina)


Lesenswert?

Peter D. schrieb:
> Das ist Quatsch.
> Der MUX wird gelatcht, während eine Wandlung läuft.

Ich schrieb extra "meiner Erfahrung nach" und wenn ich ADEN nicht aus 
machen bekomme ich auch ab und an schöne Wandlungsfehler, mach ich ADEN 
aus, änder MUX und mach ADEN wieder an hatte ich bisher noch nie 
(unerwartete) Wandlungsfehler.

von Klaus (Gast)


Lesenswert?

M. K. schrieb:
> Ich schrieb extra "meiner Erfahrung nach" und wenn ich ADEN nicht aus
> machen bekomme ich auch ab und an schöne Wandlungsfehler, mach ich ADEN
> aus, änder MUX und mach ADEN wieder an hatte ich bisher noch nie
> (unerwartete) Wandlungsfehler.

Muxer umschalten, minimale Samplezeit abwarten, Wandlung starten. Dazu 
muß man den ADC nicht ausschalten. Und wenn man das beachtet, wird aus 
dem ADC auch kein Zufallsgenerator.

MfG Klaus

von M. K. (sylaina)


Lesenswert?

Klaus schrieb:
> Muxer umschalten, minimale Samplezeit abwarten, Wandlung starten.

Auch eine Möglichkeit, wird aber hier

Marius D. schrieb:
> ADMUX = (ADMUX & ~(0x3F)) | (channel & 0x3F);
> ADCSRA |= ( 1 << ADSC);

nicht gemacht, deshalb eben kurz den Wandler aus, Mux umschalten, 
Wandler ein. Man kann auch die minimale Wandlerzeit abwarten, keine 
Frage.

von Marius D. (lonestarr102)


Lesenswert?

Wow... hier kam aber reichlich resonanz. Vielen Dank an eure Mithilfe.

Ich werde es jezt mal mit cli() und  sei() versuchen und ein neuen 
Testlaufstarten. Werde dann berichten.

Ja ich nutze adcval und schicke es über I2C weiter. Habe mir da eine 
Funktion gebaut die die Daten zuverlässig wegschickt. Das ist recht 
umfangreich. Kann mir nicht vorstellen, dass bei der Übertragung was 
verloren geht, da es mit anderen Daten wunderbar funktioniert.  Kurz 
zusammengefasst wird auf ein I2C-Interrupt reagiert, auf die dann die 
Temperatur zurückgeschickt wird.

1
//Main()  habe das jetzt so gemacht. Hoffe es wirkt.
2
3
        tmp_adcval =  1273-(ADC_Read(1) *22/10);
4
5
        cli();
6
        adcval = tmp_adcval;
7
        sei();
8
9
10
// Interrupt
11
ISR( USI_OVF_vect )
12
{
13
  ...
14
      //case erstes byte
15
      I2C_DataAck( ((UINT8)(adcval >> 7)|0b11000000) , 14);
16
...
17
18
...   //case zweites byte
19
      I2C_DataAck( (UINT8)((adcval&0b01111111) << 1) , 0);
20
21
  ...
22
}

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

M. K. schrieb:
> nicht gemacht, deshalb eben kurz den Wandler aus, Mux umschalten,
> Wandler ein.

Das dauernde ein und Ausschalten des Wandlers verbessert sicher die 
Qualität des Ergegnisses, ist ja jedesmal frisch und neu gestartet.

MfG Klaus

von Dirk K. (dekoepi)


Lesenswert?

Marius D. schrieb:
> umfangreich. Kann mir nicht vorstellen, dass bei der Übertragung was
> verloren geht, da es mit anderen Daten wunderbar funktioniert.  Kurz
> zusammengefasst wird auf ein I2C-Interrupt reagiert, auf die dann die
> Temperatur zurückgeschickt wird.

Öhm. Genau da liegt gerne mal der Hase im Pfeffer. Hatte hier auch bei 
zigtausend Samples paar Ausreißer, sehr sporadisch, vielleicht einmal am 
Tag. Leitungslänge und zu klein dimensionierte Pullups waren schuld. Die 
ab Werk verbauten 10kOhm am anderen Ende der Strippe um 1kOhm ergänzt 
(ich weiß, knapp außerhalb der Spec, auch bei 3.3V Speisung). Seitdem 
sind meine Kurven "glatt wie ein Babypopo", ohne Ausreißer.

Ich würde die I2C-Übertragung als Fehlerquelle daher nicht einfach 
ausklammern ...

P.S.: Sind 100nF eigentlich auch an die Versorgung gepackt? Falls der 
ATtiny AVcc hat, auch dort? Versorgt via Ferritperle zum Abblocken 
höherfrequenter Störungen?

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> Das dauernde ein und Ausschalten des Wandlers verbessert sicher die
> Qualität des Ergegnisses, ist ja jedesmal frisch und neu gestartet.

Das aus und EInschalten hilft deswegen, weil die erste Wandlung nach dem 
Einschalten einige Takte länger dauert. Zuerst initialisiert sich der 
ADC, dann erst beginnt er mit der Messung. Bis dahin ist der S&H 
Kondensator aufgeladen.

von m.n. (Gast)


Lesenswert?

Klaus schrieb:
> Das dauernde ein und Ausschalten des Wandlers verbessert sicher die
> Qualität des Ergegnisses, ist ja jedesmal frisch und neu gestartet.

Vielleicht vor jeder Messung noch einen Reset vom µC auslösen. Dann ist 
er immer wieder frisch.
Hier gibt's ja heute wieder die heißen Tipps :-(

Wenn ich mir die oben gezeigte Kurve ansehe, sehe ich u.a. Werte von 16, 
32 und 48. Das kommt vermutlich nicht vom ADC.

von Klaus (Gast)


Lesenswert?

m.n. schrieb:
> Klaus schrieb:
>> Das dauernde ein und Ausschalten des Wandlers verbessert sicher die
>> Qualität des Ergegnisses, ist ja jedesmal frisch und neu gestartet.
>
> Vielleicht vor jeder Messung noch einen Reset vom µC auslösen. Dann ist
> er immer wieder frisch.

Hab ich mich nicht getraut zu schreiben. Aber so bekommt man genug Noise 
auf die Referenz, daß man mit Oversampling die Auflösung massiv erhöhen 
kann.

MfG Klaus

von Planlos (Gast)


Lesenswert?

klinke schrieb:
> Ich kann nur jeden auffordern noch schnell einen Tip abzugeben bevor der
> TO weitere sachdienliche Indizien liefert. Danach werden keine Wetten
> mehr angenommen.

da gibt's nix zu Wetten.

Bild im Eröffnungs-Post anschauen.

Die meisten der genannten Ursachen (ADC zu oft enabled, zu Hochohmig, zu 
schneller MUX-Wechsel, falsche REF-Spannung, blah blah blah) können 
dieses Fehlerbild nicht erklären.

von Marius D. (lonestarr102)


Angehängte Dateien:

Lesenswert?

soo
1
        cli();
2
        adcval = tmp_adcval;
3
        sei();

hat mein Problem gelöst. Siehe Dateianhang.

 Danke nochmals

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.