PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Mit Atmega8 Hardware PWM mit Timer2



hosti
24.05.2009, 13:43
Guten Tag

Ich habe auf einem Atmega8, mit 16mhz getacktet, 3 Hardware PWM's am laufen.
Die des Timer 1 laufen auch richtig:


TCCR1A= (1<<Com1A1) | (1<<COM1B1);
TCCR1B = (1<<WGM13) | (1<<CS11);

ICR = 20000; //Dauer 20ms
OCR1A = 1000; //Dauer 1ms
OCR1B = 1000; //"



Jetzt brauche ich aber noch den 3 PWM kanal.


TCCR2 = (1<<WGM20) | (1<<COM21) | (1<<CS21)
OCR2 = 250;

Das ist der Teil von dem ich ausging das er stimmt,
Durch herumspielen habe ich aber gemerkt das hier die Frequenz nicht stimmt.
Mir fehlt hier irgendwie auch die möglichkeit ICR1 aus dem Timer 1.

Nun meine Frage, hat sich jemand mit dem Timer 2 schonmal beschäftigt und weis vieleicht eine lösung?
Danke

pyr0skull
24.05.2009, 13:56
Timer2 ist, wenn ich mich recht erinnere, ein 8-Bit-Timer. Deshalb kann OCR2 nicht den Wert 1000 annehmen.

hosti
24.05.2009, 14:02
Richtig, der Wert war zum rumspielen.

Was mich Wundert ist, dass das Signal von Timer2 gegenüber Timer1 invertiert ist. Obwohl ich nach Einstellungen den selben Modus benutzen müsste.

Auch ist bei 250 die low flanke gerade mal 5microSekunden. Was eigentlich auch nicht stimmen dürfte?

hosti
25.05.2009, 21:53
TCCR2 = (1<<WGM20) | (1<<COM21) | (1<<CS20) | (1<<CS21) | (1<<CS22);
OCR2 = 30;


Ich krieg mit obigem code einen 1ms High Impuls(Sehr gut)

Aber mir fehlt einfach ein ICR1 damit ich die 20ms Periodendauer bestimmen kann. Das sollte doch möglich sein, ich habe jetzt das Datenblatt mehrmals gelesen aber ich find nichts. (Leider spreche/lese ich nicht gerade fliessend Englisch)

hosti
27.05.2009, 21:57
hat keiner eine Idee?

yaro
29.05.2009, 20:01
du kannst versuchen, mit dem Prescaler (und Fast PWM oder Phase Correct PWM) einen Wert für TOP (0xFF) zu generieren, der den 20ms, die du brauchst ähnelt.

Gruß, Yaro

yaro
29.05.2009, 20:13
Ich habe das mal kurz durchgerechnet.....
Mit einem Takt von 16Mhz geht das leider nicht auf...
was du aber machen könntest ist: Du hast doch einen 16bit Timer, der im 20ms-Rhythmus wieder von vorne anfängt. Dies kannst du dir zunutze machen, und diesen Takt für deinen 8bit Timer verwenden, indem du jedes mal, wenn der 16bit Timer bei TOP angekommen ist, einen Interrupt generierst, der deinen 8bit Timer wieder auf 0 setzt.
Du musst nur darauf aufpassen, dass der 8bit Timer etwas länger als 20ms für seinen durchlauf braucht.

Ich hoffe, ich konnte dir helfen.
Gruß, Yaro

hosti
30.05.2009, 09:43
Besten dank für deine Antwort,

Nur das Problem ist, wen ich den Timer so laufen lasse das ich über 20ms komme(mit dem 8bit Timer). Dann ist die Auflösung um zwischen 1 und 2 ms zu variieren extrem tief.

Oder verrechne ich mich da?
Vieleicht hast du dazu eine Idee

yaro
30.05.2009, 17:28
Du hast Recht.....die Auflösung ist wirklich extrem schlecht!
Hier ist dann eine andere Idee, sie kommt zwar einem software-PWM näher, als einem Hardware-PWM, aber so sollte es trotzdem funktionieren:
du stellst ein, dass dein TOP(0xFF) etwas über 2ms liegt.
Du stellst OCR so ein, wie du es brauchst. Nun hast du einen Timer, der deine 2ms am Anfang bedienen kann. Als overflow-interrupt machst du dann folgendes: du stellst deinen 8bit Timer aus!
Und in dem input-capture-interrupt bei deinem 16bit timer(der jede 20ms auftritt) machst du deinen 8bit-timer wieder an.

Es sieht dann folgendermaßen aus: der 16bit timer mach den 8bit-timer an, dieser durchläuft seine 2ms und geht aus.

