;Testphase, noch nicht fertig... ;Servo-Programm für AVR Tiny15 zum Einsatz im Modellbau. ;Es wird mit einem Kanalimpuls und einem Poti ein Servomotor gesteuert, ;Das Servo hat einen Impulseingang (1,0ms...2,0ms), ;ein Poti zum Ermitteln der momentanen Ruderstellung, ;zwei Ausgänge zum Ansteuern einer H-Brücke für den Motor im PWM-Mode. ;Das Modul arbeitet mit Auto-Neutralscan, der 15. empfangenen Impuls ;(300ms nach dem Einschalten) wird als Neutralpunkt gewertet. ;Dies gibt der Anlage Zeit zum Einschwingen. ;Erst danach wird der Servomotor gesteuert. ;Die empfangenen Impulsbreiten werden digitalisiert. Dazu wird der Wertebereich ;eines Bytes (0...255) bei einer Auflösung von 10µs je Ziffer genutzt. ;Gültige Impulsbreiten von 1,0ms bis 2,0ms entsprechen Zahlenwerten von 100 bis 200. ; (c) 08/2004 by ...HanneS... ;Anschlussbelegung des Tiny15: ;Pin 1: Reset (über R an +Vcc) ;Pin 2: PB4, Ausgang 2 für H-Brücke ;Pin 3: PB3, ADC2, Poti für Ruderstellung (Analogeingang) ;Pin 4: GND ;Pin 5: PB0, ;Pin 6: PB1, Ausgang 1 für H-Brücke ;Pin 7: PB2, INT0, Impulseingang ;Pin 8: +Vcc, Betriebsspannung +5V ;Das Poti ist zwischen GND und Betriebsspannung zu schalten, ;mit Schleifer am AVR-Eingang (C gegen Masse)... ;Damit die Kalibration des internen RC-Oszillators auf 1,6MHz funktionieren kann, ;muss das "Calibrationsbyte" mittels eines ISP-Programms aus dem Signature-Bereich ;des Tiny15 gelesen werden und in die letzten beiden Bytes des Flash-Speicherbereiches ;eingetragen werden. (eigentlich nur in das L-Byte der letzten Word-Zelle) ;Wird das versäumt, stimmt das Timing nicht!!! ;Funktionsbeschreibung (grob): ;- Analog-Digitalwandler (Vorteiler 128, Messdauer etwa 1,05ms) ; * ADC arbeitet freilaufend und misst die Ruderstellung (Servo-Poti). ; * Die Abfrage erfolgt vom Hauptprogramm beim vorletzten PWM-Schritt. ;- Steigende Flanke am Impulseingang (Eingangsimpulsanfang) ; * setzt Timer1 auf 0 und startet ihn mit 100kHz, ;- Fallende Flanke am Impulseingang (Eingangsimpulsende) ; * liest Timer1 aus (Eingangsimpulsbreite), ; * sorgt dafür, dass die ersten 14 empfangenen Impulse verworfen werden und ; der 15. Impuls als Neutralpunkt des Senders interpretiert wird, und ; erst ab 16.Impuls nach dem Einschalten Schalthandlungen ausgelöst werden. ; * stoppt Timer1, ; * prüft Impulsbreite auf Gültigkeit (imin, imax), ; * prüft Impulsabstand auf Minimalwert (mittels Timer0), ; * schaltet nach mehreren ungültigen Impulsen (Anzahl in Konstante "errors") Ausgänge ab, ; * löscht Impulsabstandzähler für Messung des Impulsabstandes), ; * sorgt mittels Hysterese für jitterfreie Impulsbreitenerkennung. ;- Timer1-Überlauf (Überlauf bei Impulsmessung wegen zu langem Impuls, Error-Handler) ; * vermindert Error-Zähler, bei dessen Ablauf Neutralstellung (150) aktiviert wird. ;- Timer0-Überlauf (Zeitbasis 8kHz, enthält das eigentliche "Hauptprogramm") ; * Timer0-Int. stellt eine Zeitbasis von 200 Takte (8kHz) bereit, das eigentliche Hauptprogramm ; läuft in der Timer0-Überlauf-ISR ; * erhöht Impulsabstandzähler, verzweigt bei Überlauf zum Error-Handler, ; * erhöht PWM-Zähler, ; * schaltet Motor ab, wenn PWM-Zähler > Differenzwert Impulsbreite-Ruderstellung ; * liest bei vorletztem PWM-Schritt ADC (Poti) mit Hysterese ein und berechnet daraus einen ; Wert zwischen 86 und 213 (150 +/- 63), der zum Vergleich mit der Impulsbreite taugt, ; ermittelt die Differenz zwischen Impulsbreite (Sollwert) und Ruderstellung (Istwert) als ; PWM-Referenz und Drehrichtung, ; * setzt im letzten PWM-Schritt den PWM-Zähler auf 0 zurück und schaltet Motor mit zuvor ermittelter ; Drehrichtung ein, ; * kann von anderen ISRs unterbrochen werden, um exakte Impulsbreitenmessung zu ermöglichen. ;- Reset-Routine ; * kalibriert den internen RC-Oszillator auf 1,6MHz, ; * initialisiert beim Einschalten alle benötigten Ressourcen, ; * aktiviert die erste ADC-Messung ;- Hauptprogramm ; * schickt den AVR in den Schlafmodus, von dem er von einem der auftretenden Interrupts ; geweckt wird. Dies erhöht die Genauigkeit der Timer, was aber hier keine große Rolle spielt. ;Timing: ;- Alle Programmteile (außer 'Schlafen') laufen in Interrupts. ;- Zwischen den Auslösen des Impulsflanken-IRQ vergeht 1...20ms, genug Zeit zur Abarbeitung ;- ADC wird immer nach Impulsende aktiviert, da sind min. 18ms Zeit, er braucht 2,1ms für beide Messungen ;- Timer0-Int tritt nur im Fehlerfall auf (Impulsausfall), kann daher den Ablauf nicht stören ;- Timer1-Int tritt nur im Fehlerfall auf (Impulsbreite zu lang), kann daher den Ablauf nicht stören ;Somit ist bei Empfang gültiger Fernsteuerimpulse sichergestellt, dass kein Interrupt aufgerufen wird, ;während noch eine andere ISR abgearbeitet wird. ;------------------------------------------------------------------------------ ;AVR-interne Hardware-Ressourcen: ;Timer0: Impulsabstandmessung für Error ;Timer0-Überlauf: Impulsausfall-Timeout ;Timer1: Impulsbreitenmessung Eingangsimpuls ;Timer1-Überlauf: Impulsbreitenfehlerbehandlung ;Ext.Interrupt, steigende Flanke: Beginn Impulsmessung, Zeitmessung/Abschaltung bei Zeitschalter ;Ext.Interrupt, fallende Flanke: Ende Impulsmessung, Auswertung, Gesamtsteuerung, ADC-Start ;ADC: periodisch laufend mit Interrupt, Start durch Reset und Impulsende, Auslesen wenn fertig .include"tn15def.inc" ;Konstanten / Parameter (Anpassung in sinnvollen Grenzen erlaubt): .equ tmin=128 ;Vergleichswert für minimalen Impulsabstand (8 Werte/ms... 16ms, 128) .equ tmax=200 ;Vergleichswert für maximalen Impulsabstand (8 Werte/ms... 25ms, 200) .equ imin=70 ;Minimale Impulsbreite für Error (100 Werte/ms... 70, 0,7ms) .equ imax=231 ;Maximale Impulsbreite für Error (100 Werte/ms... 231, 2,3ms) .equ errors=10 ;Anzahl der erlaubten Impulsfehler (Def. 10) .equ imp=pb2 ;Impulseingang (muss INT0 sein) .equ aul=pb1 ;Ausgang links für H-Brücke .equ aur=pb4 ;Ausgang rechts für H-Brücke ;Konstanten, fix (nicht ändern!) .equ tim0start=256+10-200 ;Startwert Timer0 (PWM-Zeitbasis 200 Takte=8kHz IRQ-Frequenz) .equ links=(1< Hysterese... subi tmp,2 ;um (doppelte) Hysterese vermindern cp iw,tmp ;Vergleich mit altem Impulsbreitenwert brlo fallend5 ;Änderung > Hysterese... fallend4: reti ;neuen Impulsbreitenwert verwerfen da innerhalb Hysterese fallend5: mov iw,sw ;gemessener/korregierter Impuls ist nun gültig cp iw,ibmin ;ist Impulsbreite kleiner als Servo-Stellbereich? brlo fallend2 ;nein... mov iw,ibmin ;ja, Impulsbreite auf Stellbereich anheben fallend2: cp ibmax,iw ;ist Impulsbreite größer als Servo-Stellbereich? brlo fallend3 ;nein... mov iw,ibmax ;ja, Impulsbreite auf Stellbereich absenken fallend3: ldi erz,errors ;Error-Zähler auf Startwert setzen out sreg,srsk ;SREG wiederherstellen reti tim1_ovf: ;Impuls zu lang, Error in srsk,sreg ;SREG sichern out timsk,timaus ;Timer1-INT deaktivieren out tccr1,null ;Timer1 stoppen error: ;Error, gemessenen Impuls verwerfen, da ungültig clr iaz ;Timeout Impulsabstand löschen dec erz ;Error-Zähler runter brne error1 ;unten? out portb,aus ;ja, Motor erstmal aus mov iw,neut0 ;Servo zur Mitte fahren error1: out sreg,srsk ;SREG wiederherstellen reti tim0_ovf: ;das ist eigentlich das Hauptprogramm... ;daher keine SREG-Sicherung... out tcnt0,tsw ;Startwert setzen inc iaz ;Impulsabstand-Zähler hoch brne tim0_1 ;kein Überlauf... out portb,aus ;Überlauf, also Fehler, Motor aus, in srsk,sreg ;SREG doch noch sichern, da es in ERROR rjmp error ;wiederhergestellt wird... tim0_1: sei ;andere Interrupts erlauben (besonders Impulsmessung) inc pwz ;PWM-Zähler hoch cp pwz,pwu ;Endwert erreicht? brlo tim0_2 ;ja... clr pwz ;von 0 beginnen out portb,aus ;Motor aus mov ref,ist ;neuer Referenzwert mov ri,rn ;neuer Richtungswert out portb,ri ;Motor einschalten, falls eine Richtung gesetzt war tim0_2: cp pwz,ref ;Referenz überschritten? brlo tim0_3 ;nein... out portb,aus ;ja, Motor aus tim0_3: mov tmp,pwu ;Kopie vom Zählumfang dec tmp ;um 1 vermindert cp pwz,tmp ;vorletzter Schritt? (da wird Ruderstellung ausgemessen) brne tim0_ende ;nein, zum Ende... ;ADC lesen in tmp,adch ;ADC-H lesen ldi zl,low(cortab*2) ;Pointer auf Anfang ldi zh,high(cortab*2) ;der Korrekturtabelle add zl,tmp ;Potistellung addieren adc zh,null ;Übertrag auch lpm ;Tabellenwert aus Flash nach r0 mov tmp,r0 ;Kopie wegen Hysterese subi tmp,(-2) ;Hysterese - cp tmp,rud ;überschritten? brlo awneu ;ja... subi tmp,4 ;Hysterese + cp rud,tmp ;überschritten? brlo awneu ;ja... rjmp tim0_4 ;weiter... awneu: ;Hysterese mov rud,r0 ;Übernahme Neuwert Ruderstellung tim0_4: ;nun ist rud (Ruderstellung) aktualisiert ; mov rud,neut0 ;Debug, kein Nachlauf ldi rn,rechts ;Impuls-PullUp und PB1 (Richtung A) mov ist,rud ;neuen Istwert aus Ruderstellung sub ist,iw ;und (korregierter) Impulsbreite ermitteln breq tim0_e1 ;Gleichheit... brsh tim0_ende ;Größer, also fertig... neg ist ;kleiner, also Wert positiv machen ldi rn,links ;und Richtung umkehren tim0_ende: reti ;fertig... tim0_e1: ;Gleichheit mov rn,aus ;Richtung aus reti ;fertig ;------------------------------------------------------------------------------ ;Programmstart: reset: ;Initialisierung ldi zl,low(1022) ;Pointer auf ldi zh,high(1022) ;Kalibrationsbyte lpm ;nach r0 holen ldi tmp,255 ;Referenz cpse tmp,r0 ;Kalibrationsbyte gültig (<>$ff)? out osccal,r0 ;ja, kalibrieren ldi tmp,1<