Forum: Mikrocontroller und Digitale Elektronik ADC-Kanäle wechseln bei Attiny24


von Fabian S. (ih453)


Lesenswert?

Hallo zusammen,

ich versuche seit einiger Zeit den ADC-Wert des Attiny24 auszulesen.
Nachdem ich das -nach Datenblatt- für einen Kanal hinbekommen habe 
gelingt mir leider das Umschalten auf einen 2. Kanal nicht.
Daraufhin habe ich mir das AVR-GCC-Tutorial angesehen und mich da 
entlang gehangelt, doch leider klappt aktuell noch nicht mal mehr bei 
einem Kanal die Wandlung :-(

Seht ihr vielleicht auf Anhieb, wo sich der Fehler versteckt?
Die Spannung wurde mittels Multimeter am uC-Pin überprüft, diese liegt 
mit knapp 0,5V an.

Da ich keine Debugschnittstelle habe, verändere ich in Abhängigkeit von 
dem ADC-Wert das Signal an PB0.



Danke und Gruß

Fabian


C-Code
1
#ifndef F_CPU
2
#define F_CPU 1000000UL    // Interner RC-Schwingkreis
3
#endif
4
5
#include <avr/io.h>
6
#include <util/delay.h>
7
8
9
10
void port_init();
11
void init_adc();
12
uint16_t adc_lesen(uint8_t kanal);
13
uint16_t adc_lesen_avg(uint8_t kanal, uint8_t messwerte);
14
15
uint16_t adc_current_summe    = 0;  
16
uint16_t dummy          = 0;
17
18
19
int main(void)
20
{
21
  port_init();  // Ports initialisieren
22
  init_adc();    // ADC initialisieren
23
  
24
    while (1) 
25
    {
26
    adc_current_summe = adc_lesen_avg(0, 16);
27
    PORTB |= (1 << PB1);
28
    if(adc_current_summe>=400){PORTB |= (1 << PB0);}else{PORTB &= ~(1 << PB0);}
29
    _delay_ms(5000);  
30
  }
31
}
32
33
34
void port_init()
35
{
36
  //Eingänge
37
  DDRA &= ~(1 << PA0);          // Referenzsspannung
38
  DDRA &= ~(1 << PA1);          // ADC Mess-Spannung
39
    
40
  //Ausgänge
41
  DDRB |= (1 << PB0);            // Ausgabe 1
42
  
43
}
44
45
void init_adc()
46
{  
47
  ADMUX = (1<<REFS1);              // Interne Referenzspannung
48
  ADCSRA = (1<<ADPS1)|(1<<ADPS0);        // Prescaler einstellen 8
49
  ADCSRA = (1<<ADEN);              // ADC aktivieren    
50
  //DIDR0 = (1<<ADC7D)|(1<<ADC3D)|(1<<ADC2D)|(1<<ADC1D)|(1<<ADC0D);
51
  //"Dummy-Readout"
52
  ADCSRA |= (1<<ADSC);            // Starten einer ADC-Wandlung
53
  while (ADCSRA & (1 << ADSC))
54
  {
55
    dummy = (ADCH << 8) | ADCL;  
56
    //dummy=ADC;
57
  }
58
}
59
60
uint16_t adc_lesen(uint8_t kanal)
61
{
62
  uint16_t wert = 0;
63
  ADMUX = (ADMUX & ~(0x3F)) | (kanal & 0x3F);  // Löschen des ausgewählten Kanals
64
  ADCSRA |= (1<<ADSC);                  // eine Wandlung "single conversion"
65
  while (ADCSRA & (1<<ADSC) ) {         // auf Abschluss der Konvertierung warten
66
  }
67
  return ((ADCH << 8) | ADCL);   
68
}
69
70
uint16_t adc_lesen_avg(uint8_t kanal, uint8_t messwerte)
71
{
72
  uint32_t sum = 0;
73
  for (uint8_t i = 0; i < messwerte; ++i ) {
74
    sum += adc_lesen( kanal );
75
  
76
  return (uint16_t)( sum / messwerte );
77
}

: Bearbeitet durch User
von STK500-Besitzer (Gast)


Lesenswert?

Fabian S. schrieb:
> adc_current_summe = adc_lesen_avg(0, 16);
Wenn du auch immer nur Kanal 0 ausliest...

von Karl M. (Gast)


Lesenswert?

Hallo,

frage Dich bitte mal, in welcher Reihenfolge das Lesen erfolgt:
1
return ((ADCH << 8) | ADCL);

Und was steht im Datenblatt?
Und was ist dann das "ADCW"?

von Fabian S. (ih453)


Lesenswert?

Hallo,

STK500-Besitzer schrieb:
> Fabian S. schrieb:
>> adc_current_summe = adc_lesen_avg(0, 16);
> Wenn du auch immer nur Kanal 0 ausliest...

aktuell will es es (wieder) hinbekommen, einen Kanal richtig auslesen zu 
können.
Aus diesem Grund lese ich immer nur Kanal 0 ein/aus.

Karl M. schrieb:
> frage Dich bitte mal, in welcher Reihenfolge das Lesen erfolgt:return
> ((ADCH << 8) | ADCL);
> Und was steht im Datenblatt?
> Und was ist dann das "ADCW"?

In dem Datenlatt des Attiny24 gibt es meines Erachtens kein ADCW.

"... ADCL must be read first, then ADCH" steht im Datenblatt.
In meinem Code wird jedoch erst ADCH gelesen.

Folglich müsste ich das so schreiben?(ADCL|(ADCH << 8))
Das interpretiere ich so, dass erst ADCL gelesen wird, anschließend wird 
ADCH gelesen, um 8 Bit nach links verschoben und mit ADCL "verodert"?


Danke und Gruß

Fabian

: Bearbeitet durch User
von leo (Gast)


Lesenswert?

Fabian S. schrieb:
> Folglich müsste ich das so schreiben?(ADCL|(ADCH << 8))
> Das interpretiere ich so, dass erst ADCL gelesen wird,

Vorsicht! Der oder-Operator ist kein sequence point, die Reihenfolge ist 
nicht garantiert. Deine Interpretation kann vom Compiler ignoriert 
werden. Ich wuerde zwei getrennte Befehle verwenden.

leo

von Karl M. (Gast)


Lesenswert?

Fabian S. schrieb:
> In dem Datenlatt des Attiny24 gibt es meines Erachtens kein ADCW.

Hatte ich auch nicht geschrieben!

Siehe avr gcc LibC - FAQ 8

https://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_16bitio
https://www.nongnu.org/avr-libc/user-manual/index.html

https://www.mikrocontroller.net/articles/AVR_16-Bit-Register

von Karl M. (Gast)


Lesenswert?

Aus dem Datenblatt hast Du für 16 Bit Zugriffe die richtige Reihenfolge 
gefunden.

In Assembler erst ADCL und danach ADCH.

Für C gibt es deshalb ADCW als 16 Bit Zugriff!

von Fabian S. (ih453)


Lesenswert?

Hallo,

leo schrieb:
> Vorsicht! Der oder-Operator ist kein sequence point, die Reihenfolge ist
> nicht garantiert. Deine Interpretation kann vom Compiler ignoriert
> werden. Ich wuerde zwei getrennte Befehle verwenden.

Gut zu wissen, dann werde ich es separat machen oder wie von Karl. M. 
beschrieben mit ADCW.

Karl M. schrieb:
> Fabian S. schrieb:
>> In dem Datenlatt des Attiny24 gibt es meines Erachtens kein ADCW.
>
> Hatte ich auch nicht geschrieben!

Da hatte ich jedoch als erstes gesucht...

Karl M. schrieb:
> Aus dem Datenblatt hast Du für 16 Bit Zugriffe die richtige Reihenfolge
> gefunden.
>
> In Assembler erst ADCL und danach ADCH.
>
> Für C gibt es deshalb ADCW als 16 Bit Zugriff!


Super, danke für die Erklärung. Das hätte ich (so schnell) nicht 
gefunden.

Ich habe
1
((ADCH << 8) | ADCL)
 durch
1
ADCW
 ersetzt, doch leider bin ich noch nicht zufrieden mit dem Ergebnis.

Aktuell liegt eine Spannung von 487 mV an dem Pin an (Multimeter und 
Oszilloskop liefern den gleichen Wert). Was bei der internen 
Referenzspannung einem Wert von 453 entsprechen müsste.
Die Abfrage:
1
adc_current_summe>=430
 ist nicht wahr, was bedeutet, dass ich eine Abweichung von über 23 
habe. Irgendwo muss ich noch einen Fehler haben.
Habt ihr noch eine Idee?
Den Quarz des Controllers habe ich überprüft (Pin High, 10ms warten, Pin 
Low), dieser war mit 1MHz passend eingestellt.

von Karl M. (Gast)


Lesenswert?

Hallo,

die Eingangsimpedanz soll laut Datenblatt <=10KΩ sein.

Es müssen sich erst Eingangskondensator(en) umladen.

Entweder einen OPV als Impendanzwandler nehmen,
oder einen kleinen Kondensator ~10nF an jeden ADC-Eingang legen.
Und nach der Wahl des Eingangs, eine Zeitspanne warten ~1µs, bis man die 
erste Wandlung startet!
Ich messe dann immer mehrmals nacheinander und bilde einen Mittelwert: 
2^n; mit n E { 2,..,6 }.

von Karl M. (Gast)


Lesenswert?

Dann hat der ADC noch Fehler, die sind in 2-3 Application Notes 
beschrieben.

von S. Landolt (Gast)


Lesenswert?

> ... Abweichung ...

Wie gerechnet? Laut Datenblatt unter 'ADC Characteristics' liegt die 
interne Referenz zwischen 1.0 und 1.2 V.
  Ein kleiner Fehler entsteht auch durch den zu hohen ADC-Takt mit 500 
kHz, der Vorteiler steht nämlich auf /2:
1
 ADCSRA = (1<<ADPS1)|(1<<ADPS0);        // Prescaler einstellen 8
2
 ADCSRA = (1<<ADEN);              // ADC aktivieren

von A. S. (Gast)


Lesenswert?

Fabian S. schrieb:
> Die Abfrage: adc_current_summe>=430 ist nicht wahr, w

Gänzlich ungeeignet.

 Wenn du weder Debugger noch Display hast, dann häng dein Oszi an 2 
freie pins: und toggle die: den einen mit jedem Zyklus, den anderen wenn 
++i > adc_current_summe ist (und dabei i=0).

Wenn dein Zyklus genau z.b.1ms ist, kannst Du den ADC-wert direkt am 
Oscar ablesen.

von S. Landolt (Gast)


Lesenswert?

an Fabian S., vielleicht als Vergleich:
Wenn ich im vorgestellten Programm die fehlende Klammer einfüge, an zwei 
Stellen ADCW verwende und das Delay auf ein erträgliches Maß herabsetze, 
so erhalte ich bei meinem ATtiny84A eine Schaltschwelle von 426 mV.

von Fabian S. (ih453)


Lesenswert?

Hallo,

Karl M. schrieb:
> die Eingangsimpedanz soll laut Datenblatt <=10KΩ sein.

Karl M. schrieb:
> Dann hat der ADC noch Fehler, die sind in 2-3 Application Notes
> beschrieben.

Ich habe mir die Application Notes angesehen, ja mein Spannungsteiler 
ist zu hochohmig. Ich habe diesen nun auf einen Widerstand kleiner 10 kΩ 
ausgelegt.
Obwohl es eigentlich, mit dem von dir vorgeschlagenen Kondensator und 
einer "langsamen" Abtastung nacheinander auch funktionieren müsste.

S. Landolt schrieb:
> Ein kleiner Fehler entsteht auch durch den zu hohen ADC-Takt mit 500
> kHz, der Vorteiler steht nämlich auf /2:

Nach dem Datenblatt, welches ich gefunden habe, entspricht meine 
Einstellung einem Teiler von 8. Oder habe ich da etwas falsch 
interpretiert?
Datenblattlink 
[http://ww1.microchip.com/downloads/en/devicedoc/Atmel-7701_Automotive-Microcontrollers-ATtiny24-44-84_Datasheet.pdf]

S. Landolt schrieb:
> Wie gerechnet? Laut Datenblatt unter 'ADC Characteristics' liegt die
> interne Referenz zwischen 1.0 und 1.2 V.

Ich habe das noch einmal nachgerechnet und ja du hast (leider) Recht. 
Die Schwankungen der ADC-Zählwerte liegen alle in dem Bereich, welcher 
aufgrund der Referenzspannungsschwankung vorliegen kann.

A. S. schrieb:
> Fabian S. schrieb:
>> Die Abfrage: adc_current_summe>=430 ist nicht wahr, w
>
> Gänzlich ungeeignet.

Muss ich dir leider zustimmen. Habe es mir mit dem Oszilloskop noch 
einmal angesehen...

S. Landolt schrieb:
> Wenn ich im vorgestellten Programm die fehlende Klammer einfüge, an zwei
> Stellen ADCW verwende und das Delay auf ein erträgliches Maß herabsetze,
> so erhalte ich bei meinem ATtiny84A eine Schaltschwelle von 426 mV.

Danke, für die Messung. Das ist ungefähr vergleichbar mit meinem 
Ergebnis.


Vielen Dank für eure ganzen Tipps.
Ich werde an der Beschaltung noch einiges anpassen müssen.
So werde ich z.B. an den Aref-Pin eine genauere Referenzspannung 
anlegen, um die Genauigkeit zu erhöhen.

Die ADC-Wandlung sollte aber jetzt soweit funktionieren, wenn auch 
relativ ungenau. Ich werde jetzt erst einmal den weiteren C-Code 
schreiben.


Danke und Gruß

Fabian

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


Lesenswert?

Du kannst, wie oben schon mal erwähnt, den ADC sehr beruhigen, wenn du 
am Eingang einen kleinen C gegen Masse schaltest. Da sind z.B. schon 1nF 
- 4,7nF sehr hilfreich. Auch etwaiges 'Mitziehen' von Nachbarkanälen ist 
dann so gut wie verschwunden.

von S. Landolt (Gast)


Lesenswert?

> ... Teiler von 8. Oder habe ich da etwas falsch interpretiert?

Falsch programmiert:
1
 ADCSRA = (1<<ADPS1)|(1<<ADPS0);        // Prescaler einstellen 8
2
 ADCSRA = (1<<ADEN);              // ADC aktivieren
Mit der zweiten Zeile wird die erste annuliert, im Wortsinn.

von S. Landolt (Gast)


Lesenswert?

Autsch - "annulliert"

von F. Schneider (Gast)


Lesenswert?

S. Landolt schrieb:
> Mit der zweiten Zeile wird die erste annuliert, im Wortsinn.

Jetzt sehe ich es auch.
Datenblatt richtig  gelesen, jedoch einen Fehler beim umsetzen :-(.


Matthias S. schrieb:
> Du kannst, wie oben schon mal erwähnt, den ADC sehr beruhigen, wenn du
> am Eingang einen kleinen C gegen Masse schaltest. Da sind z.B. schon 1nF
> - 4,7nF sehr hilfreich. Auch etwaiges 'Mitziehen' von Nachbarkanälen ist
> dann so gut wie verschwunden.

Ich hatte da bereits schon einen Kondensator in meiner Schaltung.


Danke und Gruß

Fabian

von HildeK (Gast)


Lesenswert?

Matthias S. schrieb:
> Du kannst, wie oben schon mal erwähnt, den ADC sehr beruhigen, wenn du
> am Eingang einen kleinen C gegen Masse schaltest. Da sind z.B. schon 1nF
> - 4,7nF sehr hilfreich.

Für dieses Problem schon richtig.
Allerdings muss dann die Abtastperiode deutlich länger sein als das tau 
aus dem Quellwiderstand und diesem C (≈ 10 tau, abhängig von der 
ADC-Auflösung).
Ist sie zu kurz, so wir an diesem äußeren Kondensator die durch den 
Samplingkondensator entnommene Ladungsmenge nicht wieder vollständig 
aufgefüllt werden und es ergibt sich eine Ablage. Die sollte kleiner als 
ein halbes LSB sein.

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


Lesenswert?

HildeK schrieb:
> Ist sie zu kurz, so wir an diesem äußeren Kondensator die durch den
> Samplingkondensator entnommene Ladungsmenge nicht wieder vollständig
> aufgefüllt werden und es ergibt sich eine Ablage.

Für den internen S&H Kondensator im Tiny 24 gibt das Datenblatt einen 
Wert von etwa 14pF an (in Reihe mit einem 1-100k Widerstand(!)) Der 
externe C ist also in jedem Fall sehr viel grösser, so das es das 
Problem nicht gibt.
Der Eingangspfad ist sowieso sehr viel unbestimmter durch die grosse 
Streuung des Serienwiderstandes als alle Berechnung von Ladungen. Die 
durch den 14pF entnommene Ladungsmenge ist also nur theoretisch 
interessant.

von HildeK (Gast)


Lesenswert?

Matthias S. schrieb:
> Der
> externe C ist also in jedem Fall sehr viel grösser, so das es das
> Problem nicht gibt.

Das Problem gibt es immer, wenn die ext. Zeitkonstante zu groß ist im 
Verhältnis zur Abtastperiode.
Man entnimmt eine geringe Ladung, die Spannung am Cext sinkt ein wenig 
ab. Wird sie nicht bis zum nächsten Abtastzeitpunkt wieder ausgeglichen, 
sinkt sie beim nächsten sampeln weiter ab. Und so fort, bis die 
Differenzspannung Uin-U_Cext so groß wird, dass die Nachladung von Cext 
gerade den Verlust ausgleicht. Das führt zu einer dauerhaften Ablage, 
die mehrere LSB betragen kann, je nach fa und 1/tau.
Macht man den externen Kondensator größer, verlängert sich tau und bei 
gleichbleibender Abtastfrequenz wird der Fehler größer.

Der Effekt ist in der Literatur beschrieben, leider selten zu finden. 
Ich selber bin vor einigen Jahren von einem Xilinx Support-Ingenieur 
darauf aufmerksam gemacht worden.

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.