Forum: Mikrocontroller und Digitale Elektronik frequenzmessung atmega8 mit icp


von frankparker (Gast)


Lesenswert?

Hallo zusammen,

ich versuche seit einigen Tagen den atmega8 zu Programmieren. Lcd 
Ansteuerung usw ist auch kein Problem.

Ich möchte eine Frequenz die am ICP Pin angeschlossen ist messen.
Also es liegt zb. ein Signal von 10-20 Khz an welches der atmega 
ausgeben soll auf dem display.

Ich Programmiere das ganze in Assembler und irgendwie komme ich mit den 
ganzen Registern die dazu benötigt werden nicht so ganz zurecht

Wäre über eine Hilfe sehr dankbar.

MfG

von Hmm... (Gast)


Lesenswert?

Naja, soviele Register sind es nun auch wieder nicht. Wenn du den Timer1 
schon in Betrieb hast, sind es doch nur noch ein paar Bits die dich vom 
ICP trennen.

Etwas genauer erklärt (allerding in C) ist das ganze hier:

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Einfangen_eines_Eingangssignals_.28Input_Capturing.29


Kurz gesagt musst du nur den Timer1 scharf schalten:
1
  TCCR1A = 0x00;
2
  TCCR1B = (1<<CS10);   // für ICP siehe Datenblatt Bit 6 und7
3
  
4
  TIMSK  = (1<<TICIE1); // ICP-Interrupt einschalten
5
6
  sei();                // Interrupts global freischalten

Das ist es im Grunde schon. Sobald die in TCCR1B selektierte Flanke 
erkannt wird, wird in die entsprechende Interrupt-Routine verzweigt. Im 
16-Bit Register ICR1 findest du dann den Zählerwert 'eingefroren' 
welcher zum Zeitpunkt der Flanke in TCNT1 stand.

von frankparker (Gast)


Lesenswert?

ja das ist mein Prob da ich von C kein Plan hab

mein Assembler Code sieht im moment so aus:
timer:  ldi  r16,0b00001000
    out  TIMSK,r16
    ldi  r16,0b01000001
    out  TCCR1B,r16
    ldi  r16,0b00000000
    ldi  r17,0b00000000
    out  TCNT1H,r16
    out  TCNT1L,r17
    rcall LCD_line2
    in  r16,ICR1H
    rcall LCD_data
    rcall wait20ms
    in r16,ICR1L
    rcall LCD_data
    rcall wait20ms

    rcall timer

von Stefan B. (stefan) Benutzerseite


Lesenswert?

frankparker wrote:

> mein Assembler Code sieht im moment so aus:
> timer:  ldi  r16,0b00001000
>     ...
>     rcall timer

Die Rekursion (Selbstaufruf) hier ist bestimmt ein Fehler.

von frankparker (Gast)


Lesenswert?

ja der selbstaufruf ist bisher nur zum testen drin.

es soll halt ein interrupt ausgelöst werden wenn eine High Flanke an 
ICP(pb0) kommt.

rjmp  onTC1                       ;TC1 Capture

onTC1:  rcall LCD_line2
    in  r16,ICR1H
    rcall LCD_data
    rcall wait20ms
    in r16,ICR1L
    rcall LCD_data
    rcall wait20ms
    reti

timer:  ldi  r16,0b00000000
    out TCCR1A, r16
    ldi  r16,0b11000001
    out  TCCR1B,r16
    ldi  r16,0b00000000
    ldi  r17,0b00000000
    out  TCNT1H,r16
    out  TCNT1L,r17
    ldi  r16,0b00001000
    out  TIMSK,r16
    sei


    rcall timer

in main wird halt timer aufgerufen und gestartet. Dort soll der das 
programm den abstand zwischen zwei high flanken messen und erstmal 
ausgeben.

von Hmm... (Gast)


Lesenswert?

Also ehrlich gesagt fehlt bei deinen ganzen Code-Fragmenten die 
Interrupt-Vektor Tabelle. Woher soll ich denn jetzt wissen, wo dein 
Programm beginnt? Und noch wichtiger, ob deine ISR an der richtigen 
Stelle steht?

Ich rate mal:

1. "onTC1" ist deine Interrupt-Routine.

   In deiner ISR möchtest du also tatsächlich 40ms warten? Das bedeutet,
   du gehst davon aus, dass zwischen 2 steigenden oder 2 fallenden 
