ADC

XMega Blockdiagramm

Der ADC ermöglicht es dem Mikrocontroller analoge Spannungen einzulesen.
Der XMega128A1 besitzt zwei ADCs mit jeweils 8 Eingängen und einer Auflösung von 12 Bit.
Zusätzlich verfügt jeder ADC über 4 Kanäle mit denen er Messungen durchführen kann.
Dadurch wird es dem ADC ermöglicht bis zu vier Wandlungen durchführen zu können ohne das die CPU des Mikrocontrollers den ADC unterbrechen muss.
Sowohl der Port A als auch der Port B besitzen jeweils einen ADC.

-> ADC konfigurieren:

Für meine Versuche verwende ich den ADC vom Port A und das Ergebnis wird per USART an einem Computer geschickt und dort auf einem Terminal ausgegeben.
Es empfiehlt sich also das Kapitel USART etwas genauer durchzulesen bevor man mit dem ADC weiter macht.
Das Unterprogramm um den ADC zu konfigurieren sieht so aus:

Als allererstes stelle ich mit der Zeile

die Auflösung des ADCs auf 12 Bit. Möchte man eine Auflösung von nur 8 Bit verwenden muss die Zeile dementsprechend angepasst werden.
Mit dieser Zeile

stelle ich die interne 1V Referenz ein.
Das Register für die Referenzspannung sieht so aus:

XMega_ADC(1)
Zusätzlich muss noch das zweite Bit gesetzt werden um die Bandgap-Referenz zu aktivieren. Falls die Bandgap-Referenz bereits für den DAC oder die Brown-Out Detection verwendet wird, muss dieses Bit nicht noch einmal gesetzt werden!

XMega_ADC(2)
Wenn ihr eine andere Referenzspannungsquelle verwenden wollt, müsst ihr die Zeile natürlich anpassen.
Da ich in meinem Versuch einen internen Takt von 41MHz verwende und der ADC laut Datenblatt nicht mehr als 2MSPS schafft, muss ich den Prescaler verwenden um den Takt runter zu teilen. Ich habe 512 gewählt und damit erreiche ich eine Samplerate von 80kSPS. Falls ihr eine niedrigere Taktfrequenz benutzt, braucht ihr unter Umständen keinen Prescaler benutzten.
Anschließend wird der ADC in der letzten Zeile aktiviert.

-> Eine Spannung messen:

Als erstes wir der PortA als Eingang deklariert. Dies geschieht so:

Das Unterprogramm welches die Messung übernimmt, wird so deklariert:

Diese Funktion gibt einen Wert vom Typ unsigned int zurück und sie erwartet als Übergabeparameter einen Pointer vom Typ ADC_CH_t (dies ist eine Struktur die AVR Studio verwendet und in der die gesamten Register die für einen Kanal verwendet werden gespeichert sind), sowie den Pin an der die Spannung gemessen werden soll.
Die Funktion wird so aufgerufen:

Das Ergebnis der Wandlung wird in der Variable ADC_Value gespeichert.
Die Funktion erhält als Übergabeparameter die Adresse von ADCA.CH0, sowie die Nummer des gewünschten Eingangspins, hier in dem Fall der Pin 0.
Wenn ihr anstelle von ADCA lieber ADCB oder statt Kanal 0 (CH0) lieber Kanal 1 (CH1) verwenden wollt, so müsst ihr dies nur einmal im Funktionsaufruf ändern, anstatt sonst etwa fünf mal in der Funktion selber.
Dies macht die Auswahl des ADCs und des Channels ein bisschen einfacher und bequemer.
Das Programm für die Wandlung sieht nun so aus:

Wie ihr seht, verwende ich hier folgenden Ausdruck ein paar mal:

