Forum: Mikrocontroller und Digitale Elektronik ADC Programm


von snoopy (Gast)


Lesenswert?

hallihallo!


Wer kann mir helfen, dieses ADC Programm zum laufen zu bringen? Ich
finde den Fehler nicht!


Der Code sieht so aus:


include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include "adc.h"
#include "USART.h"

volatile uint16_t result;
volatile unsigned char buffer[20];

ISR(ADC_vect)
{
  result = ADCL + ADCH;    // 10 Bit Ergebnis
  ADCSRA &= ~(1<<ADEN);    //ADEN "0" : ADC ausschalten

  itoa(result,buffer,10);
}

uint16_t ADC_free_running(uint8_t channel)
{
  DDRC = 0x00;                    //PC0 als Eingang definieren
  PORTC = 0x00;                    //Pullup an PORTC aus

  ADCSRA = ( (1<<ADFR) | (1<<ADPS2) | (1<<ADPS1) |(1<<ADIE) );
        ADMUX =   (1<<REFS0) | channel;

        ADCSRA |=( (1<<ADEN) | (1<<ADSC) );
                        //ADEN "1"...ADC enable
      //ADSC set to "1" : start conversation
  sei();

}

int main(void)
{
  init_USART();
  uart_puts("Controller online.....  OK\n\r");

  for(int i=0;i<20;i++)
  {
  uart_puts("Aktueller Wert auf PC1 :");

  ADC_free_running(1);

  uart_puts(buffer);
  uart_puts("\n");

  }
}




Thx!

von Christian Rötzer (Gast)


Lesenswert?

Es "stechen" mehrere Dinge ins "Auge". Aua.

Im Prinzip ist Deine Vorgehensweise ja korrekt, allerdings solltest Du
im ADC nicht Laufzeitroutinen der C-Bibliothek aufrufen. Das kann arge
Interferenzen mit dem Hauptprogramm geben, da evtl. gemeinsame
Variablen benutzt werden.

Ausserdem erfährt das Hauptprogramm gar nicht, wann den nun eine
Messung fertig ist. Daher wird wohl mit maximaler Geschwindigkeit
"Käse" auf der seriellen Schnittstelle ausgegeben.

Statt

  result = ADCL + ADCH

solltest Du besser

  result = ADCL<<8 + ADCH

schreiben, womit Du gleich

  result = ADC

schreiben kannst.

Vorschlag:

Du nimmst ja schon ADEN zum Starten einer Wandlung her.
Warte doch im Hauptprogramm, bis ADEN durch den Interrupt zurückgesetzt
wird.
Dann wirfst Du im Hauptprogramm den itoa an, und schiebst den Buffer
raus. Viel Glück :-)

von Miha (Gast)


Lesenswert?

Ich glaube nicht
result=ADCL<<8 + ADCH;
sondern
result=ADCL + (ADCH<<8);

von snoopy (Gast)


Lesenswert?

Danke euch beiden für die Antwort...

@Christian:

Du meinst, ich soll den Aufruf von Standartbibliothelsfunktionen in der
ISR vermeiden, nehmen ich an... Das dachte ich mir auch schon ;-)

Naja, ich könnte das warten, bis ADEN zurückgesetzt wird, im main ja so
machen, oder?

while((ADCSRA & (1<<ADEN))==1 );    //Warte, bis ADEN gelöscht


Was das ADC Ergebnis betrifft.

Ich dachte, wenn ich

result = ADCL+ADCH schreibe, werden die zwei Regsiter aneinandergereiht
in der richtigen Reihenfolge,also das MSB (das 10. Bit) ganz links....
naja, wohl ein Irrtum :-)

Kann mir die "+" Operation irgendjemand erklären, also wie das nun
genau auszusehen hat bzw. wie die arbeitet?!

Danke!

von snoopy (Gast)


Lesenswert?

Ich meine, wenn ich mir die Verschiebung so vorstelle, sollte Miha Recht
haben und es müsste lauten :

result=ADCL + (ADCH<<8);

Nur, was genau passiert, wenn ich es so (falsch) schreibe, wie ich
ursprünglich.

Wie addiert der Compiler dann diese zwei Register?

von snoopy (Gast)


Lesenswert?

Denn obwohl ich nun diese Sache ausgebessert habe, bekomme ich über die
USART weiterhin folgendes:


Controller online.....  OK
Aktueller Wert auf PC1 :0

Es kommt jedesmal "0" als Ergebnis ?! :-P

von Christian Rötzer (Gast)