Vergiss nicht darauf zu achten, dass OC2 nachdem der Timer aus ist auf GMD liegt, und nicht HIGH ist.

So erzielst du eine 10mal höhere Auflösung. Wenn dir das immernoch nicht genug ist, dann ist auch noch mehr möglich, allerdings geht das dann schon deutlicher auf die Leistung des Controllers.

Gruß, Yaro

hosti
31.05.2009, 10:41
Ist bestimmt ein Versuch wert, danke dir



TCCR1A= (1<<Com1A1) | (1<<COM1B1);
TCCR1B = (1<<WGM13) | (1<<CS11);

ICR = 20000; //Dauer 20ms
OCR1A = 1000; //Dauer 1ms
OCR1B = 1000; //

TCCR2 = (1<<WGM20) | (1<<COM21) | (1<<CS22);
OCR = .....


Damit ist mein Timer knapp über 2ms.


ISR(TIMER2_OVF_vect)
{

??
};

Wie schalte ich den Timer den nun aus?
TCCR2 = 0? Resp. wie schalte ich ihn wieder ein. Es wäre toll wen du mir da mit etwas Code aushelfen könntest.

yaro
31.05.2009, 14:48
Du kannst, um den Timer abzuschalten einfach den Prescaler auf 0 setzen (in TCCR2).
Genauso kanst du TCCR2 ganz löschen (auf 0x00 setzten), was fast den selben Effekt hat, nur dass es zusätzlich (zu deinem Vorteil) die normale Port-operation wieder freigibt (also vergiss nicht den PORT als Ausgang, und auf GND zu legen).

Wie schaltest du den Timer wieder an?: du Setzt TCCR2 einfach wieder auf den Wert, der er vorher war, bevor du das Register gelöscht hast.
Und wann stellst du den Timer wieder an?: Hierzu benutzt du den "Input Capture Interrupt" des 16bit-Timers, der ja jede 20ms auftritt. (ISR(TIMER1_CAPT_vect){...}). Vergiss nicht, ihn zu initialisieren: TIMSK |= (1<<TICIE1);
In ihm machst du also: TCCR2 = (1<<WGM20) | (1<<COM21) | (1<<CS22);

So sollte es eigentlich funktionieren. Wenn nicht, schick mir das Programm, dann schau ich mir das nochmal an.

Gruß, Yaro

hosti
31.05.2009, 19:58
Klappt leider nicht, es scheint als würde TCCR2 nicht auf Null gesetzt werden. Oder der Timer gleich wieder gestartet.
Mein Code sieht komplett im Moment so aus:


#include <avr/io.h> // I/O Port definitions
#include <avr/interrupt.h> // Interrupt macros

#define F_CPU 16000000
#define timer 236

volatile int ms = 0;

void pwm_init(void)
{
TIMSK |= (1<<TICIE1);

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8

ICR1 = 20000; //Periodendauer Servo 20ms
OCR1A = 1000; //Servosignal (Port 0)
OCR1B = 1000; //Servosignal (PORT 1)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<COM21) /*| (1<<CS21)*/ | (1<<CS22);
OCR2 = 150;

};

ISR(TIMER2_OVF_vect)
{
TCCR2 = 0x00;
};

ISR(TIMER1_CAPT_vect)
{
TCCR2 = (1<<WGM20) | (1<<COM21) | (1<<CS22);
};

/*void timer_init(void)
{

TCCR0 |= (1<<CS01);
TCNT0 = timer;
TIMSK |= (1<<TOIE0);
TIFR |= (1<<TOV0);
};

ISR(TIMER0_OVF_vect)
{
TCNT0 = timer;
ms++;
if (ms >= 1000)
{
OCR1A++;
ms = 0;
}
if (OCR1A >= 2000)
OCR1A = 1000;

};*/

int main(void)
{
//Initialisierungen
sei(); //Globale Interupts zulassen
pwm_init(); //PWM initialisieren
/*timer_init(); //Timer initialisieren*/

DDRB |= (1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3); //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

while(1)
{

}

}

yaro
31.05.2009, 20:45
Damit ein Interrupt ausgelöst wird muss generell folgendes geschehen: das I-bit in SREG muss gesetzt sein, der Interrupt muss freigeschaltet sein, das interruptauslösende Ereigniss muss auftreten.
2 davon hast du gemacht, 1 nicht. =)

TIMSK | = (1<<TOIE2);

Nicht vergessen: immer, wenn du ein interrupt benutzen willst, musst du ihn erstmal erlauben (freischalten)

Damit sollte es funktionieren.