Flanken
   40ms vergehen. Das entspricht ganzen 25 Hz...

   Versuch die ISR so kurz wie möglich zu halten und gibt die Ergebnisse
   in deiner Hauptschleife aus. Alles andere ist murks.

   Achja, denkst du wirklich, du erkennst so schnell aufflackernde Werte
   auf deinem LCD?

2. "timer" ist deine Hauptschleife, oder?

    Die Initialsierung (Prescaler 1, steigende Flanke und Noise Canceler
    ein) schaut soweit gut aus. Allerdings schaltest du den Interrupt 
für
    den Output Compare/Match1 Interrupt ein, nicht den für den Input-
    Capture (Bit 5 laut Datenblatt!)

    Ausserdem ist es etwas unüblich die Initialisierung permanent zu
    wiederholen. Setz lieber nach 'sei' eine Endlosschleife, in der
    du die gemessenen Daten ausgibts. Am besten über einige Messungen
    gemittelt.

von frankparker (Gast)


Lesenswert?

Ja OnTC1 ist meine Interrupt Routine.

Die 40 ms waren nur drin um halt auf dem Display was erkennen zu können.

timer ist nicht unbedingt meine Hauptschleife. In der Haupschleife hab 
ich einen Anzeige-Text fürs display und ein rcall aufruf zum "Timer"

Bei TIMSK war das Bit falsch gesetzt - das ist mir eben auch schon 
aufgefallen.

Ich hab das jetzt mal geändert

timer:  cli
    ldi  r16,0b00000000
    out TCCR1A, r16
    ldi  r16,0b11000101
    out  TCCR1B,r16

    ldi  r16,0b00000000
    ldi  r17,0b00000000
    out  TCNT1H,r16
    out  TCNT1L,r17

    ldi  r16,0b00100000
    out  TIMSK,r16
    sei
    ret

ausgabe:rcall LCD_line2
    in  r16,ICR1L
    rcall LCD_data
    in r16,ICR1H
    rcall LCD_data
    reti

    rcall ausgabe
das Programm ruft nun einmal Timer auf zur initialisierung und danach 
nur noch die Ausgabe.

Intterupt hab ich mit rjmp  onTC1                       ;TC1 Capture
aktiviert
Nur was ich jetzt bei onTC1 eintragen muss weis ich nicht

von Hmm... (Gast)


Lesenswert?

> Intterupt hab ich mit rjmp  onTC1       ;TC1 Capture aktiviert

Du hast den Sinn einer Interrupt-Routine nicht verstanden. Du darfst 
diese Routine NIEMALS aufrufen, da der Befehl "reti" für die Rückkehr 
aus einem echten Interrupt gedacht ist. Und der wird von deiner Hardware 
automatisch aufgerufen sobald eine Flanke erkannt wurde. Um die Routine 
zu triggern musst du also eine Flanke an den ICP1 Pin anlegen. Dann 
sucht sich der Atmega8 automatisch die Adresse deiner ISR aus der 
Vektortabelle.

Ob du deine ISR in den richtigen Vektor geschrieben hast kann ich 
allerdings nicht sagen da du dich so beharrlich weigerst mal die 
Vektortabelle oder besser noch deinen ganzen Source mal anzuhängen.

von frankparker (Gast)


Lesenswert?

ja aber das Programm soll doch den Interrupt selber auslösen - also beim 
ersten mal zähler auf 0 und beim zweiten aufruf den Wert ausgeben.

Ich hab am ICP Pin eine Frequenz angelegt und das Display gibt mir 
wechselnde zeichen aus

Ich schreib das ganze mit SISY - und hab das da in ein Main Programm und 
unterprogramme unterteil - ich weis nich wie ich das in eine Datei zum 
hochladen kriege.

Das sind halt meine ersten schritte - vorher hab ich bisher nur den 8085 
Programmiert.

von frankparker (Gast)


Lesenswert?

; Reset and Interrupt vector                Beschreibung
begin:   rjmp    main                       ;POWER ON RESET
         reti                               ;Int0-Interrupt
         reti                               ;Int1-Interrupt
         reti                               ;TC2 Compare Match
         reti                               ;TC2 Overflow
  rjmp  onTC1                       ;TC1 Capture
         reti                               ;TC1 Compare Match A
         reti                               ;TC1 Compare Match B
         reti                               ;TC1 Overflow
         reti                               ;TC0 Overflow
         reti                               ;SPI, STC Serial Transfer 
