Hallo Forum
Ich bin noch Anänger in Mikrocontroller Programmierung. Ich möchte ein
Programm schreiben das die Freuqenz eines Rechtecksignals Berechnet
Ich möchte dieses mit der Capture Mode des Timer1 (ATMEGA168)
realisieren.
Ich hab das Programm unten mal angehängt Im Debuggermodus scheint das
Programm das zu tun was es soll villeciht kann mir jemand einen Tip
geben ob ich auf dem richtigen weg bin. Bzw bin ich über alle
verbesserungsvorschläge dankbar
mfg
Dann stört mich die Rechnerei mit den vielen double Variablen. Der Timer
liefert von Natur aus einen Ganzzahlwert und den sollte man nicht in
einen Gleitkommawert vergewaltigen. Bleibst du bei Ganzzahlen,
vereinfacht sich die Rechnung auf Assemblerebene. Das spart Platz und
bringt Speed.
Speed ist ggf. notwendig, wenn du sie Berechnung innerhalb der
Interruptroutine machst. Der nächste Interrupt ist dir nämlich schon auf
den Fersen und lauert nur darauf, dass er dir Variablen überschreiben
kann, mit denen du noch fleissig am rechnen bist.
Abhilfe ist entweder schnell den Interrupt abzuarbeiten oder während der
Interruptbearbeitung Interrupts zu sperren (weniger sinnvoll bei einem
Timer/Zähler).
Die schnellere Bearbeitung könnte z.B. so aussehen, dass du zunächst nur
Variablen füllst, die später im Hauptprogramm ausgewertet werden. Auch
den Spezialfall der 1. Flanke kannst du bei der Auswertung behandeln und
du musst nicht bei jedem Interrupt den alten Käse vom Anfang per if
testen. Vor dem Einschalten der Interrupts den Zählerstand holen und
pronto hast du den counterold-Wert. Und wie gesagt, keine oder sowenig
wie möglich Gleitkommabearbeitung. Maximal diff als Gleitkommawert
berechnen. Besser wäre es sich Gedanken über den Wertebereich zu machen
und einen passenden Typ auswählen. Mit unsigned long kann man z.B. bis
über 4 GHz ausgeben ;-)
Für unveränderliche Werte wie dieses cmax solltest du auch eine
Konstante nehmen, also z.B. ein Makro definieren.
Hallo Stefan
Danke erstmal für deine Antwort. Wenn ich im Debugger das Programm
durchspiele funktioniert alles soweit. Wenn ich es aber auf dem u
controller spiele und will mir eine Frequenz ausgeben lassen. Kommt nur
müll raus. Kannst Du mir eventuell bei meinem Problem helfen??
mfg
Sicher.
Stell mal das komplette Programm rein.
Ansonsten:
* Stell sicher, dass die Ausgafunktion funktioniert.
* Lass dir erst mal die gezählten counter Ereignisse ausgeben
(eine bekannte Frequenz anlegen und nachschauen ob das
gewünschte Ergebnis rauskommt)
* Erst wenn counter den richtigen Wert hat, rechne um
auf die Frequenz
* ISR Funktionen kurz halten.
In der ISR wirklich nur die Flanken zählen (Dazu reicht
auch ein int + ein int für die Overflows). Die eigentliche
Berechnung und Ausgabe in der Hauptschleife machen.
* Für einen Frequenzzähler brauchst du auch noch einen
zweiten Timer, der dir zb. 0.1 Sekunden Intervalle
generiert.
* Verzichte auf double. Du brauchst sie in der ISR nicht.
* Achte auf deine Programmformatierung.
Zumindest ich weigere mich so ein unübersichtliches Chaos
wie da oben auch nur näher in Augenschein zu nehmen.
Hallo Karl heinz
Anbei mal das ganze programm. Ich hab versucht es ein wenig bessert zu
ormatieren. Auch das sperren der Interrupts hat nix gebracht. Villeicht
hättest Du mal zeit eine Blick darauf zu werfen weis momentan echt nicht
wo der fehler liegen könnt. Wenn ich es im Debugger durchspiele kann ich
einen ehler Finden aber am uc kommt nur Müll raus
merci
Peter
ps: Ich möcht das Programm mal zum laufen bringen, da es mir ausreicht
frequenzen mit 10khz zu messen kann ich es bzgl doubles auch später noch
abspecken
Du hast nichts von den bereit svorgeschlagenen Dingen
umgesetzt.
Ersetz mal die double durch int!
In einer ISR ist Zeit sparen oberstes Gebot! Floating
Point Berechnungen, die man auch in int oder long
machen kann, gehören da auch dazu!
Ansonsten: Zuerst muss der counter Wert stimmen. Hast du
das überprüft? Mach das mal.
Und sag bitte nicht: Da kommt nur Müll raus.
Darunter kann sich nämlich keiner was vorstellen. Das
hilft niemandem weiter, gibt keinerlei Hinweise in
welche Richtung man suchen soll.
> ps: Ich möcht das Programm mal zum laufen bringen, da es mir> ausreicht frequenzen mit 10khz zu messen kann ich es bzgl doubles> auch später noch abspecken
Das nicht vollzogene 'Abspecken' ist einer der Gründe warum
dein Pgm nicht funktioniert. Du kommst mit dem Timing nicht
hin!
Wahrscheinlich. Ohne die Hardware hier zu haben
und Messungen vornehmen zu können ist das aus der Entfernung
schwierig zu sagen.
Ausserdem: Ein Programm das ordentlich formatiert ist und
vernünftig geschrieben ist hat meistens weniger Fehler.
Das sagt die Erfahrung. Ich hab schon ne Menge Programme
gesehen die grauslich formatiert waren, wo Code aus vorhergehenden
Versuchen entweder einfach drinnen gelassen wurde oder auskommentiert
wurde. Das Ergebnis: Ein Programm in dem man absolut nichts mehr
sehen konnte. AUch keine Fehler. Nach einer halben Stunde Arbeit
durch Code-vereinfacheung, Umformatierung und was sonst noch
so nötig ist um das ganze übersichtlich zu kriegen ist man
dann plötzlich in einem Zustand, wo selbst ein Blinder mit
Krückstock den oder die Fehler aus 2 Meter Entfernung ertasten
kann.
Ich hab nur mal einen Blick auf deine ISR geworfen:
1
ISR(TIMER1_CAPT_vect)
2
3
{
4
cli();// völlig unnötig!
5
counter=ICR1H*0x100+ICR1L;// auch Quatsch: counter = ICR1; sollte reichen
6
flanke=1;
7
sei();// siehe cli();
8
}
Im Hauptprogramm:
1
oldcounter=0;
2
// Timer konfigurieren und starten...
3
while(1)
4
{
5
.
6
.
7
.
8
9
if(flanke)
10
{
11
flanke=0;// Ereignis zurücksetzen.
12
Periode=counter-oldcounter;
13
oldcounter=counter;
14
frequenz=1/Periode;// das muss noch entsprechend optimiert werden...F_Clock...
15
// Frequenz für Ausgabe formatieren...
16
// LCD akutalisieren
17
}
Es hilft übrigens ungemein, sich vor dem Programmieren Gedanken (auch
auf Papier) zum eigentlichen Ablauf zu machen. Da kann man sogar schon
optimieren...
Zum Thema Formatierung:
Stefan hat weiter oben schon mal gezeigt, wie eine
vernünftige Formatierung aussieht. Nichts gegen
Whitespace (also Leerraum). Der kann gut sein! Aber
4 Leerzeilen untereinander bringen nichts. Die erzeugen
nur Unübersicht, weil Dinge unnötiger weise über ner Menge
Platz verstreut werden.
Schau dir mal das hier an:
fällt dir was auf? Ursprünglich stand da
TIMSK1 = 0x21;
Obiges ist komplett äquivalent dazu. Trotzdem sehe ich in meiner Version
etwas, was du in deiner Version nicht siehst: Du aktivierst den Capture
für
Timer 1, aktivierst aber den Overflow für Timer 0 !
Ob das hier:
1
for(i=1000000;i>0;i/=10)
2
{
3
anz=rest/i;
4
rest%=i;
5
6
if(rest!=zahl)
7
{
8
lcd_write(anz+'0');
9
}
eine Zahl korrekt ausgibt, wage ich mal zu bezweifeln. Teil meines
Zweifels
liegt hier
1
if(rest!=zahl)
Da Zahl den Wert 0 hat und, soweit ich auf die Schnelle gesehen habe,
auch
nie geändert wird, bedeutet das, dass eine 0 nicht ausgegeben wird. Ein
Wert von 500 wird also als 5 ausgegeben.
Wozu der ganze Aufwand? Es ist ok, wenn man sich mal Gedanken drüber
macht, wie
man einen int in seine Stringrepräsentierung überführt. Aber für realen
Code
verwendet man fertige Funktionen itoa() bzw. ltoa().
Dazu braucht man natürlich eine Funktion, die einen String ausgeben
kann.
Die ist aber kein Problem:
1
voidlcd_write_string(char*String)
2
{
3
while(*String)
4
lcd_write(*String++);
5
}
Damit reduziert sich die auch sofort die Initialisierungssequenz in der
main()
1
intmain(void)
2
{
3
unsignedlongi=0;//lokale Zähler Variable
4
unsignedlongrest=0;
5
longanz;
6
7
b_f=0;// Flag für die allerste Flanke auf: noch nicht da gewesen
8
ueberlauf=0;// Zählt die Anzahl der Überläufe des Timers von einem Capture zum nächsten
9
diff=0;// Die Zaehldifferenz dees Timers von einer Flanke zur nächsten
Das sieht doch schon mal besser aus als das Original.
Bist du dir sicher, dass du zuerst die Zahl am Display ausgeben
willst und danach das Display löschen möchtest? Ich denke nicht,
dass du das willst. Denn in der Zeit die zwischen dem
lcd_write_string und dem lcd_clear liegt, wirst du wahrscheinlich
am Display nichts ablesen können. Das Display ist aber leer
bis zur nächsten Ausgabe und besonders bei kleinen Frequenzen
(< 10Hz) kann das ganz schön dauern. Also, wenn schon löschen,
dann lösch das Display bevor du eine Ausgabe machst!
1
while(1==1)
2
{
3
if(flanke==1)
4
{
5
Frequenz=F_CLOCK*cmax/diff;
6
7
ltoa(Frequenz,Buffer,10);
8
lcd_home();
9
lcd_clear();
10
lcd_write_string(Buffer);
11
12
flanke=0;
13
}
14
}
Für itoa() musst du am Anfang noch stdlib.h inkludieren:
1
#include<stdlib.h>
Schaun wir uns mal den Interrupt Handler an:
1
ISR(TIMER1_CAPT_vect)
2
{
3
cli();//intterrupt spreeren
4
5
counter=ICR1H*0x100+ICR1L;//16 Bit Verrechung H und L Reg Timer 1 capure Register
Das cli() bzw. sei() sind unnötig. Das macht der µC ganz von alleine.
Auch das Auslesen des Capture Registers muss man nicht so kompliziert
machen.
Das kann der Compiler auch alleine:
counter = ICR1;
An dieser Stelle:
diff = cmax - counterold + (ueberlauf-1) *cmax +
counternew;//Berechnungsformel
musste ich mal scharf überlegen, ob das auch stimmen kann, bzw. was
hier überhaupt passieren soll. Der Kommentar ist dabei nicht
hilfreich. Das das eine Formel ist, die ausgewertet wird, seh ich
auch alleine. Da brauch ich keinen Kommentar dazu. Was mir aber
ein Kommentar erzählen sollte, aber nicht tut, ist: Was passiert
denn hier überhaupt.
Hier wird die Differenz zum vorhergehenden Zählerstand ausgerechnet.
Warum du allerdings von der Anzahl der Überläufe 1 abziehst und dafür
noch mal cmax dazuzählst ist mir ehrlich gesagt ein Rätsel. Ich würde
es so schreiben:
1
counternew=ueberlauf*cmax+counter;// Auf welchem Wert steht
2
// der Counter jetzt unter Berücksichtung
3
// der inzwishcen aufgetretenen Überläufe
4
5
diff=counterold-counternew;// seit dem letzten Interrupt sind daher
6
// wieviele Ticks vergangen ?
Ohne die LCD Funktionen, sieht dein Pgm also so aus, nachdem etwas
aufgeräumt wurde. Ich hab mal unnötige Variablen rausgeschmissen,
hab einige Kommentare so geändert, dass mir der Kommentar auch
etwas erzählt und nicht nur das wiedergibt, was sowieso schon im
Code steht:
Und dann würde ich mal hergehen und würde am µC mal ausprobieren, ob
in diff vernünftige Werte rauskommen. D.h. zum Testen ersetzte ich
in der Ausgabe
ltoa( Frequenz, Buffer, 10 );
durch
ltoa( diff, Buffer, 10 );
und wenn das auch noch kein vernünftiges Bild ergibt, dann seh
ich mir die Werte von counternew und counterold an.
Das war jetzt noch lang nicht alles. Es gibt noch immer viel zu tun
in dem Programm. Aber es soll dir mal einen Einblick geben wie
man an solche Dinge rangeht. Der völlig falsche Weg ist es
Unmengen an Code zu schreiben, der nie oder nicht ausreichend
getestet wird und dann glauben, dass wird schon iregendwie klappen.
Das tut es nämlich nicht. Ich kenne keinen Programmierer (inklusive
mir) der in der Lage ist mehr als 100 Zeilen in einem Rutsch zu
schreiben ohne dass ein Fehler (Syntax oder Logik) drinnen ist.
Und ich würde sagen: Ich bin nicht der schlechtesten einer. Bei
einem Anfänger kannst du getrost aus den 100 Zeilen 5 Zeilen
machen.
Hallo,
hab nur mal schnell rübergeschaut, weil ich immernoch hoffe, mehr von C
zu verstehen (nur ASM sonst). ;)
Frage: wenn ich richtig gelesen habe, will er ca. 10kHz per
Periodendauer messen? Wären dann rund 100µs/Periode.
Jede erkannte Flanke will er in main() dann umgerechnet anzeigen?
Mit einer Displayroutine, die bei jedem Zugriff mindestens 1ms wartet?
@unsichtbarer WM-Rahul:
Da spielt es dann auch keine Rolle, wenn er es gleich wieder löscht. ;)
Falls ich mich irre, bitte korrigieren.
Gruß aus Berlin
Michael
> Frage: wenn ich richtig gelesen habe, will er ca. 10kHz per> Periodendauer messen? Wären dann rund 100µs/Periode.> Jede erkannte Flanke will er in main() dann umgerechnet anzeigen?> Mit einer Displayroutine, die bei jedem Zugriff mindestens 1ms wartet?
Das ist nicht so schlimm. Er muss ja nicht den Zählerstand
für jede gemessene Periode ausgeben. Vermisst er halt
zwischen 2 Ausgaben 100 Perioden.
char Buffer[10]; // Ausgabepuffer für itoa, 10 Zeichen sollte
// für einen int locker reichen
Das hatten wir schon mal ;-)
http://www.mkssoftware.com/docs/man3/itoa.3.asp
"The resulting string may be as long as seventeen bytes."
Um ohne Gefahren einen kleineren Puffer als als 17 Bytes zu verwenden,
muss man die Implementierung für die verwendete Library im Sourcecode
checken.
Hallo Karl heinz
Danke erstmal für die konstruktive Kritik. Ich werd heut abend mal
versuchen deine Vorschläge umzusetzen und dann nochmal zu testen.
Wenn man das letztde mal vor 5 jahren programiert hat ist halt aller
anfang wieder schwer. Ich dachte wenn ich zu beginn überall double nehme
dann kann sicher nix falsch laufen.
Ich hab mir eigentlich schon vor dem programmierung mittels Flowchart
gedanken gemacht, nur wenn man keien erfahrung hat muss man es halt
irgendwie lösen und kann nicht abschätzen was gut/schlecht ist. Den Tip
mit abschalten der Innerrupts (cli, sei) hab ich von einem anderem
Teilnehmer bekommen.
Danke nochmals für die Mühe. Wenn man erst mal siet wie es besser geht
kann man auch draus lernen
mfg
Stefan wrote:
> char Buffer[10]; // Ausgabepuffer für itoa, 10 Zeichen sollte> // für einen int locker reichen>> Das hatten wir schon mal ;-)>> http://www.mkssoftware.com/docs/man3/itoa.3.asp> "The resulting string may be as long as seventeen bytes.">> Um ohne Gefahren einen kleineren Puffer als als 17 Bytes zu verwenden,> muss man die Implementierung für die verwendete Library im Sourcecode> checken.
Wir reden hier von einem AVR. Der hat 16 Bit int.
Wie du mit 16 Bit eine Zahl mit 16 Stellen hinkriegen willst,
musst du mir erst mal zeigen, bei einer Basis von 10. Bei
einer anderen Basis ist das allerdings klar. Wenn ich einen
16 Bit in zur Basis 2 ausgeben lasse, krieg ich klarerweise
16 Stellen + abschliessendes '\0' also 17 Stellen.
(Daher die 17, das hat nichts mit der Implementierung zu tun)
Aber du hast Recht. Ich verwende nämlich gar kein itoa, sondern
ltoa. Und ein long geht auch auf einem AVR höher.
Also nicht kleckern, klotzen: Rauf mit der Zahl!
Karl Heinz Buchegger, ich habe da was falsch im Kopf gehabt, sorry.
Das Stringdrehen (strreverse) in ltoa/itoa passiert auf der Stelle am
Anfang des Puffers und nicht wie ich fälschlich angenommen habe zwischen
Anfang und Ende des Puffers. Da müsste es auch mit deinem kurzen Puffer
gehen.
BTW vgl. die verschiedenen Implementationen insbesondere die
Kernighan/Ritchie Version bei
http://www.jb.man.ac.uk/~slowe/cpp/itoa.html
Und beim Nachschlagen, wie es in der avr-libc aussieht, bin ich auf die
interessanten speedoptimierten *toa Funktionen mit den Namen *toa_fast
gestossen. So hat denn alles sein gutes ;-)
Peter, ich, Stefan, hatte "Interrupts abzuschalten" als eine von zwei
Möglichkeiten genannt und extra mit dem Hinweis versehen "(weniger
sinnvoll bei einem Timer/Zähler)" sowie anschliessend ausführliche
Optimierungstipps zu der anderen Möglichkeit (schnellere Abarbeitung)
gegeben.
> Und beim Nachschlagen, wie es in der avr-libc aussieht, bin ich auf> die interessanten speedoptimierten *toa Funktionen mit den Namen> *toa_fast gestossen. So hat denn alles sein gutes ;-)
Ganz genau.
Die kenne ich nämlich auch nicht.
Werde ich mir aber gleich mal anschauen.
ICH BIN KEIN LEICHENSCHÄNDER!
(hallo alle..)
aber ich habe 2 tage damit zugebracht, um zu sehen dass der code von
Karl-Heinz mir falsche ergebnisse liefert. ich bin dumm. bei allem
respekt betreffend der formatierung - sollte man doch sein ziel nicht
aus den augen verlieren. ich bezweifel, dass unser lieber karl den von
ihm veröffentlichen code jemals an einen atmel geschickt hat. ein
einfacher taschenrechner hat mir bessere ergebnisse geliefert.
als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht
verstehe, was da passiert.
nichts für ungut,
schönes wochenende
bernd
kein_guter_nic_mehr_frei schrieb:
> ICH BIN KEIN LEICHENSCHÄNDER!>> (hallo alle..)>> aber ich habe 2 tage damit zugebracht, um zu sehen dass der code von> Karl-Heinz mir falsche ergebnisse liefert.
Dann hast du sicher auch den Abschnitt hier gelesen:
> Wahrscheinlich. Ohne die Hardware hier zu haben> und Messungen vornehmen zu können ist das aus der Entfernung> schwierig zu sagen.
Was schliesst du daraus: Ich konnte dieses Programm nie testen. Warum?
Weil ich seine Hardware nicht hier hatte.
> dass unser lieber karl den von> ihm veröffentlichen code jemals an einen atmel geschickt hat.
Da hast du recht. Ich habe den Thread noch einmal durchgelesen. Ich
denke ich habe genug Hinweise hinterlassen, wie man bei der Fehlersuche
vorgeht.
> ein> einfacher taschenrechner hat mir bessere ergebnisse geliefert.
Wow. Du hast einen Taschenrechner, der Frequenzen messen kann :-)
> als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht> verstehe, was da passiert.
Gratulation.
Du hast das Wichtigste bei der Übernahme von fremden Code erkannt. Code
zu übernehmen ist OK. Aber nur dann wenn man nicht einfach stumpfsinnig
abtippt. Man muss das Abgetippte auch verstehen! In den seltensten
Fällen kann man ein Programm einfach so übernehmen ohne es anzupassen.
Und die Gefahr von Fehlern besteht natürlich immer.
> nichts für ungut,
ALso ich habe kein Problem damit, wenn du sagst, dass die letzte Version
nicht funktioniert. Es gibt im Forum wahrscheinlich 1000-e Stellen an
denen ich mich geirrt habe. Es wäre allerdings schön gewesen, wenn du
auch noch gesagt hättest was das Problem war und wie du es behoben hast.
(Ich schätze mal, dass zb. der nicht atomare Zugriff auf diff bei
schnellen Frequenzen ein Problem darstellt)
>als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht>verstehe, was da passiert.
Das gilt auch für 'oldbies'. Eigenes Hirn zu haben, ist immer noch
aktuell.