Und noch ein Tipp: wenn du Konstanten eingibst, die größer sind als 32768, dann schreibe ein UL (= unsigned long) dahinter, denn viele compiler nehmen konstanten generell als ein int auf, was bei den AVRs 16bit ist. Also: F_CPU 16000000UL (ist besser).

Gruß, Yaro

hosti
31.05.2009, 20:53
Ok, das hat was geändert. Danke dir
Aber ich krieg jetzt ein Signal das .5ms high .8ms low .6ms high und dann
14ms low ist.

yaro
31.05.2009, 23:40
wow.....das ist wirklich seltsam......
Wie kommst du auf diese Zeiten? Wie hast du sie gemessen? Mit einem Oszi?
Wenn es digital ist, könntest du dann die Kurven posten (neu und alt)?
Du könntest versuchen, TCNT2 im Interrupt des 16bit Timers auf 0x00 zu setzen, sollte aber nur minimalen Erfolg bringen.

[edit]Versuch den Timer2 mal auf Fast-PWM umzustellen, das sollte eine deutlich Verbesserung bringen. Achte dabei auf die Frequenz, wirst den Prescaler auf eine Stufe höher stellen müssen, da Fast-PWM doppelt so schnell ist, wie phase-correct PWM.

hosti
01.06.2009, 09:55
Ich hab jetzt das ganze invertiert, sprich es wird gesetzt beim hochzählen und gelöscht beim runterzählen.
So funktionierts, mit dem Nachteil das ich auch OCR2 invertiert übergeben muss.

Da gibts aber noch Fast PWM und das funktioniert perfekt (Es sieht auf jedenfall so aus :))

Vielen herzlichen dank für deine wirklich tolle Hilfe =D>

yaro
01.06.2009, 14:33
Immer doch gerne.

Mich interessiert trotzdem noch, wie du die Zeiten gemessen hast =)

Gruß, Yaro

hosti
01.06.2009, 16:45
Achso entschuldige, mit einem Oszi :)
Das Ding ist sowas von praktisch wen's um solche Signale geht.

hosti
09.07.2009, 18:56
Ich krieg die Krise

Die sache läuft ja jetzt... könnte man meinen.

Auf dem Oszi sehen alle Signale gleich aus.

Wen ich die ersten beiden PWMkanäle benutze fahren die Servos bei 1,5ms Highflanke in die mitte,
Wen ich aber den dritten Kanal benutze fährt das Servo an eine Positon die weiter liegt als der mechanische Anschlag. Also völlig falsch da es auch zur Mitte fahren sollte.

Ich hab mit dem Oszi die Signale verglichen. Und der einzige Unterschied von Signal drei zu den anderen ist der, dass es 10ms später startet.
Das heisst die beiden ersten Signale haben im gleichen Moment die Highflanke und das dritte hat erst 10ms später die Highflanke.

Aber das sollte dem Servo doch egal sein, das interessiert sich doch nur für die 1-2ms High und eine Periodendauer von 20ms.

Am Servo liegt es nicht!

Ich bin am verzweifeln.

Die ersten beiden Signale:
http://8ung.at/hosti/s1.jpg
Das erste und das dritte Signal:
http://8ung.at/hosti/s2.jpg

yaro
09.07.2009, 21:20
Hmm das ist wirklich sehr komisch! Dem Servo sollte das eigentlich egal sein... er weiß doch nicht, wie sich die beiden anderen verhalten... und sollte sich dafür auch nicht kümmern.

Wie sieht es denn mit deiner Hardware aus? was genau hast du gebaut? besteht womöglich die Möglichkeit, dass der dritte Servo gestört wird? Hast du vielleicht irgendeinen unbemerkten Kontakt mit einem anderen Leiter? (jetzt nicht auf der Signalleitung sondern auf der Versorgungsleitung des Servos)

Diese 4 (2Paar) Pukte in der unteren Mitte der Bildes, kommen die vom Controller, oder zeichnet sie das Oszi immer dahin?

Gruß, Yaro

hosti
09.07.2009, 21:37
Danke für deine Antwort.

Die 3 Servos sind parallel an der selben Versorgung (je ca. 10mm von einander enfernt).
Ich habe das Signal auch schon mit angeschlossenem Servo gemessen und es ändert sich eigentlich nicht.

Es ist zum verzweifeln.

Die Punkte sind vom Oszi.
Eine Brücke kann ausgeschlossen werden, ich habe die Schaltung 6mal.
Es muss am Signal liegen....

hosti
09.07.2009, 22:44
Ich habe gerade entdeckt, das ich 550microsekunden nach der highflanke Eine "schwingende" spitze habe. Kännte das vieleicht der Grund sein?
Wobei 0,5ms das macht selbst für einen Servo nicht viel aus, oder?
Und wie krieg ich das weg?

