Hallo, ich bin noch recht frisch in der MC Welt. Ich möchte sechs Servo-Motoren mit einem ATmega48 betreiben. Dazu benötige ich ein Fast-PWM Signal, mit einem Grundtakt von 20 ms und variablen Impulslängen (1-Signal) von 1 ms bis 2 ms. Ich habe ein erstes Experiment mit Timer1 gemacht: #include <avr/io.h> #include <avr/interrupt.h> #include <avr/signal.h> #include <avr/delay.h> // Geblinkt wird PortB.1 (push-pull) #define PAD_LED 1 #define PORT_LED PORTB #define DDR_LED DDRB #define F_CPU 1000000 uint8_t temp; void timer1_init() { TCCR1A = (1 << WGM12) | (1 << WGM11) | (1 << WGM10) | (1 << COM1A1); TCCR1B = (1 << CS11); OCR1A = 62; TIMSK1 |= (1 << OCIE1A); } SIGNAL (SIG_OUTPUT_COMPARE1A) { if ((PAD_LED << PORT_LED) == 1) { PORT_LED |= (1 << PAD_LED); } else { PORT_LED &= ~(1 << PAD_LED); } } int main() { DDR_LED |= (1 << PAD_LED); timer1_init(); sei(); while (1) { temp = 0; while(temp < 6) { _delay_ms(262); temp++; } if (OCR1A == 62) OCR1A = 125; else OCR1A = 62; } return 0; } Ich benutze Mode 7. Somit habe ich FastPWM mit einer Auflösung N von 1024 (10 Bit). Mein CPU-Takt (F_CPU) sind die internen 1 MHz. Und einen eingestellten Vorteiler von 8 (CS11). Grundfrequenz = (F_CPU / Vorteiler) / (N * 2) = (1e+06 Hz / 8) / (1024 * 2) = ~61 Hz. --> 16,4 ms. Impulslänge 1 ms: 1024 / 16.4 * 1 = 62 Impulslänge 2 ms: 1024 / 16.4 * 2 = 125 Die 16,4 ms Näherungsweise an meinen 20 ms. OCR1A wechselt testweise zwischen 62 und 125. Nun möchte ich die Grundfrequenz aber genau einstellen. Dazu möchte ich Mode 14 verwenden und TOP über ICR1 festlegen: void timer1_init() { TCCR1A = (1 << WGM13) | (1 << WGM12) | (1 << WGM11) | (1 << COM1A1); TCCR1B = (1 << CS11); ICR1 = 0x03FF; OCR1A = 40; TIMSK1 |= (1 << OCIE1A); } Ich habe ICR1 auf den ursprünglichen Wert von Mode 7 gesetzt. Nur ist das Ergebnis nicht mit dem oberen Programm identisch. Der Grundtakt beträgt nu 8.2 ms, also doppelt so schnell. Auch Veränderungen an ICR1 haben keine Auswirkungen. Die Impulslänge wechselt aber noch korrekt zwischen 1 ms und 2 ms. Wo ist mein Fehler? Wieso wird ICR1 nicht ausgewertet? Ich habe es auch mit ICR1H = 0x3 und ICR1L = 0xFF versucht, was das gleiche Ergebnis lieferte. Viele Gruesse Martin (martin.baum AT berlin.de)
Weiterhin habe ich noch eine Frage. ;-) Ich möchte 6 Servo-Motoren betreiben. Dies könnte ich mit allen 6 PWM-Kanälen des ATmega48 machen. Ist die Ansteuerung von Timer0 und Timer2 ähnlich? Meine alternative Idee war es, die Grundfrequenz meines Timer1 nicht auf 20 ms, sondern auf 20/6 ms zu stellen. Da die benötigte Impulslänge 2 ms beträgt und die Taktlänge insgesamt 20ms lang ist, könnte ich innerhalb eines Taktes auch alle Impulse Zeitversetzt anordnen. Die kleine Verzögerungen wäre vernachlässigbar. Sprich, ich bekomme alle 20/6 ms einen Interrupt und steuer in der ISR immer einen anderen Ausgang des Port C an, an dehnen die Motoren hängen. Ist das pragmatisch oder ist Verwendung aller Timer und PWM-Ausgänge zu empfehlen? Vielen Dank für eure Tips. Martin
Dir ist klar, dass WGM12 und WGM13 in TCCR1B stehen und nicht in TCCR1A?
> Ist die Ansteuerung von Timer0 und Timer2 ähnlich?
Timer 0 und 2 sind 8-Bit-Timer. In der Hinsicht ist da schon ein
erheblicher Unterschied in der Konfiguration.
Ja, danke. Ich habe mein TCCR1B auch gerade entdeckt. :-( void timer1_init() { TCCR1A = (1 << WGM11) | (1 << COM1A1); TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS11); ICR1 = 0x0800; OCR1A = 80; TIMSK1 |= (1 << OCIE1A); } Funktioniert nun korrekt. Nun wird alle 16,4 ms ein Interrupt ausgeführt. Da komme ich zu meinem nächsten Problem, 6 Motoren zu betreiben. // Motoren haengen alle an Port C #define DDR_MOTOR DDRC #define PORT_MOTOR PORTC #define MOTOR_1 5 #define MOTOR_2 4 uint8_t motor; // motor = 0; SIGNAL (SIG_OUTPUT_COMPARE1A) { motor++; PORT_MOTOR &= ~(1 << MOTOR_1); // Motor1 aus PORT_MOTOR &= ~(1 << MOTOR_2); // Motor2 aus if (motor == 1) { PORT_MOTOR |= (1 << MOTOR_1); // Motor1 ein } else { motor = 0; PORT_MOTOR |= (1 << MOTOR_2); // Motor2 ein } } Dies ist natürlich alles Schwachsinnig, da der Interrupt nur alle 16 ms kommt und die Ausgangspins MOTOR_1, ... in dieser Zeit nicht verändert werden. Erst beim nächsten Interrupt. Nur der PWM-Ausgang verhält sich korrekt. Wie kann ich sinnvoll 6 Ausgangspins mit PWM-Signalen beschalten? Geht das Softwaremäßig über Interrupts wie oben beschrieben oder nur durch 6 echte PWM-Ausgänge? Möglichkeit 1: Ich erzeuge einen normalen Takt, der z.B alle 16/120 ms einen Interrupt auslöst. Die ersten 20 ISR-Aufrufe sind für Motor1 und bieten im eine Auflösung von 20. Die nächsten 20 ISR-Aufrufe sind für Motor2, usw. Möglichkeit 2: Alle Timer mit PWM-Ausgängen verwenden. Was ist sinnvoller? Danke schon einmal für weitere Tips.
Die Idee mit dem Umschalten finde ich sogar sehr gut. Benutz doch dazu den Timer Overflow Interrupt. Ein shift und eine Kontrolle, ob du schon im 7. Bit angelangt bist, sollte genügen. Dann muss natürlich noch der PWM-Wert für den entsprechenden Pin aktualisiert werden.
> Wie kann ich sinnvoll 6 Ausgangspins mit PWM-Signalen beschalten? Geht > das Softwaremäßig über Interrupts wie oben beschrieben oder nur durch 6 > echte PWM-Ausgänge? Falls Du mit "PWM" Signale für Modellbauservos meinst, dann geht das auch in Software ohne Verwendung der PWM-Features der Timer. Ein Beispiel mit Mega48 findest Du hier: http://www.hanneslux.de/avr/mobau/7ksend/7ksend02.html ...
@ Martin Baum: Wenn Du sonst nichts auf dem µC groß zu tun hast kannst Du eine Software PWM realisieren, die dann auf beliebigen Pins ausgegeben werden kann. Dazu reicht ein Timer aus und in dessen ISR mußt Du die Pins entsprechend toggeln, so wie Du das ja schon tust ;) Ansonsten bleibt Dir nichts anderes übrig als die PWM-Pins zu nehmen, also PB1, PB2, PB3, PD3, PD5 und PD6 ! Ich persönlich würde die Hardware PWM nehmen und alle Timer als 8bit Fast PWM initialisieren. Nach Deinem Code zu urteilen benutzt Du den internen RC-Oszillator bei 1MHz. Wenn Du einen externen Quarz mit z.B. 8MHz nimmst hast Du mehr Luft und einen genaueren Takt = Zeiten ;)
Danke für die ganzen Tips. :-) Ich habe mich nu für einen normalen Takt des Timer1 entschieden und möchte die PWM-Signale softwaremäßig generieren. Dies funktioniert soweit auch. Die Impulslänge (und damit die Stellung der Motoren) möchte ich in der main() Funktion verändern. Dazu habe ich testweise die Variable pos_motor1. Diese soll alle paar Sekunden zwischen 12 und 22 wechseln: #include <avr/io.h> #include <avr/interrupt.h> #include <avr/signal.h> #include <avr/delay.h> // Motoren haengen alle an Port C #define DDR_MOTOR DDRC #define PORT_MOTOR PORTC #define MOTOR_1 5 #define MOTOR_2 4 #define MOTOR_1_EIN PORT_MOTOR |= (1 << MOTOR_1) #define MOTOR_1_AUS PORT_MOTOR &= ~(1 << MOTOR_1) #define MOTOR_2_EIN PORT_MOTOR |= (1 << MOTOR_2) #define MOTOR_2_AUS PORT_MOTOR &= ~(1 << MOTOR_2) #define F_CPU 1000000 uint8_t temp; uint8_t motor; uint8_t pos_motor1; void timer1_init () { TCCR1A = (1 << COM1A1); TCCR1B = (1 << WGM12) | (1 << CS11); OCR1A = 9; TIMSK1 |= (1 << OCIE1A); } SIGNAL (SIG_OUTPUT_COMPARE1A) { motor++; if ((motor > 0) && (motor < pos_motor1)) // 0,9 - 1,7 ms MOTOR_1_EIN; else MOTOR_1_AUS; if ((motor > 40) && (motor < 40+22)) // 1,7 ms MOTOR_2_EIN; else MOTOR_2_AUS; // [ ... weitere Motoren ... ] if (motor >= 480) motor = 0; // nach 20 ms von vorne } int main () { DDR_MOTOR |= (1 << MOTOR_1) | (1 << MOTOR_2); timer1_init (); sei (); motor = 0; pos_motor1 = 22; while (1) { temp = 0; /* Pause */ while (temp < 6) { _delay_ms (262); temp++; } if (pos_motor1 == 22) pos_motor1 = 12; else pos_motor1 = 22; } return 0; } Leider wird aber in der while-Schleife der Wert von pos_motor1 nicht mehr veraendert oder zumindest im Interrupt nicht beruecksichtigt. Die Zuweisung in der main()-Funktion ueber der while-Schleife funktioniert noch. Der Vergleich von pos_motor1 in der ISR sollte doch problemlos funktionieren, oder? Viele Gruesse Martin
Ein Problem könnte sein, dass Deine globalen Variablen, auf die Du sowohl in der ISR als auch im Hauptprogramm zugreifst, nicht "volatile" deklariert sind. Das kann dazu führen, dass Zugriffe auf die Variablen wegoptimiert werden, was bedeuten würde, dass u.U. das Hauptprogramm gar nicht mitbekommt, dass sich etwas tut. Also zumindest motor und pos_motor mit dem Typqualifizierer volatile versehen:
1 | volatile uint8_t pos_motor1, motor; |
BTW: "SIGNAL" ist mittlerweile veraltet. Es wird zwar (afaik) von der aktuellen WINAVR-Version noch unterstützt, Du solltest aber wenns geht im Hinblick auf zukünftige Projekte auf das aktuelle "ISR" umsteigen.
Mit "volatile" verändert sich das Verhalten nicht. :-(( Das "SIGNAL" veraltet ist, hatte ich bereits gelesen, doch habe ich mit meinem avr-gcc (GCC) 4.0.2 es bisher leider nicht mit "ISR" geschafft. Und ist momentan auch nicht so wichtig, da das Projekt eh bald wieder vorbei ist. ;-) Weitere Tips für oberes Problem sind gern willkommen. Martin
Das zeitversetzte schalten der Ausgänge ist ja nicht mehr notwendig. Und mit der vereinfachten if-Anweisung funktioniert es nun problemlos. :-) SIGNAL (SIG_OUTPUT_COMPARE1A) { motor++; if (motor < pos_motor1) MOTOR_1_EIN; else MOTOR_1_AUS; if (motor < pos_motor2) MOTOR_2_EIN; else MOTOR_2_AUS; if (motor >= 480) motor = 0; }
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.