Complete
         reti                               ;UART Rx Complete
         reti                               ;UART Data Register Empty
         reti                               ;UART Tx complete
         reti                               ;ADC Conversion Complete
         reti                               ;EEPROM Ready
         reti                               ;Analog Comparator
         reti                               ;TWI (I²C) Serial Interface
         reti                               ;Store Program Memory Redy

von Hmm... (Gast)


Lesenswert?

> ja aber das Programm soll doch den Interrupt selber auslösen - also beim
> ersten mal zähler auf 0 und beim zweiten aufruf den Wert ausgeben.

Interrupts werden nicht vom Programm sondern von der Peripherie, in 
deinem Fall der Input-Capture-Einheit ausgelöst. Wenn du das nicht 
möchtest, kannst du auch das Bit #5 im Register TIFR (Timer Interrupt 
Flag Register) pollen. Wenn es gesetzt ist kannst du aus ICR1 deinen 
Timerstand sichern und es wieder löschen (siehe Datenblatt).

Nochmal: Du kannst per Software keinen Interrupt auslösen.

> Ich hab am ICP Pin eine Frequenz angelegt und das Display gibt mir
> wechselnde zeichen aus

Ich kenn deine Display-Ansteuerung nicht. Wenn du deine wie auch immer 
ermittelte Zahl ausgeben möchtest, musst du sie sicher erst noch in 
einen darstellbaren ASCII-String umwandeln.

> Ich schreib das ganze mit SISY - und hab das da in ein Main Programm und
> unterprogramme unterteil - ich weis nich wie ich das in eine Datei zum
> hochladen kriege.

Alles in eine Textdatei kopieren?
Oder notfalls mehrere Dateien anhängen?
Oder zippe das Projekt, wenn's denn unbedingt sein muss.

> Das sind halt meine ersten schritte - vorher hab ich bisher nur den 8085
> Programmiert.

Meine ersten Schritte hab ich auf einem KC89 gemacht. Da gabs kein 
Internet, dafür Stern-Rekorder um Programme aufzuzeichnen... ;)

von frankparker (Gast)


Angehängte Dateien:

Lesenswert?

Ja mit dem Interrupt ist ja auch ok über den ICP Pin

Der soll ja nur eine Periode Messen, die ich dann später auswerte...

Ich hab dir das ganze mal angehängt - sind aber noch viele Programm 
teile drin die noch nicht benutzt werden.

Wenn ich die frequenzauswertung hingekriegt hab wird später ein 
Farbsensor am ICP Pin angeschlossen.

von Hmm... (Gast)


Lesenswert?

Langsam verliere ich ehrlich gesagt die Lust mir dein ungetestetes Zeug 
weiter anzuschauen :(

1. Du hast lauter .txt-Dateien in deinem RAR-Archiv. Du bindest aber .s
   Dateien ein. Selbst wenn ich für teuer Geld dein SisyAVR Paket 
gekauft
   hätte, könnt ich es nicht einmal assemblieren.

2. Deine Main sieht anders aus, als das was du oben gepostest hast.
   Wieso initialisierst du den Timer plötzlich nicht mehr vor deiner
   Hauptschleife sondern erst bei der Ausgabe in der Subroutine "Farbe"?

3. Deine ISR ist auf einmal leer??

Ich würde vorschlagen, du malst dir mal einen schnöden, altmodischen PAP 
(Programmablaufplan) auf, in dem du dir erstmal klar machst, welche 
Aufgaben in welcher Reihenfolge erledigt werden müssen.

Beachte hierbei, dass du einen PAP für deine Anwendung und einen 
weiteren für deine Interrupt-Routine brauchst. Beide laufen quasi 
getrennt voneinander ab.

von frankparker (Gast)


Lesenswert?

zu1 ) ja das sind txt dateien da ich in SISY einfach alles in txt 
dateien kopiert hab.
Das Assemblieren klappt ja ;)

zu2 ) der Timer initialiesiere ich schon die ganze Zeit so - oben war ja 
nur ein auschnitt des Programms.

Ich hab das nur so aufgeteilt damit ich nicht eine riesiege Datei haben 
möchte sondern übersichtlicher in einzelnen dateien.

zu3) ISR ist leer weil du ja sagtest das ich die Ausgabe lieber unter 
sei bei timer setzten sollte.
Das war ja eben meine Frage was ich nun da eintragen soll.


Ein PAP hab ich ja - ich weis ja was das Programm machen soll
Display usw initalisieren
auf eine High Flanke warten
timer starten
bei nächsten High Flanke den Zähler Wert ausgeben