Lesenswert?

@Miha

war nur ein Test für den aufmerksamen Leser schluck

@snoopy

Naja, was erwartest Du von einem '+'? Es werden zwei Werte addiert.
Das ADC-Ergebnis liegt in zwei Registern vor ADCL für die unteren 8 Bit
und ADCH für die oberen 2 Bit. Aber der Compiler weiss ja nicht, was Du
von Ihm willst, bzw. was er mit den Werten tun soll. Das musst Du ihm
schon selber mitteilen. Und mit der Operation

result=ADCL + (ADCH<<8); // Gell, Miha?

erreichst Du genau dieses:

ADCL:       . . . . . . . . 7 6 5 4 3 2 1 0

ADCH<<8:    . . . . . . 9 8 . . . . . . . .

result:     . . . . . . 9 8 7 6 5 4 3 2 1 0

Statt '+' darf auch verodert ('|') werden.

von Christian Rötzer (Gast)


Lesenswert?

Quelltext her. Aus dem Kopf lässt sich nun mal schwer programmieren.

von snoopy (Gast)


Lesenswert?

Ok,der ADC Wert wird nun korrekt ausgegeben!


Was aber die Frage bezüglich der Addition betrifft:

Angenommen der Fall, man hätte zwei Register namens "a" und "b".
Beide Register enthielten 4 Bits.
a = 1111
und
b = 0000

Würde ich nun schreiben:

result = a + b;

Bekomme ich dann "1111" als Ergebnis, also die "normale " Addition
des 1.Bits von "a" mit dem 1.Bit von "b" ... das 2. Bit von "a"
mit dem 2. Bit von "b"....... usw ??


Und wenn ich dann schreibe:

result = (a << 4) + b;

als Ergebnis : "11110000"  oder "111100000000" ?

Mit anderen Worten: Werden beim verschieben von "a" um vier Stellen
nach links, die STellen wo voher die "1111" standen,dieser Platz mit
"0000" aufgefüllt oder was steht an diesen Stellen?

Danke!

von snoopy (Gast)


Lesenswert?

Ok, sry, du hast wohl gerade gepostet, als ich die letzte Antwort
schrieb ;-)

von snoopy (Gast)


Lesenswert?

Nachdem ich jetzt aus dem ADC Wert die Spannung am Pin berechnen möchte,
muss ich ja wohl die Formel

Vin = (ADC*Vref) /1024   verwenden.

Als Referenz verwende ich derzeit die Spannung VCC, also in meinem
Falle 3.3V mit dem "mega8L".

Da komme ich wohl kaum drumherum, float- variablen zu verwenden, ohne
das Ergebnis grob zu verhunzen ( dann sit die 10 Bit - Genauigkeit auch
für nix :-) )

Grüße...

von Christian Rötzer (Gast)


Lesenswert?

Es wird mit nullen aufgefüllt. Kleiner Rundschlag:

Schieben um eine Positon nach links entspricht der Multiplikation mit
2; Schieben nach recht entspricht der Division durch 2.
C führt immer ein arithmetisches Schieben aus. Dies macht aber nur
einen Unterschied bei vorzeichenbehafteten Zahlen (speziell negativen)
die nach rechts geschoben werden. Aus -4 >> 2 wird also in der Tat -1.
Nur in diesem Fall wird links mit Einsen aufgefüllt:

-4 = 11111100

>> 2

-1 = 11111111

Das gilt also für signed int, signed char usw... Wenn man nicht
aufpasst, beist man sich daran die Zähne aus.

von Rolf Magnus (Gast)


Lesenswert?

> Naja, ich könnte das warten, bis ADEN zurückgesetzt wird, im main
> ja so machen, oder?
>
> while((ADCSRA & (1<<ADEN))==1 );    //Warte, bis ADEN gelöscht

Nein, so nicht. Du mußt das "==1" weglassen.
Außerdem wäre es an sich besser, ADSC zu verwenden. Das wird
automatisch vom ADC zurückgesetzt#, wenn die Wandlung beendet ist.
Übrigens: Warum benutzt du den ADC eigentlich im "free-running mode",
wenn du ihn sowieso nach jeder Wandlung wieder ausschaltest? Da wäre es
besser, diesen Modus nicht zu benutzen. Das hat auch den Vorteil, daß
der ADC nicht nach jeder Wandlung komplett neu initialisiert werden muß
und daß die Wandlung daher schneller ist.
Letztendlich kannst du dir die ganze ADC-ISR sparen, wenn du sowieso in
der Hauptschleife auf den ADC-Wert wartest, bevor du die nächste
Wandlung anstößt.

