Forum: Mikrocontroller und Digitale Elektronik AVR Timer + ADC


von Balazs B. (bbking)


Lesenswert?

Hallo Leute,

das ist mein erster Beitrag hier, muss mich aber schon für die versch. 
Tutorials und Beiträge bedanken, sie sind recht nützlich!

Nun habe ich auch selbst ein Problem, an dem ich schon eine Weile 
grüble:

Gegeben ist ein 
AVR-Board(http://ti.tuwien.ac.at/ecs/teaching/courses/mclu/manuals/atmega16) 
und ein Erweiterungsboard 
(http://ti.tuwien.ac.at/ecs/teaching/courses/mclu/manuals/i-o-board), 
das wir auf der Uni verwenden. Ich habe mir das Board selbst zugelegt 
und möchte nun eine Aufgabe erweitern:

Über den ADC möchte ich den Wert von einem Poti einlesen und dann den 
Wert auf 4 Stk. der 7Segment Anzeigen ausgeben.

Nun zu meinen Problemen:
Ich rechne den Wert von ADC in 0-5V um, und gebe das auf den Anzeigen 
aus. Um die einzelnen Stellen bestimmen zu können, experiemntiere ich 
mal mit einem fixen Wert und beachet den Wert von ADC vorerst nicht.
Ich verwende Timer1 (also 16bit, mit Prescaler 8 und CPU_F 16MHz, CTC 
Mode, OCR1A auf 8000) um meien Displays zu multiplexen. Das funktioniert 
auch gut, davon vielleicht abgesehen, dass ich bei manchen Ziffern 
einige Segmente als ganz leicht beleuchtet sehe. Ich nehme an, das 
könnte davon kommen, dass ich zu schnell multiplexe und auf den 
Ausgangspins noch ein wenig Spannung von den vorhergehenden Ziffern 
anliegt (bin dem noch nicht nachgegangen).

Was aber für mich verwunderlich ist, sobald ich Timer2 (8bit, Prescaler 
auf 32, CTC, OCR2 auf 200) nehme, bekomme ich statt 3.906 nur noch 3.880 
auf dem Display angezeigt. Woran kann das liegen?

Schalte ich meine ADC-Init Routine dazu, dann erscheinen überhaupt nur 
noch "3.3.", wobei der zweite "3." nur noch halb so hell leuchtet, wie 
der erste. Das ist mit beiden Timern der Fall. Ich nehme an, die 
ADC-Konversionsroutine würde zu viel Zeit brauchen, sodass die Anzeige 
nicht mehr richtig dargestellt werden kann?

Da wäre noch dazu zu sagen, dass ich in der Timer-ISR nur die aktuelle 
Position für das Multiplexing berechne, die Umrechnung und die Anzeige 
mache ich in der For-Schleife in der main() Funtkion.
Verschiebe ich die Berechnung und die Anzeige in die ISR, wird "3.0" 
anzeigt, wobei auch hier die "0" schwächer leuchtet.

Achja, ich programmiere in C, unter Linux.

Vielen Dank schon mal im Voraus.

MfG
BBKing

von STK500-Besitzer (Gast)


Lesenswert?

Wie wäre es, wenn du dein Programm posten würdest?
Alles andere, was man dazu sagen kann, ist Wahrsagerei...

von Balazs B. (bbking)


Angehängte Dateien:

Lesenswert?

okok, ich weiss, ihr habt keine Glaskugel...

zum Code:
ich weiss, dass man da noch einiges "besser" machen kann, er ist noch 
lange nicht "vollendet"

in der initTimer() Funktion gibt es die Initialisierung für den 8bit 
bzw. 16bit Timer...wie oben schon beschrieben, ergeben sich die zwei 
verschiedenen Verhaltensweisen bei der Ausgabe.

Weiters verstehe ich nicht, warum initADC() ebenfalls die Ausgabe 
beeinflusst?

VIelen Dank im Voraus.

MfG
BBKing

von Hubert G. (hubertg)


Lesenswert?

Du liest zwei mal ADCH aus. Laut Datenblatt ist es bereits nach dem 
ersten auslesen auf 0.
Für was ist diese float Berechnung?
Da kannst du ein Zeitproblem haben, sollte man vermeiden.

von Balazs B. (bbking)


Lesenswert?

Mit der Float-Berechnung skaliere ich den Wert aus ADCH auf 0-5V hinauf, 
und bestimme danach die Ziffern für die 1er, 10tel, 100stel und 1000stel 
Stelle.

Bin jetzt schon ein bisschen weiter, dieser Thread hat mir einiges 
geholfen ADC mit Multiplexanzeige, aber dei Anzeige flimmert immer 
noch irsinnig.


Wie müsste man soetwas prinzipiell machen?

Danke

von Stefan E. (sternst)


Lesenswert?

Hubert G. schrieb:
> Du liest zwei mal ADCH aus. Laut Datenblatt ist es bereits nach dem
> ersten auslesen auf 0.

Quelle?
Ich glaube nämlich nicht, dass das Register nach dem Auslesen gelöscht 
wird.

von Balazs B. (bbking)


Lesenswert?

Soweit ich verstanden habe, stimmt das mit dem 0-Wert nicht:

"If the result is left adjusted and no more than 8-bit precision is 
required, it is sufficient to read ADCH. Otherwise, ADCL must be read 
first, then ADCH, to ensure that the content of the Data Registers 
belongs to the same conversion. Once ADCL is read, ADC access to Data 
Registers is blocked. This means that if ADCL has been read, and a 
conversion completes before ADCH is read, neither register is updated 
and the result from the conversion
is lost. When ADCH is read, ADC access to the ADCH and ADCL Registers is
re-enabled."

aber das ist eh egal, weil ich lese ADCH nur einmal, die andere Zeile 
ist auskommentiert...

Ich glaube auch, dass das ein Timing-Problem ist, weiss nur nicht, wie 
ich anfang soll zu debuggen....

von Hubert G. (hubertg)


Lesenswert?

Fließkommaberechnungen sind irre zeitaufwändig, mach das mit Ganzzahlen 
und dividiere am Schluß.
Die Toleranz von Referenzspannung ist sicher größer als die 
Ungenauigkeit wenn du ein paar Stellen weglässt.


Das mit dem ADCH auf 0 stellen ist falsch, the ADC Data Register is not 
updated until ADCH is read.

von STK500-Besitzer (Gast)


Lesenswert?

>SFIOR &= ~(1<<ADTS2) | ~(1<<ADTS1) | ~(1<<ADTS0);

Ob das den Effekt hat, den du gerne hättest?

von Karl H. (kbuchegg)


Lesenswert?

Deine ganze Multiplexerei ist .... Müll

Du kannst dir ruhig 4 globale unsigned char (zb als ein Array) 
zurechtlegen, in die du das auszugebende Bitmuster ablegst.

In der ISR machst du dann
   momentan angezeigte Stelle abschalten
   Stelle um 1 weiterschalten (unter Berücksichtigung des 'Überlaufs'
   Die für diese Stelle notwendigen LED einschalten (Bitmuster
      dafür hlst du dir aus dem Array
   Anzeige einschalten.

Und du brauchst auch keine so dermassen hohe Multiplexfrequenz. Wenn 
deine 4 Stück 7-Segment 100 mal in der Sekunde reihum einmal 
eingeschaltet werden, dann langt das normalerweise dicke.

Wenn du dann eine Ausgabe machen willst, dann schreibst du einfach in 
das globale Array für die richtige Stelle das Bitmuster, welches die 
richtigen LED einer Stelle zum Leuchten bringt.

von Balazs B. (bbking)


Lesenswert?

Habe das jetzt ausprobiert, der Unterschied ist irre! Ich bin zwar mit 
den angezeigten noch nicht zufrieden, aber die Anzeige selbst ist schon 
viel schöner (100%ig aber noch nicht OK)!

Danke!

von Karl H. (kbuchegg)


Lesenswert?

Ungefähr so
1
....
2
unsigned char Stelle[4];
3
4
SIGNAL(SIG_OUTPUT_COMPARE2)
5
{
6
  // PORTB =  Wert, damit die momentane iPos Anzeige dunkel wird
7
  // Ich rate mal
8
  PORTB = 0;
9
10
  if (iPos < 3)
11
    iPos++;
12
  else
13
    iPos = 0;
14
15
  PORTC = Stelle[iPos]; 
16
  PORTB = aDisplayPos[iPos];
17
}

Wenn du einen neuen Wert auszugeben hast, dann schreibst du das Ergebnis 
von aIntDisplayDecode in die jeweilige Stelle[i], je nach Wert.

1
void OutNumber( unsigned int Number )
2
{
3
  Stelle[3] = aIntDisplayDecode[ Number % 10 ];
4
  Number /= 10;
5
  Stelle[2] = aIntDisplayDecode[ Number % 10 ];
6
  Number /= 10;
7
  Stelle[1] = aIntDisplayDecode[ Number % 10 ];
8
  Number /= 10;
9
  Stelle[0] = aIntDisplayDecode[ Number % 10 ];
10
}

(Hab nicht rausgefunden, wie man bei dir eine 7_Segment über PORTB 
abschalten kann)
(Wozu hast du eigentlich verschiedene Decodiertabellen für int und 
float? Die einzelnen Ziffern 0 bis 9, A bis F sehen doch völlig gleich 
aus?)

Jetzt hast du dann alle Zeit der Welt, eine Zahl in 4 Ziffern zu 
zerlegen, unabhängig vom Multiplexing der Anzeige.

Die Werte treiben das Display! Und nicht die Multiplexfrequenz bestimmt, 
wann du deine Zahl zerlegen musst!

von Karl H. (kbuchegg)


Lesenswert?

Balazs B. schrieb:
> Mit der Float-Berechnung skaliere ich den Wert aus ADCH auf 0-5V hinauf,
> und bestimme danach die Ziffern für die 1er, 10tel, 100stel und 1000stel
> Stelle.

Wozu?

#define ADC_REF_QUOT_8BIT 0.01953125 //= 5/(255+1)

Wenn du nicht mal 5 sondern mal 5000 rechnen würdest, dann ist das 
Ergebnis deiner Umrechnerei eine ganze Zahl von 0 bis 5000. Noch den 
Dezimalpunkt nach der dritten Stelle einschalten und schon steht auf der 
Anzeige 5.000 anstatt 5000 oder 3.654 anstelle von 3654.
Aber: Du hast alles in int gerechnet (oder in long, wegen der Überläufe) 
und das geht um einiges schneller als float.

von Balazs B. (bbking)


Lesenswert?

Karl heinz Buchegger schrieb:
> Deine ganze Multiplexerei ist .... Müll
>
> Du kannst dir ruhig 4 globale unsigned char (zb als ein Array)
> zurechtlegen, in die du das auszugebende Bitmuster ablegst.
>
> In der ISR machst du dann
>    momentan angezeigte Stelle abschalten
>    Stelle um 1 weiterschalten (unter Berücksichtigung des 'Überlaufs'
>    Die für diese Stelle notwendigen LED einschalten (Bitmuster
>       dafür hlst du dir aus dem Array
>    Anzeige einschalten.

ich mach das jetzt so:
die Berechnung der gerade aktuellen Displaystelle mach ich in der ISR 
vom Timer und starte dort die Konvertierung.

in der ISR vom ADC lese ich den Wert aus, mache meine Berechnungen und 
schalte dort die aktuelle Anzeige ein. Jetzt ist die Anzeige 
wunderschön!

> Und du brauchst auch keine so dermassen hohe Multiplexfrequenz. Wenn
> deine 4 Stück 7-Segment 100 mal in der Sekunde reihum einmal
> eingeschaltet werden, dann langt das normalerweise dicke.
>
> Wenn du dann eine Ausgabe machen willst, dann schreibst du einfach in
> das globale Array für die richtige Stelle das Bitmuster, welches die
> richtigen LED einer Stelle zum Leuchten bringt.

Das habe ich mittlerweile schon so umgebaut, der Code von Peter 
Dannegger war dazu echt hilfreich!

Habe noch den Code erweitert, der ADC Wert wird auf Timer1 als PWM 
Impuls ausgegeben um einen Motor anzutreiben.

Danke an alle für die Tipps!

von Balazs B. (bbking)


Lesenswert?

Karl heinz Buchegger schrieb:
> Ungefähr so
> [C]
> ....
> unsigned char Stelle[4];
>
> SIGNAL(SIG_OUTPUT_COMPARE2)
> {
>   // PORTB =  Wert, damit die momentane iPos Anzeige dunkel wird
>   // Ich rate mal
>   PORTB = 0;

geht eh so. Ich möchte dann das ganze in allgemeine Funktionen 
umschreiben, sodass man die Ports einmal am Anfang angeben muss und 
sonst nie wieder. Work in Progress...


> (Wozu hast du eigentlich verschiedene Decodiertabellen für int und
> float? Die einzelnen Ziffern 0 bis 9, A bis F sehen doch völlig gleich
> aus?)

Vielleicht eine Overkill Lösung, aber im Array mit den Floats habe ich 
den Punkt noch dazugeschaltet...

> Die Werte treiben das Display! Und nicht die Multiplexfrequenz bestimmt,
> wann du deine Zahl zerlegen musst!

Das ist wohl der Punkt! Die Frage ist nur, wo Du die Funktion dazu 
aufrufst? In meinem vorigen Posting, das ca. zeitgleich mit Deinem 
eingetroffen ist, habe ichs ein bisschen beschrieben....

Riesen Dank für die Mühe!

von Karl H. (kbuchegg)


Lesenswert?

Balazs B. schrieb:

>> Die Werte treiben das Display! Und nicht die Multiplexfrequenz bestimmt,
>> wann du deine Zahl zerlegen musst!
>
> Das ist wohl der Punkt! Die Frage ist nur, wo Du die Funktion dazu
> aufrufst?

Gar nicht.
Das Multiplexing findet völlig unabhängig von allem anderen statt.
Das globale Array kann man sich als eine Art Displayspeicher darstellen, 
welches die Anzeige repräsentiert. Schreibt man dort ein Muster für die 
7-Segment Anzeige hinein, dann sorgt ein 'magischer Mechanismus' (die 
ISR) dafür, dass dieses Muster auf der Anzeige erscheint. Für denjenigen 
Programmteil, der sich um die eigentliche Auswertung kümmert und 
letztendlich mit einem Zahlenwert hochkommt, ist es völlig unerheblich, 
wie dieser Mechanismus passiert. Der beschreibt einfach nur seinen 
'Displayspeicher'. Für ihn sind diese 4 Elemente des globalen Arrays 
die 7-Segmentanzeige.

Und die Funktion, die eine Zahl in 4 Stellen aufdröselt: Die wird 
klarerweise aufgerufen, wenn eine neue auszugebende Zahl vorliegt.

von Karl H. (kbuchegg)


Lesenswert?

Balazs B. schrieb:
>
> Vielleicht eine Overkill Lösung, aber im Array mit den Floats habe ich
> den Punkt noch dazugeschaltet...

Wenn du eine allgmeine Lösung haben willst, dann lass dieses zusätzliche 
Kodierarray weg. Das verwirrt ungemein.

Alles was du brauchst ist eine Funktion
1
void OutNumber( unsigned int Number, unsigned char KommaPosition )
2
{
3
  ...

der du eine Zahl übergibst und auch noch dazusagst an welcher Stelle der 
Dezimalpunkt auftauchen soll.

Von mir aus mach dir noch eine Funktion für einen float, aber wie schon 
gesagt wurde, vermeidet man float. In den allermeisten Fällen benötigt 
man sie nicht. Man macht das eher so, dass alle Zahlen zb mal 1000 
genommen werden und bei der Anzeige der Dezimalpunkt an der richtigen 
Stelle eingeschmuggelt wird. Für den Benutzer, der aufs Display schaut, 
kommt es aufs gleiche raus. Aber den µC entlastet man damit schon enorm.

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.