Forum: Mikrocontroller und Digitale Elektronik Programmablauf, Interrupts, Timer, Schrittmotor, PWM


von leif (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?


von Winfried (Gast)


Lesenswert?

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?...

von leif (Gast)


Lesenswert?

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 :-)

von leif (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von leif (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Winfried (Gast)


Lesenswert?

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.

von Bolle (Gast)


Lesenswert?

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

von leif (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?

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();

von leif (Gast)


Lesenswert?

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!

von Bolle (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?

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!

von Hannes L. (hannes)


Lesenswert?

Ich bedanke mich auch...

von leif (Gast)


Lesenswert?

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.

von Bolle (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?

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!

von Hannes L. (hannes)


Lesenswert?

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...

von Rick Dangerus (Gast)


Lesenswert?

Hi!
Wäre echt schon, wenn das irgendwie ins Wiki wandert.
(Auch das anschauliche Beispiel mit den Stoppuhren ;-)

Rick

von Hannes L. (hannes)


Lesenswert?

Ich habe aber keine rechte Lust, auch noch die (schlecht erklärte)
Beschreibungssprache des WIKI zu erlernen.

...

von Bolle (Gast)


Lesenswert?

>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?

von Hannes L. (hannes)


Lesenswert?

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.

...

von leif (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?

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 :-)

von leif (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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.

...

von leif (Gast)


Lesenswert?

Hallo HanneS,

danke und gute Nacht, das wird jetzt meine Bettlektüre!

von leif (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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?

...

von leif (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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)...

;-)

...

von leif (Gast)


Lesenswert?

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.

von leif (Gast)


Lesenswert?

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 ..

von leif (Gast)


Lesenswert?

Jetzt verstehe ich auch, warum in den von mir zerlegten Druckern eine
Gabellichtschranke zur Endlagenerkennung genutzt wird - sie prellt
nicht. [nächstes Mal]

von leif (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

Na dann frohes Schaffen...

Bit- & Bytebruch...
...HanneS...

von leif (Gast)


Lesenswert?

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!

von leif (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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.

...

von leif (Gast)


Lesenswert?

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..

von Hannes L. (hannes)


Lesenswert?

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...

...

von Mark S. (struberg)


Lesenswert?

passend zum Thema hab ich mal einen eigenen Thread reingestellt
http://www.mikrocontroller.net/forum/read-1-240578.html#new

von hpl (Gast)


Lesenswert?

>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
Noch kein Account? Hier anmelden.