Damit man den CTC mode kriegt, müßte man WGM12 = 1 setzen. Der Interrupt sollte aber trotzdem gehen, wenn auch seltener. Könnte es sein dass ein globales SEI() fehlt ?
Ok, anscheinend ist das nicht mein Tag, oder ich bin zu dumm um es zu verstehen. Aus irgend einem Grund Löst dieser Timer keinen Interrupt aus.
In der Lib steht nun folgendes:
Damit sollte 20ms nach dem Reset des Timers (TCNT1=0) ein Interrupt ausgelöst werden, und zwar das TIMER1_COMPA_vect wie ich aus der Tabelle von oben herausgefunden habe (ist für ATmega32, und löst bei Comparematsch A aus, wie der name schon zu vermuten lässt).Code://alle 20ms auslösen auf COMPA TCCR1A= (0 << COM1A1) | (0 << COM1A0) | (0 << COM1B1) | (0 << COM1B0) | (0 << FOC1A) | (0 << FOC1B) | (0 << WGM11) | (0 << WGM10); TCCR1B= (0 << ICNC1) | (0 << ICES1) | (0 << WGM13) | (0 << WGM12) | (1 << CS12) //Prescaller = 256 | (0 << CS11) | (0 << CS10); OCR1A = 625; TCNT1 = 0; //Reset TIMSK = (0 << TICIE1) | (1 << OCIE1A) | (0 << OCIE1B) | (0 << TOIE1);
in meinem Mainfile steht folgendes:
Da ich die Servos noch nicht habe, habe ich mir gedacht ich gebe einfach mal etwas auf dem Display aus, und zwar "Interrupt TIMER1", das funktioniert allerdings nicht, da er anscheinend niemals in dieses unterprogramm wechselt.Code:ISR(TIMER1_COMPA_vect) { uint16_t Achse1; uint16_t Achse2; TCNT1 = 0; //reset DDRC |= IO_PC5; DDRC |= IO_PC3; Achse1=Greifer_Achse1*10+250; Achse2=Greifer_Achse2*10+250; PORTC |= IO_PC5; delay_us(Achse1); PORTC &= ~IO_PC5; PORTC |= IO_PC3; delay_us(Achse2); PORTC &= ~IO_PC3; setCursorPosLCD(1,0); writeStringLCD_P("Interrupt TIMER1"); }
Ich habe anschließend Zyklisch abgefragt wie der Wert des Timers ist, das war zwar sehr verwirrend, da sich das ziemlich schnell wechselt, doch ich kann sagen, das er läuft, und zwar von 0 - 65535.
Frage: Warum passiert nix?
Damit man den CTC mode kriegt, müßte man WGM12 = 1 setzen. Der Interrupt sollte aber trotzdem gehen, wenn auch seltener. Könnte es sein dass ein globales SEI() fehlt ?
ich verwende nicht den CTC mode, sondern Compare Match A, da du mir vorher gesagt hast das der CTC mode keine Interrupt auslöst, das sei() ist vorhanden, und es wird nichteinmal nach 5 minuten ein Interrupt ausgelöst
EDIT: Habe das gleiche mit WGM12 gesetz versucht, hat allerdings nichts geändert.
Aha, einmal drüber geschlafen, und schon habe ich die Lösung, das Programm war eigentlich richtig, nur dass ich nicht gewusst habe das das TIMSK register schon bei Timer0 verwendet wird. Bisher dachte ich immer das man mit dem Befehl (1<<OCIE1A) nur dieses eine Bit verändert, dann habe ich an den Satz von SlyD gedacht, der sagte: "C-Compiler finden fast garNICHTS selber" und das bezieht sich sogar auf BitoperationenNaja, jetzt steht das eben weiter unten und wird nicht überschrieben und das beste ist: JETZT FUNKTIONIERTS!!! Danke für eure Hilfe, ich glaube ohne euch würde ich noch einige Tage daran sitzen und dann den RP6 im Schrank verrotten lassen, aber so kann er mir vielleicht einmal mein Glas bringen, das Ziel ist doch immer "James, hole mir den Kaffee!"
Bei mir functioniert das mit folgende code :Timer 1 wird genutzt um servopulsen zu generieren. Forteil : lauft ohne uberhead, da wird nur eine Timerinterrupt getriggerd jeden 1 bis 2mS, je nach den Pulslaenge von actuele Servo.Code:/* Timer 1 is used for servo-puls generation */ ISR(TIMER1_COMPA_vect) { if(servocount==0){PORTC|=IO_PC2;OCR1A=servo4;} if(servocount==1){PORTC&= ~IO_PC2;PORTC|=IO_PC3;OCR1A=servo3;} if(servocount==2){PORTC&= ~IO_PC3;PORTC|=IO_PC5;OCR1A=servo2;} if(servocount==3){PORTC&= ~IO_PC5;PORTC|=IO_PC7;OCR1A=servo1;} if(servocount==4){PORTC&= ~IO_PC7;PORTD|=IO_PD5;OCR1A=servo5;} if(servocount==5){PORTD&= ~IO_PD5;OCR1A=250*12;} servocount++; if(servocount>5) servocount=0; } /* Timer 1 is free for your application! Prescaler = 64, CTC mode, 4µs resolution */ TCCR1A = (0<<WGM10) |(0<<WGM11) |(0<<COM1A1) |(0<<COM1B1); TCCR1B = (1<<WGM12) |(0<<CS12) |(1<<CS11) |(1<<CS10); OCR1A = 1000; // Enable timer interrupts: TIMSK = (1 << OCIE0)|(1<<OCIE1A); uint16_t servo1=350; uint16_t servo2=350; uint16_t servo3=250; uint16_t servo4=500; uint16_t servo5=350; uint8_t servocount=0; void servo (uint8_t nummer,uint16_t puls) { if (puls<200) puls=200; //beveiliging tegen extreme pulslengte!! if (puls>550) puls=550; //beveiliging tegen extreme pulslengte!! if(nummer==1) servo1=puls; if(nummer==2) servo2=puls; if(nummer==3) servo3=puls; if(nummer==4) servo4=puls; if(nummer==5) servo5=puls; }
hast du das funktionierende programm jetzt in der library oder ist das dein richtiges programm?
kannst du bitte noch alles posten, wo du für die timerprogrammierung in der lib bzw. im programm geändert hast?
hab nämlich auch vor diesen timer demnächst zu verwenden.
danke schon mal im voraus.
mfg
Hallo,
also ich habe für diese Programm eigentlich nur die Funktion initRP6 Controll etwas abgeändert:
Dieser Code steht in der Library, hier ist lediglich die Einstellung für Timer 1 und das Interrupt hinzgefügt.Code:void initRP6Control(void) { portInit(); // Setup port directions and initial values. // This is the most important step! cli(); // Disable global interrupts. // UART: UBRRH = UBRR_BAUD_LOW >> 8; // Setup UART: Baud is Low Speed UBRRL = (uint8_t) UBRR_BAUD_LOW; UCSRA = 0x00; UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); UCSRB = (1 << TXEN) | (1 << RXEN) | (1 << RXCIE); // Initialize ADC: ADMUX = 0; //external reference ADCSRA = (0<<ADIE) | (0<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADIF); SFIOR = 0; // Initialize External interrupts - all disabled: MCUCR = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00); GICR = (0 << INT2) | (0 << INT1) | (0 << INT0); MCUCSR = (0 << ISC2); // 10kHz Timer 0: TCCR0 = (0 << WGM00) | (1 << WGM01) | (0 << COM00) | (0 << COM01) | (0 << CS02) | (1 << CS01) | (0 << CS00); OCR0 = 199; //Timer 1 is free for your application! //alle 20ms auslösen auf COMPA TCCR1A= (0 << COM1A1) | (0 << COM1A0) | (0 << COM1B1) | (0 << COM1B0) | (0 << FOC1A) | (0 << FOC1B) | (0 << WGM11) | (0 << WGM10); TCCR1B= (0 << ICNC1) | (0 << ICES1) | (0 << WGM13) | (0 << WGM12) | (1 << CS12) //Prescaller = 256 | (0 << CS11) | (0 << CS10); OCR1A = 625; TCNT1 = 0; //Reset // Timer 2 - used for beeper: TCCR2 = 0; OCR2 = 0xFF; // Enable timer interrupts: TIMSK = (1 << OCIE0) | (1 << OCIE1A); // SPI Master (SPI Mode 0, SCK Frequency is F_CPU/2, which means it is 8MHz // on the RP6 CONTROL M32...): SPCR = (0<<SPIE) | (1<<SPE) | (1<<MSTR) | (0<<SPR0) | (0<<SPR1) | (0<<CPOL) | (0<<CPHA); SPSR = (1<<SPI2X); sei(); // Enable Global Interrupts }
Im meinem "richtigen Programm" ^^ habe ich ein Unterprogramm geschrieben:
es gibt zusätzlich noch 2 Globale Variablen (Greifer_Achse1, und Greifer Achse2 vom Typ uint16_t) hier steht der jeweilige Winkelwert des servos, also für Mittelposition z.b 90. Mit 4 Tastern, die bereits auf der M32 Controll vorhanden sind, wird nun diese Variable verändert:Code:ISR(TIMER1_COMPA_vect) { uint16_t Achse1; uint16_t Achse2; TCNT1 = 0; //reset DDRC |= IO_PC5; DDRC |= IO_PC3; Achse1=Greifer_Achse1*10+250; Achse2=Greifer_Achse2*10+250; PORTC |= IO_PC5; delay_us(Achse1); PORTC &= ~IO_PC5; PORTC |= IO_PC3; delay_us(Achse2); PORTC &= ~IO_PC3; }
Also hier nochmal mein ganzes Hauptprogramm:
Achja, ich verwende ein 4 Zeilendisplay, also musst du eventuell noch etwas ändern.Code:#include "RP6ControlLib.h" //Globale Variablen: uint16_t Greifer_Achse1=90; uint16_t Greifer_Achse2=150; //*** Unterprogramme *** ISR(TIMER1_COMPA_vect) { uint16_t Achse1; uint16_t Achse2; TCNT1 = 0; //reset DDRC |= IO_PC5; DDRC |= IO_PC3; Achse1=Greifer_Achse1*10+250; Achse2=Greifer_Achse2*10+250; PORTC |= IO_PC5; delay_us(Achse1); PORTC &= ~IO_PC5; PORTC |= IO_PC3; delay_us(Achse2); PORTC &= ~IO_PC3; } void displayAktualisieren() { clearPosLCD(3,10,3); setCursorPosLCD(3,10); writeIntegerLCD(Greifer_Achse1,DEC); clearPosLCD(4,10,3); setCursorPosLCD(4,10); writeIntegerLCD(Greifer_Achse2,DEC); } //*** Hauptprogramm *** int main(void) { uint16_t keys; initRP6Control(); initLCD(); clearLCD(); _showScreenLCD_P(PSTR("! Willkommen !"),PSTR("2-Achsen-Greifer:"),PSTR("1. Achse: xxx Grad"),PSTR("2. Achse: xxx Grad")); displayAktualisieren(); while(1) { uint8_t key = getPressedKeyNumber(); switch(key) { case 5: if ((Greifer_Achse1 <= 190) && (Greifer_Achse1 > 80)) { Greifer_Achse1--; displayAktualisieren(); } mSleep(15); break; case 4: if ((Greifer_Achse1 >= 80) && (Greifer_Achse1 < 190)) { Greifer_Achse1++; displayAktualisieren(); } mSleep(15); break; case 3: if (Greifer_Achse2 < 170) { Greifer_Achse2++; displayAktualisieren(); } mSleep(5); break; case 2: if (Greifer_Achse2 > 20) { Greifer_Achse2--; displayAktualisieren(); } mSleep(5); break; } } return 0; }
mfg
Hallo gerko,
vielleicht 2 kleine Anmerkungen zu deiner Lösung:
1.
Ich würde die RP6ControlLib nicht verändern, sondern die Timer-Parameter im eigenen Programm aufnehmen. Das ist aber natürlich eine Geschmacksfrage.
2.
Du nimmst den tollen Timer1, um einen 20ms Interrupt zu erzeugen, dann machst du die Impulserzeugung von 1..2ms in der ISR(TIMER1_COMPA_vect) mit Sleep-Befehlen.
Das ist aus 2 Gründen nicht gut:
a) Man sollte in einer ISR keine Wartebefehle verwenden, weil dadurch an irgendeiner Stelle im übrigen Programm (das könnten auch wichtige Systemroutinen sein!) eine lange Unterbrechung entsteht. Jede ISR sollte so schnell wie möglich ablaufen!
b) Die 20ms, die bei Servos zwischen den Impulsen liegen, sind gar nicht kritisch. Viele Servos funktionieren wunderbar, selbst wenn 30ms zwischen den Impulsen liegen (oder auch nur 10ms).
Das heißt: Man braucht da keinen exakten Timer für die Impuls-Wiederholung alle 20ms, sondern könnte das gut z.B. mit den Stopwatches des RP6 machen.
Wofür man den Timer1 aber gut gebrauchen könnte: Für die exakte Impulsdauer selbst (1..2ms)! Dafür sollte man ihn unbedingt nehmen.
Gruß Dirk
An proevofreak :
Ich habe die RP6Controllig geandert/erweitert. Das sind drei Sachen :
1. Initialisierung von Timer 1 register TCCR1A, TCCR1B, TIMSK. Bei mir lauft er dan mit prescaler 64. An 16 MHz bedeutet das er jeden 4µsek hochgezahlt wird.
2. Ab die Timer 1 die Wert von OCR1A erreicht, wird eine Interrupt Sub Routine ausgelost. Die ist dan programmiert in ISR(TIMER1_COMPA_vect) . In diesen ISR werden verschiedene Ausgangen für servopulses angesteurt.
3. Dan habe ich auch noch in die Library diesen Servo() Function programmiert. In Hauptprogram muss ich nur diese Servo(nummer, pulslaenge) einmal aufrufen. Die Pulserzeugung wird dan automatisch von diesen ISR abgehandelt, die eine Servo nach den andere. Ca jeden 20 mS wird das ganse wieder erhohlt.
ok, das waren ja eine Menge Tips für Verbesserungen und ich habe natürlich gleich versucht das umzusetzen!
Also der Timer 1 läuft jetzt mit einem Prescaller von 8 (für eine noch bessere Genauigkeit
In der Interruptroutine habe ich nun eine Statemaschine verwendet, das hat den vorteil dass man es im Prinzip beliebig erweitern kann, und da die 20ms sowieso nicht so genau sind, laufen diese einfach nach dem letzen Servo ab, Also der Ablauf ist:
Servo 1 Starten und Timer1 einstellen
Servo 1 Stoppen
Servo 2 Starten und Timer1 einstellen
Servo 2 Stoppen
20ms Warten und zurück zu Servo1
das ganze sieht dann so aus:
Ich glaube damit habe ich eine Minimale Interruptzeit, und muss auch keine Stopwatsches verwenden, d.h. alles läuft quasi im Hintergrund ab.Code://Globale Variablen: uint16_t Greifer_Achse1=90; uint16_t Greifer_Achse2=150; uint8_t State; //*** Interrupt von Timer 1 *** ISR(TIMER1_COMPA_vect) { #define Servo1Start 1 #define Servo1Stop 2 #define Servo2Start 3 #define Servo2Stop 4 #define Wait20 5 if (State == Servo1Start) { PORTC |= IO_PC5; TCNT1 = 0; OCR1A = Greifer_Achse1*22+400; State ++; } else if (State == Servo1Stop) { PORTC &= ~IO_PC5; TCNT1= 0; OCR1A = 100; State ++; } else if (State == Servo2Start) { PORTC |= IO_PC3; TCNT1 = 0; OCR1A = Greifer_Achse2*22+400; State ++; } else if (State == Servo2Stop) { PORTC &= ~IO_PC3; TCNT1 = 0; OCR1A = 100; State ++; } else if (State == Wait20) { TCNT1 = 0; OCR1A = 40000; State = Servo1Start; } else { DDRC |= IO_PC5; //Ausgang Servo1 DDRC |= IO_PC3; //Ausgang Servo2 State = Servo1Start; } }
Ich hoffe ihr hab das so gemeint, allerdigns bin ich zufrieden mit dem Ergebnis, die Servos laufen nun um einiges flüsssiger und der Winkel stimmt noch genauer
Als nächstes werde ich versuchen die überaus "hässlichen" Globalen Variablen loszubekommen, hat dafür vielleicht noch jemand einen Tipp?
Danke an alle die mir dabei geholfen haben
mfg Gerko
EDIT: Eine kleine Änderung habe ich noch vorgenommen, anstadt den Timer andauern zurückzusetzen (mit TCNT1=0) zähle ich einfach immer bei OCR1A den neuen Wert dazu. Das hat den Vorteil, dass man OCR1B noch für andere Funktionen verwenden könnte, außerdem spart man damit wieder 2 Taktzyklen.
Lesezeichen