Forum: Mikrocontroller und Digitale Elektronik ADC ATXMega256A3 initialisieren


von Jan-Henrik B. (vedaykin)


Lesenswert?

Guten Tag,

ich versuche einen der ADC-Wandler eines ATXMegas256A3 zum Einlesen 
einer Gleichspannung zu programmieren. Ich habe mich bisher stark am 
XMEGA-C-Tutorial vom stromflo orientiert.

Der Code unten lässt sich ohne Fehlermeldung hochladen, jedoch messe ich 
am Port A Kanal 0 mit dem Oszi eine Gleichspannung von ca. 1.5V, ohne 
das überhaupt ein externes Signal anliegt. In der Variable "temp" steht 
dann immer der Wert 255. An VTG messe ich die 3.6Volt Betriebsspannung.

Habe ich die ADC-Ports falsch initialisert, bzw. muss ich was in der 
Hardwarekonfiguration ändern, sodass ich an Port A0 auch 0V messe? Ich 
bin für jeden Hinweis dankbar!
1
#include <avr/io.h>
2
 
3
int main (void) {
4
5
//ADC enable  
6
ADCA.CTRLA = 0x01;     
7
//unsigned mode und 8Bit, Einzelsignale und Interne Signale können gemessen werden
8
ADCA.CTRLB = 0x04;      
9
//Interne 1.0V Referenz
10
ADCA.REFCTRL = 0x00;    
11
12
uint8_t temp;
13
14
while(1) {  
15
//input mode single ended Port A Kanal 0, die Messung wird gestartet
16
ADCA.CH0.CTRL = 0b10000001;
17
//Ergebnis der Messung wird in temp gespeichert
18
temp = ADCA_CH0RES;
19
}
20
}

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


Lesenswert?

Hab dir mal was rausgesucht, was allerdings einen 3-Kanal Channel Sweep 
im Freerun macht:
1
#define ADC ADCA
2
// Setup the ADC for 3-Channel sampling
3
// runs free with 3 channels , zero effort
4
void ADCInit(void)
5
{
6
  ADC.CTRLA = 0x01;
7
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) ;  // 12 bit right adjusted
8
  ADC.PRESCALER = ADC_PRESCALER_DIV256_gc;
9
// reference set to Vcc/1.6
10
  ADC.REFCTRL = ADC_REFSEL_VCC_gc;
11
  ADC.EVCTRL = ADC_SWEEP_012_gc ;
12
// get calibration data , ymmv
13
  ADC.CALL = adc_get_calibration_data() && 0xff;
14
  ADC.CALH = (uint16_t)adc_get_calibration_data() >> 8;
15
// speed input
16
  ADC.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
17
  ADC.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN0_gc;
18
// current measure
19
  ADC.CH1.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
20
  ADC.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN1_gc;
21
// temperature
22
  ADC.CH2.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
23
  ADC.CH2.MUXCTRL = ADC_CH_MUXPOS_PIN2_gc;
24
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);  // enable freerun
25
}
26
// read result from channel 0
27
uint16_t ReadChannel0(void) {
28
 return ADC.CH0RES;
29
}
Oh, noch vergessen. Du solltest natürlich deine ADC Inputs auf ISC 
Disable konfigurieren:
1
#define USER_PORT PORTA
2
#define ADCMASK 0x07
3
// Port A 0-2 are analog inputs
4
  PORT_ConfigurePins(&USER_PORT,
5
      ADCMASK,
6
      0,
7
      0,
8
      PORT_OPC_PULLDOWN_gc,
9
      PORT_ISC_INPUT_DISABLE_gc);
Ich benutze hier eine Library Funktion, aber das kannst du natürlich 
auch anders hinfummeln.

von Jan-Henrik B. (vedaykin)


Lesenswert?

Ich habe die Bibliothek port_driver.h aus dem AVR Tuturial "AVR1313" 
eingefügt. Es scheint noch Probleme mit dem Befehl: ADC.CALH = 
(uint16_t)adc_get_calibration_data() >> 8; zu geben. Wenn ich das 
richtig verstanden habe muss ein Wert zwecks Kalibrierung in das 
Register CALH geschrieben werden. Fragt sich nur welcher ;)? Der initial 
Wert ist 0b00000000 glaube ich. Du möchtest einen Wert 8 mal nach rechts 
schieben, um die Kalbrierung durchzuführen richtig?

Auch die Variable: ADC_Mode kann ich noch nicht richtig nachvollziehen. 
In der Bibliothek iox256a3.h bedeutet ADC_RESOLUTION_12BIT_gc: 12-bit 
right-adjusted result. Ist das nicht schon genau das was ich möchte? Ich 
verstehe gerade nicht so ganz die Oderverknüpfung ADC_MODE << 4 in: 
ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);  // 
enable freerun