http://8ung.at/hosti/s3.jpg

yaro
09.07.2009, 23:50
Es könnte sein, dass diese Spannungsspitze tatsächlich irgendwas ausmacht, denn was anderes fällt mir da nicht wirklich ein...
Könntest versuchen, einen Widerstand in Reihe zu schalten (größe musst du ausprobieren...kann von 10-1000Ohm liegen).
Wenn das nichts bringt, könnte mans mit einem kleinen Kondensator versuchen (kein Elko!). Der Kondensator sollte sehr klein sein, im Berich pico oder einige nano (musst eben auch ausprobieren)

Hoffe, es hilft was.
Gruß, Yaro

hosti
10.07.2009, 09:19
Widerstände sind drin und haben sich eigentlich auch bewährt (also schon länger und auf allen Kanälen)

Ich hab jetzt einmal versucht das Servo auf andere Positionen zu steuern.
Laut Oszi funktioniert das, nur der Servo fährt immer an die selbe Position(An den mech. Anschlag).
Das kann doch nicht sein, dass das Oszi mir ein sauberes Signal anzeigt und der Servo macht was er möchte???

Betreffend der Elektronik.

Ich fahre direkt vom Port auf einen Widerstand und dann auf das Servo,
die Versorgung ist bei allen Kanälen parallel.

Es muss mit der Ansteuerung zusammen liegen.
Die wie folgt aussieht:


ISR(TIMER2_OVF_vect)
{
TCCR2 = 0x00;
}

ISR(TIMER_CAPT_vect)
{
TCCR2 = (1<<WGM20 | (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20);
}

//Initialisierungs Funktion
TIMSK |= (1<<TICIE1) | (1<<TOIE2);

TCCR1A = (1<<COM1A1) | (1<<COM1B1);
TCCR1B = (1<<WGM13) | (1<<CS11);
ICR1 = 20000;
OCR1A = 1500 //1.5ms
OCR1B = 1500;

OCR2 = 187; //ca. 1.5ms
}
Ich habs abgeschrieben, möglich das sich ein Schreibfehler eingeschlichen hat.

The Man
10.07.2009, 09:25
Hi,

was du machen kannst ist dieses IC zu nehemen:
74HC 4017
Das macht dir aus einem Summensignal mehrere Kanäle.
Das bedeutet, du kannst für alle Servos (sofern du mehr als einen nehmen willst) das Signal über den TIMER 1 generieren.
Dazu würde man den Timer so aufsetzten, dass er mit möglichst viel auflösung die und dem richtigen Wert für TOP 20ms braucht.
Den Wert in OCR1 würdest du bei jedem Erreichen von Top mit der nächsten Servoimpulsdauer füllen. Das oben genannte IC macht dir daraus dann wieder einzelne Kanäle.

mfg,
The Man

hosti
10.07.2009, 09:34
Wen es irgendwie möglich ist möchte ich die Schaltung mehr oder weniger so belassen wie sie ist.
Aber danke für die Info.

yaro
10.07.2009, 16:21
Hmm, das ist ein eriklich interessantes Problem!
An der Software sollte es eigentlich nicht liegen, denn das Oszi zeigt es ja alles richtig an!

Liegt OC2 standartgemäß auf GND?
Also: DDRB &= ~(0x08);
PORTB &= ~(0x08);


Wieso fängt der Impuls auf OC" eigentlich erst nach 10ms an?
Der Software nach sollten die ja beide ziemlich gleichzeitig sein....

Gruß, Yaro

hosti
10.07.2009, 17:13
Alle Ports werden als erstes auf GND gelegt.

Wieso OC2 verspätet beginnt habe ich mich auch schon gefragt. erklären kann ich's mir nicht.

Ich habe in der Software auch noch die TWI ISR laufen und dachte die pfuscht vieleicht rein.
Also hab ich das ganze Programm dezimiert bis auf die PWM und das Problem besteht noch immer.

Ich mach kurz Fotos der Platine und stell sie hoch.

Danke für eure Bemühungen

EDIT:
http://8ung.at/hosti/s4.jpg
http://8ung.at/hosti/s5.jpg

yaro
10.07.2009, 20:17
Das Board sieht eigentlich ganz sauber aus. Die Leitung, die beim ISP verläuft berührt die GND-Pins nicht, oder? (kann man schlecht von hier sehen).
ISP ist beim Testen abgeschlossen, oder?

Poste mal dein komplettes (aber auf PWM reduziertes) Programm rein, ich teste es mal bei mir. Wärst du bitte auch so lieb, den Prescaler auf 8MHz umzustellen (beide Timer)?
Hab zwar im mom kein Oszi zur Hand, habe aber andere Möglichkeiten =)

