Forum: Mikrocontroller und Digitale Elektronik Wert des ADC Wandlers wird immer sofort ausgegeben (ATMega 2561, AVR-Studio 4)


von Fabian B. (Gast)


Lesenswert?

Hallo liebe Community,

ich bin langsam kreativ an meinen Grenzen und weiß nicht mehr weiter.
Es geht um folgendes, ich möchte zuerst, dass ein analoger Wert von 
einem Potentiometer eingelesen wird. Danach soll nach einem Startbefehl 
eine Variable (in einer ISR) von maximal (1024 digits) zu dem 
eingelesenen Wert dekrementiert werden.

Wenn der Anlauf durchgelaufen ist, soll danach der Wert des 
Potentiometers normal eingelesen werden.

Zuerst der Code:
1
volatile int ALPHAsoll  =    0;
2
volatile int ALPHAIst  =    1023;
3
volatile int zaehler4 =     0; 
4
volatile int sanftanlauf_durch   =   0;
5
6
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7
8
int main (void)
9
.
10
.
11
.
12
while(1)
13
.
14
.
15
if (sanftanlauf_durch == 0)
16
{  
17
  ALPHA = (((ALPHAmax - ALPHAmin) / 1023) * (ALPHAIst) + ALPHAmin); //ALPHAIst wird im Timer 4 dekrementiert
18
}
19
else
20
{
21
  ALPHA = (((ALPHAmax - ALPHAmin) / 1023) * (ADC_Lesen())+ ALPHAmin);
22
}
23
.
24
.
25
.
26
if(ButtonPressed(0, PINA, 2, 500))  //an-/ausschalten
27
{
28
    if(gezuendet) //aus
29
    {
30
     Hier werden LED geschaltet
31
    }
32
    else  //an
33
    {
34
        ALPHAsoll = ADC_Lesen();
35
   ALPHAIst = 1023;
36
   sanftanlauf_durch=0;
37
    TCNT4 = 0;
38
    zaehler4 = 0;
39
        gezuendet = 1;
40
    }
41
42
++++++++++++++++++++++++++++++++++++++++++++
43
.
44
.
45
.
46
47
ISR (TIMER4_COMPA_vect)
48
{
49
zaehler4 ++;           // Zähler für Sanftanlauf
50
51
if (ALPHAsoll <= ALPHAIst)
52
{
53
  ALPHAIst = 1023 - zaehler4;           // Jeden Interrupt soll AlPHaIst an AlPHASoll näher herangetragen werden
54
  
55
}
56
else
57
{
58
  sanftanlauf_durch = 1;    // Sanftanlauf wird beendet
59
}
60
61
TCNT4 = 0;      // Timer 4 wird wieder auf 0 gesetzt
62
}
63
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
64
65
/*Analog-Digital-Konvertierung*/
66
int ADC_Lesen(void)
67
{
68
ADCSRA |=(1<<ADEN);       //ADC aktivieren
69
ADCSRA |= (1<<ADSC);              //nächste Umwandlung starten
70
  
71
    while (ADCSRA & (1<<ADSC));    //warten bis die Umwandlung abgeschlossen ist
72
73
  ADCSRA &= ~(1<<ADEN);      //ADC deaktivieren
74
return ADCW;        //Ergebnis von 0 bis 1023 ausgeben
75
}
76
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Der Code macht auch alles schön was er soll. Nur leider halt nicht alles 
;)

Zwei Probleme hab ich:

1) Könnt ihr mir sagen, warum ALPHAIst den Wert aus der ISR zur Main 
überträgt, obwohl ich der ISR keine Adresse übergeben habe

2) Wenn ich den Controller aktiviere (Buttenpressed ok) dann zeigt er 
mir zuerst den Wert an der am Potentiometer (ADC_Lesen()) eingestellt 
ist, springt danach auf AlPHAIst = 1023 und verkleinert ALPHAIst solange 
bis er auf ALPHAsoll (ADC_Lesen()) trifft.

Alles wäre so schön wenn er diesen allerersten Impuls auf (ADC_Lesen()) 
nicht machen würde.

Achja mit ALPHA wird der Mikrocontroller für die Anwendung die schon 
läuft auf unser 50 Hz Netz synchronisiert.
Damit muss irgendein ALPHA Wert zwingend zurückgegeben werden.

Ich habe irgendwie schon alles ausprobiert und bekomme das Problem 
dieses ersten ADC_Lesen impulses nicht in den Griff.

Entweder bin ich zu blöd oder der ATMega ist zu stark (Aber die Nerven 
sind weg ^^^;))

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Keine Ahnung was du da rumrechnest. Im Moment tu ich mir extrem schwer 
deinen Code mit der Beschreibung dessen, was du erreichen willst, in 
Einklang zu bringen. Das kommt mir alles deutlich zu kompliziert vor.

