Das Servo versucht möglichst schnell die Position zu erreichen, die ihm die Pulsweite vorgibt. Man müße also die Sollpulsweite, die durch die ADC Messung vorgegeben wird, von der Istpulsweite aus langsam erreichen. Deine If Bedingung, in der OCR1A um eins verändert wird, steht vermutlich in einer Schleife, die "irre schnell" immer wieder durchlaufen wird. Schneller, als das Servo der durch OCR1A veränderten Pulsweite folgen kann und deshalb versucht es also so schnell es ihm möglich ist, ihr zu folgen.
Gibt sicher viele Möglichkeiten, die Pulsweite von Ist nach Soll langsam zu verändern. Eine simple wäre, die Schleife, in der die IF Abfragen stehen einfach durch ein delay zu verzögern. Welches delay passend ist müßte man ausprobieren. Schätze mal so mit 5ms bis 20ms könnte man anfangen. Dadurch würde auch die ADC Abfrage verlangsamt, sollte aber bei dem Sonnenfolger und den langsamen LDR keine Rolle spielen.
Eine ausgefeilte Möglichkeit bietet oberallgeier an, bedeutet aber auch schon einiges an Einarbeitungszeit.
Kann man auch meistens vernachlässigen. Nur wenn unerklärbare Problem auftauchen, sollte man auch an so etwas denken.@Searcher: Hallo Searcher!
Ja das klingt logisch, dass beim ersten initialisieren die TOP-Werte warscheinlich unsauber sind. Auch wenn ich diese heute beim Test nicht bemerken konnte. Ich werde sie trotzdem anpassen!
Gruß
Searcher
Hoffentlich liegt das Ziel auch am Weg
..................................................................Der Wegzu einigen meiner Konstruktionen
Die Impulsaufbereitung findet komplett in der Hardware statt, soweit ich das kapiert habe.Was meinst du genau mit Verzögerungsglieder? Die Drehgeschwindigkeit verändern?
Die verzögerung kann also nur auf der Eingabeseite für die Servoimpulslänge erfolgen.
Am einfachsten wäre es einfach vor die if Abfrage ein delay_ms einzufügen und zusätzlich die geänderten Werte nur um wenige Werte zu erhöhen bzw. zu erniedrigen.
Über die Anzahl der maximalen änderbaren Schrittweite und der delayzeit kann man die maximale Geschwindigkeit des Servos dann einstellen.
delay_ms hat aber leider den fiesen Nachteil, das dadurch das komplette Hauptprogramm verzögert wird.
Ich benutze dazu dann lieber einen zusätzlichen Timer, der in seinen Overflow Interrupts eine Variable runterzählt.
Wenn die Variable den Wert 0 hat wird die if abfrage durchlaufen, die Variable wieder auf den Initialwert gesetzt und die Impulsweite eingestellt.
Den Grad der Servoverzögerung kann man dabei über den Initialwert der Variable verändern.
Dann wird die if abfrage nicht mehr durchlaufen, bis die Variable wieder den Wert 0 hat.
Will man weitere zeitabhängige Vorgänge Triggern kann man das mit zusätzlichen Variablen in diesem Timer Overflow Interrupt realisieren.
Die Variablen sollten aber als volatile definiert werden, da sie sich ja in einem Interrupt ändern können ( und sollen ).
Diese Methode hat den Vorteil, das das restliche Hauptprogramm nicht ausgebremst wird, wie das bei delay_ms der Fall wäre.
Die konkreten Werte für die Timer Register müsstest Du selber mal imDatenblatt nachschauen.Code:// Timer 0 overflow interrupt service routine interrupt [TIM0_OVF] void timer0_ovf_isr(void) { if (uc_sectimer>0) { uc_sectimer--; } } ... // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: 7,813 kHz TCCR0=0x05; TCNT0=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x01; while (1) { if (uc_sectimer==0) { uc_sectimer=10; if(....//Dein Code }....
Die Aufrufe für die Interrupts sind bei AVR GCC anders, das Beispiel ist für CodeVision AVR.
Für das Hauptprogramm "sieht" es so aus, als ob sich diese Variablen wie von Geisterhand in festen Zeitabständen verringern würden.
Ich würde in deinem System auch ne Realtime Clock ( als externer Chip ) einsetzten um bei ungünstigen Witterungsverhältnissen ( bewölkter Himmel ) eine Position aus einer Tabelle ansteuern zu können.
Die Tabelle kann auch bei gutem Wetter dafür genutzt werden die Werte der Lichtsensoren zu verifizieren und das System am Morgen in eine definierte Startposition zu bringen.
Das hört sich vieleicht ein wenig "Oversized" an aber zumindest im Platinenlayout würd ich diese Option mal vorsehen.
Zudem würde ich mir überlegen einen Getriebemotor mit Spindelantrieb vorzusehen.
Dieser braucht im Ruhezustand keinen Strom und hat dann auch ein relativ gutes Haltemoment.
Als Ansteuerung könnte dann eine H-Brücke dienen.
Servos brauchen auch wenn sie sich nicht bewegen einen Strom für das Haltemoment und die interne Elektronik.
Geändert von wkrug (20.11.2013 um 23:18 Uhr)
Also als erstes habe ich das das langsamer drehen des Servos mit delay_ms ausprobiert mit erfolg.
Des weiteren hab ich das Programm nach den Tipps von Searcher angepasst.
Als nächstes habe ich jetzt mal versucht einen einen Timer für dieses delay_ms zu programmieren.
Wenn in die variable us_counter eine 1 geschrieben wird, soll das 1µs entsprechen (d.h. us_counter =1000 = 1000µs).
Kann das so funktionieren?
Eine Frage habe ich noch: Warum wird erst in der if-Abfrage (if (us_counter == 0)) der Wert von us_counter mit einem Wert beschrieben (us_counter = 1000; ) und nicht schon davor?
GrußCode:#define F_CPU 8000000UL #include <avr/io.h> #include <stdint.h> #include <util/delay.h> #include <avr/interrupt.h> #include <stdio.h> #include "ADC.h" ISR TIMER0_OVF_vect //interrupt für timer 0 { if (us_counter > 0) //wenn us_counter größer als 0 ist, dann... { us_counter--; //us_counter-1 } } int main(void) { uint16_t Servo_soll = 512, Servo_ist; volatile uint16_t us_counter; ADC_Init(); //ADC initialisieren //############################ TIMER1 Einstellen für PWM der Nachführung ####################################### ICR1 = 19999; // ICR1 = 19999 als TOP-Wert, hier werden die 20ms gebildet (8MHz / 8 * (1 + 19999)) = 50Hz OCR1A = 1499; // OCR1A = 1499, Servomittelstellung, (8MHz / 8 * (1 + 1499)) = 666,6Hz = 1,5ms TCCR1A = (1<<COM1A1) | (1<<WGM11); //clear OC1A on compare match, set at bottom (non-inverting) TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS11); //WGM12,WGM13,WGM11 -> Mode14 -> Fast PWM, ICRn, set OCR1A at Bottom | prescaler = 8 DDRD = (1<<PIND5); //PORTD pin 5 auf ausgang //############################ TIMER0 Einstellen für Millisekunden ####################################### OCR0A = 3; //OCR0A = 3 als TOP-Wert festlegen TCCR0A = (1<<WGM01) | (1<<WGM00); //Topwert = 3, CTC-Modus,bei overflow den Timer0 auf 0 setzen TCCR0B = (1<<CS00); //prescaler = 1 -> (8MHz/(2*1*(1+3))=1MHz = 1µs TCNT0 = 0; TIMSK0 = (1<<TOIE0); //interrupts freischalten _delay_ms(2000); // kurz warten bis der Servo sich ausgerichtet hat while(1) { //########################################### Nachführung des Panels ####################################### if (us_counter == 0) { us_counter = 1000; //_delay_ms(1); //der Motor brauch 2s um sich um 180° zu drehen -> (2000 schritte und je schritt 1ms) Servo_ist = ADC_Read(1); //ADC kanal 1 auslesen und in die variable adcwert_1_LDRs speichern if (Servo_ist != Servo_soll) //Wenn der ADC-Wert der LDRs nicht mit dem Servo_soll Wert von 512 überein stimmt, dann... { if (Servo_ist < Servo_soll && OCR1A > 600) //Wenn der ADC-Wert kleiner ist als 512 und 0,6ms noch nicht erreicht sind... { OCR1A = OCR1A--; //verringere OCR1A um 1 } if (Servo_ist > Servo_soll && OCR1A < 2600) //Wenn der ADC-Wert größer ist als 512 und 2,6ms noch nicht erreicht sind... { OCR1A = OCR1A++; //erhöhe OCR1A um 1 } } } } }
Technik_Amateur
Geändert von Technik_Amateur (21.11.2013 um 15:40 Uhr)
Hallo,
ich kann erstmal nur auf die Timer0 Konfiguration eingehen. Leider paßt die Timer0 Konfiguration nicht mit Deinen Kommentaren zusammen.
Nach DB "Table 13-8. Waveform Generation Mode Bit Description" hast Du nicht CTC sondern Fast PWM eingestellt.
Wenn Mode 2 (CTC mit OCR1A als TOP) eingestellt wäre, wäre OCR0A mit 3 für 1µs Interruptfrequenz falsch belegt.
Zur Berechnung hast Du die Formel der Frequenz an OC0A genommen, wenn Du diesen über OCR0A mit COM0A0=1 toggeln läßt.
Für den 1µs Interruptabstand braucht der Timer nur halb so schnell laufen.
Auch ist das falsche Interrupt Enable Bit gesetzt. In DB "Table 13-8. Waveform Generation Mode Bit Description" letzte Spalte, wird das TOV bei MAX gesetzt. MAX wäre beim 8Bit Timer0=0xFF. Du müßtest den Timer/Counter0 Compare Match A interrupt verwenden.
Gruß
Searcher
Hoffentlich liegt das Ziel auch am Weg
..................................................................Der Wegzu einigen meiner Konstruktionen
ohbei dem Modus bin ich wohl in der Zeile verrutscht.
Aber wie kommst du darauf, dass der Timer nur halb so schnell laufen muss?
Ich habe im CTC Modus nur eine Formel gefunden. Im Datenblatt steht nur, dass wenn keine prescaler oder ein niedriger verwendet wird, der CTC Modus keine double buffering funktion mehr hat. Heisst das das die 2 in der Formel raus muss und man nur noch den fclk/(N*(1+OCR0A)) benutzt? Ich kann es mir nicht so richtig erklären.
Ok das mit dem Interrupt leutet mir ein, allerdings muss ich dann zusätzlich noch das OCF0A im TIFR0-Register setzen?
Und die ISR heißt dann so?: ISR TIMER0_COMPA_vect
Gruß
Technik_Amateur
Im Mode 2 läuft der Timer immer von 0 nach OCR0A. Bei OCR0A setzt er das OCF0A Flag und toggelt, falls eingestellt, den OC0A Pin. Für die Frequenz an OC0A beginnt eine Periode. Timer0 springt nach 0 und läuft wieder nach OCR0A. Bei Erreichen OCR0A wird OCF0A Flag wieder neu gesetzt, wenn es zwischendurch gelöscht wurde und OC0A getoggelt. Frequenz: 1/2 Priode ist um und zweite Halbperiode beginnt. Timer -> 0 -> OCR0A. Nun ist eine Periode der Frequenz um, das OCF0A Flag wurde zweimal gesetzt. Also für eine Periodendauer von 1µs durch Toggeln gibt es zwei Compare A Interrupts. -> Läuft Timer halb so schnell, dann Compare A Interrupt Abstand 1µs.
Ja, die 2 muß raus. Siehe Erklärung oben. Für eine Frequenzperiode durch Toggeln gibt es zwei Compare A Interrupts. (Ist anders als im Fast PWM Modus, wenn dort die Frequenz nicht durch Toggeln erzeugt wird. Ist aber weiteres Thema)Ich habe im CTC Modus nur eine Formel gefunden. Im Datenblatt steht nur, dass wenn keine prescaler oder ein niedriger verwendet wird, der CTC Modus keine double buffering funktion mehr hat. Heisst das das die 2 in der Formel raus muss und man nur noch den fclk/(N*(1+OCR0A)) benutzt? Ich kann es mir nicht so richtig erklären.
Sacken lassen...
Na ja, vermutlich meinst Du das Richtige. Statt des TIMSK0 = (1<<TOIE0); muß es TIMSK0 = (1<<OCIE0A); sein. Das OCF0A Flag wird ja von der HW gesetzt. Man kann es durch setzen nur löschen.Ok das mit dem Interrupt leutet mir ein, allerdings muss ich dann zusätzlich noch das OCF0A im TIFR0-Register setzen?
Ja. (Wenn das der korrekte Name in C für den Timer0 Compare Interrupt A ist - ich kann nicht wirklich C)Und die ISR heißt dann so?: ISR TIMER0_COMPA_vect
PS. ...und das "sei;" zum globalen Interrupt Enable nicht vergessen ...
Gruß
Searcher
Geändert von Searcher (21.11.2013 um 22:30 Uhr) Grund: sei hinzugefügt
Hoffentlich liegt das Ziel auch am Weg
..................................................................Der Wegzu einigen meiner Konstruktionen
Heißt schon so, wird aber im AVR-GCC so geschrieben :... die ISR heißt dann so?: ISR TIMER0_COMPA_vect ...
Code:ISR(TIMER0_COMPA_vect)
Ciao sagt der JoeamBerg
Lesezeichen