Gruß, Yaro

hosti
10.07.2009, 22:14
Die Leitungen berühren sich nicht, und ich habe 6 Stück von diesen Platinen... es funktioniert bei keiner

Der ISP ist weg beim testen.

Ich poste Morgen den Code.

hosti
11.07.2009, 17:10
Mein originalercode 16Mhz:


#include <avr/io.h> //I/O Port definitions
#include <avr/interrupt.h> //Interrupt macros
#include <util/twi.h> //TWI STATUS

#define F_CPU 16000000UL //CPU Tackt

/*----------------------------------------------------------------------------------------*/
//PWM
/*----------------------------------------------------------------------------------------*/

//1000 = 1ms(links), 1500 = 1,5ms(mitte), 2000 = 2ms(rechts)
volatile int schulter= 0;
volatile int huefte = 0;
volatile int knie = 0;


void pwm_init(int schulter, int huefte, int knie); //PWM_init Funktion
void pwm_chance(int schulter, int huefte, int knie);//Anpassen der PWM grössen

/*----------------------------------------------------------------------------------------*/
//MAIN AUFRUF
/*----------------------------------------------------------------------------------------*/

int main(void)
{
DDRB = 0xFF; //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

sei(); //Globale Interupts zulassen
pwm_init(schulter, huefte, knie); //PWM initialisieren



while(1)
{


}

}


ISR(TIMER2_OVF_vect) //Überlaufinterupt Timer2
{
TCCR2 = 0x00; //Löschen von Timer2
}

ISR(TIMER1_CAPT_vect) //Captureinterupt Timer1
{
TCCR2 = (1<<WGM20) | (1<<WGM21) |(1<<COM21) | (1<<CS22) | (1<<CS20); //Setzten von Timer2
}



void pwm_init(int schulter, int huefte, int knie) //PWM initialisieren
{
TIMSK |= (1<<TICIE1) | (1<<TOIE2); //Interupt initialisieren, freischalten

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8
ICR1 = 20000; //Periodendauer Servo 20ms
OCR1A = schulter; //Servosignal (Port 0, Schulter)
OCR1B = huefte; //Servosignal (PORT 1, Hüfte)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20); //Fast PWM
OCR2 = (knie/8); //Servosignal (Port2, Knie)

}



Ich muss den Code noch auf 8mhz anpassen. Ich versuche noch heute dazu zu kommen.

yaro
11.07.2009, 19:45
A propos... wenns nicht klappt (aus welchen Gründen auch immer), könnte ich dir auch noch ein Software-PWM anbieten. Es verbraucht zwar etwas Rechenleistung (um genau zu sein ca. 2,5 von 20ms), aber es funktioniert.

Aber natürlich werden wir zuerst versuchen es so hinzubekommen, wie es jetzt ist.

Ich bin gerade dabei, ein Software-PWM zu schreiben (in ASM), dass in 2,5 von 20ms gleichzeitig 18 Servos bedienen kann (für meinen Hexa). Ob es gelingt, weiß ich noch nicht, aber mit 5 von 20ms sollte es sicherlich funktionieren).
Dann könntest auch du dieses Prog beutzen, um alle Servos gleichzeitig mit einem Controller anzusteuern. Ich schreibe das Prog für einen Atmega 644, weil der 20MHz kann und mehr pins hat.

Gruß, Yaro

hosti
11.07.2009, 20:19
Ursprünglich habe ich auch mit einem Atmega644 mit 20Mhz gearbeitet und Software PWM eingesetzt. Das hat aber nicht zufriedenstellend funktioniert.
Deshalb diese Lösung.......

(Bin nicht zuhause, deshalb auch noch kein angepasster Code)

hosti
12.07.2009, 13:13
Hier der Code, sorry hat etwas gedauert:


#include <avr/io.h> //I/O Port definitions
#include <avr/interrupt.h> //Interrupt macros
#include <util/twi.h> //TWI STATUS

#define F_CPU 16000000UL //CPU Tackt

/*----------------------------------------------------------------------------------------*/
//PWM
/*----------------------------------------------------------------------------------------*/

//750 = 1.5ms
volatile int schulter= 750;
volatile int huefte = 750;
volatile int knie = 750;


void pwm_init(int schulter, int huefte, int knie); //PWM_init Funktion
void pwm_chance(int schulter, int huefte, int knie);//Anpassen der PWM grössen

/*----------------------------------------------------------------------------------------*/
//MAIN AUFRUF
/*----------------------------------------------------------------------------------------*/

int main(void)
{
DDRB = 0xFF; //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

sei(); //Globale Interupts zulassen
pwm_init(schulter, huefte, knie); //PWM initialisieren



while(1)
{


}

}


