Forum: Mikrocontroller und Digitale Elektronik ATmega8 - Manueller BPM-Counter läuft zu schnell :-/


von Paul H. (powl)


Angehängte Dateien:

Lesenswert?

So, zum zweiten Versuch.

Ich möchte mir einen manuellen BPM-Counter und Lauflicht in einem 
basteln. Per Taster soll der Takt vorgegeben werden, danach soll das 
Lauflicht im Takt weiter laufen. Das ganze habe ich mit einem Atmel 
ATmega8 der mit einem 8 Mhz Quarz läuft aufgebaut.

In meinem Fall besteht die Lauflichtaktion nur aus einem einfachen Teil 
das eine LED an Port B zum blinken bringt, reicht ja auch für den Anfang 
bis ich den rest programmiert habe.

Der Timer1 läuft im CTC-Modus und löst alle 0,1ms einen Interrupt aus. 
Der Taster1 an PD2 löst ebenfalls einen Interrupt aus, genau wie der 
Taster2 an PD3 der nur dazu dient die Lauflichtfunktion auf Pause zu 
schalten.

Drückt man auf den Taster1 wird im Programmeigenen Statusregister das 
cnt-flag gesetzt und ein Counter in gang gesetzt der nach dem Timer alle 
0,1ms eins hoch zählt. Beim nächsten drücken des Taster1s wird nun die 
verstrichene Zeit des Counters zum Zeispeicher dazuaddiert und der 
Counter gelöscht. Ausserdem wird der Divisor inkrementiert und damit 
eine Division durch den Zeitspeicher durchgeführt. Somit erhalte ich die 
durchschnittliche Zeit eines Takts, und das eigentlich ziemlich genau 
weil ich ca 6,5 Sekunden Zeit hab (Bis das 16-bit Zeitspeicherregister 
beim Wert ~65000 überläuft) um den Takt vorzugeben. Läuft der 
Zeitspeicher über wird das ovf-flag gesetzt und die rote status-led 
eingeschaltet. Hier dient der Taster1 dann nur noch zum Syncronisieren 
des Takts. Hört man zwischendurch auf den Takt weiter einzugeben wird 
cnt nach 1 Sekunde wieder auf 0 gesetzt. Drückt man den Taster1 nur ein 
mal dient er nur dazu den Takt zu syncronisieren.

Was noch kommen soll sind grafische Ausgabe der BPM mit 
7-Segment-anzeigen und eben die Lauflichtfunktion.

Zum debuggen habe ich an PC0-4 mal 5 LEDs angeschlossen an denen ich 
derzeit den Divisor ausgebe.

Sollte eigentlich prima funktionieren, leider blinkt die LED etwas zu 
schnell so dass ich nach ein paar Sekunden schon wieder ausser Takt 
läuft. Ich frage mich nun warum, ich schließe den Fall aus dass ich zu 
blöd bin den Takt einzugeben da sowieso nur die verstrichene Zeit 
zwischen dem ersten und dem letzten Tastendruck zählt und die Anzahl der 
Tastendrücke dazwischen. Im übrigen läuft sie immer zu schnell, niemals 
zu langsam, vorrausgesetzt ich gebe den Takt richtig ein. Gefühlsmäßig 
läuft sie auch immer so um den gleichen faktor zu schnell. Ich frage 
mich nun woran das liegt :-/

Tut mir leid dass der Code so umfangreich ist aber ich glaube es hätte 
nichts gebracht wenn ich den code auseinandergepfriemelt hätte, da wäre 
nicht viel weg gefallen. Danke im vorraus an alle die sich damit 
auseinandersetzen!

Im Anhang befinden sich das AVR-Studio Project samt Assemblercode, ein 
Programmablaufplan und ein Bild vom Testaufbau. Diese LED Links ist 
meien Status-LED. Eine DUO-LED die je nach ihrer Polung entweder Grün 
oder Rot leuchtet.

mfg Paul H.

von crazy horse (Gast)


Lesenswert?

