Forum: Mikrocontroller und Digitale Elektronik ADC bei Atmega 32


von Christoph A. (shadowrunner93)


Lesenswert?

Hallo Leute!

Ich bin neu hier und habe natürlich eine Frage:

Ich versuche zurzeit einen Temperatursensor zu bauen.
Die Temperatur habe ich schon in Form von Spannung (10mV / °Kelvin).

Nun will ich diese in Form von Grad auf 3 Siebensegmentanzeigen 
ausgeben. (falls die Temperatur dreistellig ist).

Dies ist das erste mal dass ich mit einem ADC arbeite, deswegen 
funktioniert es natürlich nicht gleich..

Hier der Code:
1
#include <avr/io.h>
2
3
#define LOOP 5
4
#define COMM_ANODE_R 0
5
#define COMM_ANODE_M 0
6
#define COMM_ANODE_L 0
7
8
void ADC_init(void);
9
void test_readout(void);
10
double measurement(void); 
11
void sev_segm_l(int number);
12
void sev_segm_m(int number);
13
void sev_segm_r(int number);
14
15
void main()
16
{
17
18
  DDRB=0xFF; // als Ausgang
19
  DDRC=0xFF; 
20
  DDRD=0xFF; 
21
22
  double erg;
23
24
25
  ADC_init();  
26
  test_readout();
27
  
28
  while(1)
29
  {
30
    erg=measurement();
31
    
32
    erg*=19,1;    //4,87V / 255 = 19,1 mV
33
    erg*=10;
34
35
    erg/=10;
36
    
37
    sev_segm_l((int)(erg/100));
38
    // die letzte Stelle des Ergebnises
39
    sev_segm_m((int)(erg/10-(erg/100)*10));
40
    // die mittlere Stelle...
41
    sev_segm_r((int)(erg-(erg/10)*10));
42
    // usw.
43
  }
44
45
}
46
47
void ADC_init(void)
48
{
49
  /*  
50
51
  ADEN=0;   aktiviert ADC (Analog-Digital-Converter)
52
53
  ADSC=0;    setzt auf (nicht)freilaufenden Betrieb
54
           Der ADC misst (nicht)durchgehend
55
56
  ADATE=1;   Für freilaufenden Modus
57
  
58
  ADIF=0;    Interrupt-Flag aus
59
60
  ADIE=0;   Kein Interrupt Flag
61
62
  ADPS0...ADPS2 : Teilungsfaktor 
63
          CPU_Clk/Fakt. = >40kHz & <150 kHz
64
65
  */
66
67
  ADCSRA=0b01000101;
68
  
69
  /*
70
71
  REFS0..REFS1 Referenzspannungsmodus
72
73
  ADLAR Darstellung des Ergebnisses
74
75
  MUX Pinnummer des eingehenden A/D Ports
76
77
  */
78
79
  ADMUX=0b00000000;
80
}
81
82
void test_readout(void)
83
{
84
  
85
  double dummy_erg;
86
  
87
88
  ADCSRA |= (1<<ADSC); // "Warmlaufrunde" für ADC
89
90
  while(ADCSRA & (1<<ADSC) )
91
  {
92
    //auf Abschluss der Konvertierung warten  
93
  }
94
  
95
  dummy_erg=ADCW; //ADCW muss einmal gelesen werden 
96
          //sonst wird das Ergebnis der
97
          //nächsten Wandlung nicht übernommen
98
  
99
}
100
101
double measurement(void)
102
{
103
104
  double erg=0;
105
  
106
107
  for(int i=0;i<LOOP;i++)    // zur Genauigkeit wird der
108
  {              // Mittelwert von LOOP-Messungen
109
                // verwendet      
110
    ADCSRA |= (1<<ADSC);  // Eine Wandlung      
111
    
112
    while(  ADCSRA & (1<<ADSC) )
113
    {
114
      ;//auf Abschluss der Konvertierung warten  
115
    }    
116
      
117
    erg += ADCW;
118
  }
119
  
120
  erg  /= LOOP;
121
  
122
  return erg;
123
124
}
125
126
void sev_segm_l(int number)
127
{
128
  if(COMM_ANODE_L)
129
  {
130
    switch(number)
131
    {
132
      case 0:
133
134
      //wobei PORT1 = a usw.  
135
      PORTB=~(0b00111111);
136
      break;
137
138
      case 1:
139
140
      PORTB=~(0b00000110);
141
      break;
142
143
      case 2:
144
145
      PORTB=~(0b01011011);
146
      break;
147
148
      case 3:
149
150
      PORTB=~(0b01001111);
151
      break;
152
153
      case 4:
154
155
      PORTB=~(0b01100110);
156
      break;
157
158
      case 5:
159
160
      PORTB=~(0b01101101);
161
      break;
162
163
      case 6:
164
165
      PORTB=~(0b01111101);
166
      break;
167
168
      case 7:
169
170
      PORTB=~(0b00000111);
171
      break;
172
173
      case 8:
174
175
      PORTB=~(0b01111111);
176
      break;
177
178
      case 9:
179
180
      PORTB=~(0b01101111);
181
      break;
182
183
      default:
184
185
      PORTB=0b01000000;
186
      break;
187
    }
188
  }
189
190
  else
191
  {
192
    switch(number)
193
    {
194
      case 0:
195
196
      //wobei PORT1 = a usw.  
197
      PORTB=0b00111111;
198
      break;
199
200
      case 1:
201
202
      PORTB=0b00000110;
203
      break;
204
205
      case 2:
206
207
      PORTB=0b01011011;
208
      break;
209
210
      case 3:
211
212
      PORTB=0b01001111;
213
      break;
214
215
      case 4:
216
217
      PORTB=0b01100110;
218
      break;
219
220
      case 5:
221
222
      PORTB=0b01101101;
223
      break;
224
225
      case 6:
226
227
      PORTB=0b01111101;
228
      break;
229
230
      case 7:
231
232
      PORTB=0b00000111;
233
      break;
234
235
      case 8:
236
237
      PORTB=0b01111111;
238
      break;
239
240
      case 9:
241
242
      PORTB=0b01101111;
243
      break;
244
245
      default:
246
247
      PORTB=0b01000000;
248
      break;
249
    }
250
  }
251
}
252
253
void sev_segm_m(int number)
254
{
255
  if(COMM_ANODE_M)
256
  {
257
    switch(number)
258
    {
259
      case 0:
260
261
      //wobei PORT1 = a usw.  
262
      PORTC=~(0b00111111);
263
      break;
264
265
      case 1:
266
267
      PORTC=~(0b00000110);
268
      break;
269
270
      case 2:
271
272
      PORTC=~(0b01011011);
273
      break;
274
USW...


Die Siebensegmentanzeigen geben nur Nullen aus.

Die analoge Spannung habe ich auf den PORT A0 angelegt. Ist das bei 
diesem Code richtig?

Mfg
Christoph

von Gerald D. (saleru)


Lesenswert?

Also in diner ADC_init funktion schaltest du den ADC nicht ein sondern 
startest ihn blos (du setzt enable auf 0 aber start auf 1). es müsste 
eigentlich genau umgekehrt sein:

ADCSRA=0b10000101;

und nicht ADCSRA=0b01000101;


probier das mal ... mir fallen in dem code zwar noch andre sachen auf 
aber die sollten zumindest die konvertierung nicht verhindern.

von Christoph A. (shadowrunner93)


Lesenswert?

Okay, probier ich!

Schonmal Danke für die Hilfe ;)

Welche Dinge fallen dir denn noch auf?

Mfg
Christoph

von Karl H. (kbuchegg)


Lesenswert?

Gerald D. schrieb:
> Also in diner ADC_init funktion schaltest du den ADC nicht ein sondern
> startest ihn blos (du setzt enable auf 0 aber start auf 1). es müsste
> eigentlich genau umgekehrt sein:
>
> ADCSRA=0b10000101;
>
> und nicht ADCSRA=0b01000101;
>
>
> probier das mal ... mir fallen in dem code zwar noch andre sachen auf
> aber die sollten zumindest die konvertierung nicht verhindern.

Und ändere deine Syntax

ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0);

ist doch leichter zu lesen als

ADCSRA=0b10000101

von Gerald D. (saleru)


Lesenswert?

beispielsweise verwendest du den datentyp double für das ergebnis. ich 
meine es ist ein atmega32 ... mit limitiertem speicher und befehlssatz. 
eine double operation benötigt unmengen an rechenleistung ... wennste 
stattdessen einen int16 hernimmst und die werte schön aufskalierst 
kommst auch auf das selbe ergebnis ohne rießen platzbedarf und 
performanceverlust.

weiters ...

erg*=19,1;

wieso kompiliert dieser ausdruck ? sind commazahlen nicht eigentlich mit 
punkt festgelegt?

oder die zeilen

erg*=10;
erg/=10;

nacheinander wirken ein wenig naja überflüssig, weis nicht was du damit 
erreichen willst, da es sich ja um einen double handelt.

aber das sind nur eben kleinigkeiten die hier nicht ins gewicht fallen 
aber bei projekten die mehr logik erfordern dir den speicher aussaugen 
könnten :)