ISR(TIMER2_OVF_vect) //Überlaufinterupt Timer2
{
TCCR2 = 0x00; //Löschen von Timer2
}

ISR(TIMER1_CAPT_vect) //Captureinterupt Timer1
{
TCCR2 = (1<<WGM20) | (1<<WGM21) |(1<<COM21) | (1<<CS22) | (1<<CS20); //Setzten von Timer2
}



void pwm_init(int schulter, int huefte, int knie) //PWM initialisieren
{
TIMSK |= (1<<TICIE1) | (1<<TOIE2); //Interupt initialisieren, freischalten

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8
ICR1 = 10000; //Periodendauer Servo 20ms
OCR1A = schulter; //Servosignal (Port 0, Schulter)
OCR1B = huefte; //Servosignal (PORT 1, Hüfte)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20); //Fast PWM
OCR2 = (knie/8); //Servosignal (Port2, Knie)

}



Müsste so für 8Mhz passen

yaro
12.07.2009, 14:53
soooooooo =)
ich hab mir das alles nochmal angeschaut, und tatsächlich hat es bei mir auch nicht funktioniert (was zu erwarten war).
Komisch war es dennoch, denn... der Servo ließ sich per Hand zwar in die eine Richtung drehen, in die andere sperrte er aber. Ich kenn mich zwar mit der Elektronik von Servos nichtt richtig aus, aber es deutet schon darauf hin, dass da irgendwo was kleines schief läuft.
Dann hab ich mich nochmal an deine Spannungsspitze erinnert und versucht, in der Software eine Erklärung dafür zu finden, und wurde fündig (zumindest glaube ich, dass es "diese" Spitze war (hab wie gesagt grad kein Oszi zur Hand).
Und zwar ist das Problem folgendes: Beim Overflow-Interrupt wird ja der Timer abgestellt, was dazu führt, dass die PWM-Unit nicht mehr die Kontrolle über den Port hat, sondern sie an die normalen Portbefehle übergibt. Und dabei kommt es wahrscheinlich zu dieser Spitze.
Wie ändert man das nun? Ganz einfach! Man schaltet den Timer nicht mit dem overflow-interrupt, sondern mit dem compare-interrupt ab. Zu den Zeitpunkt macht die Spannungsspitze nämlich nix aus =)

Ich hab den Code ausprobiert, er funktioniert!



#include <avr/io.h> //I/O Port definitions
#include <avr/interrupt.h> //Interrupt macros
#include <util/twi.h> //TWI STATUS


/*----------------------------------------------------------------------------------------*/
//PWM
/*----------------------------------------------------------------------------------------*/

//750 = 1.5ms
volatile int schulter= 750;
volatile int huefte = 750;
volatile int knie = 750;


void pwm_init(int schulter, int huefte, int knie); //PWM_init Funktion
//void pwm_chance(int schulter, int huefte, int knie);//Anpassen der PWM grössen

/*----------------------------------------------------------------------------------------*/
//MAIN AUFRUF
/*----------------------------------------------------------------------------------------*/

int main(void)
{
DDRB = 0xFF; //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

sei(); //Globale Interupts zulassen
pwm_init(schulter, huefte, knie); //PWM initialisieren



while(1)
{


}

}


ISR(TIMER2_COMP_vect) //Überlaufinterupt Timer2
{
TCCR2 = 0x00; //Löschen von Timer2
}

ISR(TIMER1_CAPT_vect) //Captureinterupt Timer1
{
TCNT2 = 0;
TCCR2 = (1<<WGM20) | (1<<WGM21) |(1<<COM21) | (1<<CS22) | (1<<CS20); //Setzten von Timer2
}



void pwm_init(int schulter, int huefte, int knie) //PWM initialisieren
{
TIMSK |= (1<<TICIE1) | (1<<TOIE2); //Interupt initialisieren, freischalten

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8
ICR1 = 10000; //Periodendauer Servo 20ms
OCR1A = schulter; //Servosignal (Port 0, Schulter)
OCR1B = huefte; //Servosignal (PORT 1, Hüfte)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20); //Fast PWM
OCR2 = (knie/8); //Servosignal (Port2, Knie)

}


Gruß, Yaro

hosti
12.07.2009, 15:24
Vielen Dank für deine bemühungen.

Ich habs jetzt ausprobiert. Und es hat funktioniert.. fast. Der Servo hat nicht richtig kraft und knurrt :)
Was auf ein falsches Timing des Signals deutet.
Und mit dem dem Oszi kontrolliert hat sich gezeigt, das die Perioden dauer zu kurz ist(2ms anstatt 20ms).

