Forum: Mikrocontroller und Digitale Elektronik ADC Mittelwertbildung am ATmega1284P


von Michael S. (bazillus)


Lesenswert?

Hi,

ich habe gewisse Troubles, am ATmega1284P eine Mittelwertbildung aus 
gemessenen ADC Werten zu bekommen. Das sequentielle Auslesen von ADC 
Werten ist kein Problem, einzig und allein beim Aufsummieren dieser 
Werte und anschließender Mittelwertbildung erreiche ich nicht das 
gewünschte Ergebnis.

Anbei die schaltungstechnischen Eckdaten:
- Analogspannung liegt über einen externen Multiplexer (MUX_A0-MUX_A2 
sowie EN_ADC10MUX) am ADC0 an
- Es wird eine externe Referenzspannung 2.560V verwendet
- Das LCD Display ist an 6 Pins von Port C und 2 Pins von Port B 
angeschlossen (4Bit Daten, 3Bit Control, 1Bit LED Beleuchtung via 
Transistor geschaltet)

Nach dem Einschalten des ADCs wird eine Dummy-Wandlung durchgeführt, 
alle weiteren Messungen werden verwertet. Am ADC0 Eingang liegt eine 
Spannung von 1.94V an. Als LCD Library wurde die von Peter Fleury 
eingebunden.

Hier mal der entsprechende Code (die ADC_COUNTS sind bewusst auf 1 
gesetzt), der ein funktionierendes Ergebnis liefert:
1
#define XTAL 8000000
2
#define F_CPU XTAL
3
4
#include <avr/io.h>
5
#include <util/delay.h>
6
#include <stdio.h>
7
#include <stdlib.h>
8
#include "lcd.h"
9
10
// Define hardware constants
11
#define LCD_LED_PORT  PORTB
12
#define LCD_LED_PIN    PB1
13
#define EN_ADC10MUX    PA2
14
#define MUX_A0      PA5
15
#define MUX_A1      PA6
16
#define MUX_A2      PA7
17
#define AREF      2.560
18
#define ADC_COUNTS    1
19
20
// Define uint8_t variables
21
uint8_t i = 0;  // Define uint8_t variable i
22
23
// Define double variables
24
double adc_value = 0; // Define double variable adc_value
25
double adc_voltage = 0;  // Define double variable adc_voltage
26
27
// Define char arrays
28
char str_adc_value[5];  // Define char array str_adc_value
29
char str_adc_voltage[5];  // Define char array str_adc_voltage
30
31
int main(void)
32
{
33
  DDRA |= (1<<DDA7)|(1<<DDA6)|(1<<DDA5)|(1<<DDA2);  // Define MUX ports as outputs
34
  DDRA &= ~(1<<DDA0); // Define ADC0 port as input
35
  DDRB |= (1<<DDB1)|(1<<DDB2);  // Define LCD ports as outputs
36
  DDRC = 0xFF;  // Define Port C as output
37
  
38
  LCD_LED_PORT |= (1<<LCD_LED_PIN); // Switch on LCD illumination
39
  
40
  lcd_init(LCD_DISP_ON);  // Initialize LCD Display
41
  lcd_clrscr();  // Clear LCD Display
42
43
  PORTA |= (1<<EN_ADC10MUX);  // Enable EN_ADC10MUX
44
  PORTA &= ~((1<<PA7)|(1<<PA6)|(1<<PA5));  // Select MUX channel 0
45
  ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1);  // ADC Enable, ADC Prescaler = 64
46
  ADMUX = 0x00;  // Set internal multiplexer to channel 0, reference voltage = AREF
47
  
48
  ADCSRA |= (1<<ADSC);  // Start ADC conversion
49
  while (ADCSRA & (1<<ADSC))  // Wait for the conversion to complete
50
  adc_value = ADCW;  // Write ADC value to adc_value
51
  adc_value = 0;  // Reset adc_value
52
  
53
  lcd_gotoxy(0,0);  // Set cursor to pos. 0, line 0
54
  lcd_puts("ADC value:          ");  // Write "ADC value:          " to LCD display
55
  lcd_gotoxy(0,1);  // Set cursor to pos. 0, line 1
56
  lcd_puts("ADC voltage:     V  ");  // Write "ADC voltage:     V  " to LCD display
57
    
58
  while(1)