von Karl H. (kbuchegg)


Lesenswert?

In der ISR
>
1
>  ...
2
>   if (ALPHAsoll <= ALPHAIst)
3
>   {
4
>     ALPHAIst = 1023 - zaehler4;           // Jeden Interrupt soll AlPHaIst an AlPHASoll näher herangetragen werden
5
>   
6
>   }
7
>

was spricht gegen
1
ISR (TIMER4_COMPA_vect)
2
{
3
  if( !sanftanlauf_durch )
4
  {
5
    if (ALPHAsoll < ALPHAIst)
6
      ALPHAIst--;
7
    else if (ALPHAsoll > ALPHAIst)
8
      ALPHAIst++;
9
    else
10
      sanftanlauf_durch = 1;
11
  }
12
13
  TCNT4 = 0;      // Timer 4 wird wieder auf 0 gesetzt
14
}

Mit jedem ISR Aufruf wird ALPHAIst ein Stückchen näher an ALPHAsoll 
herangeführt.

Was du da allerdings in der Hauptschleife inszenierst, ist mir nicht 
wirklich klar.

(anstatt dem TCNT4 auf 0 setzen, wäre ein CTC Modus die bessere Wahl, 
aber das ist momentan ein Nebenschauplatz).

: Bearbeitet durch User
von Fabian B. (Gast)


Lesenswert?

Hallo Karl-Heinz,

danke für die schnelle Antwort.
Das mit der komplizierten Beschreibung tut mir leid.
Ich probier es nochmal anders:

Das Problem liegt nicht unbedingt in der ISR. (Es spricht absolut nichst 
gegen deine Möglichkeit)

Der ALPHAIst wird ja verkleinert und das geänderte Ergebnis auch 
ausgegeben bis sanftanlauf_durch ist.

Leider gibt er auch direkt am Anfang das Signal aus, welches man 
eingestellt hat. Und nicht den maximalen Wert 1023, damit gewährleistet 
ist, das immer ein Maximaler ALPHA Wert zuerst ausgegeben wird und 
danach die entsprechend kleineren.

Ich weis gerade nicht wie ich das Problem näher einkreisen könnte.

Und ja der CTC Modus wäre das selbe.

von Karl H. (kbuchegg)


Lesenswert?

So wie ich das sehe, hast du doch im main() im Prinzip eine 
Statemaschine vorliegen, oder nicht.
Die hat 3 Zustände
* vor dem Start
* nach dem Start, aber der Softanlauf läuft noch
* nach dem Softanlauf