von Christoph A. (shadowrunner93)


Lesenswert?

Karl heinz Buchegger schrieb:

>
> Und ändere deine Syntax
>
> ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0);
>
> ist doch leichter zu lesen als
>
> ADCSRA=0b10000101

Okay, mach ich ;)

So, folgendes:

Die erste Siebensegmentanzeige zeigt 0 an.
Die zweite - (also den default-Wert bei der switch-case Anweisung)
Die dritte : PortC gibt bei Pin 4 einen Wechselspannung mit dem RMS-Wert 
von 0,6V aus. Der Rest ist wie bei der ersten Siebensegmentanzeige.

Nur zur Info: Die angelegte Wechselspannung beträgt ungefähr 2,9V.

Liefert das STK500 bei AVCC und AGND die nötige Spannung automatisch?

Mfg
Christoph

von Karl H. (kbuchegg)


Lesenswert?

Christoph Anlauf schrieb:
> Okay, probier ich!
>
> Schonmal Danke für die Hilfe ;)
>
> Welche Dinge fallen dir denn noch auf?

Oh. Da gibt es noch jede Menge.
Zb. Schon mal was von Arrays gehört?
1
unsigned char digits[] = { 0b00111111,    // 0
2
                           0b00000110,    // 1
3
                           0b01011011,    // 2
4
                           0b01001111,    // 3
5
                           0b01100110,    // 4
6
                           0b01101101,    // 5
7
                           0b01111101,    // 6
8
                           0b00000111,    // 7
9
                           0b01111111,    // 8
10
                           0b01101111,    // 9
11
                           0b01000000     // alles andere
12
                         };
