Forum: Mikrocontroller und Digitale Elektronik ATmega32 - ADC mit Potentiometer


von Danny (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe auf einem Breadboard eine einfache Schaltung aufgebaut.
Dabei ist PORTB0 ein Ausgang an welcher eine LED hängt. Und an ADC0 
hängt mein Potentiometer.

VCC-GND, AREF-GND und AVCC-AGND sind jeweils mit einem 100nF 
Keramikkondensator bestückt.

Speisung erfolgt mittels Netzteil (5V).

Betreibe den ATmega32 mit dem internen Oszillator bei 1MHz.

Wie im nachfolgenden Programmcode ersichtlich, möchte ich gerne mit dem 
Potentiometer die Blinkgeschwindigkeit der LED einstellen. Mein Problem 
jedoch, drehe ich extrem langsam, passt sich die Blinkgeschwindigkeit 
an. Geht es an die obere oder untere Grenze oder drehe zu schnell, dann 
habe ich einen Dauerzustand (An oder Aus). Erst nach einigen Sekunden 
erholt sich das ganze wieder. Woran kann das liegen?
1
#define F_CPU 1000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
void interrupt_timer1()
6
{
7
  DDRB |= 1 << PINB0;
8
  
9
  TCCR1B |= 1 << CS10 | 1 << CS12 | 1 << WGM12;
10
  TIMSK |= 1 << OCIE1A;
11
  OCR1A = 976;
12
}
13
14
ISR(TIMER1_COMPA_vect)
15
{
16
  PORTB ^= 1 << PINB0;
17
}
18
19
void adc()
20
{
21
  ADMUX |= 1<<ADLAR; // Shift to the right
22
  ADMUX |= 1<<REFS0; // AVCC with external capacitor at AREF pin
23
  
24
  ADCSRA |= 1<<ADPS0 | 1<<ADPS1; // Prescaler to 16 -> between 1'000'000 / 50'000 = 20 and 8'000'000 / 200'000 = 5
25
  ADCSRA |= 1<<ADIE; // Activating IRS
26
  ADCSRA |= 1<<ADEN; // enable ADC
27
  
28
  sei();
29
  
30
  ADCSRA |= 1<<ADSC; // Start conversion
31
}
32
33
ISR(ADC_vect)
34
{
35
  unsigned int lowNumber = ADCL >> 6;
36
  unsigned int number = ADCH << 2;
37
  
38
  number += lowNumber;
39
40
  unsigned long ocr1a = (878L * number) / 1024 + 98;
41
  OCR1A = ocr1a;
42
  
43
  ADCSRA |= 1<<ADSC;
44
}
45
46
int main(void)
47
{
48
  adc();
49
  interrupt_timer1();
50
  
51
  while(1)
52
  {
53
    
54
  }
55
}

von Karl H. (kbuchegg)


Lesenswert?

Was treibst du da?
1
  unsigned int lowNumber = ADCL >> 6;
2
  unsigned int number = ADCH << 2;
3
  
4
  number += lowNumber;

wenn du sowieso Zahlen von 0 bis 1023 vom ADC willst, dann lass doch 
ADLAR in Ruhe und lies den ADC Wert ganz einfach durch lesen von ADCW 
aus.

1
  number = ADCW;

fertig. (ADLAR wegnehmen nicht vergessen).


Bis jetzt hab ich noch keinen wirklich vernnftigen Fall gesehen, an dem 
man einen ADC mittels Interrupt auslesen muss. OK, das mag es geben, 
wenn man wirklich schnell operieren muss. Aber für alles, wo ein 
Benutzer am Poti dreht, ist 10 oder 20 mal in der Sekunde schnell genug.

Aber dein Problem ist ein anderes.
Du stellst den CTC Modus am Timer ein.
Wenn du mal die Modus Tabelle im Datenblatt studierst, dann stellst du 
fest, dass es da eine Spalte mit dem Titel 'Update of OCR1x' gibt. Beim 
CTC Modus steht da Immediate.
Und genau da liegt dein Problem.

Denn wenn der Timer zb gerade einen Zählerstand von zb 800 hat (bei 
einem OCR1A Wert von zb 1000) und du weist dem OCR1A Register einen 
neuen Wert von 500 zu, dann bedeutet das, dass du in diesem 
Timer-Durchlauf keinen Compare Match mehr haben wirst. Denn die 
vorherigen 1000, auf die der Timer bei einem Stand von 800 gelaufen 
wäre, die gibt es ja nicht mehr. Und die neu zugewiesenen 500, die 
wurden bereits verpasst. Der Zählerstand ist ja bereits 800.

Das heißt aber auch, dass der Timer jetzt bis 65537 hoch zählen muss, 
ehe dann der Überlauf auf 0 kommt, der Timer also wieder von vorne 
anfängt und es dann irgendwann mal zum Compare Match mit den 500 kommt. 
Und das dauert bei dir eben ein paar Sekunden.

In so einem Fall benutzt man einen der PWM-MOdi, auch wenn man nicht auf 
die PWM an sich aus ist und daher den entsprechenden Pin nicht mit dem 
Timer koppelt. PWM Modus einfach nur aus dem Grund, weil dann der Update 
des OCR1A Registers von der Hardware mit der Timeroperation 
synchronisiert wird. Weißt du dem OCR1A einen Wert von 500 zu, dann wird 
der nicht sofort aktiv, sondern erst dann wenn der Timer seinen 
Höchststand erreicht hat. Bis dorthin gilt noch der alte Wert.
Bei dir würde sich dann eben der Modus 15 anbieten. Schaltest du keinen 
Pin für die PWM frei, dann funktioniert der genau wie der jetzt von die 
benutzte CTC Modus. Nur mit dem einen kleinen, aber wichtigen, 
Unterschied, dass die Zuweisung an OCR1A von der Hardware mit der 
Zähloperation des Timers synchronisiert wird. Eine Zuweisung an OCR1A 
wird erst wirksam, wenn der Zählerstand des Timers 0 ist.

: Bearbeitet durch User
von Danny (Gast)


Lesenswert?

Vielen Dank war mir so nicht bewusst. Habe in diverses Einführungen und 
danach gebrandmarkt davon im Manual immer vom ADLAR gehört. Und den ADCW 
völlig übersehen.

Werde mich heute Abend intensiv mit den verschiedenen Timer/Clock-Arten 
beschäftigen. Muss gestehe, habe im Datenblatt nachgeschaut, jedoch ohne 
andere alternativen zu prüfen, da die Informationsflut mich etwas 
erschlagen hat =)