hm, ich werde da jetzt nicht durchsteigen.
Auf jeden Fall solltest du das sreg in den Interruptroutinen sichern, 
ist zwar im Moment noch kein Problem, da deine main nur aus jmp main 
besteht. Kommt da was hinzu, gibts Murks.
Schon mal über Einsatz einer Hochsprache nachgedacht?
Ansonsten kan ich dir nur raten, das AVR-Studio richtig zu benutzen. 
Dort gibts einen Cycle-Counter, der dir ganz genau sagt, wieviel Takte 
das ganze dauert. Z.B. erst mal einen Breakpoint auf den Timer-Int, dann 
siehst du, ob der tatsächlich in der von dir gewollten Zeit auslöst. Das 
kann man weiter betreiben, indem du deine Software-Zähler überprüfst.
Und dann bliebe noch die Variante, dass du mit dem internen RC-Osz 
arbeitest, Fuses überprüfen.

von Paul H. (powl)


Lesenswert?

Hi, danke erstmal für die Antwort. Ja, habe schon daran gedacht das SREG 
zu sichern, habe das aber bewusst weggelassen um unnötigen Code erstmal 
weitestgehend zu vermeiden.

Ja, hatte schonmal daran gedacht das ganze in C zu programmieren aber 
ich wollte meine ersten Schritte zunächst mal mit Assembler machen, 
finds auch interessanter so :-)

Also hab das ganze mal teilweise im AVRstudio getestet. Der Timer löst 
exakt bei jeder 1/10 ms einen Interrupt aus, das stimmt also.

Die Fuses hab ich nach Datenblatt richtig gesetzt. Die Frequenz stimmt. 
Selbst wenn nicht wäre das egal, da er theoretisch nur die anzahl der 
interrupts zählt die in einem bestimmten Zeitraum ausgelöst wurden und 
hiervon den durchschnitt zwischen zwei tastendrücken ausrechnet. Mit der 
gleichen Frequenz blinkt ja dann auch die LED. Somit gleicht sich das 
wieder aus.

Gibt es beim ATmega nicht auch die möglichkeit zur laufzeit auf 
registerwerte zuzugreifen?

von Hannes L. (hannes)


Lesenswert?

Paul Hamacher wrote:
>
> Gibt es beim ATmega nicht auch die möglichkeit zur laufzeit auf
> registerwerte zuzugreifen?

Irgendwie verstehe ich Dich nicht richtig...

Selbstverständlich greift der AVR zur Laufzeit auf Register zu.
Oder meinst Du, ob Du bei der Simulation die Registerinhalte sehen 
kannst? Falls ja, da gibt es mehrere Möglichkeiten, schau mal in den 
Menüpunkt view. Da gibt es das Fenster für Register. Ich bevorzuge aber 
das Fenster für Memory und wähle dort Register aus, finde ich 
übersichtlicher und platzsparender auf dem Bildschirm.

Da ich nicht bereit bin, zum Lesen einer browsertauglichen Textdatei 
(ASM-Quelltext) ein Zip-Archiv herunterzuladen und zu entpacken (das 
Projekt läuft ja doch nicht, da meine Ordnerstruktur nicht mit Deiner 
übereinstimmt), habe ich mir Deinen Quelltext nicht angesehen. Das 
Anhängen im Textformat erhöht die Chance, dass sich jemand den Text 
etwas genauer ansieht.

Was soll das eigentlich werden? Eine Art Metronom (ja, so heißt dieses 
im Takt knackende "Musikinstrument")?

...

von Michael Wilhelm (Gast)


Lesenswert?

Tasterprellen?

MW

von Paul H. (powl)


Angehängte Dateien:

Lesenswert?

Tut mir leid, habe mich vielleicht etwas unklar ausgedrückt. Ich meinte
damit ob man während der ATmega8 in der Schaltung seinen dienst tut die
Werte der Register über den PC auslesen kann, zum debuggen. Ich spreche
nicht vom Simulator.

Die Zip-Datei habe ich angehängt damit ich den PAP und das Bild zum
Aufbau noch mit reinkrig. Habe die asm-datei nochmal angehängt. Den
Programmablaufplan hast du ja schon.