13
14
void sev_segm_l(unsigned char number)
15
{
16
17
  if(COMM_ANODE_L)
18
  {
19
    if( number < 10 )
20
      PORTB = ~digits[number];
21
    else
22
      PORTB = ~digits[10];
23
  }
24
  else
25
  {
26
    if( number < 10 )
27
      PORTB = digits[number];
28
    else
29
      PORTB = digits[10];
30
  }
31
}
32
33
void sev_segm_m(unsigned char number)
34
{
35
36
  if(COMM_ANODE_M)
37
  {
38
    if( number < 10 )
39
      PORTA = ~digits[number];
40
    else
41
      PORTA = ~digits[10];
42
  }
43
  else
44
  {
45
    if( number < 10 )
46
      PORTA = digits[number];
47
    else
48
      PORTA = digits[10];
49
  }
50
}
51
52
....


Ist doch viel kürzer :-)

Die richtigen Datentypen benutzen. int ist kein Allheilmittel für alles.

von Gerald D. (saleru)


Lesenswert?

Soweit ich weis wird beim stk500 avcc auf die versorgungsspannung also 
5V gesetzt. außer man setzt den jumper um und stellt was eigenes ein.

von STK500-Besitzer (Gast)


Lesenswert?

>Soweit ich weis wird beim stk500 avcc auf die versorgungsspannung also
>5V gesetzt. außer man setzt den jumper um und stellt was eigenes ein.

