PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Timer1 verwenden



Gerko
08.02.2009, 21:53
Hallo!

Ich habe eine simple Frage, wie kann ich beim Timer1 einen Interrupt auslösen?, ich habe ihn glaub ich schon eingestellt, also ctc mode, prescaler,... und jetzt sollte er wenn er OCR1A erreicht einen Interrupt auf TOV1 auslösen. Nur ich weiß nicht wo die Interruptvektortabelle ist. Kann mir da jemand helfen?

SlyD
09.02.2009, 10:43
Hallo,

wo jetzt - RP6 M32 Erweiterungsmodul?
Auf dem Roboter selbst wird Timer 1 schon für die Motoren verwendet (PWM) - nur auf dem Erweiterungsmodul ist der noch frei...

Schau Dir doch einfach mal den Library Code an - da wird ja schon einer der Timer verwendet. Wie dann der passende Timer1 Interrupt heisst kannst Du dann raten bzw. im Datenblatt nachlesen ;)

MfG,
SlyD

Gerko
09.02.2009, 10:52
Ok, es handelt sich ums Erweiterungsmodul, also das M32.
Mit Raten hab ichs nicht so, und die Library habe ich bereits Stundenlang danach durchforstet, anscheinden hab ich da was übersehn...

Das mit dem Datenblatt, also in Assembler wäre das kein Problem, nur ich programmier hald noch nicht lange in C, und jetzt weiß ich nicht wie ich zur Adresse $012 (das ist die Timer1 OVF) einen Sprungbefehl auf mein Unterprogramm geben kann.

mfg Gerko

SlyD
09.02.2009, 11:44
Stundenlang?

Such mal nach (in RP6RobotBaseLib.c bzw. RP6ControlLib.c):
ISR(TIMER0_COMP_vect)
(recht weit unten)


Der Compiler setzt an die Adressen automatisch Sprungbefehle zu den entsprechend deklarierten ISRs. Die sind alle nach demselben Schema benannt: HardWareMODUL_EVENT_vect...

MfG,
SlyD

uwegw
09.02.2009, 11:46
Die Interruptroutine sieht so aus:

ISR(TIMER1_OVF_vect )
{
/* Interrupt Code */
}

Die nötigen Namen der Vektoren findet man in der Doku der AVR-libc, auf der Seite zur <avr/interrupt.h>:
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Gerko
09.02.2009, 11:47
Wow, danke vielmals, ich werd das gleich ausprobieren wenn ich nach Hause komme
DANKE

Gerko
09.02.2009, 17:00
Problem:
Leider hat das auch noch nicht ganz funktioniert, der Compiler schreibt mir folgende Fehlermeldungen hin:


../RP6Lib/RP6control/RP6ControlLib.c: In function 'initRP6Control':
../RP6Lib/RP6control/RP6ControlLib.c:935: error: static declaration of '__vector_9' follows non-static declaration
../RP6Lib/RP6control/RP6ControlLib.c:934: error: previous declaration of '__vector_9' was here
../RP6Lib/RP6control/RP6ControlLib.c: In function '__vector_9':
../RP6Lib/RP6control/RP6ControlLib.c:936: warning: implicit declaration of function 'servoAnsteuerung'

Die Einstellungen für den Timer 1 sehen wie folgt aus:

TCCR1A= (0 << COM1A1)
| (0 << COM1A0)
| (0 << COM1B1)
| (0 << COM1B0)
| (0 << FOC1A)
| (0 << FOC1B)
| (0 << WGM11)
| (0 << WGM10);

TCCR1B= (0 << ICNC1)
| (0 << ICES1)
| (0 << WGM13)
| (1 << WGM12) //CTC-Mode (Auslösen bei OCR1A-->TOV1 to MAX
| (1 << CS12) //Prescaller = 256
| (0 << CS11)
| (0 << CS10);

OCR1A = 625;

TIMSK = (0 << TICIE1)
| (0 << OCIE1A)
| (0 << OCIE1B)
| (1 << TOIE1); //wird ausgelöst wenn TOV1 set


ISR(TIMER1_OVF_vect)
{
servoAnsteuerung();
}

Ich habe das direkt in die bereits vorhandene RP6Controlllib.c geschrieben. Das Unterprogramm servoAnsteuerung steht in einem anderen File (wo auch das Hauptprogramm steht) aber deswegen müsste er es doch trotzdem finden. Warum funktioniert der Timer1 nicht so wie ich das mache? Ich habe mir auch die funktion von TIMER0 angesehen, aber das ist im Prinzip das gleiche denke ich.


Aber ich habe auch eine erfreuliche Nachricht:
Inzwischen ist das 4x20 Display vom Conrad gekommen, ich habe schon geschafft auf dem ziemlich viel Text auszugeben und vielleicht bau ich mir in den nächsten Tagen zusätzlich zur Zustandsanzeige, die derzeit die Position der einzelnen Servos angibt noch eine kleine Wetterstationd dazu.

SlyD
09.02.2009, 17:09
> deswegen müsste er es doch trotzdem finden.

NEIN.
C-Compiler finden fast garNICHTS selber ;)
Deklarier die ISR doch einfach in deinem Hauptprogramm - das hat in der Library eigentlich nix verloren denn alle anderen Programme (die Beispielprogramme z.B.) haben diese servoAnsteuerung() Funktion ja gar nicht...
In einer Library steht normalerweise nur allgemeiner Code der mit allen Programmen die die lib nutzen funktioniert.


Ist der Code da oben exakt so in der Lib drin?
Tipp: Funktion (hier die ISR) innerhalb einer Funktion deklarieren ist meistens böse ;)

MfG,
SlyD

Besserwessi
09.02.2009, 17:38
Im CTC mode des Timers sollte er overflow Interrupt normalerweise nicht auftreten. Da müßte man Ersatzweise den output compare Interrupt nehmen.

Gerko
09.02.2009, 17:41
Wow, danke keine Fehler mehr, du bist eine Maschine :)

Der Timer macht zwar noch nichts, aber das heißt nur das ich etwas aus dem Datenblatt falsch gelesen habe.

Danke für die rasche Hilfe, da macht das Programmieren wirklich Spaß!

Gerko
09.02.2009, 18:33
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:

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

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

in meinem Mainfile steht folgendes:


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");
}

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.

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?

Besserwessi
09.02.2009, 20:10
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 ?

Gerko
09.02.2009, 20:51
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.

Gerko
10.02.2009, 18:08
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 Bitoperationen :) Naja, 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!"

RP6conrad
10.02.2009, 18:52
Bei mir functioniert das mit folgende 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;
}

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.

proevofreak
11.02.2009, 08:41
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

Gerko
11.02.2009, 12:30
Hallo,

also ich habe für diese Programm eigentlich nur die Funktion initRP6 Controll etwas abgeändert:


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
}

Dieser Code steht in der Library, hier ist lediglich die Einstellung für Timer 1 und das Interrupt hinzgefügt.

Im meinem "richtigen Programm" ^^ habe ich ein Unterprogramm geschrieben:


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;
}

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:

Also hier nochmal mein ganzes Hauptprogramm:

#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;
}

Achja, ich verwende ein 4 Zeilendisplay, also musst du eventuell noch etwas ändern.

mfg

Dirk
11.02.2009, 17:33
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

RP6conrad
11.02.2009, 19:33
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.

Gerko
11.02.2009, 21:27
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:

//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 glaube damit habe ich eine Minimale Interruptzeit, und muss auch keine Stopwatsches verwenden, d.h. alles läuft quasi im Hintergrund ab.
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.