Und ich frage mich wieso du TCBT2 auf 0 setzt in der Capture ISR, das führt bei mir zu einem unsymetrischen Signal...

Ich bin heut irgendwie echt nicht ganz fit, ich versteh mein eigenes Programm nicht mehr richtig ](*,)

yaro
12.07.2009, 16:02
Wir lassen den Timer ja nicht mehr zuende laufen, sondern brechen bei dem compare-Wert ab, was bedeutet dass der Timer sich nicht selber auf 0 stellt, sondern bei dem compare-Wert bleibt. Wenn wir ihn nicht zurückstellen, dann wird das Signal unregelmäßig, denn beim ertsen mal bricht er nach dem comp-Wert ab, beim zweiten läuft er erstmal zuende durch, fängt von vorne an und bricht wieder beim comp-Wert ab, was natürlich einen längeren Impuls zufolge hat.

Bist du dir sicher, dass du das Programm richtig abgeschrieben hast? Bei mir funktioniert es nämlich so wie es ist ganz gut, und der Servo hat auch die volle Kraft. Versuch es doch mal bei dir reinzukopieren, und nur die Prescaler zu ändern.

a propos.... ich habe auch noch die #define F_CPU 16000000UL weggenommen.....die solltest du auch wieder setzten, sonst funktioniert es nicht richtig.

Bin mal gespannt, obs jetzt klappt.

Gruß, Yaro

hosti
12.07.2009, 16:10
Ich denke schon das ich es richtig übernommen habe.

Die einzigen änderungen sind ja:

ISR(TIMER2_COMP_vect)
und
in der ISR TIMER1_CAPT_vect
TCNT2 = 0;


die CPU definition hab ich drin und natürlich den rest an 16Mhz angepasst.

Aber da scheint echt was nicht zu passen.
So siehts jetzt aus:

#include <avr/io.h> //I/O Port definitions
#include <avr/interrupt.h> //Interrupt macros
#include <util/twi.h> //TWI STATUS

#define F_CPU 16000000UL //CPU Tackt

/*----------------------------------------------------------------------------------------*/
//PWM
/*----------------------------------------------------------------------------------------*/

//750 = 1.5ms
volatile int schulter= 1500;
volatile int huefte = 1500;
volatile int knie = 1500;


void pwm_init(int schulter, int huefte, int knie); //PWM_init Funktion
void pwm_chance(int schulter, int huefte, int knie);//Anpassen der PWM grössen

/*----------------------------------------------------------------------------------------*/
//MAIN AUFRUF
/*----------------------------------------------------------------------------------------*/

int main(void)
{
DDRB = 0xFF; //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

sei(); //Globale Interupts zulassen
pwm_init(schulter, huefte, knie); //PWM initialisieren



while(1)
{


}

}


ISR(TIMER2_COMP_vect) //Überlaufinterupt Timer2
{
TCCR2 = 0x00; //Löschen von Timer2
}

ISR(TIMER1_CAPT_vect) //Captureinterupt Timer1
{
TCNT2 = 0;
TCCR2 = (1<<WGM20) | (1<<WGM21) |(1<<COM21) | (1<<CS22) | (1<<CS20); //Setzten von Timer2
}



void pwm_init(int schulter, int huefte, int knie) //PWM initialisieren
{
TIMSK |= (1<<TICIE1) | (1<<TOIE2); //Interupt initialisieren, freischalten

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8
ICR1 = 20000; //Periodendauer Servo 20ms
OCR1A = schulter; //Servosignal (Port 0, Schulter)
OCR1B = huefte; //Servosignal (PORT 1, Hüfte)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20); //Fast PWM
OCR2 = (knie/8); //Servosignal (Port2, Knie)

}

yaro
12.07.2009, 17:37
hmm, das ist wirklich sehr ungewöhnlich....
Ich weiß jetzt aber, wieso der dritte Servo bei dir um 10ms verschoben ist: das ist so, weil du beim 16bit Timer ein phase-correct PWM benutzt, da ist der Overflow natürlich in der Mitte der Ganzen Zeit, und nicht am Ende, wie beim Fast-PWM. Das sollte aber keine Probleme bereiten.

Die Periodenlängen und so sind alle richtig umgestellt.

Könntest du vielleicht noch einige Messungen mit dem Oszi hochladen?
Die vom neuen Programm.
Das ist wirklich komisch alles...

Gruß, yaro

hosti
12.07.2009, 17:41
ok, ich stell gleich mal ein paar fotos hoch

Der neue Code erzeugt folgendes Signal ( Bild 1 und 2)
http://8ung.at/hosti/s6.jpg
http://8ung.at/hosti/s7.jpg