Sowas kann man auch im AVRStudio nachgucken.
Die Spannung ist in einem bestimmten Bereich frei einstellbar; 
grundsätzlich aber kleiner als VCC.

von Christoph A. (shadowrunner93)


Lesenswert?

Zuerst eine Ausbesserung meinerseits :

 "Die angelegte Wechselspannung beträgt ungefähr 2,9V."

Hierbei meinte ich analoge Spannung^^.

 Gerald D. schrieb:

>
>
> erg*=10;
> erg/=10;
>
>

Das ist Absicht, wegen der Übersicht. So wird mein Gedankenvorgang 
gezeigt ;)

 Karl heinz Buchegger schrieb:


>Ist doch viel kürzer :-)
>
>Die richtigen Datentypen benutzen. int ist kein Allheilmittel für alles.

Natürlich kenne ich Arrays!
Ja, das ist ja jetzt nur der Prototyp Code. ;)

Mfg
Christoph

von Gerald D. (saleru)


Lesenswert?

>>
>>
>> erg*=10;
>> erg/=10;
>>
>>
>
> Das ist Absicht, wegen der Übersicht. So wird mein Gedankenvorgang
> gezeigt ;)

hihi ;) ... hast in deinen gedanken an eine andere schreibweise für noop 
instruktionen gedacht ? ;)

von Christoph A. (shadowrunner93)


Lesenswert?

Also hier mal ein paar Daten:

Die Taktfrequenz ist bei 4 Mhz.
Somit stimmt der Teiler hoffentlich ;)

Der AREF-Jumper auf dem STK500 ist gesetzt.

Hmm, sonst noch irgendwelche Tipps?

Mfg
Christoph

von Christoph A. (shadowrunner93)


Lesenswert?

Gerald D. schrieb:
>>>
>>>
>>> erg*=10;
>>> erg/=10;
>>>
>>>
>>
>> Das ist Absicht, wegen der Übersicht. So wird mein Gedankenvorgang
>> gezeigt ;)
>
> hihi ;) ... hast in deinen gedanken an eine andere schreibweise für noop
> instruktionen gedacht ? ;)

Na klar doch ;)

Auch wenn ihr keine Noobs seit, habt ihr den Code dadurch wahrscheinlich 
trotzdem schneller kapiert^^. (Wenn ihr über den Zusammenhang überhaupt 
eine Gedanken verliert)

von STK500-Besitzer (Gast)


Lesenswert?

>Die Taktfrequenz ist bei 4 Mhz.
>Somit stimmt der Teiler hoffentlich ;)
Wie hast du das eingestellt?
Das STK500 kann maximal 3,686MHz von sich aus erzeugen.

Der AREF-Jumper auf dem STK500 ist gesetzt.
Damit sollte zumindest die interne Referenz angeschlossen sein.
Wie hoch die Spannung ist, kannst du im AVRStudio auslesen und 
einstellen.

von Gerald D. (saleru)


Lesenswert?

(1) erg=measurement();
(2) erg*=19,1;    //4,87V / 255 = 19,1 mV
(3) erg*=10;
(4) erg/=10;


Ahlso:

nach dem schritt 1 sollte erg einen wert von 0-1024 haben (0-2^10) da 
der adc eine 10 Bit genauigkeit hat.

bei schritt 2 multiplizierst du dann erg mit 19.1 ... aber nimmst 
gleiczeitig an, nur eine adc auflösung von 8 bit zu haben da du durch 
255 dividierst. das wäre mal der erste wiederspruch

schritte 3 und 4 wären dann mal überflüssig

danach gibst du erg auf der 7 segmentanzeige aus. wo du hergehst und 
völlig wirr herummultiplizierst und dividierst. also ich kann aus der 
ausgabe auch nicht wirklich was schließen.