von Christian Rötzer (Gast)


Lesenswert?

float ist was für Dilletanten :-)

Als Übergang und wenn das Platz mal wieder etwas knapper ist, empfiehlt
sich der Umweg über einen long:

unsigned int Vin = ADC*3300L/1024;  // Result in mV

und zur Ausgabe:

printf_P(PSTR("Vin = %d.%03dV\r\n"),Vin/1000,Vin%1000);

Man beachte das dezente 'L' hinter '3300'.

von Rolf Magnus (Gast)


Lesenswert?

Statt Vin/1000 und dann Vin%1000 zu rechnen, empfielt sich übrigens die
Funktion div().

von snoopy (Gast)


Lesenswert?

>Nein, so nicht. Du mußt das "==1" weglassen.

Wieso? Solange der Ausdruck

(ADCSRA & (1<<ADEN))

eins ist, solange ist das Bit doch gesetzt, und ich will ja in der
Schleife bleiben, bis das nicht merh der Fall ist?!

Ohne dem "==1", würde die Schleife so lange laufen, solange der
Ausdruck  "ungleich "0" , oder?
Würde dann zwar hier auch gehen, aber warum nicht mit "==1"?

Das mit dem "ADSC" ist wohl korrekt und auch deine weiteren Aussagen.
Ich werde das alles noch besser machen, ich bin momentan noch am
"spielen", um mich mit der Materie vertraut zu machen! ;-)

Das mit der Funktion div() muss ich mir erst genauer ansehen, morgen
:-)

von Rolf Magnus (Gast)


Lesenswert?

Der Ausdruck ist nicht gleich 1, sondern (1<<ADEN). "&" ist eine
bitweise Verknüpfung, d.h. daß im Ergebnis genau die Bits gesetzt sind,
die in beiden Operanden gesetzt sind.

> Ohne dem "==1", würde die Schleife so lange laufen, solange der
> Ausdruck  "ungleich "0" , oder?

Genau.

> Das mit der Funktion div() muss ich mir erst genauer ansehen,
> morgen :-)

div() hat den Vorteil, daß es dir gleich Divisionsergebnis und -rest
zusammen zurückgibt. Bei einer Integer-Division fällt der Rest
automatisch als "Abfallprodukt an". Wenn du die Operatoren / und %
benutzt, werden zwei Divisionen durchgeführt. Bei einer wird das
Ergebnis verwendet und der Rest weggeworfen, bei der anderen umgekehrt.
Mit div() braucht's nur eine Division.

von snoopy (Gast)


Lesenswert?

Aha, seh schon :-)

Mann, ihr seid ja echt aufs optimieren aus, gell? :-)

Fällt eine Division mehr oder weniger tatsächlich so ins Gewicht? ICh
denke da immer an eine Taktgeschwindigkeit von ein paar MHz :-)

Aber nicht falsch verstehen, ich finde es absolut spitze, wie ihr
Anfängern( in diesem Falle mir) weiterhelft und bin dankbar für jeden
Ratschlag!!

Grüße...

von Christian Rötzer (Gast)


Lesenswert?

@Rolf

Das mit div() scheint interessant zu sein:

printf_P(PSTR("Vin = %d.%03dV\r\n"),div(Vin,1000));

ist fies, könnte aber zum erhofften Ergebnis führen, da der struct
gleich wieder exakt in der gewollten Reihenfolge auf den Stack kommt.
Aber das nur am Rande. Muss ich selber mal testen :-)

von snoopy (Gast)


Lesenswert?

So... das mit dem div() und dem umrechnen muss ich mir erst zu Gemüte
führen, jedenfalls hab ich nun mal die "single conversion" verwendet,
und zwar möchte ich die Anzahl der Abtastungen wählen können.

Am Ende sollte das Ergebnis der Mittelwert aus der Anzahl der
Abtastunge sein...

Ich hab mir das so gedacht:


uint16_t ADC_single_con(uint8_t channel,uint8_t anzahl)
{
  DDRC = 0x00;
  PORTC = 0x00;

  ADCSRA = ( (1<<ADPS2) | (1<<ADPS1) );
    ADMUX =   (1<<REFS0) | channel;
  ADCSRA |=( (1<<ADEN) | (1<<ADSC) );

  for(int i=1;i==anzahl;i++)
  {

    while(ADCSRA & (1<<ADSC) );

    if(i==1) //Erstes Ergebnis vorhanden
    {
      result = ADCL + (ADCH<<8);
    }

    else
    {
      result+= ADCL + (ADCH<<8);
    }
  }

  result / = anzahl;
}