59
  {
60
    for (i=0;i<ADC_COUNTS;i++)
61
    {
62
      ADCSRA |= (1<<ADSC);  // Start ADC conversion
63
      while (ADCSRA & (1<<ADSC))  // Wait for the conversion to complete
64
      adc_value = ADCW;  // Write ADCW value to adc_value
65
    }  
66
    adc_value /= ADC_COUNTS;  // Calculate mean value
67
    adc_voltage = adc_value*AREF/1024;  // Calculate adc_voltage
68
    sprintf(str_adc_value,"%04.0f",adc_value);  // Format adc_value as string and save as str_adc_value
69
    sprintf(str_adc_voltage,"%4.2f",adc_voltage);  // Format adc_voltage as string and save as str_adc_voltage
70
    lcd_gotoxy(13,0);  // Set cursor to pos. 13, line 0
71
    lcd_puts(str_adc_value);  // Write str_adc_value to LCD display
72
    lcd_gotoxy(13,1);  // Set cursor to pos. 13, line 1
73
    lcd_puts(str_adc_voltage);  // Write str_adc_voltage to LCD display
74
    adc_value = 0;  // Reset adc_value
75
    _delay_ms(1000);  // Wait 1000ms
76
  }    
77
  return 0;
78
}

Ergebnis am LCD Display:
ADC value  : 0777
ADC voltage: 1.94V

Ersetze ich jetzt folgende Zeile
1
adc_value = ADCW;

durch
1
adc_value += ADCW;

bekomme ich folgendes, seltsames Ergebnis am LCD Display:

ADC value:   3885
ADC voltage: 9.71V

adc_value ist als double definiert, damit es bei der Mittelwertbildung 
nicht zu Problemen kommt, wenn Kommastellen auftreten. Ich habe auch 
schon probiert, AREF und ADC_COUNTS als double und uint8_t zu definieren 
mit dem gleichen Ergebnis. Der angezeigte Wert ist bei Mittelwertbildung 
immer um den Faktor 4 zu hoch. Kann mir wer von euch bei diesem Problem 
weiterhelfen?

LG, Michael

Ps: Sollte der obige Source-Code als "zu lang für einen Beitrag" gelten, 
bitte um kurze Nachricht, dann änder ich es umgehend auf einen Anhang - 
da es mein erster Beitrag ist, bitte um Nachsicht.

von Walter S. (avatar)


Lesenswert?

da fehlt ein Semikolon

von Wolfgang (Gast)


Lesenswert?

Michael S. schrieb:
> adc_value ist als double definiert, damit es bei der Mittelwertbildung
> nicht zu Problemen kommt, wenn Kommastellen auftreten.

Erster Schritt wäre, die ganze Floatingpointrechnerei rauszuschmeißen.
Nicht ohne Grund hat die Referenzspannung einen Wert von 2560 mV. Wenn 
du deine ganze Rechnung mal etwas zusammenfasst und in mV betrachtest 
kürzt sich da vieles weg und du kannst bei einer passenden Wahl von 
ACD_COUNTS wunderbar in Integer rechnen, ohne dabei irgendwelche 
Probleme mit Nachkommastellen zu bekommen.

von Anja (Gast)


Lesenswert?

Michael S. schrieb:
> adc_value ist als double definiert, damit es bei der Mittelwertbildung
> nicht zu Problemen kommt,

beim GCC gibt es gar kein double. double wird als single gerechnet.

Gruß Anja

von Uwe (de0508)


Lesenswert?

hallo,

gemeint sind diese Zeilen
1
while (ADCSRA & (1<<ADSC))  // Wait for the conversion to complete

double macht da keinen sinn !
Ist auf AVR GCC auch nur float.

von Uwe (de0508)


Lesenswert?

Hallo,

hier ist ein Auszug aus meinem Programm:

Beitrag "Re: Fehler rechnung bei ADC Messung"

es werden 128 Mittelwerte gebildet.

von Walter S. (avatar)


Lesenswert?

Michael S. schrieb:
> while (ADCSRA & (1<<ADSC))  // Wait for the conversion to complete

vielleicht ist vFloatibg point nicht sinnvoll
aber in der Zeile fehlt weiterhin ein Semikolon

von Michael S. (Gast)


Lesenswert?

Vielen Dank für die raschen Antworten,

der Punkt der Fließkomma-Rechnerei ist durchaus zu überdenken, 
allerdings verlangt der Compiler nach "double" wenn man sprintf 
verwendet und für die Ausgangsformatierung "4.2f" angibt. Definiert man 
die Eingangsvariable als "float", so meckert er mit der Warnmeldung 
"format '%4.2f' expects type 'double', but argument 3 has type 'float'" 
- aus dem Grund habe ich hier double ausgewählt.

Bezüglich des fehlenden Semikolons muss ich euch enttäuschen - hier ist 
auch im AVR-GCC-Tutorial unter 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Analoge_Ein-_und_Ausgabe#Nutzung_des_ADC 
die Zeile ohne Semikolon angegeben - und ich bilde mir ein, das auch so 
in einem Atmel Datenblatt gelesen zu haben - dementsprechend weiß ich 
jetzt nicht ganz, was richtig ist.

