Hi , ich habe mir eine Schaltung aufgebaut, in der ein channel eines Inkrementalgebers auf den Int0 Eingang eines Atmega32 geht. Beim Drehen soll der uC nun die Impulse zählen und ab einer gewissen Zahl (in diesem Fall 1350) ein Relais schalten. Bei 1500 Impulsen soll der counter auf 0 zurück gesetzt werden. So weit funktioniert das Programm auch, jedoch habe ich noch ein Problem. Wenn ich am Motor Drehe (Übersetzung 1:15) dreht sich die Scheibe des Inkrementalgebers also 15 mal schneller. Auf dieser Scheibe sind 500 Löcher. Bei einer Drehzahl von 200 u/min würde ich also 1.500.000 Impulse die Minute eingehen. Der uC läuft auf 16MHz. Wenn ich jetzt aber schon etwas schneller mit der hand diesen Motor drehe kommt der Punkt wo das Relais geschaltet wird sehr unregelmäßig. Ist es möglich das er uC das nicht verarbeiten kann ? Und wie könnte ich dieses Problem beheben? Gruß
:
Verschoben durch Moderator
Die Werte von der Variable Count im Programm sind noch auf Testwerte gesetzt
> Ist es möglich das er uC das nicht verarbeiten kann ? Ja. > Und wie könnte ich dieses Problem beheben? Counter nehmen statt Interrupt. Selbst dann wirds aber knapp bei einem Taktverhältnis von 16:1.5 (müsstest mal ins Datenplatt schauen ob das reicht). Wenn das noch nicht langt einen externen Counter davorhängen.
Ich kenne zwar den Atmeg32 nicht, aber bei Dir kommt alle 40µs ein Impuls an. Bei 16MHz beträgt die Befehlsabarbeitungszeit 62,5ns (?). Damit hast Du 640 Befehlszeiten, um einen Impuls zu verarbeiten. Wenn meine Einschätzung zum Atmeg32 richtig ist, dann sollte das locker reichen.
Ich würde mal mit der Optimierung (-o?) rumspielen. Das unnötige &= aus der Unterbrechungsroutine herausnehmen. Die ISR-Struktur in: if (<80) ... else { if(<=150) ... else ... } ändern. Eine weitere Möglichkeit ist: Auslagern und in assembler neu schreiben. Zählen kommt wohl nur infrage, wenn die Hardware noch nicht steht.
Amateur schrieb: > Eine weitere Möglichkeit ist: Auslagern und in assembler neu schreiben. das bringt hier wenig. lass doch mal das volatile von count weg, es ist hier nicht notwendig und kostet sehr viel rechenzeit weil jedesmal die Daten aus dem Ram neu gelesen und geschrieben werden. Das sollte die geschwindigkeit der ISR verdoppeln.
Du solltest dir das Eingangssignal mit einem Scope betrachen. Evtl. sind da Störungen drauf bzw. zu kurze Nadeln. Ggf. Schmitttrigger wie 74HC132 dazwischenschalten. Arbeitet der INT auf steigende, fallende oder beiden Flanken? In deinem Programm fehlen sämtliche Kommentare. Der Rat mit "Assembler neu schreiben" ist natürlich Quatsch. Optimierung kontrollieren; ggf. Listingfile ansehen. Gruss
>Der Rat mit "Assembler neu schreiben" ist natürlich Quatsch. >Optimierung kontrollieren; ggf. Listingfile ansehen. Ich habe mir das .lss angesehen. Da kann man schon was optimieren. Vor allem, wenn man, lt. s.g., nur 640 Taktzyklen, für den ganzen Rest, zur Verfügung hat.
Pro Puls hast der µC 1/1500000=666ns Zeit den interupt aufzurufen, Register zu sichern, zu Zählen, Register zurückzuschreiben. 666ns sind ca. 10 Taktzyklen. Sagen wir 2 um in den Interupt zu springen,2 zum Register sichern, 2 zum Zählen, 2 für Register zurückschreiben, 2 um zurückzuspringen. Dann sind wir bei 10 Taken und der µC wäre voll ausgelastet. Er verhaspelt sich. Du mußt in Harware Zählen oder nen schnellern µC nehmen.
Nimm doch nen STM32F4 der hat nen Inkrementalencoder in Hardware eingebaut. Das Discovery Board kostet 14€.
Uwe schrieb: > µC 1/1500000=666ns ??? 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen.
Benutze einen Hardware Counter und einen Counter Compare Interrupt.
> 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen
Ups naja ist halt Freitag ... Gehüähn schon offline ...
Uwe schrieb: > Nimm doch nen STM32F4 der hat nen Inkrementalencoder in Hardware > eingebaut. > Das Discovery Board kostet 14€. Für so einen Piffelkram den Timer zu nehmen, kostet, ausser ein bisschen Nachdenken, gar nichts. Aber schön, daß sich die STM32-Jünger auch mal wieder melden. > (in diesem Fall 1350) ein Relais schalten. Bei 1500 Impulsen soll der > counter auf 0 zurück gesetzt werden.
1 | //Atmega48
|
2 | TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC, ext. |
3 | OCR1A = 1500; |
4 | OCR1B = 1350; |
5 | TIMSK1 |= (1 << OCIE1B); |
6 | //...
|
7 | |
8 | ISR{TIMER1_COMPB_vect) |
9 | {
|
10 | //Schalte Relais
|
11 | }
|
mfg.
@Peter II und Amateur: Habe das Programm ein wenig optimiert, der Fehler tritt jedoch weiter auf @Uwe Der Interrupt arbeitet mit steigender Flanke, und ein Scope habe ich leider gerade nicht im equipment ;) @Mo Könntest du mir das ein wenig genauer beschreiben? Wäre dir sehr dankbar :) Danke für eure schnellen Antworten, ich bleib dran
@ Thomas Eckmann Ich denke das dies der Part des Timers ist?! Beszieht sich dieser auch auf die Ingterrupt Ports? Zudem OCR1A und OCR1B, sind dies die Grenzen für den Bereich wo geschaltet werden soll? Hatte mit Timern leider noch nichts wirklich am Hut. > //Atmega48 > TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC, > ext. > OCR1A = 1500; > OCR1B = 1350; > TIMSK1 |= (1 << OCIE1B); > //... > > ISR{TIMER1_COMPB_vect) > { > //Schalte Relais > } Gruß
Wenn es ein zeitliches Problem ist, so kannst Du dies "mechanisch" herausfinden. Einfach mal, einige Zeit, gemächlich drehen/pulsen und alternativ volle Pulle. Im letzten Falle müsste sich ein Absturz reproduzieren lassen. Im ersten, müsste sich dein Port wie gewünscht verhalten – auch über längere Zeit hinweg.
Johannes schrieb: > @Peter II und Amateur: > Habe das Programm ein wenig optimiert, der Fehler tritt jedoch weiter > auf zeigt es uns doch bitte mal.
mal eins schuss ins blaue: dein code stimmt und deine HW passt. schaltet dein relais bei 1350 ein und bei 1500 wieder aus? => kann es sein, dass dein armes relais bei 50-60U/min einfach nicht mehr nach kommt? das muss dann immerhin in 50-60ms ein und aus schalten. da ist es gut möglich dass schaltspiele nicht ausgeführt werden. sg
Johannes schrieb: > @ Thomas Eckmann > > Ich denke das dies der Part des Timers ist?! > Beszieht sich dieser auch auf die Ingterrupt Ports? > Zudem OCR1A und OCR1B, sind dies die Grenzen für den Bereich wo > geschaltet werden soll? > Hatte mit Timern leider noch nichts wirklich am Hut. > > >> //Atmega48 >> TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC, >> ext. >> OCR1A = 1500; >> OCR1B = 1350; >> TIMSK1 |= (1 << OCIE1B); >> //... >> >> ISR{TIMER1_COMPB_vect) >> { >> //Schalte Relais >> } > > > Gruß Im CTC-Mode zählt der Timer bis zum in OCR1A eingestellten Wert, geht auf 0 und fängt wieder von vorne an. > Bei 1500 Impulsen soll der counter auf 0 zurück gesetzt werden. Vorher löst er bei Erreichen von OCR1B einen Interrupt aus, in dem man das Relais schalten kann. > (in diesem Fall 1350) ein Relais schalten. Das einzige, was der Controller in Software zu erledigen hat, ist in der ISR einen Port zu schalten. Den Rest erledigt der Timer in Hardware. Währenddessen dreht die CPU Däumchen. Der Zähleingang ist natürlich nicht mehr INT0 sondern T1. Falls beim Erreichen von 1500 noch etwas gemacht werden soll, kann man hierzu den COMPA-Interrupt zusätzlich einschalten. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Z%C3%A4hler_des_AVR mfg.
:
Bearbeitet durch User
So ich habe mein Prgramm nun etwas umgeschrieben. Eine Funktion ist leider noch nicht gegeben. Zum testen drehe ich den motor am Getriebe mit der Hand, deswegen sollte dieser Bereich eigentlich passen. Habe ihn jetzt aber ein wenig angepasst bzw ein wenig vergößert.
count muss volatile sein. Da nur bis 151 gezählt wird, reicht auch uint8_t. mfg.
:
Bearbeitet durch User
Also erst mal nochmals vielen Dank für eure Hilfe.
@Thomas
Habe das Programm nun noch mal verändert. Den Bereich erweitert und
trotzdem passiert nichts.
In einer Zeile schriebst du
>> TIMSK1 |= (1 << OCIE1B);
jedoch sagt mir mein AtmelStudio das es nur TIMSK kennt, war dies
unbeabsichtigt?
Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)
Aber warum zählt der Timer nur bis 151?
Gruß
Thomas Eckmann schrieb: > count muss volatile sein. und warum? Es gibt keine! Sie wird nur ein der ISR verwendet.
Johannes schrieb: > Also erst mal nochmals vielen Dank für eure Hilfe. > > @Thomas > > Habe das Programm nun noch mal verändert. Den Bereich erweitert und > trotzdem passiert nichts. > > In einer Zeile schriebst du >>> TIMSK1 |= (1 << OCIE1B); > > jedoch sagt mir mein AtmelStudio das es nur TIMSK kennt, war dies > unbeabsichtigt? Das ist das Register vom Atmega48. Ich hatte keine Lust ins Datenblatt vom 32er zu gucken. Die 16-Bit-Timer haben aber die gleichen Funktionen. > Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1) Muss da noch ein Pullup gesetzt werden? > Aber warum zählt der Timer nur bis 151? Nicht der Timer. Deine Variable count. Die setzt du selbst bei >150, also 151, zurück. Dafür reicht uint8_t. mfg.
Ein großes großes Ups. Die Variable Counter wird jetzt langsam überflüssig, weil durch den Timer ja schon der Bereich gegeben ist. Das heißt im ISR muss ich nur einen Ausgang schalten.....Ich seh den Wald vor lauter Bäumen nicht mehr. Also müsste mein Prgramm in so weit nun besser sein. Noch kurz herausfinden wie man noch mal Pullups setzt und dann folgt ein weiterer Statusbericht
Thomas Eckmann schrieb: >> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1) > Muss da noch ein Pullup gesetzt werden? Wie erkenne ich dies denn und wie schalte ich die Pullups ein ? Grüße
Wenn der Pin als Eingang konfiguriert ist normaler weiße einfach auf das Ausgang eine 1 Schreiben, aber habe grad nicht geprüft wenn man den Pin als Sonderfunktion benutzt. Also am beispiel Port B 0. Beinchen:
1 | PortB|=0x01; |
MfG Shee2e
Johannes schrieb: > Thomas Eckmann schrieb: >>> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1) >> Muss da noch ein Pullup gesetzt werden? > > Wie erkenne ich dies denn und wie schalte ich die Pullups ein ? ? Das solltest du aber schon wissen. Brauchst du einen Pullup?` Das hängt davon ab, wie dein Encoder verschaltet ist. Wie schaltet man ihn ein: Wenn der Pin auf Eingang konfiguriert ist, dann wird er durch Setzen einer 1 für diesen Pin im PORTx Register eingeschaltet. AVR-Tutorial: IO-Grundlagen AVR-GCC-Tutorial
:
Bearbeitet durch User
Aus deinem Code: > DDRB = 0xe0; Gehört irgendwie gar nicht dahin. Gewöhn dir auch die hexadezimale Schreibweise schnell wieder ab. Und schreib das so: DDRB = (1 << DDB7) | (1 << DDB6) | (1 << DDB5); Ist zwar mehr Schreibarbeit, aber man sieht auf einen Blick welche Bits gesetzt werden. > PORTD |= (1<<PD5); Der Port muss auf Ausgang geschaltet werden. mfg.
Karl Heinz Buchegger schrieb: > Johannes schrieb: >> Thomas Eckmann schrieb: >>>> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1) >>> Muss da noch ein Pullup gesetzt werden? >> >> Wie erkenne ich dies denn und wie schalte ich die Pullups ein ? > > ? > Das solltest du aber schon wissen. > > Brauchst du einen Pullup?` > Das hängt davon ab, wie dein Encoder verschaltet ist. > > Wie schaltet man ihn ein: > Wenn der Pin auf Eingang konfiguriert ist, dann wird er durch Setzen > einer 1 für diesen Pin im PORTx Register eingeschaltet. > > AVR-Tutorial: IO-Grundlagen > AVR-GCC-Tutorial Ja das mit den Pullups ist noch Neuland für mich, aber danke für die Hilfe. Thomas Eckmann schrieb: > Aus deinem Code: >> DDRB = 0xe0; > Gehört irgendwie gar nicht dahin. > Gewöhn dir auch die hexadezimale Schreibweise schnell wieder ab. Und > schreib das so: DDRB = (1 << DDB7) | (1 << DDB6) | (1 << DDB5); > Ist zwar mehr Schreibarbeit, aber man sieht auf einen Blick welche Bits > gesetzt werden. > >> PORTD |= (1<<PD5); > Der Port muss auf Ausgang geschaltet werden. Ja da hatte ich noch einen Fehler. Habe es korrigiert und Tatsache: Bei 350 Impulsen schaltet das Relais. Jedoch schaltet es sich nicht mehr aus. Ich dachte beim erreichen von OCR1B wird der ISR quasi wieder deaktiviert?! Oder muss ich da noch etwas hinzufügen? gruß
Johannes schrieb: > Bei einer Drehzahl von 200 u/min würde ich also 1.500.000 Impulse die > Minute eingehen. Der uC läuft auf 16MHz. > > Wenn ich jetzt aber schon etwas schneller mit der hand diesen Motor > drehe kommt der Punkt wo das Relais geschaltet wird sehr unregelmäßig. > Ist es möglich das er uC das nicht verarbeiten kann ? Natürlich. 1.5E6*11T=16.5 D.h.: selbst bei optimaler Umsetzung in Assembler (ISR direkt in der Vektortabelle, Zahlregister und Backup-Register für Flags reserviert, keine konkurrierenden Interrupts, kein cli()..sei()-Orgien) reicht es nichtmal für die 1,5MHz Interruptrate, geschweige denn für noch mehr. Ein Interupt-Frame dauert immer mindestens acht Takte. Vier Takte, um die Rücksprungadresse auf den Stack zu sichern und weitere vier Takte, um den Rücksprung am Ende der ISR auszuführen. Die optimale ISR müßte direkt in der Vektortabelle liegen und sähe so aus: vektor: ;[4] in BACKUP,SREG ; 1 inc COUNT ; 1 out SREG,BACKUP ; 1 reti ; 4 ;-- ;11 Daher die "11T" aus der Rechnung von oben. Die Gleichung geht also frühestens bei 16.5MHz Takt auf. Dann ist die MCU aber immer noch vollständig durch die ISR ausgelastet, kommt also überhaupt nicht dazu, auch mal was in main() zu machen. Im Übrigen ist es sowieso völlig bescheuert, so hohe Frequenzen mit einer ISR zählen zu wollen. Dafür gibt es Timer (die eigentlich ZÄHLER sind).
c-hater schrieb: > Natürlich. 1.5E6*11T=16.5 ??? 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen.
c-hater schrieb: > Im Übrigen ist es sowieso völlig bescheuert, so hohe Frequenzen mit > einer ISR zählen zu wollen. Dafür gibt es Timer (die eigentlich ZÄHLER > sind). Danke für die Rechnung erstmal. Aber ich glaube das ich nicht mehr über den ISR zähle, sondern bereits über einen Timer, wie man in meinem letzten Programm sehen kann.
Johannes schrieb: > Ja da hatte ich noch einen Fehler. Habe es korrigiert und Tatsache: > Bei 350 Impulsen schaltet das Relais. Jedoch schaltet es sich nicht mehr > aus. Ich dachte beim erreichen von OCR1B wird der ISR quasi wieder > deaktiviert?! Wieso soll der deaktiviert werden? Der Portpin schaltet auf 1, sobald du
1 | PORTD |= (1<<PD5); |
ausführen lässt. Wenn der schon auf 1 ist, dann bewirkt das nicht. Denn wenn du am Lichtschalter auf "ein" drückst und das Licht schon eingeschaltet ist, passiert nichts weiter. Wenn das Licht wieder aus gehen soll, dann musst du auf 'Aus' drücken.
1 | PORTD &= ~( 1 << PD5 ); |
Oder aber, wenn du ausdrücken willst "umschalten", dann eben
1 | PORTD ^= (1<<PD5); |
eine XOR-Operation dafür einsetzen. Dann wechselt der Pin von 1 auf 0 bzw. von 0 auf 1, jedesmal wenn diese Operation durchgeführt wird.
:
Bearbeitet durch User
>> Der Port muss auf Ausgang geschaltet werden. > Ja da hatte ich noch einen Fehler. Habe es korrigiert Ja. An der blödest möglichen Stelle. Es reicht völlig aus, wenn du derartige Konfigurationen EINMAL, am Programmanfang machst. Es hat schon seinen Grund, warum wir Anfänger erst mal durch die Mühle von 'Led gezielt einschalten', 'Led gezielt ausschalten', 'Led blinken lassen', 'Lauflichter' usw. durchschicken. Denn der ganz banale sichere Umgang mit Portpins ist Grundvoraussetzung für alles weitere.
:
Bearbeitet durch User
Karl Heinz Buchegger schrieb: >>> Der Port muss auf Ausgang geschaltet werden. >> Ja da hatte ich noch einen Fehler. Habe es korrigiert > > Ja. An der blödest möglichen Stelle. > > Es reicht völlig aus, wenn du derartige Konfigurationen EINMAL, am > Programmanfang machst. > > Es hat schon seinen Grund, warum wir Anfänger erst mal durch die Mühle > von 'Led gezielt einschalten', 'Led gezielt ausschalten', 'Led blinken > lassen', 'Lauflichter' usw. durchschicken. Denn der ganz banale sichere > Umgang mit Portpins ist Grundvoraussetzung für alles weitere. Ja ich weiß, doch man lernt ja aus seinen Fehlern ;) Die Grundschaltungen bin ich auch schon durchgegangen, da ich aber alleine arbeite und noch ein paar höher gesteckte Ziele verfolge kommt da wohl manches zu kurz. Werde mich aber noch mal mit dem Umgang der Portpins beschäftigen, die Fehler geben einem ja doch zu denken. Vielen Dank euch allen für die Hilfe Gruß
@Johannes Zum Abschalten wirst Du wohl einen zweiten Interrupt auf TIMER1_COMPA_vect legen müssen. Aktivierung nicht vergessen. "DDRD = (1<<PD5);" In TIMER1_COMPB_vect bewirkt, dass PD5 zum Ausgang wird. Alle anderen Anschlüsse werden zu Eingängen - egal was eventuell an anderer Stelle "gesagt" wurde. Vorsicht also mit dem Gleichheitszeichen. Warum machst Du das nicht einmalig während der Initialisierung, sondern ständig?
s.g. schrieb: > 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen. Oops. Das habe ich tatsächlich nicht korrekt gelesen. Wer zum Teufel gibt bei bei Problemen rund um die Rechenleistung auch irgendwas in min-1 an, damit kann man doch nicht ernsthaft rechnen? Das ist, als wenn ein handwerklicher Goldschmied in Tonnen rechnen würde, einfach nur idiotisch. Aber OK, mit 60 mal mehr Zeit entspannt sich die Lage natürlich ganz erheblich. 25kHz Interruptrate sind zwar auch nicht ganz ohne, aber sogar in C gut beherrschbar.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.