Forum: Mikrocontroller und Digitale Elektronik ATtiny45 ADC Problem


von Roland K. (chozo)


Angehängte Dateien:

Lesenswert?

Ich möchte mit einem ATtiny45 eine Spannung messen und abhängig davon 
eine LED ein- bzw. ausschalten. Programmiert wird er tiny mittels ISP.
Im Anhang ist die Schaltung zu finden.

Das Problem ist nun, dass am ADC-Eingang PB4 immer 0 gelesen wird.
Ich habe die Schaltung auch schon mit und ohne Kondensator an AREF 
ausprobiert, und den ADC auch laut Datenblatt dementsprechend 
konfiguriert. Jedoch ohne Erfolg.

Bei den ATmega war der ADC noch nie ein Problem, jedoch bereitet mir der 
tiny nun etwas Kopfzerbrechen.

Bitte um Hilfe.

Edit: Den Schaltplan habe ich irrtümlich doppelt hochgeladen. Code 
richtiggestellt

Hier nun der Code:
1
#define F_CPU 1000000
2
3
#include <avr/io.h>
4
#include <util/delay.h>
5
6
void init_adc(void) {
7
  uint16_t result;
8
9
  ADMUX = (1<<REFS2) | (1 << REFS1);
10
  ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1<<ADPS0);
11
  DIDR0 = (1<<ADC1D) | (1<<ADC2D) | (1<<ADC3D);
12
13
  ADCSRA |= (1 << ADSC);
14
  while (ADCSRA & (1 << ADSC)) {
15
    asm("nop");
16
  }
17
18
  result = ADCW;
19
}
20
21
uint16_t adc_read(uint8_t channel) {
22
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
23
  ADCSRA |= (1 << ADSC);
24
  while (ADCSRA & (1 << ADSC))
25
    ;
26
  return ADCW;
27
}
28
29
uint16_t adc_read_avg(uint8_t channel, uint8_t average) {
30
  uint16_t result = 0;
31
  uint8_t i;
32
33
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
34
35
  for (i = 0; i < average; ++i) {
36
    ADCSRA |= (1 << ADSC);
37
    while (ADCSRA & (1 << ADSC))
38
        ;
39
    result += ADCW;
40
  }
41
42
  return result / average;
43
}
44
45
void init()
46
{
47
  DDRB |= (1<<PB1);
48
}
49
50
int main(void)
51
{
52
  init();
53
  init_adc();
54
    uint16_t adc;
55
  
56
    while (1) 
57
    {
58
    adc = adc_read_avg(PB4, 5);
59
    
60
    if(adc< 500)
61
    {
62
      PORTB |= (1<<PB1);
63
    }
64
    else
65
    {
66
      PORTB &= ~(1<<PB1);
67
    }
68
    }
69
}

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Hallo!

Du weisst schon, dass an PB4 ADC2 liegt?
Dann solltest Du auch die richtigen ADC-Kanal auswählen.

http://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf

Man findet im Datenblatt unter "Table 17-4 Input Channel Selections" die 
richtige Zuordnung:
ADC2 (PB4): MUX[3:0] = 0010

Es lohnt sich also das Datenblatt auch zu lesen!

von M. K. (sylaina)


Lesenswert?

Karl M. schrieb:
> Du weisst schon, dass an PB4 ADC2 liegt?
> Dann solltest Du auch die richtigen ADC-Kanal auswählen.

Und an welcher Stelle hat er den falschen Kanal gewählt?

@TE: Was ist denn cell1 bei dir? Jedenfalls nicht dein Wandlerergebnis 
;)

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Hallo,

M. K. schrieb:
> Karl M. schrieb:
>> Du weisst schon, dass an PB4 ADC2 liegt?
>> Dann solltest Du auch die richtigen ADC-Kanal auswählen.
>
> Und an welcher Stelle hat er den falschen Kanal gewählt?
>
> @TE: Was ist denn cell1 bei dir? Jedenfalls nicht dein Wandlerergebnis
> ;)

Hier:
1
adc = adc_read_avg(PB4, 5);

von Roland K. (chozo)


Lesenswert?

cell1 soll natürlich adc sein.
PB4 als Kanal sollte doch passen?

von Georg G. (df2au)


Lesenswert?

Bei init setzt du ref2, bei read löscht du es wieder. So gewollt? ref0 
ist implizit 0?

Welchen Wert hat PB4 laut Headerfile?

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Georg G. schrieb:
> Bei init setzt du ref2, bei read löscht du es wieder.
> So gewollt?
> ref0 ist implizit 0?
1
void init_adc(void) {
2
  uint16_t result;
3
4
  ADMUX = (1<<REFS2) | (1 << REFS1); //internal 2.56V Voltage Reference without external bypass capacitor, disconnected from PB0 (AREF)
5
6
  ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1<<ADPS0);
7
  DIDR0 = (1<<ADC1D) | (1<<ADC2D) | (1<<ADC3D);