@Uwe: Vielen Dank für deinen Link, ich werde ihn mir mal zu Gemüte 
führen ...

LG, Michael

von Karl H. (kbuchegg)


Lesenswert?

Michael S. schrieb:

> der Punkt der Fließkomma-Rechnerei ist durchaus zu überdenken,

richtig.

> allerdings verlangt der Compiler nach "double" wenn man sprintf
> verwendet und für die Ausgangsformatierung "4.2f" angibt.

Na ja.
Aber eigentlich zwingt dich ja keiner dazu "4.2f" anzugeben.
D.h. das ist eine etwas lahme Begründung, warum da Floating Point 
gerechnet wird.

> Definiert man
> die Eingangsvariable als "float", so meckert er mit der Warnmeldung
> "format '%4.2f' expects type 'double', but argument 3 has type 'float'"
> - aus dem Grund habe ich hier double ausgewählt.

Das ist letzten Endes egal.
Es geht um die Grundsatzfrage: Gleitkomma oder nicht.
Ob float oder double ist dann sekundär. Vor allem auch deshalb, weil 
double beim gcc auf einem AVR dasselbe wie float ist.

> Bezüglich des fehlenden Semikolons muss ich euch enttäuschen - hier ist
> auch im AVR-GCC-Tutorial unter
> 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Analoge_Ein-_und_Ausgabe#Nutzung_des_ADC
> die Zeile ohne Semikolon angegeben

1) nachdenken, was das Semikolon eigentlich tut
2) Schau noch einmal genau, wodurch sich die Dinge im Tutorial
   von deinen unterscheiden (Ich gebe zu, es ist durch die Farbgebung
   nicht so gut zu sehen. Wenn du fertig bist, werde ich das mal ein
   wenig umformatieren, so dass man das auch besser sieht)
3) darüber nachdenken, was die C-Regeln zu diesem Fall sagen, und
   wie der Compiler das was du geschrieben hast, lesen wird
   Was eng damit zusammenhängt: Welche Konsequenzen hat das für
   deine Programmlogik: Was sollte eigentlich an dieser Stelle
   geschehen und was passiert statt dessen.

von Michael S. (bazillus)


Lesenswert?

Karl Heinz Buchegger schrieb:
> 2) Schau noch einmal genau, wodurch sich die Dinge im Tutorial
>    von deinen unterscheiden (Ich gebe zu, es ist durch die Farbgebung
>    nicht so gut zu sehen. Wenn du fertig bist, werde ich das mal ein
>    wenig umformatieren, so dass man das auch besser sieht)
> 3) darüber nachdenken, was die C-Regeln zu diesem Fall sagen, und
>    wie der Compiler das was du geschrieben hast, lesen wird
>    Was eng damit zusammenhängt: Welche Konsequenzen hat das für
>    deine Programmlogik: Was sollte eigentlich an dieser Stelle
>    geschehen und was passiert statt dessen.

Alles klar, der Tip mit der schlechten Farbgebung war Gold wert. Werd 
ich gleich mal ändern.

LG, Michael

von Ingo (Gast)


Lesenswert?

Michael S. schrieb:
> ADCSRA |= (1<<ADSC);  // Start ADC conversion
>       while (ADCSRA & (1<<ADSC))  // Wait for the conversion to complete
>       adc_value = ADCW;  // Write ADCW value to adc_value

Das ist keine Mittelwertbildung, dafür musst du auch aufsummieren, also
1
adc_value = ADCW;  // Write ADCW value to adc_value
2
gegen
3
adc_value += ADCW;  // Write ADCW value to adc_value

ersetzen ;-)


Ingo

von Uwe (de0508)


Angehängte Dateien:

Lesenswert?

Hallo,

zum ";" einfach mal Google verwenden oder ein "C" Buch.

Ich hoffe Du glaubst uns nun:

http://www.keil.com/support/docs/1950.htm

Ich verwende zur Formatierung fast nur noch |valout| von Peter PeDa.

* Beitrag "Formatierte Zahlenausgabe in C"

Im Anhang meine angepasste Lib.

Da spare ich mir die 'Kosten' für die Funktion |sprintf|.

Für einige spezial Fälle nehme mich auch gerne |xprintf| von Chan:

* http://elm-chan.org/fsw/strf/xprintf.html

Oder |xitoa| von Chan, ist im Paket enthalten:

* http://elm-chan.org/fsw/ff/ffsample.zip

_

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.