Ist das so richtig?!

von Christian Rötzer (Gast)


Lesenswert?

Naja, eigentlich ist das der typische Programmierloop: Programmieren -
Testen - Programmieren - ....

Deiner ist jetzt:

Programmieren - Posten - Testen :-)

Aber gut:

Auf Anhieb sieht man, dass es so besser ist:

  result = 0;
  for (int i=0; i<anzahl; i++)
  {
    while(ADCSRA & (1<<ADSC) )
      ;
    result += ADCL + (ADCH<<8);
  }
  result /= anzahl;


In einer for-Schleife schreibt man nicht die Bedingung für den Abbruch,
sondern die Bedingung für die Fortführung der for-Schleife!

von johnny.m (Gast)


Lesenswert?

>result += ADCL + (ADCH<<8);
Nimm besser ADC anstatt ADCL und ADCH. Es ist bei diesen Registern
essentiell wichtig, dass sie in der richtigen Reihenfolge ausgelesen
werden (erst Low-, dann High-Byte). Wenn der Compiler das schon für
Dich sicherstellt, solltest Du die Funktion auch nutzen.

von snoopy (Gast)


Lesenswert?

Gut, aber von der Funktion her betrachtet, mus es doch das selbe sein,
ob ich nun


for(int i=0;i<anzahl;i++)

oder

for(int i=1;i==anzahl;i++)

schreibe. Die Anzahl der Durchläufe ist beide Male gleich und auf das
komtm es ja an?!

Nun habe ich jedoch einen Compilererror :
Und zwar in der Zeile:

result / = anzahl;
error: syntax error before '=' token


Ach und was bedeutet das?!

itoa(result,buffer,10);

warning: passing arg 2 of `itoa' discards qualifiers from pointer
target type

von snoopy (Gast)


Lesenswert?

Ok, den error hab ich nun behoben:

Bei:

result /= anzahl;

Darf zwischen den "/" und dem "=" wohl kein Leerzeichen sein. Auf
das muss man mal kommen :-)

von Rahul (Gast)


Lesenswert?

>for(int i=0;i<anzahl;i++)
>oder
>for(int i=1;i==anzahl;i++)

Hast du dir schon mal ein C-Buch angegguckt, und darin die Definintion
der for-Schleife?
1. Parameter: Startbedingung
2. Parameter: Schleifenbedingung, solange diese erfüllt ist, wird die
Schleife ausgeführt.
3. Parameter (kann ich mir wohl sparen)

von snoopy (Gast)


Lesenswert?

Naja, ich hab mir nur gedacht, weil ich das getestet und laut meiner
Ansicht laufen beide Varianten gleich. Es hat funktioniert..

von Karl heinz B. (kbucheg)


Lesenswert?

> Gut, aber von der Funktion her betrachtet, mus es doch das selbe
> sein, ob ich nun
>
> for(int i=0;i<anzahl;i++)
>
> oder
>
> for(int i=1;i==anzahl;i++)

Mach mal ein Gedankenexperiment.
Das 2.-te Argument in der for-Schleife liest Du dabei als:
"Wiederhole, solange gilt, dass"

Dann wirst Du sehen, dass wenn 'anzahl' nicht grade den Wert
1 besitzt, Deine for Schleife überhaupt nicht ausgeführt wird.

eine for Schleife ist equivalent zu:

   i = 1;
   while( i == anzahl ) {
     ...
     i++;
   }

> result / = anzahl;
> error: syntax error before '=' token

Nimm das Leerzeichen raus.  /=  ist 1 Token und muss auch
als solches geschrieben werden. Genauso wie ++ oder <=

> warning: passing arg 2 of `itoa' discards qualifiers from pointer
> target type

Ein qualifier ist eine Attributierung des Datentyps, wie zb
volatile oder const. Beim Aufruf von itoa geht die verloren.

zb.

const char* string = "check";

schreibst du jetzt eine Funktion

  void foo( char* a );

dann wird dir der Compiler mitteilen, dass ein 'qualifier'
discarded wird. Er meint damit das const. Beim Aufrufer gilt
das const noch und der Compiler stellt sicher, dass es auch
eingehalten wird (dass keine Zuweisungen an string erfolgen).
Übergibst du allerdings das 'string' an die Funktion foo, so
ist es innerhalb der Funktion nicht mehr const und theoretisch
könnte innerhalb der Funktion genau das passieren: das string
verändert wird.