Dieser Ausdruck ist eine andere Schreibweise für *Channel.Register.
Genauer gesagt, zeigt der Pointer Channel auf die Adresse von ADCA.CH0 und diese Adresse wird über den -> Operator den dementsprechenden Registern in der Struktur ADC_CH_t hinzugefügt.
Für den Compiler ist dies das selbe als wenn ihr z.B. direkt ADCA.CH0.CTRL schreibt.
Der Vorteil von dieser Methode, gegenüber dem direkten eintippen des Namens ist. dass so die Namen flexibel vergeben werden könnt. Dadurch wird es möglich direkt im Funktionsaufruf zu bestimmen welcher ADC und welcher Kanal verwendet werden soll, ohne das die gesamte Funktion geändert werden muss.
Doch nun zum Unterprogramm…
Fangen wir mit dieser Zeile an:

Mit dieser Zeile stelle ich den Single ended Mode für den ausgewählten Kanal ein.
Die nächste Zeile schreibt den Wert für unseren Eingangspin ins MUX-Register.
In meinem Beispiel wäre das eine 0. Es können so aber auch alle anderen Pins vom ADC genutzt werden. Auch hierfür muss lediglich der Funktionsaufruf angepasst werden.
Mit der Zeile

starte ich die Messung an meinem Pin und diese Zeile

hält das Programm solange an bis eine Wandlung abgeschlossen wurde.
Dadurch wird verhindert, dass zu viele Wandlungen hintereinander durchgeführt werden sollen und der ADC dadurch ins schleudern gerät.
Die letzte Zeile

ließt das Datenregister des Kanals aus und gibt das Ergebnis als Funktionswert zurück.
Anschließend verwende ich noch diese drei Zeilen um das Ergebnis in einen String umzuwandeln und diesen per USART an meinen PC zu senden wo das Ergebnis anschließend mittels TeraTerm ausgegeben wird.

-> ADC kalibrieren:

Damit der ADC möglichst genau arbeitet, solltet ihr ihn zusätzlich noch kalibrieren.
Die Unterprogramme hierfür sehen so aus:

Durch diese beiden Unterprogramme werden die Kalibrationsbytes vom ADCA ausgelesen und in die dafür vorgesehenen Register geschrieben.
Wie dies genau funktioniert könnt ihr hier nachlesen.

 

-> Zurück zum XMega Tutorial