Mein noch nicht funktionierender Code sieht jetzt so aus:
1
#include <avr/io.h>
2
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.h>
3
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.c>
4
5
#define ADC ADCA
6
#define USER_PORT PORTA
7
#define ADCMASK 0x07
8
9
uint16_t t;
10
11
int main(void)
12
{
13
    ADCInit();
14
    PORT_ConfigurePins(&USER_PORT,ADCMASK,0,0,PORT_OPC_PULLDOWN_gc,PORT_ISC_INPUT_DISABLE_gc);
15
  
16
    while(1)
17
    {
18
    t = ADC.CH0RES; //Variable für Watchdog, um z.B. Sinus am Port A Pin 0 zu beobachten
19
    }
20
}
21
22
// Setup the ADC for 3-Channel sampling
23
// runs free with 3 channels , zero effort
24
void ADCInit(void)
25
{    
26
  ADC.CTRLA = 0x01;
27
  // 12 bit right adjusted
28
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4);  
29
  ADC.PRESCALER = ADC_PRESCALER_DIV256_gc;
30
  // reference set to Vcc/1.6
31
  ADC.REFCTRL = ADC_REFSEL_VCC_gc;
32
  ADC.EVCTRL = ADC_SWEEP_012_gc ;
33
  // get calibration data , ymmv
34
  ADC.CALL = adc_get_calibration_data() && 0xff;
35
  ADC.CALH = (uint16_t)adc_get_calibration_data() >> 8;
36
  // speed input
37
  ADC.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
38
  ADC.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN0_gc;
39
  // current measure
40
  ADC.CH1.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
41
  ADC.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN1_gc;
42
  // temperature
43
  ADC.CH2.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
44
  ADC.CH2.MUXCTRL = ADC_CH_MUXPOS_PIN2_gc;
45
  // enable freerun
46
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);  
47
}
48
// read result from channel 0
49
uint16_t ReadChannel0(void) {
50
 return ADC.CH0RES;
51
}

von Jan-Henrik B. (vedaykin)


Lesenswert?

Zusatz: Die Variable ADC_MODE verstehe ich nicht nur, sie ist auch in 
der Bibliothek nicht definiert.

von Alex (Gast)


Lesenswert?

Hallo,

>>Wenn ich das
richtig verstanden habe muss ein Wert zwecks Kalibrierung in das
Register CALH geschrieben werden.

Zum Testen braucht man keinen Kalibrier-Wert!

>> Fragt sich nur welcher ;)?
Aus der "User Signature Row" - habe das aber noch nicht benutzt



>> In der Bibliothek iox256a3.h bedeutet ADC_RESOLUTION_12BIT_gc: 12-bit
right-adjusted result. Ist das nicht schon genau das was ich möchte?
Das schätze ich auch - warum nur 8Bit einlesen, wenn du 12 Bit haben 
kannst?

>> Ich verstehe gerade nicht so ganz die Oderverknüpfung ADC_MODE << 4 in:
ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);  //
enable freerun

Die Konstante "ADC_MODE" bedeutet wohll "CONVMODE": Laut 
A-Family-Manual:
"this bit is zero and the ADC is then configured for unsigned mode where 
single ended and internal signals can be measured." Das sollte passen.

Prinzipelle Fehler:
- Der ADC benötigt einige Zeit, bis das Ergebnis verfügbar ist. Deshalb 
zuerst das INTFLAG in INTFLAGS des Kanals löschen (1 darauf schreiben), 
Wandlung starten und nun auf das IF-Bit warten, dann Wert lesen und Flag 
wieder löschen....
- Wie in den Beispielen musst du ADC.CHx.MUXCTRL auf Port A0 setzen!

PS: (Tipp 1) Halte dir die iox256A3.h im Editor offen und benutze die 
dort definierten Konstante (Suchfunktion verwenden!), das ist viel(!) 
leserlicher!

(Tipp 2) Wenn du einen Debugger hast, schaue dir im IO-View die 
tatsächlichen Einstellungen des ADCA an.

Gruß

Alex

von Jan-Henrik B. (vedaykin)


Lesenswert?

Hallo Alex,

ich muss glaube ich nochmal erwähnen, dass ich recht neu bin was die 
muPc-Programmierung angeht ;). Ich habe unten mal versucht Deinen 
Kommentar umzusetzen. Ich habe Deine Vorgehensweise versucht in der 
while-Schleife umzusetzen.

1) INTFLAGS löschen
2) Wandlung starten
3) IF-Bit? sorry, wie frage ich das ab?
4) Kanal auslesen in Variable t speichern
5) INTFLAGS löschen