Habe inzwischen mal die Divisionsroutine zur Unterbrechung durch einen
Interrupt freigegeben. Dachte dass die Division eventuell zu lange
dauert und den Timer-interrupt so lange blockiert dass ein paar
zähl-takte verloren gehen. Bei 8 Mhz schneidet die Divisinsroutine den
Timerinterrupt zwar gelegentlich aber der timerinterrupt wird dann
trotzdem noch ausgeführt bevor ein zweiter Timerinterrupt ausgelöst
wird.. mit der Division sollte es also eigentlich keine Probleme geben,
wie es bei höheren Zahlen aussieht weiß ich nicht deswegen hab ich das
nun einfach mal so stehen gelassen. Der Praxistest zeigt dass die LED
immernoch ausser Takt blinkt

Nochmal was das werden soll: Ein Lauflicht das im Takt zur Musik läuft.
Der Takt wird schlag für schlag über einen Taster eingegeben. Der
Einfachheit halber bringe ich im Moment aber kein Lauflicht zum laufen
sondern nur eine LED am Port B zum blinken.

Btw: Taster sind per Kondensator zuverlässig hardwareentprellt. Dass
diese Entprellung funktioniert sieht man auch an den gelben LEDs die an
Port C angeschlossen sind.

mfg Paul H.

von Paul H. (powl)


Lesenswert?

Tut mir leid den Thread zu pushen aber ich suche immernoch verzweifelt 
nach einer Lösung. Ich verstehe das nicht, normalerweise müsste alles 
funktionieren. Eventuell baue ich zum besseren Verständnis noch eine 
vereinfachte Version. Danke.

mfg Paul

von Hannes L. (hannes)


Lesenswert?

Paul Hamacher wrote:
> Tut mir leid den Thread zu pushen aber ich suche immernoch verzweifelt
> nach einer Lösung. Ich verstehe das nicht, normalerweise müsste alles
> funktionieren. Eventuell baue ich zum besseren Verständnis noch eine
> vereinfachte Version. Danke.
>
> mfg Paul

Ein Lösungsansatz wäre:
Timer1 mit entsprechendem Vorteiler laufen lassen, so dass längstes und 
kürzestes benötigtes Intervall in seinen Zählumfang passen.
Einen hardware-entprelltem Taster am ICP-Eingang anschließen und per 
Input-Capture-Interrupt den Zeitabstand zwischen zwei Tastendrücken 
scannen. Diesen Wert (16 Bit) dann als Intervall für den 
Output-Compare-Interrupt des Timer1 benutzen.

In der ICP-ISR:
- Neuen Zeitstempel einlesen (ICRL/ICRH)
- Alten (gemerkten) Zeitsempel aus SRAM in Hilfsregister kopieren
- Neuen Zeitstempel (für nächste Runde) im SRAM speichern
- Zeitstempel in OCR1B(H/L) schreiben, um Timeout zu setzen
- Hilfsregister vom neuen Zeitstempel subtrahieren und Ergebnis als
  "Intervall" sichern, falls anhand des Gültigkeitsflags das Intervall
  verwertbar ist. Dann auch "neu"-Flag setzen.
- danach Gültigkeitsflag setzen

In der OC1B-ISR:
- Gültigkeitsflag löschen (der Zeitabstand zum vorhergehenden 
Tastendruck
  ist zu groß um das gemessene Intervall zu verwerten, es gab einen
  Überlauf, der Timer hat also den Zählerstand des letzten Tastendrucks
  wieder erreicht)

In der OC1A-ISR:
- OCR1A(L/H) einlesen,
- Intervall dazuaddieren
- als Termin für den nächsten Interrupt in OCR1(L/H) schreiben
- Lauflicht weiterschalten

In der Mainloop:
- Wenn neu-Flag gesetzt ist, dann
  - neu-Flag löschen (Job wird ja erledigt)
  - Kopie vom Intervall holen
  - in Frequenz (BpM) umrechnen
  - ausgabegerecht in einen SRAM-Bereich schreiben
  - Sleep bis zum nächsten Interrupt...

In der OVF0-ISR:
- Ausgabe der in der Mainloop vorbereiteten Ziffern (im SRAM) per
  Multiplex an die 7-Segment-Ziffernanzeigen

In der Init-Routine:
- Stackpointer, Timer, Ports usw. initialisieren

Gültigkeits-Flag und neu-Flag sind Boolsche Variablen (einzelne Bits in
einem Register).

...

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.