16 thoughts on “ADC
  1. Hallo, ich habe fast eins zu eins deinen code kopiert aber leider bin ich trotzdem nicht im Stande das es funktioniert. Kannst du mir vielleicht helfen?

    • Hallo,

      ich habe deinen Code mal entfernt, da der Kommentar sonst zu lang wird. Sowas bitte demnächst per Mail schicken.
      Auf den ersten Blick ist dein Code ziemlich durcheinander. Ich schaue mal ob ich ihn vernünftig zusammengebastelt bekomme.
      Du hast auch kein richtiges Hauptprogramm und alles.
      Wenn du mir deine E-Mailadresse gibst, kann ich dir den Code gerne zuschicken.
      Laut AVR Studio ist er in Ordnun….was die Syntax angeht. Ob er dann bei dir funktioniert ist ein anderes Thema :)

  2. s.reps@gmx.at

    Sorry das es so ein starkes durcheinander ist, normalerweiße programmier ich nicht so, nur normalerwieße benutz ich einen pic und da ist für mich alles einfacher :)
    Ich hab in dem programm schon einiges probiert und deswegen dann nichts mehr sortiert und auch nicht mehr alles herausgelöscht. :S

    Ich hoffe du kannst mir helfen bei einer einfachen ausgabe auf die uart vom adc.
    Danke

  3. Tolle, wirklich tolle Seite!!!
    Habe sie leider erst viel zu spät entdeckt :) nachdem ich mich schon durch gefühlte hundert Atmel User Guides durch gelesen habe.

    Weiter so!!!

  4. Hallo zusammen,
    kann nur meinen Vorgängern zustimmen, wirklich eine tolle Seite die ihr hier habt.

    Ich habe bisher immer mit dem ATmega programmiert und bin soweit klar gekommen. Seit kurzem bin ich auf den Xmega umgestiegen und komme bei dem ADC Initialisierung nicht weiter.

    Bitte deshalb um hilfe
    Könn

    • Hey,

      da ich jetzt endlich ein neues XMega Board habe, kann ich auch endlich weiter machen :)
      Schick mir das Programm mal bitte per E-Mail zu.
      Ich gucke dann die Tage / Wochenende drüber (bin erst wieder Freitag zu Hause).

      Gruß
      Daniel

  5. Diese Zeile funktioniert bei mir offenbar nicht:
    while(!Channel->INTFLAGS);
    Wenn ich statt dessen eine Millisekunde warte, dann geht es.

    • Habe inzwischen rausgefunden woran es wirklich lag.
      In der Funktion ADCA_Conversion() fehlt das Ruecksetzen der INTFLAGS.
      Es sollte also noch folgende Zeile eingefuegt werden:
      Channel->INTFLAGS = ADC_CH_CHIF_bm; //INTFLAGS loeschen

      Ausserdem ist noch die Zeile „int Result = 0x00;“ ueberfluessig.
      (Ergibt eine entsprechende Compiler-Warnung)

      Nach dem Umrechnen der gelesenen Werte in Millivolts bekomme
      ich immer einen signifikanten Offset. Wobei es keine Rolle spielt ob
      ADCA_Cal() benutzt oder nicht.
      Der Offset ist auch noch leicht unterschiedlich ob PA1 oder PA2 benutzt.
      Ich habe mir mit diesem Unterprogramm fuer die Umrechnung beholfen:

      uint adc_millivolt(uint adwert,int offset)
      {
      long mvlong = (adwert<=offset) ? 0 : (long)REFERENZSPANNUNG*(adwert-offset);
      return (mvlong+2048)/4096;
      }

      Aufgerufen wird es dann jeweils so:
      sprintf(text,"%4d mV %4d mV ",adc_millivolt(adwert1,156),adc_millivolt(adwert2,166));

      Statt mit dem Uart gebe ich es jeweils auf ein LCD-Modul aus.

  6. Hallo Kampi,

    danke für deinen Code. Hab den ADC vom Xmega mal ausprobiert. Jetzt ist mir aufgefallen: Der Offset beträgt bei mir 129, ist das nicht ein bisschen arg viel? Hab jetzt schon zwei Controller ausprobiert, beide ziemlich identisch. Wie viel beträgt der Offset bei dir?

    Lg Andreas

    • Hey Andreas,

      der ADC hat 12-Bit Auflösung. Das macht bei einer Referenzspannung von Vcc, also 3,3V, etwa 0,0008056640625V. Das mit 129 multipliziert ergibt 0,1039306640625V. Du siehst, das es nicht ganz so viel ist. Der Fehler kann schon durch eine fehlende Kalibration auftreten oder wenn deine Referenzspannung (hier exemplarisch dein Vcc) schwankt.
      Gerade wenn du mehrere Controller getestet hast, würde ich es mit der Versorgungsspannung / fehlender Kalibration in Verbindung bringen.

      Gruß
      Daniel

      • Also das Kalibrationsregister hab ich sogar ausgelesen, dachte auch dass es daran liegt. Dadurch hat sich aber nix geändert.
        Ja wahrscheinlich liegts an der Stromversorgung. Ich stabilisiere die Eingangsspannung mit einem LM317 auf 3,3V, vielleicht sollt ich mal die Pufferkondensatoren vergrößern.

  7. Hallo,
    super Seite erst mal.

    Ich habe dein Beispiel versucht und es funktioniert Grundsetzlich. Allerdings habe ich folgendes Problem. Wenn mein ADC auf GNG liegt zeigt es immer noch LSB von 11 an.
    ich habe den ADC auf 8Bit laufen, Single Ended.Habe auch schon mit den Referenzspg. gespielt. Meine Ref erzeuge ich über einen Spannungsteiler der an einem OP als Buffer geschaltet hängt. Mein µC ist ein XMEGA 32D3.
    Vielleicht kennst du das Problem.

    • Hallo Matze,

      hast du den ADC mal kalibriert?
      Eventuell ist es eine fehlende Kalibration / Rauschen im ADC?
      Eine andere Idee hätte ich jetzt auch nicht.

      Gruß
      Daniel

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload CAPTCHA.