von Christoph A. (shadowrunner93)


Lesenswert?

STK500-Besitzer schrieb:
>>Die Taktfrequenz ist bei 4 Mhz.
>>Somit stimmt der Teiler hoffentlich ;)
> Wie hast du das eingestellt?
> Das STK500 kann maximal 3,686MHz von sich aus erzeugen.
>
> Der AREF-Jumper auf dem STK500 ist gesetzt.
> Damit sollte zumindest die interne Referenz angeschlossen sein.
> Wie hoch die Spannung ist, kannst du im AVRStudio auslesen und
> einstellen.

Es heißt im STK500 zwar 4 Mhz int. Clk, aber sind tatsächlich nur 3,6 
Mhz.

von Christoph A. (shadowrunner93)


Lesenswert?

Gerald D. schrieb:
> (1) erg=measurement();
> (2) erg*=19,1;    //4,87V / 255 = 19,1 mV
> (3) erg*=10;
> (4) erg/=10;
>
>
> Ahlso:
>
> nach dem schritt 1 sollte erg einen wert von 0-1024 haben (0-2^10) da
> der adc eine 10 Bit genauigkeit hat.
>
> bei schritt 2 multiplizierst du dann erg mit 19.1 ... aber nimmst
> gleiczeitig an, nur eine adc auflösung von 8 bit zu haben da du durch
> 255 dividierst. das wäre mal der erste wiederspruch
>
> schritte 3 und 4 wären dann mal überflüssig
>
> danach gibst du erg auf der 7 segmentanzeige aus. wo du hergehst und
> völlig wirr herummultiplizierst und dividierst. also ich kann aus der
> ausgabe auch nicht wirklich was schließen.

So stimmt es aber, keine Ahnung was ich mir vorher dabei gedacht habe..

erg*=4,7;    //4,87V / 1023 = 4,7 mV
erg/=10;    //Ergebnis in °Kelvin
erg-=273.15*10          //Ergebnis in °Celsius

Nachtrag:
Dass mit der Rechnerei im Funktionsaufruf war nur ein Versuch folgendes 
hinzubekommen:

Ich möchte einfach nur eine 3-stellige Zahl auf 3 Anzeigen aufteilen.

Also z.b die Zahl ist 450.

1.Anzeige: 4
2. 5
3. 0

Aber nichts sagen, ich bin gerade am Überlegen ;-)

von Christoph A. (shadowrunner93)


Lesenswert?

Eine Frage:

Wird das Ergebnis einer Division in C gerundet?
Wenn nicht, habe ich eine Lösung^^

Mfg
Christoph

von Christoph A. (shadowrunner93)


Lesenswert?

So ich hab's jetzt!
Anscheinend wird in C nicht automatisch gerundet.
1
sev_segm_l((int)(erg/100));
2
// die letzte Stelle des Ergebnises
3
sev_segm_m((int)((erg%100)/10);
4
// die mittlere Stelle...
5
sev_segm_r((int)(erg%10));
6
// usw.

Mfg
Christoph

von Karl H. (kbuchegg)


Lesenswert?

Christoph Anlauf schrieb:

> erg*=4,7;    //4,87V / 1023 = 4,7 mV

ist immer noch falsch.
Das ist keine Multiplikation mit 4.7 (man beachte den Dezimalpunkt(!)) 
sondern eine Multiplikation mit 7

von Karl H. (kbuchegg)


Lesenswert?

Christoph Anlauf schrieb:
> So ich hab's jetzt!
> Anscheinend wird in C nicht automatisch gerundet.

Nein.
Das steht aber auch in jedem noch so grindigem C-Buch.
Bei der Konvertierung von double auf int, werden Kommastellen 
abgeschnitten.

von STK500-Besitzer (Gast)


Lesenswert?

>Wird das Ergebnis einer Division in C gerundet?
>Wenn nicht, habe ich eine Lösung^^

Wie sieht die aus?
Die einfachste Methode ist:

double x;

int y;

y = x+0.5; // Aufrunden

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.