Irgendwas hat sich zum positiven hin geähndert. In der Variable t steht 
schon mal ein Wert um 180, obwohl 1,6Volt an PORT A Kanal 0 anliegen. 
Ich weiß nicht woher die 1,6V kommen, ich habe nur das Oszi dran. 
Irgendwas stimmt noch überhaupt nicht mit der Initialisierung, ansonsten 
würde ich doch nicht einfach immer 1,6V an PORTA0 messen oder? Es soll 
doch ein Eingang sein und nicht ein Ausgang. Die anderen Kanäle 1-7 sind 
auf 0V.

[c]
#include <avr/io.h>
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.h>
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.c>

#define ADC ADCA
#define USER_PORT PORTA
#define ADCMASK 0x07
#define ADC_MODE 0x00

uint8_t t;

int main(void)
{
  ADCInit();
  PORT_ConfigurePins(&USER_PORT,ADCMASK,0,0,PORT_OPC_PULLDOWN_gc,PORT_ISC_ 
INPUT_DISABLE_gc);

    while(1)
    {
    //1) INTFLAGS des Kanals löschen
    ADC.CH0.INTFLAGS = 0x01;
    //2) Wandlung starten
    ADC.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN0_gc;
    //4) dann Wert lesen => Watchdog zeigt den Wert 180. Es liegen ca. 
1,6Volt an Port A Kanal 0
    t = ADC.CH0RES;
    //5) INTFLAGS des Kanals löschen
    ADC.CH0.INTFLAGS = 0x01;
    }
}

// Setup the ADC for sampling
void ADCInit(void)
{
  ADC.CTRLA |= 0x01;
  // 12 bit right adjusted
  ADC.CTRLB |= ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4);
  ADC.PRESCALER |= ADC_PRESCALER_DIV16_gc;
  // reference set to Vcc/1.6
  ADC.REFCTRL |= ADC_REFSEL_VCC_gc;
  ADC.EVCTRL |= ADC_SWEEP_012_gc ;
  // speed input
  ADC.CH0.CTRL |= ADC_CH_INPUTMODE_SINGLEENDED_gc;
  // enable freerun
  ADC.CTRLB |= ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);
}
[c/]

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


Lesenswert?

Jan-Henrik Bathelt schrieb:
> Zusatz: Die Variable ADC_MODE verstehe ich nicht nur, sie ist auch in
> der Bibliothek nicht definiert.

Ich bin gerade nochmal meine Sourcen durchgegangen. ADC_MODE ist bei mir 
auch als 0 definiert, wenn ich unsigned benutze. Ein anderer Sensor bei 
mir erfordert ADC_MODE 1, weil der den Verstärker braucht.

Jan-Henrik Bathelt schrieb:
> ADC.CTRLA |= 0x01;

Tue das nicht. Schreibe lieber wirklich:
 ADC.CTRLA = 0x01;
Damit du nicht irgendwelches Zeugs in den Registern mit verODERerst, was 
du nicht haben willst. Solltest du im ganzen ADCInit() ändern.

Beachte, das mein Code den Freerun Mode benutzt. Das heisst, das der ADC 
einmal angestossen, selbstständig weiterarbeitet und nicht immer wieder 
gestartet wird. Der 012 Sweep sampelt also alleine die 3 Kanäle 0,1 und 
2. (Deswegen der 'zero effort' Kommentar) Die Resultate findest du in 
den ADC.CHnRES Registern als 12 bit Werte. Dein t muss also eine 
uint16_t sein und nicht nur eine uint8_t.

Ohne irgendwelche Eingangspulldowns driften die ADC Eingänge in die 
Gegend der halben Betriebsspannung. Mit einem z.B. 10k Pulldown gehen 
sie dann runter.

von Jan-Henrik B. (vedaykin)


Lesenswert?

Ok, ich habe alles zurückgeändert und es schwankt um den Wert 3070 in 
der Variable t. Irgendwas scheint er ja schonmal zu messen. Trotz 10k 
Pulldown Widerstands ist die Spannung aber immer noch per default auf 
1.5V an PortA0. Ich werde nochmal die Einführungsbeispiele von AVR 
durchgehen. Falls Du noch einen einen Tipp hast, ich bin ganz Ohr ;).

von Jan-Henrik B. (vedaykin)


Lesenswert?

Also ich habe jetzt noch mal die Pins PORTA1 und PORTA2 getestet. Dort 
ist keine Offsetspannung zu messen und ich kann über t einen Messwert 
porportional zur Eingangsspannung erfassen. Irgendwas scheint mit Kanal 
0 nicht richtig zu sein. An sich gute Nachrichten, aber irgendwie auch 
ein wenig unbefriedigend, da ich nicht nachvollziehen kann woran es 
liegen könnte. Ich verwende ein STK600.