Und dieses Signal ist ohne das auf Null setzen von TCNT2
http://8ung.at/hosti/s8.jpg

yaro
12.07.2009, 18:17
Es scheint so zu sein, dass der compare-interrupt den Timer nicht ausschaltet. Aus welchen Gründen auch immer.
Die Rücksetzung des Timers auf 0 ist aber auf jeden Fall richtig. (TCNT2 = 0)

Ich habe es grad getestet, und es ist tatsächlich so, dass der compare-Interrupt garnicht auftritt. Jetzt muss nur noch geklärt werden, warum nicht.

Gruß, Yaro

yaro
12.07.2009, 18:20
Hahahaha, natürlich tritt er nicht auf! hahahaha
so ein Anfängerfehler hahaha.
man muss ihn erstmal in der Mask freigeben.

Man sieht....Anfängerfehler macht man immer =)

Ich versuche es gleich, ob es dann funktionieren wird =)

Gruß, Yaro

yaro
12.07.2009, 18:24
Nunja.....Der interrupt funktioniert jetzt zwar, allerdings funktioniert jetzt der Rest nicht =)
Ich guck nochma woran das liegen kann....

Gruß, Yaro

yaro
12.07.2009, 18:39
Ich habe den Fehler gefunden: Hardware Fast PWM funktioniert folgendermaßen: bei überganz von TOP zu BOTTOM wird auf HIGH gesetzt, und beim compare-match auf LOW.
Mit dem Programm haben wir jetzt das "HIGH setzen" übersprungen, was natürlich scheiße ist....

Hier ist das fertige Programm (8MHz)


#include <avr/io.h> //I/O Port definitions
#include <avr/interrupt.h> //Interrupt macros
#include <util/twi.h> //TWI STATUS


/*----------------------------------------------------------------------------------------*/
//PWM
/*----------------------------------------------------------------------------------------*/

//750 = 1.5ms
volatile int schulter= 750;
volatile int huefte = 750;
volatile int knie = 750;


void pwm_init(int schulter, int huefte, int knie); //PWM_init Funktion
//void pwm_chance(int schulter, int huefte, int knie);//Anpassen der PWM grössen

/*----------------------------------------------------------------------------------------*/
//MAIN AUFRUF
/*----------------------------------------------------------------------------------------*/

int main(void)
{
DDRB = 0xFF; //B... AUSGANG
PORTB &= ~((1<<PORTB1) | (1<<PORTB2) | (1<<PORTB3)); //B.. Low

sei(); //Globale Interupts zulassen
pwm_init(schulter, huefte, knie); //PWM initialisieren



while(1)
{


}

}


ISR(TIMER2_COMP_vect) //Überlaufinterupt Timer2
{
TCCR2 &= 0x00; //Löschen von Timer2
}

ISR(TIMER1_CAPT_vect) //Captureinterupt Timer1
{
TCNT2 = 255;
TCCR2 = (1<<WGM20) | (1<<WGM21) |(1<<COM21) | (1<<CS22) | (1<<CS20); //Setzten von Timer2
}



void pwm_init(int schulter, int huefte, int knie) //PWM initialisieren
{
TIMSK |= (1<<TICIE1) | (1<<OCIE2); //Interupt initialisieren, freischalten

//Timer 1 (Port 0/1)
TCCR1A = (1<<COM1A1)| (1<<COM1B1); //Clear OC on Comp Match when up-count, set downcount
TCCR1B = (1<<WGM13) | (1<<CS11) ; //PWM Phase abd Freq Correct. Prescaler 8
ICR1 = 10000; //Periodendauer Servo 20ms
OCR1A = schulter; //Servosignal (Port 0, Schulter)
OCR1B = huefte; //Servosignal (PORT 1, Hüfte)

//Timer 2 (Port 2)
TCCR2 = (1<<WGM20)| (1<<WGM21) | (1<<COM21) | (1<<CS22) | (1<<CS20); //Fast PWM
OCR2 = (knie/8); //Servosignal (Port2, Knie)

}

hosti
12.07.2009, 18:52
=D>

Es funktioniert. Das Signal ist sehr sauber und auch die angeschlossenen Servos funktionieren. Echt toll. Vielen herzlichen dank.

Jetzt muss ich mir die Sache noch genauer ansehen damit ich nachvollziehen kann woran es lag.

yaro
12.07.2009, 19:10
Sehr schön!
Achte übrigens darauf, dass du beim dritten Servo immer 1 abziehen musst (statt 1500 nur noch 1499), weil der Timer nicht bei 0, sondern bei sozusagen -1 anfängt

Gruß, Yaro

hosti
12.07.2009, 19:16
werd ich beachten, danke schön