Ich denke mal, in Deinem Fall wird die Sachlage so sein, dass
'buffer' ein volatile char Array sein wird. Und dieses volatile
geht verloren, wenn du es an itoa übergibst.

In diesem speziellen Fall ist das aber kein Beinbruch, und
du kannst den Compiler ruhig stellen:

itoa( result, ( char* )Buffer, 10 );

Mit dem cast castest du das volatile weg, bevor das ganze an
itoa geht. Aber Achtung: cast's sind Waffen! Im Endeffekt
überschreibst du damit die Typprüfung des Compilers und stellst
ihn ruhig. Manchmal muss man casten um Warnungen des Compilers
wegzukriegen allerdings muss sichergestellt sein, dass der
Programmierer weiss was er tut.

von Karl heinz B. (kbucheg)


Lesenswert?

> Auf das muss man mal kommen :-)

Nun ja. Das steht in jedem schlechteren Lehrbuch über C
drinnen. Hab ich Dir schon angeraten, (mindestens) ein Buch
zu kaufen? Du wirst es brauchen.

von Rolf Magnus (Gast)


Lesenswert?

> for(int i=0;i<anzahl;i++)

Hier wird i vor de Schleife auf 0 gesetzt. Sie wird ausgeführt, solange
i kleiner als anzahl ist, und bei jedem Durchlauf wird i um 1 erhöht.
Die Zahl der Schleifendurchläufe entspricht also anzahl.

> for(int i=1;i==anzahl;i++)

Dies hier setzt i vor der Schleife auf 1. Solange i denselben Wert hat,
wie anzahl, wird die Schleife durchlaufen. Es wird wieder bei jedem
Durchlauf i inkrementiert.
Falls anzahl einen Wert von 1 hat, wird die Schleife also einmal
durchlaufen. Für jeden anderen Wert von anzahl wird sie niemals
ausgeführt.

> Ach und was bedeutet das?!
>
> itoa(result,buffer,10);
>
> warning: passing arg 2 of `itoa' discards qualifiers from pointer
> target type

Es sieht so aus, als ob du versuchst, mit itoa in einen String zu
schreiben, der eigentlich konstant ist.

von snoopy (Gast)


Lesenswert?

Aja.... mal wieder was gelernt :-)
Dann hab ich bislang wohl tatsächlich die for-Schleife nicht ganz
verstanden...

Also das mit den cast´s ist mir im Moment ein wenig zu hoch, aber ich
werd mcih damit auseinandersetzen...

Das Problem ist halt, man braucht dazu nicht nur einfach
"C-Kenntnisse", sondern wie es scheint auch spezielle Kenntnisse über
den Compiler :-P

Naja, jedenfalls hab ich heute meine C-Lektüre erhalten.

Hab mir das Buch

"C von A-Z" von Jürgen Wolf besorgt..

Ich hoffe, ich werde damit klüger :-)

Grüße...

von snoopy (Gast)


Lesenswert?

Bezüglich diesem Ausdruck:

unsigned int Vin = ADC*3300L/1024;  // Result in mV

Das "L" bedeutet da ja, dass der Compiler die Konstante davor als
"long" nehmen soll...

Nur, die zahl 3300 ist ja eh nicht so groß, das Ergebnis ( Vin )der
Multiplikation überschreitet also einen uint16_t.

Warum steht also das "L" also nach der zahl 3300 und nicht bei der
variable "Vin" dabei??

Danke!

von johnny.m (Gast)


Lesenswert?

Die Zuweisung wird in C als letztes gemacht. Erst wird der Term auf der
rechten Seite ausgewertet. Und wenn da nicht nach long gecastet wird,
gibts Müll. Selbst wenn Du den Müll (der dann evtl. tatsächlich
uint16_t ist) einem long zuweist, fehlt dann was.

von Rolf Magnus (Gast)


Lesenswert?

Man kann's auch so formulieren: Der Datentyp, mit dem die Operation
durchgeführt wird, hängt ausschließlich von den Operanden ab und nicht
davon, was du mit dem Ergebnis nachher machst. Ohne das L würde die
Berechnung daher nur mit 16 Bits durchgeführt werden.
Das L sorgt dafür, daß ein Operand 32 Bit breit ist, wodurch auch der
andere nach 32 Bit konvertiert und die Rechnung dann entsprechend
durchgeführt wird.

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.