Auf die Gefahr hin das ich mich stark wiederhole hier nochmal der 
funktionierende Code für die Kanäle 1 und 2. Kanal 0 ist noch immer die 
1.5V Spannung trotz Pulldown Widerstand zu messen.

[c]

#include <avr/io.h>
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.h>
#include <C:/Program Files (x86)/Atmel/Drivers/AVR1313/port_driver.c>

#define ADC ADCA
#define USER_PORT PORTA
#define ADCMASK 0x07
#define ADC_MODE 0x00

uint16_t t = 0;

int main(void)
{
  ADCInit();
  PORT_ConfigurePins(&USER_PORT,ADCMASK,0,0,PORT_OPC_PULLDOWN_gc,PORT_ISC_ 
INPUT_DISABLE_gc);

    while(1)
  {
    t = ADC.CH2RES;
    }
}

// Setup the ADC for sampling
void ADCInit(void)
{
  ADC.CTRLA = 0x01;
  // 12 bit right adjusted
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4);
  ADC.PRESCALER = ADC_PRESCALER_DIV256_gc;
  // reference set to Vcc/1.6
  ADC.REFCTRL = ADC_REFSEL_VCC_gc;
  ADC.EVCTRL = ADC_SWEEP_012_gc ;
  // get calibration data , ymmv
  //ADC.CALL = adc_get_calibration_data() && 0xff;
  //ADC.CALH = (uint16_t)adc_get_calibration_data() >> 8;
  // speed input
  ADC.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
  ADC.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN0_gc;
  // current measure
  ADC.CH1.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
  ADC.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN1_gc;
  // temperature
  ADC.CH2.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc;
  ADC.CH2.MUXCTRL = ADC_CH_MUXPOS_PIN2_gc;
  // enable freerun
  ADC.CTRLB = ADC_RESOLUTION_12BIT_gc | (ADC_MODE << 4) | (1 << 3);
}
[c/]

von Jan-Henrik B. (vedaykin)


Angehängte Dateien:

Lesenswert?

Hier nochmal die IO View der Ports. Ich meine die Konfiguration stimmt 
doch soweit? Was mir noch unklar ist, der Internal Input Select 
unterscheidet sich. Bei Kanal 0 steht dieser Wert auf 0 = > Temperature 
Reference, bei Kanal 1 steht dieser Wert auf 1 => Bandgap Reference. 
Könnte das weiterhelfen? Anbei ein Screenshot.

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


Lesenswert?

Nee, das stimmt schon, solange die Channel Control Register auf 0x01 
stehen, denn in diesem Fall stehen in den MUXCTRL Registern nur die 
Nummern der externen Pins. Der Simulator/Debugger zeigt den Fall, wenn 
die Channel Controls auf 0x00 stehen würden, das wären die internen 
Signalquellen, was ja offensichtlich nicht mit deinen Ergebnissen von 
Kanal 1 und 2 übereinstimmt. Könnte schwören, das dir auf Kanal 2 
'ScaledVCC' angezeigt wird.
Im Moment weiss ich nicht, was mit deinem Kanal 0 sein kann, ausser das 
er kaputt ist (Schluss des internen Sample Kondensators). Im schlimmsten 
Fall müsstest du auf Kanal 1 oder 2 ausweichen, wenn du einen 
Schaltungsfehler ausschliessen kannst und die Port Initialisierung 
richtig ist.
Alles was ich dir sagen kann, ist, das der o.a. Code auf XMega128A3 und 
XMega192A3 funktioniert, auch auf Kanal 0. Xmega256A3 hatte ich hier 
noch nicht, aber da gibt es keine relevanten Unterschiede.

Du könntest nochmal probieren, mit einen richtig harten Pulldown (100R 
oder so) den Eingang runterzuziehen, oder mal zu schauen, ob er als 
normaler Port richtig funktioniert.

von Jan-Henrik B. (vedaykin)


Lesenswert?

Was 'ScaledVCC' betrifft hast Du selbstverständlich Recht ;). Dann werde 
ich nur die Kanäle 1-3 verwenden. Wenn ich Kanal 0 zu hart runterziehe 
fängt die böse rote LED auf dem Board an zu blinken. Das gleiche 
Phänomen beobachte ich auf Port B an Kanal 0. Daher glaube ich nicht, 
dass der ADC-Kanal defekt ist. Als reiner I/O-Port funktioniert er noch. 
Eventuell wird bei der nächsten Bestellung noch mal ein neuer Chip 
mitgekauft und mal getestet, ob ein Austausch das Problem behebt. Da ich 
nur 2 Kanäle brauche reichts erstmal aus. Der Code von Dir war also von 
Anfang an korrekt und es liegt wohl irgendwie an meiner 
Hardwarekonfiguration. Ein weiteres mal Danke für die Unterstützung.

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.