Hallo, nachdem ich jetzt meine Hardware erfolgreich getestet, und mit einigen Aspekten meines tiny2313 bekannter (gemacht worden) bin, stellt sich jetzt die Frage nach der Struktur des Ganzen. Im AVR-GCC-Tutorial habe ich die Bundestagswahl zwischen seriellem und interruptgesteuertem Programmablauf. Neulich habe ich zufällig einen vermutlich hilfreichen Thread zum Thema (Interruptgesteuert, mit Timer) entdeckt, den ich jetzt aber nicht mehr finde. Wie ermittelt also ein Anfänger das für seine Anwendung günstigste Programmodell? Meine Anwendung: Vermutlich als Schrittmotorsteuerung einzuschätzen. (Allerdings kann ich noch nicht absehen, wie groß die Geschwindigkeit sein soll, da es vor allem auf's Drehmoment ankommt, und ich deswegen noch ein Getriebe bestimmen muß.) Hauptaufgabe und Vermutungen: Der Motor soll einige hundert Schritte laufen (beschleunigt/verzögert), und die Position von zwei Schaltern messen. Die Positionen werden geprüft und statistisch erfasst. Evtl. Ausgabe über RS232. Anschließend das Ganze andersherum. 'Gleichzeitig' sollen zwei Taster und RS232 überwacht werden, und der Status an zwei LEDs (falls möglich PWM mit Timer 1) und einem Piezo Buzzer (Timer 0) ausgegeben. (Wobei sicherlich Timer 1 für den Programmablauf verwendet werden sollte?) Falls die Stromversorung unterbrochen wird, könnte ein Interrupt veranlassen, daß alles ausgeschaltet wird, und meine Daten im EEPROM gespeichert werden. Der tiny2313 ist übrigens mit 4MHz getaktet, ich nehme an, daß das ausreicht. Für Tipps und Quellen dankbar, Leif PS: Ich habe angefangen (als Ergänzung zum GCC-Tutorial) eine kleine Anleitung zum Verwenden der PWM für Anfänger wie mich zusammenzustellen. Ist noch lange nicht fertig, aber falls das mit gleichzeitigem Editieren kein Problem ist (ich mache jetzt weiter, während ich auf Hilfe hoffe), seid ihr herzlich eingeladen, meine Fehler rot anzustreichen!
Immer dann, wenn du mehrere Dinge parallel machen musst, also z.B. Motor bewegen, Tasten abfragen, ist es sinnvoll, wenn du auf niedriger Ebene ein Betriebssystem hast, was sowas unterstützt. Betriebssystem kann auch was ganz einfaches meinen, was koordiniert, dass mehrere Dinge quasi-parallel verarbeitet werden können. Es ist der totale Krampf und führt zu schlechtem Code, wenn mehrere eigentlich getrennte Dinge du in einem Code vermischst, also in der Art: - Bewege Motor ein Schritt -> Taste gedrückt? -> Bewege Motor ein Schritt -> Taste gedrückt?...
Hi Winfried, danke. Genau das möchte ich herausfinden: Wie koordiniere ich den Ablauf? Was ist dabei zu berücksichtigen? Wonach soll ich suchen? Das müssten hier doch eigentlich die meisten wissen :-)
Na sowas. Da steht es doch glatt im Tutorial! Wer hätte das gedacht!? http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmieren_mit_Interrupts und angeblich ein gut dokumentiertes Programm http://www.mikrocontroller.net/forum/read-4-23408.html
Das Geheimnis ist eigentlich nur, die Programmteile in kleine Aufgaben zu unterteilen, die dann so schnell sind, daß sie quasi gleichzeitig ablaufen, obwohl sie in der Mainloop hintereinander ausgeführt werden. Wesentlich dabei ist, daß man, sobald auf irgendwas gewartet werden muß, sofort zur Mainloop zurückkehrt. Und ist dann bei einem der nächsten Durchläufe das Ereignis eingetroffen, gehts weiter. Viele Prozesse sind zeitabhängig. Deshalb ist es sinnvoll, einen zentralen Timerinterrupt zu haben, der dann solche Zeitabläufe steuert. Es wird z.B. ein Register auf eine Zeitdauer gesetzt und der Timerinterrupt zählt z.B. alle 10ms diesen Wert runter. Erreicht der Wert 0, wird ein Bit gesetzt und der entsprechende Prozeß in der Mainloop testet das Bit und weiß nun, daß er weiter machen kann. Nebenbei kann der Timerinterrupt auch noch solche zentralen Aufgaben übernehmen, wie Tasten entprellen, LED-Anzeigen multiplexen, Uhrzeit und Datum zählen (RTC). Man sollte also keine Angst vor Interrupts haben, braucht man auch gar nicht, solange man keine Warteschleifen einfügt (in Interrupts strengstens verboten). Du hast also gar nicht die Wahl zwischen Interrupt oder Mainloop, sondern beide ergänzen sich hervorragend. Für die Taktung des Schrittmotors ist ein eigener Timerinterrupt im CTC-Mode sinnvoll. Die Anfahr-/Bremskurve legt man in einer Tabelle fest. Peter
N'abend Peter, vielen Dank für die Tipps! Ich sollte mir erst mal genau überlegen, was das Programm alles machen soll :-) Ein Plan hilft meistens. > Für die Taktung des Schrittmotors ist ein eigener Timerinterrupt im > CTC-Mode sinnvoll. Die Anfahr-/Bremskurve legt man in einer Tabelle > fest. Ich hatte auch schon angedacht, wie ich trotz der noch nicht genau bekannten Motor-Anforderungen die größte Flexibilität erreichen kann. Aber an sich gibt es bei mir nichts anderes zeitabhängiges, deswegen dachte ich, daß ich eigentlich genug Zeit in und zwischen den Motorschritten habe, um den Rest abzuarbeiten. Verstehe ich den CTC Mode richtig: OCRnx teilt die vom Prescaler bestimmte Frequenz, und für alle Frequenzen, die ich damit nicht Abdecke, kann ich den Prescaler ändern? Und unabhängig vom Schrittmotor: Sollte ich den Schwerpunkt dann eher auf Timerinterrupt setzen, also meine Taster pollen, oder deren Interrupts direkt nutzen? (stellt sich die Frage überhaupt?) Danke.
CTC = Clear Timer on Compare D.h. nach dem Compare fängt der Timer wieder von 0 an, mehr heißt das nicht. Und im Compareinterupt machst Du dann die 4 Phasen für den Steppermotor. In der Mainloop würde ich das nicht machen, das dürfte ordentlich Jitter geben. Auch sollen ja keine Schritte verloren gehen, wenn andere Sachen mal doch mehr Zeit brauchen. "Ich sollte mir erst mal genau überlegen, was das Programm alles machen soll :-) Ein Plan hilft meistens." Das ist ein sehr guter Ansatz, leider beherzigen das die wenigsten. Dabei die Funktionen so weit unterteilen, wie es nur irgend geht. Zusammenfassen kann man später immer noch. Peter
Tasten werden normal immer gepollt und nicht per Interrupt bedient. Einfach typisch alle 2-10ms mal nachschauen. Entprellung z.B. so, dass eine Taste erst als losgelassen gilt, wenn sie über 2-4 Poll-Zyklen nicht mehr als gedrückt erkannt wird. Schrittmotoransteuerung kann wirklich zeitkritisch werden, dass sollte besser über einen Timerinterrupt laufen. Es ist nämlich unschön, wenn der nächste Schritt nicht genau zum rechten Zeitpunkt gemacht wird. Das gibt dann einen unruhigen Lauf. Bei 20KHz Schrittfrequenz sind es ja 50us von Schritt zu Schritt! Wenn du da schon +-5us Abweichung hast, ist das klar zu viel. Das wirst du also nur timerinterruptgesteuert ordentlich hinbekommen. Es sei denn, du hast keine hohen Anforderungen, keine hohe Schrittfrequenz und alle anderen Programmteile des Mainloops sind recht kurz.
Hallo leif, fühl Dich eingeladen, mal diesen Pseudo-Code eines vollständigen timergesteuerten und damit multitasking-fähigen Programms zu studieren. Vielleicht druckst Du es aus und brütest mal eine Viertelstunde darüber. Nur soviel: Es lohnt sich bestimmt! Ich enthalte mich bewußt weiterer Kommentare dazu und wünsch Dir nur viel Spaß beim Gewinnen aller für Dich neuen Erkenntnisse. Falls Fragen auftauchen, weißt Du ja, in welchem Forum Dir gerne bei der Beseitigung aller Klarheiten geholfen wird. // Hinweis: Alles in [eckigen Klammern] Gesetzte steht // für den Code, der die beschriebene Funktionalität im // "richtigen" Programm an dieser Stelle implementieren soll. // Interrupt-Vektor-Tabelle rjmp Reset rjmp [entsprechender Interrupthandler] rjmp [entsprechender Interrupthandler] rjmp [entsprechender Interrupthandler] rjmp [entsprechender Interrupthandler] ... ... //=========================================================== //=========================================================== Timer-Output-Compare-Match-A-Interrupt: // Diese Zeile wird alle 0.5 ms durchlaufen // (0.5 ms mit Quarzgenauigkeit!) [Output-Compare-Wert A erhöhen] // Lautsprecher bedienen IF SpeakerOn // die "SpeakerOn"-Variable gehört zum "Speaker"-Task { [Speaker-Pin toggeln] // Ergebnis: Ein schöner 1 kHz-Piepston } ELSE { [Speaker-Pin = 0] } reti //=========================================================== Timer-Output-Compare-Match-B-Interrupt: // Diese Zeile wird alle 20 ms durchlaufen // (20 ms mit Quarzgenauigkeit!) [Output-Compare-Wert B erhöhen] [Zustände aller Eingänge in Variablen einlesen] // Nur einlesen, nichts rechnen hier! [Alle Ausgabepins/-ports setzen] // Und ja nichts rechen! Alle Ausgabevariablen müssen // "vorberechnet" vorliegen; die zeitintensive Berechnung // erfolgt in den Tasks, die in "Main" abgearbeitet werden // Flag für die "Main" setzen RUNTASKSFLAG = 1 reti //=========================================================== USART-Receive-Byte-Interrupt: [Empfangenes Byte in USART-Ringpuffer schreiben] // Hier nichts rechnen! Das empfangene Byte wird an der // richtigen Stelle im Empfangspuffer abgespeichert (das ist // die minimal mögliche Aktion), und dann sofort wieder // raus hier! Prüfung, ob Puffer voll, und Auswertung der // Daten erfolgt im "UART-Task". reti //=========================================================== Task_XXX_Init: [Die zum Task XXX gehörenden RAM-Variablen auf ihre Initialwerte setzen] ret Task_XXX_Run: IF TaskAktiv { [die zum Task XXX gehörenden Software-Timer/-Prescaler bedienen] [Abhängig von den Zählerständen des/der Software-Timer/-Prescaler die gewünschten Aktionen durchführen (jeder Task arbeitet als sog. "state machine"). Dazu Variablen aus RAM in Register laden, Rechnungen durchführen, Ergebnisse ins RAM zurückschreiben.] // Aber hier nichts auf die Ports schreiben! Das wird erst im // Timerinterrupt erledigt. Desgleichen nichts von Ports lesen. // Alle Eingabeports wurden bereits im Timerinterrupt eingelesen. // Grund: Wann dieser Task genau ausgeführt wird, hängt von den // Rechenzeiten der Vorgängertasks ab, ist also i. a. nicht // vorhersehbar und/oder variabel. Lesen von und Schreiben auf // Ports an dieser Stelle hätte nach außen ein unpräzises Timing- // verhalten zur Folge. // Wichtig: NIEMALS irgendwelche "Delayschleifen" hier! // Für Delayschleifen besteht keine Notwendigkeit! // Alle Wartezeiten sind ausschließlich über die Software-Timer // zu bewerkstelligen! // AUSNAHME von dieser Regel: SEHR KURZE Wartezeiten, die NUR // über Delayschleifen realisiert werden können. // Beispiel: One-Wire-Bus. Ein 1-Bit-Schreib-Lese-Vorgang dauert // ca. 600 µs und erfordert µs-genaues Timing. Dazu MÜSSEN // Delayschleifen eingesetzt werden. Das stellt aber kein Problem // dar, weil der Task damit immer noch schnell genug abgearbeitet // wird. // "Und was mach ich, wenn ich z. B. im Schema '(5 + 2 + 10) ms' // einen Bus bedienen muß?" Antwort: Für Anforderungen, die mit // dem 20 ms-Zeitraster echt "inkompatibel" sind, bleibt nur als // Ausweg, sie über einen anderen, schnelleren Hardware-Timer // (siehe "Speaker"-Bedienung!) abzuwickeln, falls vorhanden. // Ansonsten muß ein ebensolcher Timer zur Verfügung gestellt // werden. } // Wenn der Task gerade nicht aktiv ist, wird die "Run"-Routine // einfach sofort wieder verlassen. // (Beispiel für einen Task, der fast nur inaktiv ist: Der Task, // dessen Aufgabe es ist, jede volle Stunde die Temperatur zu messen, // und den Wert im EEPROM abzulegen. Er wird nur zu jeder vollen // Stunde vom RealTimeClock-Task aktiviert, erledigt seine Sache und // geht danach selbsttätig wieder in den "Inaktiv"-Zustand.) ret //=========================================================== //=========================================================== Reset: [Initialisierungskram erledigen (Konfiguration der I/O-Teile)] // Alle Tasks der Reihe nach initialisieren call Task_RealTimeClock_Init call Task_UserKeys_Init call Task_Motor_Init call Task_UART_Init call Task_LCDisplay_Init call Task_WarnLEDs_Init call Task_Speaker_Init RUNTASKSFLAG = 0 sei // Interrupts global enablen Main: sleep // Wenn das Progamm diese Zeile abarbeitet, bedeutet das, daß // der µC aufgewacht ist. Eventuell gibt es mächtig Arbeit // für die Pille, nämlich dann, wenn das "Run Tasks"-Flag // gleich 1 ist. Dann müssen die Tasks abgeackert werden! IF RUNTASKSFLAG==1 { // Alle Tasks der Reihe nach abarbeiten call Task_RealTimeClock_Run call Task_UserKeys_Run call Task_Motor_Run call Task_UART_Run call Task_LCDisplay_Run call Task_WarnLEDs_Run call Task_Speaker_Run // Beachten: das Abarbeiten aller Tasks darf nie länger // als 20 ms Sekunden in Anspruch nehmen (entsprechend // ca. 40000 Instruktionen @4MHz) RUNTASKSFLAG = 0 // Flag löschen nicht vergessen } rjmp Main
Guten Abend Peter, Winfried, und Bolle, danke für Eure Hilfe! Mit den Timerinterrupts klappt's jetzt auch bei mir: es blinkt (nachdem ich eben festgestellt habe, daß man die auch einschalten muß) mit der für den 8 Bit Timer niedrigstmöglichen Frequenz von 7.69 Hz, obwohl ich rechnerisch auf 7.62 Hz komme - das wird wohl die Toleranz des 4MHz Keramikresonators sein (0.9% kommt sogar hin). @ Winfried Also mechanische Schalter grundsätzlich nicht per Interrupt, weil das wegen des Prellens nicht sehr sinnvoll ist!? Leuchtet eigentlich ein. Mein Schrittmotor wird nicht so schnell sein müssen, auch wenn das vom noch nicht vorhandenen Getriebe abhängig sein wird. Aber dafür einen Timer zu benutzen, ist sicherlich sinnvoll. Zu den beiden Themen eine Überlegung: Wenn ich die genaue Schrittposition eines Schaltvorganges feststellen möchte - muß ich doch bei/nach jedem Schritt die Schalter pollen? Dann lege ich ggf. die Schrittzahl in einer Variablen ab, und ignoriere die nächsten x Messungen. Wenn das so stimmt, muß ich mir allerdings etwas einfallen lassen, um die variable Schrittfrequenz zu berücksichtigen.. @ Bolle Danke, ich werde Deinen Rat gleich mal befolgen. Es geht doch nichts über Papier (obwohl Sony 'mittlerweile' schon so Teile mit e-Ink verkauft).. ich habe mir auch der PWM wegen schon das Datenbuch ausgedruckt, durchgesägt, und anschließend im handlichen A5 Format mehr schlecht als recht mit Heißkleber gebunden (das geht zwar schnell, aber Ponal ist besser weil weicher) :-) Weitere bahnbrechende Sensationen folgen!
Bolle, die Lektion kommt genau zur richtigen Zeit! Was mir bewußt(er) wird: Es gibt zwei OCs pro Timer. Die Periode läßt sich auch durch Schreiben des OCR Registers einstellen. Hier kommen auch schon die Fragen: Teilen 'echte' Multitaskingsysteme nicht Einzelteilen der Tasks rechenzeit zu? Also zerschnippeln die Tasks selbst, und 'mischen' deren Abarbeitung? Denn im Prinzip wird hier 'nur' das Programm durch den Timerinterrupt auf 20ms Länge beschränkt, wobei dieser doch eigentlich 'nur' zum Aufwecken des µC dient. (?) Aha, RUNTASKFLAG kann man aber nicht einsparen, weil natürlich auch die anderen Interrupts den Schlaf stören. Was passiert, wenn die Tasks länger als 20ms benötigen? Greift dann der 20ms-Interrupt erst wieder beim nächsten Match? Die Ports werden auch 'nur' alle 20ms aktualisiert!? Ist das für z.B. LCD nicht recht langsam? Das "IF TaskAktiv" wird nicht in jedem Task geprüft, richtig? (ist nicht auf den ersten Blick ersichtlich) Wie die einzelnen Tasks gesteuert werden, verstehe ich aber definitiv nicht: > [die zum Task XXX gehörenden Software-Timer/-Prescaler bedienen] > > [Abhängig von den Zählerständen des/der Software-Timer/-Prescaler > die gewünschten Aktionen durchführen (jeder Task arbeitet als sog. > "state machine"). Dazu Variablen aus RAM in Register laden, > Rechnungen durchführen, Ergebnisse ins RAM zurückschreiben.] Also werde ich demnächst endlich mal rausfinden müssen, was eigentlich eine state-machine ist (Damit fange ich dann dort an: http://de.wikipedia.org/wiki/Finite_State_Machine) Aber jetzt erstmal sleep_mode();
So, weiter geht's > Dazu Variablen aus RAM in Register laden, Rechnungen durchführen, > Ergebnisse ins RAM zurückschreiben.] Ich habe kurz aus den Augen verloren, daß Dein Beispiel asm berücksichtigt.. Mit C kann man die Sätze mit RAM ignorieren. Vermutung: Eine state-machine (http://de.wikipedia.org/wiki/Virtueller_endlicher_Automat) ist also quasi ein Ding, Objekt mit Zuständen und Aktionen. Das ist ganz praktisch, weil die Zustände und Aktionen dann auch den anderen state-machines bekannt sein und 'beantragt' werden können (z.B. "TaskAktiv"), und dadurch alles schön ordentlich wird. Dafür braucht man für die Zustände und Aktionen natürlich entsprechend viel Platz im RAM. Apropos RAM: Wie kann ich in C eigentlich Flags (oder eine 2-Bit Variable) realisieren? Gibt es dafür C-Typen, oder benutze ich ein Byte und operiere bitweise? Aha, und das hier: > [Abhängig von den Zählerständen des/der Software-Timer/-Prescaler > die gewünschten Aktionen durchführen wird klar, wenn man sich vor Augen hält, daß die Tasks immerhin alle 20ms (50Hz) ausgeführt werden [In Mensch- und Maschinenzeit denken!]. Für zeitabhängige Aufgaben, also z.B. den Fall, daß die WarnLEDs (sichtbar) blinken sollen, vergleiche ich den aktuellen und alten Zählerstand, und gebe die LED ggf. zum Umschalten frei. Auch hier ist man vom 20ms Raster abhängig (Reicht aus, um die LED auf 50% zu dimmen). Wenn's schneller gehen soll, muß entweder das Timerintervall heruntergesetzt werden, oder aber es müsse andere Interrupts verwendet werden - wie beim Pieper, der sich alle 0.5ms in der Main dazwischendrängelt. So, jetzt bin ich Profi. Gab's nochwas ? ;-) Jetzt muß ich also zusehen, wie ich meine beiden 'zeitkritischen' Aufgaben, Schrittmotor und Tastenabfrage/Entprellung, unterkriege. Der Rest bleibt dann ja quasi der Phantasie überlassen.. Die Leute, die für die Software der Fahrkartenautomaten der Bahn zuständig sind, sollten hier mal reinschauen..Es dauert (e mal) furchtbar lange, bis ein Tastendruck erkannt wird. Wenn man schneller ist, als der Automat, wird die ganze Eingabe versaut, und der Zug ist ggf. weg! Also bis demnächst!
Hallo leif. >Also mechanische Schalter grundsätzlich nicht per Interrupt, weil das >wegen des Prellens nicht sehr sinnvoll ist!? Leuchtet eigentlich ein. Es ist auch deshalb nicht sinnvoll, weil ein externer Interrupt eine sehr wertvolle "Ressource" darstellt. Er ist einfach für spezielle Anforderungen gedacht, bei denen es wirklich auf Speed ankommt. Externer Interrupt bedeutet ja: Es gibt eine Stelle im Programm (der zugehörige Interrupthandler), mit deren Ausführung nur vier Maschinenzyklen nach Aufreten des "Auslöseereignisses" am entsprechenden Pin begonnen wird. Manchmal braucht man sowas, aber nicht für einen ollen Taster. Der ist nämlich ein sehr "langsames" Eingabegerät (genauer: sein Bediener ist langsam). Kein Mensch merkt es, wenn ein Taster nur alle 30 ms abgefragt wird (d. h. wenn die Reaktion auf einen Tastendruck erst 30 ms später erfolgt), und kein Mensch kann eine Taste kürzer als 30 ms niederdrücken (d. h. der µC bekommt bei 30 ms Abtastintervall immer noch jeden Tastendruck mit - wenigstens mit einem Sample). Fazit: Es gibt einfach keinen Grund, einen Taster über einen externen Interrupt zu betreiben. Mit folgender Ausnahme: So ein µC hat diverse Sleepmodes im Angebot. Damit kann man u. a. bei batteriebetriebenen Geräten eine Menge Strom sparen, indem die Teile des µCs, die gerade nicht benötigt werden, einfach abgeschaltet werden. Im "tiefsten" Sleepmode "power down" ist davon sogar (fast) der gesamte Controller betroffen; der Strombedarf der Pille ist dann fantastisch niedrig (nur ca. 1 µA!). Nun soll aber ihr friedlicher Schlummer durch bestimmte äußere Ereignisse (Interrupts) auch wieder beendet werden können ("He Alter, aufwachen, es gibt was zu tun!"), und das geht bei power down nur durch ein einziges: einen externen pegelgetriggerten Interrupt. Möchte man also z. B. eine Schaltung wie bei den Handys mit einem Taster statt eines Schiebeschalters ein- und ausschalten können, dann könnte man dies mit dem Power-Down-Modus realisieren, und in diesem Fall müßte man tatsächlich den Taster auf den externen Interrupt legen. >ich habe mir auch der PWM wegen schon das Datenbuch ausgedruckt, Das ist unbedingt zu empfehlen. Auch wenn ein Datenbuch so trocken ist wie die Wüste Gobi im Hochsommer: Die Mühe, es komplett durchzuackern (*) und dabei alle Details kennenzulernen (Du mußt ja nicht alles auch ausprobieren), ist zwar groß, aber der Lohn ist es auch. Und noch ein Tip: Solltest Du auch in Assembler proggen, könnte neben dem Datenbuch Deines Controllers auch das fehlerfreie Auswendiglernen (**) des 140-seitigen Instruction Set Manuals nutzbringend sein. Darin sind alle Befehle beschrieben, die die µCs der AVR-Familie verstehen. (*) und zwar am besten weit weg von Deinem Basteltisch! (**) war'n Scherz... >Es gibt zwei OCs pro Timer. >Die Periode läßt sich auch durch Schreiben des OCR Registers >einstellen. Ja. Dadurch kann ein Hardware-Timer mit n Compare-Match-Interrupts dazu genutzt werden, n Taktgeber zu realisieren (indem der k-te OC-Wert innerhalb des k-ten OC-Match-Interrupthandlers um den passenden Wert erhöht wird). Bei Betrieb des Timers im "primitiven" CTC-Mode wäre nur ein Taktgeber drin. >Teilen 'echte' Multitaskingsysteme nicht Einzelteilen der Tasks >rechenzeit zu? Also zerschnippeln die Tasks selbst, und 'mischen' >deren Abarbeitung? >Denn im Prinzip wird hier 'nur' das Programm durch den Timerinterrupt >auf 20ms Länge beschränkt, wobei dieser doch eigentlich 'nur' zum >Aufwecken des µC dient. (?) Es gibt prinzipiell zwei Arten von Multitasking: Kooperatives und preemptives. Der von mir vorgestellte Ansatz realisiert das "leichte" kooperative Multitasking; Deine Frage zielt dagegen auf das "wuchtige" preemptive Multitasking. Kennzeichen des kooperativen Multitasking ist, daß die Tasks stets von sich aus die Kontrolle an das Betriebssystem (das ist in meinem Code die Mainloop) innerhalb kurzer Zeit wieder zurückgeben. Beim preemptiven Multitasking brauchen die Tasks diese Bedingung nicht zu erfüllen, weil genau das passiert, was Du schon geschrieben hast: Das Betriebssystem kann einem Task jederzeit die Kontrolle zwangsweise entziehen, wenn es das für richtig hält (wenn der Scheduler sagt "also jetzt darf aber auch mal ein anderer Task rechnen"). Vorteil: Das Verbot von Delayschleifen entfällt! Delayschleifen belasten zwar den µC, stehen aber nicht im Widerspruch zum Konzept. Nachteil: Das "jederzeit" hat es in sich. Wenn ein Task wirklich zu jedem x-beliebigen Zeitpunkt "mittendrin" unterbrochen werden kann, dann muß offensichtlich sein kompletter "Bearbeitungszustand" gesichert werden, d. h. alle Registerwerte sowie der Stack. Das macht es notwendig, daß nicht mehr das gesamte Programm einen Stack haben muß, sondern jeder Task seinen eigenen! Die FOlge ist ein hoher RAM-Speicherbedarf pro Task. Außerdem mußt Du bedenken, daß die Tasks ja auch untereinander kommunizieren wollen/müssen, und da beginnt es dann sehr "knifflig" zu werden (Stichworte: Semaphore, Mutexe, Critical Sections, Deadlocks, Message Queues etc.). Allgemein kann man sagen, daß das Schreiben eines (funktionierenden) preemptiven Multitasking-Betriebssystems eine sehr anspruchsvolle Aufgabe ist. Dessen ungeachtet kann man ein solches System aber sehr wohl so schlank gestalten, daß man es auch auf einem der größeren AVRs erfolgreich einsetzen kann - das beweist die Existenz einiger solcher Systeme (siehe Linksammlung). Zur Praxistauglichkeit kann ich Dir leider nichts sagen, weil ich über keinerlei Erfahrungen damit verfüge. >Aha, RUNTASKFLAG kann man aber nicht einsparen, weil natürlich auch >die anderen Interrupts den Schlaf stören. So ist es. >Was passiert, wenn die Tasks länger als 20ms benötigen? Greift dann >der 20ms-Interrupt erst wieder beim nächsten Match? Einfache Antwort: Es muß sichergestellt sein, daß alle Tasks zusammen NIEMALS länger als 20 ms rechnen. "Und was mach ich, wenn ich einen Task habe, der jede Sekunde 50 ms lang was rechnen muß?" Kein Problem! Die Aufgabe wird einfach z. B. in 10 Teile "gebrochen", von denen jedes etwa 5 ms lang in Anspruch nimmt. Dann läßt sich die Chose ins 20 ms-Raster einpassen: Prima! Weniger prima: Das Ergebnis der Rechnung steht statt nach 50 ms erst nach 10*200 ms = 200 ms zur Verfügung. Diesen Nachteil muß man schlicht in Kauf nehmen. >Die Ports werden auch 'nur' alle 20ms aktualisiert!? Ist das für z.B. >LCD nicht recht langsam? Die 20 ms waren nur ein Beispielwert. Wie gesagt: Sie entsprechen bei 4 MHz Systemtakt ca. 40000 Instruktionen. 40000 Instruktionen sind eine Menge Holz! Wenn Du sicherstellen kannst, daß in Deiner Anwendung alle Tasks nur 5 ms brauchen, verbietet Dir niemand, einfach einen Zeitschritt von 5 ms zu wählen. >Das "IF TaskAktiv" wird nicht in jedem Task geprüft, richtig? (ist >nicht auf den ersten Blick ersichtlich) Ja stimmt. Das "TaskAktiv"-Flag existiert einmal für jeden Task. Ich wollte damit verdeutlichen, daß Tasks i. a. zwei grundsätzliche Zustände einnehmen können, die sich mit "aktiv" und "inaktiv" charakterisieren lassen. Die Inaktivität eines Tasks kann man auch dadurch signalisieren, indem man z. B. die mit dem Software-Timer des Tasks assoziierte Variable auf einen "Spezialwert" setzt (wenn die Variable z. B. immer 0, 1, 2 ... 78, 79, 80, 0, 1, 2 ... zählt, dann könnte man den Zustand "Task ist inaktiv" durch den Variablenwert "-1" signalisieren). >Das ist ganz praktisch, weil die Zustände und Aktionen dann auch den >anderen state-machines bekannt sein und 'beantragt' werden können >(z.B. "TaskAktiv"), und dadurch alles schön ordentlich wird. Exakt. >Dafür braucht man für die Zustände und Aktionen natürlich >entsprechend viel Platz im RAM. Der Platzbedarf hält sich in Grenzen. Für eine Warn-Blink-LED, die mit 1 Hz blinken soll, wenn irgendeine Temperatur überschritten wird, reicht ein Byte: // ---------------------------------------- // (Werte für Betriebssystem-Timestep = 20 ms) Task_WarnBlinkLED_Run: // Task wird vom Temperatur-Monitor-Task "gestartet" // durch "k = 0" und "gestoppt" durch "k = -1" IF (k==-1) { // Task inaktiv; LED soll AUS sein [LED-Pin auf 0 setzen] } ELSE { Inc(k) IF (k==25) THEN [LED-Pin auf 1 setzen] IF (k==50) THEN [LED-Pin auf 0 setzen] IF (k==50) THEN { k = 0 } } ret // ---------------------------------------- So spektakulär ;-) stellt sich die state machine einer Warn-Blink-LED dar, die der Temperatur-Monitor-Task nach Lust und Laune ein- und ausschalten kann. >wenn man sich vor Augen hält, daß die Tasks immerhin alle >20ms (50Hz) ausgeführt werden [In Mensch- und Maschinenzeit denken!]. Du hast begriffen, worauf mein Ansatz basiert: Auf dem Umstand, daß eine "typische" Anwendung den µC nur in sehr geringem Maß beschäftigt. "Typische Anwendung" meint hier grob alles, was irgendwie in die Kategorie "steuern und regeln" fällt: LEDs blinken, Motoren brummen, Lautsprecher piepen, Uhren laufen, der PC kriegt Daten über den UART, analoge Eingänge werden eingelesen, Sensoren über Busse abgefragt etc. pp.. Es werden dagegen *nicht*: Simulationen zur Entstehung des Sonnensystems gerechnet, Spracherkennung probiert, Bilder bearbeitet, Datenbanken durchforstet. Das ist einfach eine andere Klasse von Anwendungen, die dann wirklich nach preemptivem Multitasking verlangen (sowie nach anderen Prozessoren und Megabytes von RAM), weil hier viel und damit "lange" gerechnet werden muß . Es ist einfach so: Selbst wenn Du eine Anwendung schreiben würdest, bei der es aus allen Kannen blinken, brummen und piepsen würde: Der µC würde wahrscheinlich immer noch während des größten Teils der Zeit schlafen! (außer, Du setzt das Taskintervall mutwillig auf genügend kleine Werte). Diese kleinen unscheinbaren Dinger sind nämlich in Wirklichkeit echte Kraftbolzen. 40000 Instruktionen @4MHz während 20 ms; 8000 Instruktionen @16MHz während 1 ms (beachte: 1 ms entspricht bereits stolzen 1000 Aktualisierungen pro Sekunde!). Bei 10 Tasks darf jeder noch im Mittel bis zu 800 Instruktionen rechnen. Das ist immer noch beachtlich viel. Selbst einige Divisionen mit 16-Bit-Zahlen dürften damit noch keine Probleme aufwerfen. Der Rechenaufwand für so Sachen wie ein Menü mit Zeichenketten für ein LC-Display aufzubauen, ist geradezu lächerlich gering, und für eine Unmenge anderer Sachen, die dem äußeren Betrachter "ganz toll" erscheinen, ist er es ebenfalls. Jetzt kennst Du die ganze Wahrheit ;-). >Jetzt muß ich also zusehen, wie ich meine beiden 'zeitkritischen' >Aufgaben, Schrittmotor und Tastenabfrage/Entprellung, unterkriege. Jawohl, Du mußt die Sache gefühlvoll "ausbalancieren". Hier hört die Angelegenheit dann auf, "reine Routine" zu sein, und die kleinen grauen Zellen im Kopf bekommen was zu tun. >Der Rest bleibt dann ja quasi der Phantasie überlassen.. Ja. >Die Leute, die für die Software der Fahrkartenautomaten der Bahn >zuständig sind, sollten hier mal reinschauen. Ich fasse es als Kompliment auf ;-) - danke!
Hallo Bolle, bevor ich jetzt aber wirklich das Licht ausknipse mein ausdrücklichsten Dank für Deine Mühe!! (selten, aber wahr: doppeltes !) Bis demnächst!
Guten Morgen, nochmal Danke für Bestätigung & Erklärung! Da muß ich ja aufpassen, mich nicht zu sehr an so viel Hilfe zu gewöhnen :-) Vielleicht denken andere Anfänger ja in einer ähnlichen Weise/Reihenfolge, und ich nehme an, daß dem Thread ganz gut zu folgen ist. Das mit den n-Bit Variablen habe ich gestern noch im Tutorial entdeckt (man nehme: struct), und zusammen mit Deiner Programmstruktur den Motor (Minebea PM55L-048-HP69) zum Laufen gebracht; mit 195Hz Timerfrequenz läuft er eigentlich ganz ruhig (jeder Strang mit gemessenen 89.4Hz; pro U 2x; =178.8Hz. Differenz?). Wird aber langsam ganz schön warm. Vielleicht schaff ich's zeitlich gleich noch, mal zu beschleunigen und die Drehrichtung zu ändern. Übrigens ein interessanter Spannungsverlauf pro Strang |\ | '-, | \ | | | | ____| |____ das werde ich bei Gelegenheit noch mal unter die Lupe nehmen. Aber vorher noch eine vergessene Frage, wenn Du nichts dagegen hast In den Tasks: > [die zum Task XXX gehörenden Software-Timer/-Prescaler bedienen] Meinst Du damit z.B. die Variable k aus dem 1 Hz LED Beispiel? Und wenn ja, was ist mit (Software-)Prescaler gemeint? Der Wert, um den der Timer erhöht wird, oder der 'Software Compare Match' IF (k==50) THEN { k = 0 } Danke schon mal im Voraus, weiter geht's aber bei mir vorraussichtlich erst wieder Sonntag.
Hi, >Da muß ich ja aufpassen, mich nicht zu sehr an so viel Hilfe ja, in einem so guten Forum wie diesem hier kann das schnell passieren, und dann hat es den Salat mit Dir ;-). >Vielleicht denken andere Anfänger ja in einer ähnlichen >Weise/Reihenfolge, und ich nehme an, daß dem Thread ganz gut zu >folgen ist. Leider wird er wie alle anderen auch irgendwann in den Tiefen der Forums-Datenbank versunken sein. Aber vielleicht finde ich an einem verregneten Herbstsonntag genug Muße, um daraus mal einen ordentlichen Artikel zu kneten. >(Minebea PM55L-048-HP69) zum Laufen gebracht; mit 195Hz Timerfrequenz >läuft er eigentlich ganz ruhig (jeder Strang mit gemessenen 89.4Hz; >pro U 2x; =178.8Hz. Differenz?). Die Differenz kann viele Ursachen haben. Die Genauigkeit des controllerinternen RC-Systemoszillators ist begrenzt (verwendest Du diesen oder einen Quarz?). Wenn du den TC/1 ohne Prescaler betreibst, gibt es ein "-1" zu beachten beim Festlegen/Erhöhen von OC-Werten (z. B. wenn der OC-Wert 40 sein soll, dann mußt Du 39 ins OC-Register schreiben! Details im Data Sheet). Und schließlich solltest Du unbedingt Deinen Code daraufhin überprüfen, ob sich nicht doch noch irgendwelche Teile darin verstecken, die mehr Zeit als erlaubt beanspruchen, und dadurch den Verlust von Interrupts verursachen. >>In den Tasks: >> [die zum Task XXX gehörenden Software-Timer/-Prescaler bedienen] >Meinst Du damit z.B. die Variable k aus dem 1 Hz LED Beispiel? Ja, genau! >Und wenn ja, was ist mit (Software-)Prescaler gemeint? Der Wert, um >den der Timer erhöht wird, oder der 'Software Compare Match' "Software-Prescaler" meint die Funktionalität, die in diesem Stück Code steckt: Inc(k) IF (k==50) THEN { [Code, der das Modul implementiert, welches am "Ausgang" des SW-Prescalers "angeschlossen" ist] k = 0 } wobei manche Leute diese Version bevorzugen: Dec(k) IF (k==0) THEN { [Code, der das Modul implementiert, welches am "Ausgang" des SW-Prescalers "angeschlossen" ist] k = 50 } Der Wert 50 bestimmt, durch wieviel die Eingangsfrequenz durch den SW-Prescaler geteilt wird - hier eben 50. SW-Prescaler kann man kaskadieren, d. h. "hintereinanderschalten"; das sieht dann so aus: Task_RealTimeClock_Run: Inc(i) // 20 ms --> 1 s IF (i==50) { i = 0 Inc(k_Sec) IF (k_Sec==60) { Inc(k_Min) IF (k_Min==60) { k_Min = 0 Inc(k_Hour) IF (k_Hour==24) { k_Hour = 0 } } } } Eine HH:MM:SS-Echtzeituhr kann man also als SW-Prescaler-Kaskade interpretieren. Hier sind es vier Stück, wovon der erste ("i") erstmal den Betriebssystem-Timestep von 20 ms auf 1 s zähmt. Die Tatsache, daß hier jedweder sonstiger Code fehlt, macht die Sache nicht sinnlos, denn es gibt ja noch den Task "LEDDisplay", dem k_Sec, k_Min und k_Hour als Input dienen. Der Begriff SW-*Timer* bezeichnet etwas, das mehr ist als ein karger SW-Prescaler: IF (k=-1) { // Task ist inaktiv [ggf. "Inaktivitäts-Aktion" durchführen] } ELSE { // Task ist aktiv Inc(k) IF (k==...) [TueDies] IF (k==...) [TueDas] IF (k==...) [TueNochwasanderes] IF (k==...) [...] IF (k==...) [...] IF (k==...) [...] IF (k==200) { k = 0 <oder k = -1> // "k = 0" --> periodischer Task; startet sich // nach Ablauf quasi selbst wieder neu. // Beispiel: Echtzeituhr. Ist während der gesamten // Programmlaufzeit aktiv // // "k = -1" --> aperiodischer Task; versetzt sich // nach Ablauf selbst in den "Inaktiv-Zustand". // Beispiel: Treppenhauslicht-Timer. Wird vom // "UserKeys"-Task aktiviert und ist danach für // 3 min aktiv (sofern er nicht nachgetriggert wird) } } Alles bis auf die Teile in [] stellen den SW-Timer dar. Er und die Teile in Klammern zusammengenommen bilden die state machine des Tasks. >Danke schon mal im Voraus, weiter geht's aber bei mir >vorraussichtlich erst wieder Sonntag. Der positive Effekt kleiner Pausen hat mich schon mehr als einmal überrascht - wenn man etwas "Abstand zu den Dingen" gewonnen hat, kommen einem nämlich gerade dann oft die besten Ideen!
Und schon bin ich wieder da. Quasi schneller als der Timer erlaubt. Auch hier wieder meinen verbindlichsten Dank für Deine Erklärungen. Meine Experimente beim Ändern der Drehrichtung waren zwischenzeitlich fehlgeschlagen, weil ich erst die zu einem Schritt gehörenden Spulen eingeschaltet habe, und anschließend die Schrittvariable erhöht habe. Das ging in der einen Richtung noch gut, aber beim Rückwärtszählen stimmte dann die Wirklichkeit nicht mit dem gespeicherten Schrittstand überein -> Schrittsalat [vordenken!]. Auf der Suche nach einer günstigen Einstellung für den Timer, der den Schrittmotor takten soll, stellt sich mir wieder mal eine Frage (Normal Mode):
1 | SIGNAL (SIG_TIMER0_COMPA) |
2 | {
|
3 | TCNT0=248; |
4 | stepper.donextstep=1; |
5 | }
|
Ich weiß daß es sinnvoller sein kann, den Timer durchlaufen zu lassen (wie Du hier gesagt[http://www.mikrocontroller.net/forum/read-1-235092.html#236282], und da erfahren hast [http://www.mikrocontroller.net/forum/read-1-233282.html#233728] :-), aber ich wollte erstmal sehen was passiert, wenn ich's so mache. Effektiv teilt (255-TCNT0) ja die durch den Prescaler eingestellte max. Frequenz, also bei 4MHz / 1024 (max. 3,9KHz) erreiche ich theoretisch mit TCNT0 = 253 die nächstkleinere Frequenz von 3,9KHz / 2 = 1,95KHz. Demnach ist die Auflösung nicht über den Prescalerbereich gleich, was mir noch nicht aufgefallen, und außerdem schlecht für Leute ist, die die Frequenz möglichst linear ändern möchten. Ich sehe folgende Möglichkeiten: 1) Prescaler ständig auf den passenden Wertebereich umschalten. -> Ich kann nur einen OC benutzen. -> Durch das Umschalten geht bestimmt Zeit 'verloren' (geratener Nebeneffekt). -> Nicht so gut. 2) Schrittfrequenzauflösung = Timerfrequenz -> Schrittmotor als state-machine mit Software-Prescaler -> Interruptroutine wird häufiger aufgerufen. -> Besser. 3) Ich übersehe etwas -> Es geht noch besser. So, ich freue mich schon darauf, HanneS' Timergeschichte zu verstehen. Morgen geht's weiter!
Ich will mich nicht mit fremden Federn schmücken. Die Timergeschichte ist nämlich nicht meine Erfindung, ich habe sie lediglich "verstanden" und nutze sie gelegentlich (leider noch nicht konsequent bzw. immer). Inzwischen habe ich den "Urheber" wiedergefunden: http://www.mikrocontroller.net/forum/read-1-65099.html#65177 Bit- & Bytebruch... ...HanneS...
Hi! Wäre echt schon, wenn das irgendwie ins Wiki wandert. (Auch das anschauliche Beispiel mit den Stoppuhren ;-) Rick
Ich habe aber keine rechte Lust, auch noch die (schlecht erklärte) Beschreibungssprache des WIKI zu erlernen. ...
>Wäre echt schon, wenn das irgendwie ins Wiki wandert.
@Rick Dangerus:
Vielleicht machst Du Dich ja mal bei Gelegenheit an die Arbeit?
@leif:
Gibt es auf dem Gebiet der Schrittmotoransteuerung-Forschung inzwischen
Fortschritte zu vermelden?
Falls du nicht die "private" Stepperforschung meinst: http://www.mikrocontroller.net/forum/read-1-113751.html#148998 Das mag zwar zum "Spielen" zu aufwendig sein, hat aber bei ernsthaften Anwendungen sicherlich seine Vorteile. ...
Hallo Ihr zwei, gleich folgen zumindest meine Ergebnisse zur Timerforschung.. Ich habe noch nicht herausgefunden, wie ich die Sache mit der variablen Schrittgeschwindigkeit einfädeln soll, aber vielleicht finde ich dazu ja etwas in HanneS' Link. Geht meine Möglichkeit No. 2 denn in die richtige Richtung? Bis gleich!
Timergeschichten: Verstehen leichtgemacht! AHA! Timer durchlaufen lassen sagt sich so einfach .. Als Anfänger und scheidender CTC Mode Nutzer sucht man evtl. in der 'Normal Mode' Beschreibung vergeblich einen Hinweis darauf, wann denn im 'Normal Mode' etwas mit den OCRnx Registern geschieht - dabei gilt die Nennung des Registers in den anderen Modi ausschließlich dem Zeitpunkt des Zurücksetzens des Zählers, nicht dem Vergleich mit dem Zählerstand TCNTn! Der Vergleich findet statt, die Output Compare Units sind ständig aktiv (da steht zwar "The OCU can be used to generate interrupts at some given time." Aber wenn auch in den Registerbeschreibungen nichts von OCRnx zu finden ist ..). Das gilt für 8 und 16 Bit Timer. Der 'Compare Match Output n Mode' bestimmt lediglich, ob und in welcher Weise der OCnx Ausgangspin an die OC Register vom OCnx Pin gekoppelt ist. [Dann ließe sich doch auch im Normal Mode ohne Softwareaufwand PWM nutzen!? Wenn Interrupts verwendet werden müssten ergibt der Hinweis '.. PWM .. not recommended, .. too much CPU time' Sinn, aber so?] Vielleicht hat ja jemand Bretter aus ähnlichem Holz vor der Birne. Nach Peters/HanneS'/Bolles Rezept:
1 | SIGNAL (SIG_TIMER0_COMPA) |
2 | {
|
3 | OCR0A=TCNT0+50; |
4 | struct.bit=true; |
5 | }
|
6 | |
7 | |
8 | TCCR0B = (1<<CS02) ; // prescaler 1/256 (tiny2313) |
9 | TIMSK=1<<OCIE0A ; // enable interrupt |
Kommt also der Interrupt wird der 'Wecker' um 50 Schritte (1/(4MHz/256/50)=3.2ms [Keramikresonator]) vorgestellt. Und so weiter .. Im Gegensatz zum CTC Mode läßt sich so also auch die zweite OC Unit nutzen (tiny2313 hat zwei pro Timer). Soweit die Zusammenfassung meiner Erkenntnisse, schrittmotortechnisch hat sich noch nicht so viel getan, ich hatte auf Bestätigung gehofft und noch nichts ausprobieren können :-)
Ahh, das struct.bit aus obigem Code nicht so übernehmen! Das soll nur eine 1-Bit-Variable darstellen, die in meinem Fall den Weg für den nächsten Motorschritt freigibt.
Ich übernehme kein Struct-Bit, ich kann kein C. Nochmal zum 16-Bit-Timer (ich gehe vom Mega8 aus, mit dem Tiny2313 habe ich noch nix gemacht). TCNT1(H/L) läuft einfach durch. Kein Programmteil hat das Recht, den Timerstand zu verändern. So kann sich jede Timernutzung darauf verlassen, dass "die Uhr noch stimmt". Willst du mit dem OCR1A-Interrupt einen Takt erzeugen, dann musst du einen Referenzwert in das OCR1A(H/L)-Register schreiben und in TIMSK den Int freischalten. Das I-Flag in SREG muss natürlich mittels SEI auch gesetzt sein. Erreicht TCNT1 den Wert in OCR1A, dann wird der zugehörige Int ausgelöst. Ich gehe davon aus, dass Hardware-PWM deaktiviert ist, denn wir wollen ja eine ISR aufrufen, die selbstgeschrieben ist. In der ISR zu OCR1A wird nun OCR1A ausgelesen (das erspart eine Variable), das Intervall (der Abstand bis zum nächsten gewünschten Interrupt in Timertakten (abhängig vom Vorteiler)) dazuaddiert und wieder in OCR1A zurückgeschrieben. TCNT1 wird dann irgendwann den neuen Wert von OCR1A erreichen und einen neuen Int auslösen. In der ISR machtst du dann die eigentliche Arbeit. Das wäre im Fall des Schrittmotors das Addieren eines Referenzwertes (-1, 0, +1) zum "Schrittstand" des Motors. -1 wäre rückwärts, +1 vorwärts, 0 Stillstand. Dann den Wertebereich des "Schrittstandes" reduzieren (AND 7 für Halbschritt) und das zugehörige Bitmuster aus der Tabelle holen. Dann Port einlesen, Motorbits löschen, Bitmuster dazu ORen, wieder ausgeben. Das Intervall, also die Anzahl der Timertakte bis zum nächsten Interrupt kann dabei dynamisch sein. je kleiner es wird, desto schneller wird der Motor. Du kannst also auch Anfahr- und Bremsrampen programmieren um Schrittverluste zu vermeiden. Mit OCR1B kannst du dann einen weiteren Interrupt auslösen, der einen anderen Takt (auch variabel) erzeugt. Hier wird dann das (separate) Intervall auf das OCR1B-Register addiert (einlesen, addieren, zurückschreiben). Damit kannst du dann andere Anwendungen synchronisieren. Kurze effizient programmierte Routinen können in der ISR abgearbeitet werden (z.B. das Ausgeben des nächsten Motorschrittes), längere Routinen gehören in die Mainloop und bekommen ein (eigenes) Run-Flag, welches vom der ISR gesetzt wird und von der Routine selbst wieder gelöscht. Der Timer1 steht nach Nutzung von OCR1A und OCR1B aber noch für weitere Aufgaben zur Verfügung. Mit dem ICP-Interrupt kann man ein an den ICP-Pin gelegtes Signal "ausmessen". Dabei kann man die Impulsbreite, die Impulspause oder die Periodendauer ermitteln, je nachdem, ob man auf steigende oder fallende Flanke triggert. In der ICP-ISR liest man das ICP-Register und ermittelt die Differenz zur vorherigen Messung (im SRAM oder Register "gemerk(el)t" und merkt sich den neuen Wert für die nächste Runde. Weiterhin kann der aktuelle Stand von TCNT1(H/L) für weitere Vergleiche genutzt werden, z.B. in Verbindung mit den externen Int-Eingängen zum Messen weiterer Impulsdauern. Wenn man auch den Overflov-Interrupt des Timer1 nutzt, kann man in der ISR ein Register(-paar) hochzählen, das von vielen Programmteilen als Referenz auf eine Verzögerung genutzt werden kann. Wenn man z.B. eine Pause von 5 Einheiten (5 Timer1-Überläufe, 1 Überlauf entspricht Vorteiler * 65536 Takte) realisieren möchte, so holt man sich eine Kopie des Zählerstandes, addiert die 5 dazu und merkt sich den Wert im SRAM. Nun wird bei jedem Aufruf der Routine der Wert verglichen und sofort zur Mainloop zurückgesprungen, wenn der Zähler den Wert noch nicht erreicht hat. Dies kann lästige (Rechenzeit fressende) Warteschleifen ersetzen. ...
Hallo HanneS, danke und gute Nacht, das wird jetzt meine Bettlektüre!
Gut erklärt! Mir fällt gerade auf, daß der Geschwindigkeitsbereich des Schrittmotors relativ beschränkt, bzw. bei meiner Hardware durch mehrere Faktoren eingeschränkt ist. Das mag u.A. an meiner Ansteuerung liegen, ich schalte die Spulen für den nächsten Schritt sofort und gleichzeitig an (im Gegensatz zu http://www.mikrocontroller.net/forum/read-1-113751.html#148998, aber ich verstehe nicht, was die Verzögerung bewirken soll - Halbschrittbetrieb?). Neben den Eigenresonanzfrequenzen des Motors stört bei langsamer Fahrt natürlich auch der hohe Stromverbrauch. Also das nächste Mal für höhere Ansprüche einen eigenen IC für die Ansteuerung & Chopper .. Wie also bekomme ich es jetzt trotzdem hin? Ich könnte mir eine einigermaßen günstige Minimalgeschwindigkeit heraussuchen, und die Resonanzen ignorieren. Und wie von den Profis ja schon angemerkt, sollte die Schrittmotorsteuerung direkt in der ISR laufen, und nicht innerhalb von main() als state-machine. Aber ich werde mich jetzt erstmal um die Tasten/Schalter Entprellung kümmern.
Der begrenzte Drehzahlbereich ist ja das Problem bei statischer Ansteuerung der Spulen. Bedingt durch die Induktivität steigt der Strom (nacheilend in Spulen) ja nur langsam an. Bei hohen Drehzahlen wird der Strang aber schon wieder deaktiviert, bevor der Strom groß genug wurde, ein kräftiges Magnetfeld aufzubauen. Bei niedrigen Drehzahlen steigt der Strom bis zum Maximum an, das Magnetfeld geht in die Sättigung (mehr Strom bringt also nur Wärme), der Motor verheizt unnötig Energie und wird warm. Abhilfe schafft der Chopperbetrieb. Dabei wird der Motor mit einer bedeutend höheren Spannung betrieben und diese so "zerhackt" (gepulst), dass der Strom in der Wicklung annähernd konstant bleibt. Denn das Magnetfeld (und damit die mechanische Kraft) ist vom Strom abhängig, nicht von der Spannung. Da ich mich noch nicht genauer mit dieser Betriebsart beschäftigt habe, hann ich jetzt nicht sagen, ob es ausreichend wäre einfach den gemeinsamen Pluspol der Schrittmotorwicklungen (freilaufend) zu "zerhacken", oder ob man dazu einen Regelkreis benötigt (oder zumindest eine Rückkopplung). So dass vor Errichen des Sättigungsstroms ausgeschaltet wird und vor Abriss des (durch Induktion weiter fließenden) Stroms wieder eingeschaltet wird. Wie gesagt, ich weiß es (noch) nicht. Wieviele Taster hast du zu entprellen? Hast du dazu schon ein Konzept? ...
Hi HanneS,
eine Patch-Lösung wäre evtl., die Einschaltzeit und damit den
Spulenstrom per Schritt zu minimieren. Für den Rest der Schrittzeit die
Spule ausgeschaltet lassen.
Praktisch ist das dann natürlich von benötigtem Drehmoment und der
Geschwindigkeit abhängig, und muß auf den Motor abgestimmt werden. Und
der Softwareaufwand / Rechenzeit steigt.
Ein Konzept für die Eingänge habe ich noch nicht so richtig. Die zwei
Menschen-Taster sollte kein Problem sein, das wollte ich jetzt in
Angriff nehmen.
Mehr Überlegung bedarf es dagegen bei den Mechanik-Schaltern, bei denen
es um eine schnellstmögliche Erkennung geht, da ich die Schrittposition
erfassen möchte (Einer davon ist zum initialisieren der Endposition).
Im besten Fall prellen die Dinger <1ms.
Wenn ich also den Zustand ständig abfrage (polle), und das mit T>1ms
mache, muß ich davon ausgehen, daß maximal ein Wert unbrauchbar ist.
_ _______________
| | |
_____|
0 x 1 1
Wenn ich eine 0, und dann zwei 1en in folge feststelle, ist der
Schalter mit Sicherheit betätigt (und andersherum). Die Erkennung ist
im günstigsten Fall also 3x( knapp größer Prellzeit), wenn man sich auf
den Schalter [Prellzeit] verlassen kann.
Die Erkennung dauert bei mir also > 3ms, und ist damit für meine
Positionsmessung geschwindigkeitsbestimmend. Auf den Schritt genau geht
es nur, wenn nach jedem Schritt genug Zeit zur Messung bleibt.
Und wenn Motorschritte und Meßintervalle unabhängig voneinander laufen,
gibt es durch die Verschiebung eine noch größere Ungenauigkeit.
Wenn ich schon so häufig pollen muß, liegt es doch nahe, auch die
Taster gleich mitzubehandeln!?
Das sind Reichelt Smd Kurzhubtaster, die ich gegen GND schalte, das
Prellen liegt anscheinend im µs-Bereich und könnte glatt mit Rauschen
verwechselt werden. Ein typischer Fingerdruck dauert bei mir um die
100ms, schnellstens 60ms.
Also lasse ich einen Timerinterrupt alle 1,x ms ein Flag setzen, das
state-machine veranlasst, die Ports einzulesen.
if (flag) {
port einlesen;
if(port!=port_alt)
messung++;
else
messung=0;
if(messung==2){
messung=0;
port_alt=port;
// umgeschaltet!
// z.B. globales Flag togglen
// bzw. Zeitpunkt zwecks Drückdauer merken..
}
flag=0;
}
Das ist nicht getestet und mag gar nicht oder besser gehen.. und gilt
auch nur für einen Pin. Jeder Eingang müsste ein eigene 'messung'
Variable erhalten. Ich vermute, die Lösung steht in Peters
Entprellroutine :-)
Wie setzt man denn das mit der Drückdauer im Allgemeinen um? Es könnte
doch je nach Timergeschwindigkeit und -Bereich zum fehlerverursachenden
Überlauf kommen, wenn eine Taste klemmt (wenn die genaue Zeit da nicht
interessiert, könnte man ja ein 'sehr_lange_gedrückt' flag setzen).
Freue mich auf Eure Ansichten!
--
In meinem Post von gestern 23.02 steckt ein Fehler:
> Der 'Compare Match Output n Mode' bestimmt lediglich,
müsste x sein.
Peters Entprellung gibt es auch in C. In der Codesammlung wirst du fündig ("bulletproof"). Da gibt es auch einen Link auf ein Uhren/Thermometer-Projekt mit Tiny12 (auch von Peter), da sieht man, wie man mit einem halben Register eine Taste entprellen kann. Einen Link zu Diskussionen über das Thema findest du im WIKI (heißt jetzt "Artikelsammling") unter "Entprellung". Optimalere Lösungen (also schneller und/oder ressourcensparender) als Peters Code kann ich mir nicht vorstellen. Hier im Forum gibt es so viele Fehler, dass man sich schon daran gewöhnt hat und dass deine Fehler garnicht mehr auffallen. Ich habe es inzwischen auch aufgegebeb, mich für meine eigenen Schreibfehler zu schämen. Sowas passiert eben (im Gedränge auf'm Frauenklo)... ;-) ...
Ist doch fast dasselbe! ;-p Statt Zähler und 'messung' für jeden eingang halt ein bisschen Logik! Doof ist nur, daß es nicht 'einfach' auf 3 Messungen geändert werden kann, und die Erkennung bei mir dadurch nochmal eine ms länger dauert. Also entweder nehme ich mir den Code vor, und versuche den auf 3 Überprüfungen umzustricken, oder ich lebe mit der 'Schaltvorgang @ Schritt' - Ungenauigkeit bei hoher Schrittmotorgeschwindigkeit. Aber jetzt finde ich ersteinmal heraus, wie ich Ausgangspins auf dem Port ausblende.
braucht man nicht, man fragt sie einfach nicht ab. na dann probier ich's doch gleich mal aus! nur noch den passenden timer raussuchen .. kram .. so, moment ..
Jetzt verstehe ich auch, warum in den von mir zerlegten Druckern eine Gabellichtschranke zur Endlagenerkennung genutzt wird - sie prellt nicht. [nächstes Mal]
Noch was zu meinen Überlegungen zum Entprellen weiter oben: Humbug! Zumindest teilweise - wenn ein Schaltvorgang zweifelsfrei erkannt wurde, läßt sich der Zeitpunkt auf einen Messzyklus genau bestimmen; der erste geänderte 'Messwert' ist ja immer noch gültig. Im schlechtesten Fall liegt die zweite Messung genau in der Prellzeit und ergibt einen falschen Wert; das Ergebnis wird um einen Messzyklus verfälscht. Noch eine Frage (an Bolle): Wäre es im Pseudocode-Beispiel nicht günstiger, den Interrupt für den Buzzer ggf. ein- und auszuschalten, anstatt mit Interruptfrequenz jedesmal eine Variable zu prüfen? Danke für diesen tollen Thread, ich fühle mich mittlerweile gewappnet, meine Anwendung zu vollenden. Es piept, brummt, blinkt und entprellt schon quasigleichzeitig (aber praktisch sinnlos), jetzt geht's ans eigentliche Programm, und da werde ich auch noch eine Menge lernen.
Na dann frohes Schaffen... Bit- & Bytebruch... ...HanneS...
Doch noch mal eine Frage zum Schrittmotor, dazu die folgende ISR
1 | SIGNAL (SIG_TIMER0_COMPA) { // stepper motor control |
2 | interrupt
|
3 | OCR0A=TCNT0+40; |
4 | |
5 | stepper_prescaler++; |
6 | |
7 | if (stepper_prescaler==stepper.speed && stepper.power==1) { |
8 | |
9 | stepper_prescaler=0; |
10 | |
11 | if (stepper.dir==STEP_DOWN) { |
12 | stepper.step++; |
13 | stepper.pos++; |
14 | }
|
15 | else { |
16 | stepper.step--; |
17 | stepper.pos--; |
18 | }
|
19 | |
20 | PORT_STEPPER = (PORT_STEPPER & ~step_mask) // read port and |
21 | clear stepper bits |
22 | | step_seq[stepper.step]; // add new stepper bits, and put |
23 | out. |
24 | }
|
25 | |
26 | if(stepper.power==0){ |
27 | PORT_STEPPER=(PORT_STEPPER & ~step_mask); // turn off |
28 | }
|
29 | |
30 | |
31 | }
|
Abgesehen davon, daß ich den Motor noch nicht im Stillstand unter Strom halten kann, funktioniert es (da kann noch ein else rein). Zufrieden bin ich aber noch nicht, und nehme an, daß es noch besser geht. Die ISR wird alle 640µs ausgeführt, diese Zeit wird durch stepper.speed (=x) vervielfacht, d.h. die maximale Frequenz wird geteilt. In der Natur der Sache liegt, daß f(x)=f_pre/x abnehmend abnimmt, also nicht linear ist. Es kann für große x als einigermaßen linear angenommen werden, dafür ist aber die Steigung dort sehr gering. Wie kann ich unter diesen Umstängen eine konstante Beschleunigung des Schrittmotors erreichen? Je weiter ich in den 'linearen' Bereich gehe, um meine Frequenz zu erhalten, desto schneller muß der Timer sein (für hohe Schrittgeschwindigkeiten), und dann bekomme ich u.U. Probleme mit dem Rest des Programms. Weiter vorne (kleine x) bliebe nur die Möglichkeit, nicht-Inter-Berechnungen durchzuführen, aber das scheint auf einem 8-Bit Controller ebenfalls nicht weise. Wie also löst man das Problem ? :-) Geht es ganz anders (Möglichkeit 3 von oben)? Danke!
Oh, das ist ja nicht schon gesyntaxhighlighted. Timer 0 ist der 8 Bit Timer ist, der Prescaler ist bei 1/64 angezapft, und der Takt ist 4MHz.
C-Programme werde ich nicht kommentieren, dazu fehlt mir die Fachkompetenz. Das Syntax-Highlighting ist doof, es verunstaltet den ganzen Seitenaufbau. Ich werde demnächst die Threads ignorieren, in denen dieses (sabotierende) Feature benutzt wird. Linearität? Du veränderst die Periode T (Zeitdauer pro Schritt), möchtest aber Linearität in der Frequenz f (Drehzahl). Da f=1/T ist, müsstest du schon etwas rechnen. Professionell wird es aber aufgrund der weiter oben schon genannten Tatsachen sowiso nicht. ...
Mahlzeit && Danke, a) von einem Assembler-Profi hätte ich gedacht, daß er C erst recht kann!? b) Also ein bißchen blöd ist es ja schon, erst umzubrechen, und dann zu parsen.. Das sind aber drastische Maßnahmen! c) Sag ich doch ;-p Dann mach ich's doch lieber erstmal mit konstanter Geschwindigkeit..
Mahlzeit... zu a) Ich bin kein ASM-Profi, ich mach das auch nicht beruflich. Es ist nur so, dass ich ASM bevorzuge, da es nicht so viele Stolperfallen gibt. Jeder "Befehl" ist auch exakt ein Maschinensprachebefehl, von dem Speicherbedarf (1 Wort / 2 Worte) und Abarbeitungszeit (1...4 Takte) bekannt sind. Man kann also auch zeitkritische Vorgänge exakt vorhersagen, ohne dass einem ein Compiler oder Optimierer dazwischenfunkt. Es gilt halt nur das Datenblatt und der Befehlssatz (Teil des Datenblatts). Und da die meisten Befehle auf die Peripherie zugreifen, die bei jedem AVR-Typ etwas anders ist, verpufft der Vorteil einer Hochsprache, nämlich die Portablität. Ich mache mich da lieber mit der Architektur des jeweiligen AVRs (der Peripherie, der Kern ist ja überall fast gleich) vertraut und greife mit ASM-Mitteln darauf zu, als dass ich noch eine (für mich neue) Sprache lernen muss, die verdammt viele Fallstricke hat (siehe diverse C-Fragen hier im Forum). Auf dem PC benutze ich BASIC, und zwar M$-QB4.5 (DOS) und M$-VB6 (WIN). Nun könnte man vermuten, dass ich mich auch auf dem AVR mit BASIC anfreunden könnte. Kann ich aber nicht, denn BASCOM macht dumm (lässt sich kaum hinter die Kulissen schaun) und FastAVR stand mir zu Beginn nicht zur Verfügung. Und inzwischen bin ich mit ASM soweit, dass sich der Einstieg in FstAVR (für mich) nicht mehr lohnt. Wenn man für ein (abstraktes) Betriebssystem programmiert, dann ist eine Hochsprache ein Muss. Wenn man aber direkt an der Hardware programmiert, dabei meist Bits schubst und selten Nummern, dann jeder Typ eine etwas andere Hardware-Ausstattung hat, dann kann ich (ich, bitte nicht verallgemeinern) gut auf eine Hochsprache verzichten und in ASM arbeiten. Für C hatte ich auch noch keine Gelegenheit. Beim Commodore Plus/4 war BASIC3.5 und 6502-ASM angesagt, beim 8085 war es 8085-ASM und MFA-BASIC, beim PC war es zuerst QBASIC, dann QB4.5 (mit Compiler), später über VB4 das VB6. Auch PASCAL und andere Sprachen sind mir nicht geläufig. zu b) Nunja, der Seitenaufbau ist lästig. Es werden die letzte Zeile vor der Codebox, die Codebox und die erste Zeile danach ohne Umbruch nebeneinander dargestellt. Dies kann keine Dauerlösung sein. Ich werde mir also abgewöhnen, im Rahmen der Hilfe für Andere den Inhalt der Codeboxen zu lesen. Aber vielleicht gelingt es Andreas ja doch noch, Zeilenumbrüche vor und nach der Codebox einzufügen, die von jedem Browser als Zeilenumbrüche akzeptiert werden (Tabelle). zu c) Nunja, wenn ich einen halbwegs linearen Drehzahlbereich brauchen würde, dann würde ich mir die Timerwerte für den benutzten Bereich in eine Tabelle legen und indiziert darauf zugreifen. Mit 16 oder 32 Stufen kann man schon allerhand variieren. Die Tabellenwerte könnte ja ein PC-Programm vorher ausrechnen. Das wäre nicht mein erstes PC-Programm, das Include-Dateien für AVR-ASM generiert. Wenn es nur um eine Anfahr/Bremsrampe geht, dann würden vielleicht schon 4 oder 8 Stufen reichen. Bin erstmal unterwegs... ...
passend zum Thema hab ich mal einen eigenen Thread reingestellt http://www.mikrocontroller.net/forum/read-1-240578.html#new
>Leider wird er wie alle anderen auch irgendwann in den Tiefen der >Forums-Datenbank versunken sein. versinken darf er ja ruhig. aber gefunden wurde er wieder. danke für die ausführungen hannes! hat mir sehr geholfen. (ob er das noch sieht und im forum noch aktiv ist? sind ja doch schon ein paar jahre her das das hier geschrieben wurde.) sufu sei dank ;-) the net dont waste information if the information itself isnt waste!
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.