Dann programmier das doch auch so
1
#define PRE_START  0
2
#define SOFTSTART  1
3
#define REGULAR    2
4
5
uint8_t state;
6
7
int main()
8
{
9
  ....
10
11
  state = PRE_START;
12
13
  while( 1 )
14
  {
15
    switch( state )
16
    {
17
18
      case PRE_START:
19
        ALPHAsoll = ADC_Lesen();
20
        ALPHAist = ALPHA = 1024;
21
22
        if(ButtonPressed(0, PINA, 2, 500))
23
          state = SOFTSTART;
24
        break;
25
26
      case SOFTSTART:
27
        ALPHA = ALPHAist;   // ALPHAist wird von der Timer ISR runtergezählt
28
        if( ALPHA == ALPAsoll )
29
          state = REGULAR;
30
        break;
31
32
      case REGULAR:
33
        ALPHA = ADC_Lesen();
34
        break;
35
    }
36
37
    ... mach was mit ALPHA (eventuell unter berücksichtigung, dass
38
    ... der state nicht mehr PRE_START sein darf. Keine Ahnung, kenn ja
39
    ... deine Anwednung nicht
40
  }
41
}


Das wäre für mich recht naheliegend.
Wenn dieser Softwanlauf nur ein einziges mal gebraucht wird, dann könnte 
man das auch nach dem Muster vereinfachen
1
int main()
2
{
3
  ....
4
5
  // Vorgeplänkel. AUf den Start warten
6
  do
7
  {
8
     ADC einlesen und als ALPHAsoll speichern
9
     ALPHA gleich 1024 ausgeben
10
  } while( ! ButtonPressed( ... ) )
11
12
  // Softanlauf
13
  ALPHAist = 1024;
14
  while( ALPHAist != ALPHAsoll )
15
    ALPHAist ausgeben;
16
17
  // Softanlaufphase ist vorbei. reguläre Arbeit aufnehmen
18
  while( 1 )
19
  {
20
    ALPHA = ADC_Lesen();
21
    ...
22
  }
23
}

IMHO hast du dich dadurch verzettelt, dass du zuviel Logik in ein paar 
Zeilen Code quetschen willst. Dazu benutzt du dann 25 Flags und 
Hilfsvariablen und irgendwann durchschaut man dann deren Zusammenspiel 
nicht mehr.

: Bearbeitet durch User
von Fabian B. (Gast)


Angehängte Dateien:

Lesenswert?

Ok warte,

ich gehe nochmal in mich und versuche es so deutlich wie möglich zu 
formulieren.

Ich lese beim buttonpress in der Main den Wert der aus dem Poti kommt = 
den ADC zurückgibt (ADCW). Und obwohl ich dem Prozessor sage nimm den 
Wert den ich dir berechne im ISR(Timer4_CompA_vect) und erst wenn ich 
durch bin nimm den ADCW. Macht er den allersten Zugriff auf den ADCW. 
Und das allerschlimmste das macht er nur 4 von 10 mal.

Ich bekomme diese Gewissheit von einem Oszilloskop welches ich so 
eingestellt habe das es bei einer horizontalen Abweichung von einem 
Messwert einen single Shot (single seq) mir ein Bild macht welches ich 
mir dann anschauen kann.

Vielleicht wird es klarer wenn ich die Bilder hier auch poste.

Wie man sieht soll der ALPHA den roten Impuls von der Mittellinie nach 
links verschieben. Gelb ist das Netz, Blau die Nulldurchgangserkennung.

Und irgendwie schaffe ich es nicht dem Compiler zu sagen, nehme den 
maximalen möglichen Digit (1023) der 1.6 ms nach Netzdurchgang kommt und 
verkleinere diesen Digitwert bis dorthin wo du stehen sollst.

Konnte ich das so klarer Darstellen?

von Karl H. (kbuchegg)


Lesenswert?

Fabian B. schrieb:

> Und irgendwie schaffe ich es nicht dem Compiler zu sagen, nehme den
> maximalen möglichen Digit (1023) der 1.6 ms nach Netzdurchgang kommt und
> verkleinere diesen Digitwert bis dorthin wo du stehen sollst.
>
> Konnte ich das so klarer Darstellen?

Ja.
Und jetzt schau dir noch mal die Codesnippets an, die ich im letzten 
Post gemacht habe. Beide Varianten lösen IMHO genau dieses Problem, 
indem sie nicht alles in 3 Zeilen Code zu quetschen versuchen, sondern 
ganz einfach deine Anlaufphase in 3 voneinander getrennte Phasen 
aufteilen und dort explizit und leicht verständlich die jeweils 
relevanten Dinge abhandeln. Was immer das dann auch in der jeweiligen 
Phase sein mag.

: Bearbeitet durch User
von Fabian B. (Gast)


Lesenswert?

Hi Karl-Heinz,

das ist richtig.

Die drei cases existieren und an die Case funktion an sich habe ich noch 
gar nicht gedacht.

Ich programmiere diese mal aus und hoffe das das Problem dadurch gelöst 
ist.

Bis dahin danke schon mal für die Idee.

Und ich halte euch/dich auf dem laufenden.

Lg Fabian

von Fabian B. (Gast)


Lesenswert?

Hallo Community,
lieber Karl Heinz,

die Idee mit der Switch Case Funktion war super. Das Programm ist 
dadurch viel intuitiver erfassbar. Leider existiert das Problem mit den 
falschen Zündimpulsen (Bilder beim Post heute Mittag beachten) immer 
noch. So dass er manchmal (bisher 12 von 20) Zündungen richtig macht und 
manche (20-12=8 ^^) falsch.
1
###########################################################
2
#define VOR_START 0
3
#define HOCHLAUF 1
4
#define NORMAL   2
5
+++++++++++++++++++++++++++++++++++++++++++++++
6
while(1)
7
{
8
  switch (STATUS)
9
  {
10
    case VOR_START: 
11
          
12
      ALPHAsoll = ADC_Lesen();
13
      ALPHA    = ALPHAmax;
14
15
                  if(Buttonpressed(...))
16
                  {
17
    STATUS = HOCHLAUF;
18
    TCNT4 =     0;
19
    zaehler4 =     0;
20
    ALPHAIst =   1023;
21
                   }
22
    break;
23
      
24
    case HOCHLAUF:
25
        
26
            
27
ALPHA = (((ALPHAmax - ALPHAmin) / 1023) * (ALPHAIst) + ALPHAmin);  
28
29
    if (ALPHAIst == ALPHAsoll)
30
      STATUS = NORMAL;
31
32
    break;
33
34
    case NORMAL:          
35
      
36
         Berechne ALPHA über ADCW 
37
                 break;
38
}
Mir ist leider auch schleierhaft wieso er immer noch (jedoch nur ab- und 
an) den falschen ADCW wert sofort schreibt obwohl es jetzt klar ist, das 
der Compiler das so nicht tuen sollte.

Bin für jeden Hinweis dankbar auch gerne in die Richtung "Wieso braucht 
die ISR keine Schreibberechtigung um den Wert von ALPHAIst zu verändern"
(Irgendwie hab ich so ein Bauchgefühl das es daran liegt)

Lg Fabian

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Fabian B. schrieb:

> Bin für jeden Hinweis dankbar auch gerne in die Richtung "Wieso braucht
> die ISR keine Schreibberechtigung um den Wert von ALPHAIst zu verändern"


Weil das eine globale Variable ist.
Jeder der will, darf drann rummachen.

> (Irgendwie hab ich so ein Bauchgefühl das es daran liegt)

das einzige was daran problematisch ist, ist das Problem, dass du da 
einen atomaren Zugriff drumm herum legen musst, weil das eine 16 Bit 
Variable ist und der AVR die nicht in einem Rutsch lesen kann.
d.h. du solltest tunlichst in der Hauptschleife so was
1
    ....
2
    cli();
3
    Wert = ALPHAist;
4
    sei();
5
    mit 'Wert' weiterrechnen
6
    ...
machen, anstelle von direkten Zugriffen auf ALPHAist.

von Fabian B. (Gast)


Lesenswert?

Danke

Probier ich gleich aus.
Leider sieht es bisher so aus als ob das das Rätsel Lösung noch nicht 
ist. Falls noch weitere Ideen herumgeistern immer her damit.

Vielen Dank für alles bisher

Lg Fabian

von Karl H. (kbuchegg)


Lesenswert?

Sowas
1
ALPHA = (((ALPHAmax - ALPHAmin) / 1023) * (ALPHAIst) + ALPHAmin);
ist zum Beispiel eine ausgesprochen schlechte Idee.
Divisionen immer soweit wie möglich nach rechts in den Berechnungen 
schieben! Hier verlierst du extrem an Genauigkeit. Das ist eine 
Integer-Division! Die produziert keine Nachkommastellen! Das Ergebnis 
von 7/5 ist 1 und nicht 1.4

: Bearbeitet durch User
von spess53 (Gast)


Lesenswert?

Hi

>int ADC_Lesen(void)
>{
>ADCSRA |=(1<<ADEN);       //ADC aktivieren

>....

>  ADCSRA &= ~(1<<ADEN);      //ADC deaktivieren
>return ADCW;        //Ergebnis von 0 bis 1023 ausgeben
}

Datenblatt:

A normal conversion takes 13 ADC clock cycles. The first conversion 
after the ADC is switched on (ADEN in ADCSRA is set) takes 25 ADC clock 
cycles
in order to initialize the analog circuitry. When the bandgap reference
voltage is used as input to the ADC, it will take a certain time for
the voltage to stabilize. If not stabilized, the first value read after
the first conversion may be wrong.

Schalte den ADC einmal in der Initialisierung an und lass dann die 
Finger von ADEN.

MfG Spess

von Fabian B. (Gast)


Lesenswert?

Hallo Karl-Heinz,

ja das mit der Integer Rechnung ist mir bekannt. Ist jedoch erstmal kein 
Problem, da ALPHA eine Wert zwischen 20000 und 75000 bekommt, da sind 
dann die Kommazahlen rein rechnerisch nicht vorhanden.

Hallo Spess53 ein guter Hinweis. Die Stelle im Datenblatt kenne ich. Ich 
hatte euch vorenthalten das bei der Initialisierung das ADSC Bit mit 
gesetzt wird und damit schon eine Umwandlung läuft, dessen Wert dann 
durch die nächste Umwandlung in meinem Hauptprogramm wieder 
überschrieben wird.

Hm... ich hatte gedacht das das wichtige Bit das ADSC Bit ist, denn 
damit wird die Umwandlungen gestartet und man danach das ADEN Bit wieder 
löschen kann (Das ADC Register somit wieder deaktiviert [Hintegrund: 
Wenn man die Werkbank verlässt, schaltet man doch das Licht aus, oder 
habe ich mir damit ein eigenes Bein gestellt])?

Lg Fabian

von Fabian B. (Gast)


Lesenswert?

Ok,

Update:

Auch wenn ich das ADEN Bit nur in der Initialisierung setzte und von der 
ADC Hardware das ADSC Bit rücksetzen lasse, komme ich immer noch nicht 
an den allerersten Wert des ADCW der mal (52%) bei max anfängt (richtig) 
und mal (48% im groben)bei dem eingestellten Wert vom Poti (falsch).

Gut der Fehler wird durch eure Tipps immer näher eingekreist, leider ist 
er immer noch nicht erschlagen.

Um mal Starship Troopers zu zitieren: "Ich hasse Bugs"

Somit sieht es so aus:

Durch switch case Funktion ist das ganze Intuitiv erfassbar.
Durch das einmalige setzen von ADEN ist auch diese Fehlerquelle behoben.

Ich hoffe nicht das aus Starship Troopers die unendliche Geschichte 
wird.

Lg

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.