8
9
  ADCSRA |= (1 << ADSC);
10
  while (ADCSRA & (1 << ADSC)) ; // add
11
  result = ADCW;
12
}
1
uint16_t adc_read(uint8_t channel) {
2
// add das 0x1F ist eine Maske, um die Bits in MUX[3:0] zu löschen. Ok!
3
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
4
  ADCSRA |= (1 << ADSC);
5
  while (ADCSRA & (1 << ADSC)) ;
6
  return ADCW;
7
}

> "5" für Kanalwahl ist "Differenz zwischen ADC2 und ADC2, 20-fach
> verstärkt. Macht imho wenig Sinn.

5 ist die Anzahl der Werte über die der Mittelwert gebildet wird.

von Karl M. (Gast)


Lesenswert?

Sorry Georg G., habe falsch gezählt!

Du hast recht, richtig ist:
[c]uint16_t adc_read(uint8_t channel) {
// add 0x0F ist eine Maske, um die Bits in MUX[3:0] zu löschen.
  ADMUX = (ADMUX & ~(0x0F)) | (channel & 0x0F);
  ADCSRA |= (1 << ADSC);
  while (ADCSRA & (1 << ADSC)) ;
  return ADCW;
}[/]

von Roland K. (chozo)


Lesenswert?

OK, habe den Fehler:
PB4 hat den Wert 4 im Headerfile.
Beim ADC muss ich aber den Wert 2 verwenden.
Dachte PB4 hat den Wert 2.

von M. K. (sylaina)


Lesenswert?

Roland K. schrieb:
> cell1 soll natürlich adc sein.
> PB4 als Kanal sollte doch passen?

Dann mach erstmal deinen Code sauber, sprich: Mach ein ordentliches 
Beispiel, dass den Fehler zeigt. Meist kommt man bei dieser Arbeit dann 
ganz alleine auf den Fehler.
Aktuell passen da einige Dinge nicht. Das mit dem cell1 hab ich ja 
angesprochen, das "Problem" mit den Referenzquellen Georg.

EDITH
Roland K. schrieb:
> OK, habe den Fehler:
> PB4 hat den Wert 4 im Headerfile.
> Beim ADC muss ich aber den Wert 2 verwenden.
> Dachte PB4 hat den Wert 2.

Ach, da wurd ja PB4 übergeben in der ADC-Routine, das war mir gar nicht 
aufgefallen. Ist natürlich Quatsch, da muss das ADC2 rein, das Karl 
angesprochen hatte ;)

: Bearbeitet durch User
von Roland K. (chozo)


Lesenswert?

Habe meine read-Funktionen nun angepasst und als Übergabewert verwende 
ich jetzt eigene Konstanten mit den richtigen Werten.
Danke für die Hilfe!
1
uint16_t adc_read(uint8_t channel) {
2
  ADMUX &= 0b11111100;
3
  ADMUX |= channel;
4
  
5
  ADCSRA |= (1 << ADSC);
6
  while (ADCSRA & (1 << ADSC))
7
    ;
8
  return ADCW;
9
}
10
11
uint16_t adc_read_avg(uint8_t channel, uint8_t average) {
12
  uint16_t result = 0;
13
  uint8_t i;
14
15
  ADMUX &= 0b11111100;
16
  ADMUX |= channel;
17
18
  for (i = 0; i < average; ++i) {
19
    ADCSRA |= (1 << ADSC);
20
    while (ADCSRA & (1 << ADSC))
21
        ;
22
    result += ADCW;
23
  }
24
25
  return result / average;
26
}

von Boris O. (bohnsorg) Benutzerseite


Lesenswert?

Roland K. schrieb:
> ADMUX &= 0b11111100;
>   ADMUX |= channel;

Du bist echt ein Held, aus soetwas schönem wie

// add 0x0F ist eine Maske, um die Bits in MUX[3:0] zu löschen.
  ADMUX = (ADMUX & ~(0x0F)) | (channel & 0x0F);

kann nur durch Überheblichkeit deine Variante werden. Du kannst den 
Compiler nicht überlisten, nur die Lesbarkeit deines Codes verringern. 
Zudem hast du einen Fehler eingebracht. In der originalen Schreibweise 
wird das höherwertige Nibble von channel maskiert. In deiner Variante 
brettert es volle Acht in ADMUX, auch noch als OR, du kannst mithin nur 
Bits hinzufügen, keine Löschen. Sei dir versichert: das kannst du nicht 
wollen.

von M. K. (sylaina)


Lesenswert?

Boris hat recht. Der avr-gcc ist richtig richtig gut inzwischen. Da muss 
man schon was richtig Komplexes haben und richtig Kniffe kennen, um 
etwas besser als der gcc zu sein. Und eins kann ich euch sagen: viele 
Chancen gibt euch der gcc nicht besser zu sein. Die kann man an einer 
Hand abzählen.

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.