Aber nach deiner Erklärung scheint es mir klar zu sein wo der Fehler 
liegt, resp. was den Unterschied ausmacht.

von Karl H. (kbuchegg)


Lesenswert?

Danny schrieb:
> Vielen Dank war mir so nicht bewusst. Habe in diverses Einführungen und
> danach gebrandmarkt davon im Manual immer vom ADLAR gehört. Und den ADCW
> völlig übersehen.

ADLAR macht nur dann Sinn, wenn du sowieso nur an einem 8-Bit Ergebnis 
vom ADC interessiert bist. Dann schaltet man ADLAR ein und die Hardware 
platziert das Ergebnis so, dass man es aus ADCH direkt abholen kann.


> Aber nach deiner Erklärung scheint es mir klar zu sein wo der Fehler
> liegt, resp. was den Unterschied ausmacht.

Es ist ganz einfach das Problem, das du einem Autofahrer sagst, er soll 
bis zur roten Markierung auf der Strasse fahren und während der 
unterwegs ist, platzierst du ihm die Markierung um und zwar so, dass sie 
plötzlich hinter ihm an der Strasse steht, auf der er gerade fährt.
Ein Autofahrer ist intelligent. Der kommt von alleine auf die Idee, dass 
er sofort anhalten soll, weil er schon weit jenseits der Markierung ist. 
Silizium ist aber nicht intelligent. Die würde einmal rund um den Globus 
fahren, bis sie dann endlich neben der Markierung anhält, was ja auch 
die Vorgabe "bleibe neben der Markierung stehen" erfüllt.

: Bearbeitet durch User
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.