da ich ja eine feste frequenz über einen Generator einspeise müssten ja 
immer die selben zeichen auf dem Display erscheinen - mehr will ich ja 
zuerst garnicht.


Danke schonmal für deine Hilfe - kann mir schon denken des das nervt 
weil ich mich erst seit kurzem mit dem Atmega beschäftige.

von Hmm... (Gast)


Lesenswert?

> zu1 ) ja das sind txt dateien da ich in SISY einfach alles in txt
> dateien kopiert hab.
> Das Assemblieren klappt ja ;)

Schön das du es assemblieren kannst.

> zu2 ) der Timer initialiesiere ich schon die ganze Zeit so - oben war ja
> nur ein auschnitt des Programms.

Dein Problem ist nicht, WAS du in die Register schreibst, sonder WANN. 
Der Tip mit dem PAP diente dazu dir die Reihenfolge der Aktionen klarer 
zu machen.

Würde das funktionieren:

warte_das_auto_losfährt();
starte_motor();

Sicher nicht, das Auto fährt erst los, wenn der Motor an ist. Genauso 
ist es mit dem Timer, er wird erst initialisiert und kann danach 
verwendet werden. Zyklisch am Zündschlüssel zu drehen wenn der Motor 
läuft ist dem vorankommen auch nicht dienlich.

> Ich hab das nur so aufgeteilt damit ich nicht eine riesiege Datei haben
> möchte sondern übersichtlicher in einzelnen dateien.

Sehr löblich. Allerdings kenn ich deinen Assembler nicht. Und ich weiß 
auch nicht, ob du alles korrekt/vollständig im richtigen Kontext kopiert 
hast. Zippe (diese Wortschöpfung stammt entweder von 'to zip' oder auch 
von dem allgemein gebräuchlichen Archiv-Format ZIP) das Projekt so, wie 
es assemblierbar ist. Sisy werd ich mir trotzdem nicht kaufen.

> zu3) ISR ist leer weil du ja sagtest das ich die Ausgabe lieber unter
> sei bei timer setzten sollte.
> Das war ja eben meine Frage was ich nun da eintragen soll.

Wie ich bereits mehrfach sagte, dort wird der Wert aus ICR1 gesichert 
und ggf. TCNT1 zurück gesetzt. Beachte, dass die Zeit die du zum sichern 
von ICR1 brauchst, beim nächsten Interrupt fehlt, da TCNT1 je nach 
Prescaler schon ein paar Takte wieder gezählt haben kann. Den 
gesicherten Wert verarbeitest du dann in deiner Main. Am besten legst du 
hier eine Kopie von dem gesicherten Wert an, damit ein neuer Interrupt 
nicht teile der letzten Messung verfälscht.


> Ein PAP hab ich ja - ich weis ja was das Programm machen soll
> Display usw initalisieren auf eine High Flanke warten timer starten
> bei nächsten High Flanke den Zähler Wert ausgeben

Dann überdenke ihn nochmal. Ein Interrupt kann erst ausgelöst werden, 
wenn die dazugehörige Peripherie initialisiert und Interrupts global 
frei gegeben wurden. Wenn eine Peripherie initialisiert wurde, lass sie 
in Ruhe ihren Job tun ;)

> da ich ja eine feste frequenz über einen Generator einspeise müssten ja
> immer die selben zeichen auf dem Display erscheinen - mehr will ich ja
> zuerst garnicht.

Nimm die Software schrittweise in Betrieb. Prüfe zunächste, ob die ISR 
ausgeführt wird (Pin toggeln in der ISR und mit Oszi EIngangssignal und 
den Debug-Pin vergleichen) Wenn das passt, lass dir feste, hart kodierte 
Strings auf dem LCD anzeigen. Wenn das passt, mit dem Oszi schauen ob 
deine ISR noch Ok ist. Dann eine Routine für die Ausgabe von 16-Bit 
Werten testweise mit hart kodierten Zahlen prüfen. Und zum Schluß N 
getestete und funktionierende Softwareteile zusammen fügen.

> Danke schonmal für deine Hilfe - kann mir schon denken des das nervt
> weil ich mich erst seit kurzem mit dem Atmega beschäftige.

Mach vielleicht mal eine Pause und schlaf darüber / iss Mittag. Wenn man 
mit frischer Kraft und konzentriert(!)&systematisch(!) an das Problem 
heran geht, fällt vieles leichter. Trial&Error finde ich persönlich 
nicht so produktiv. ;)

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.