Archiv verlassen und diese Seite im Standarddesign anzeigen : 8 Servos mit dem Atmega32 steuern
High Light
14.04.2014, 21:57
Hallo zusammen,
seit ein paar Wochen versuche ich mit einem Atmega32 8 Servos anzusteuern, jedoch kamen einige Probleme auf mich zu.
Ich denke, dass ich alle Hardware-Fehler beseitigt habe, jedoch funktioniert noch nichts.
Zur Ansteuerung der Servomotoren wollte ich mich am Quellcode von der Seite
http://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung
orientieren.
Ich habe alle Einstellungen mit dem Datenblatt des Atmega 32 verglichen und konnte keine Fehler oder Änderungen feststellen.
Des Weiteren arbeite ich mit 16MHz, nicht mit 11Mhz wie im Beispiel. Dies habe ich im Quellcode geändert.
Alle 8 Servomotoren sollen am PortC angeschlossen werden.
//
// Programm fuer einen ATmega32
//
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
//
// Der Prescaler muss so gewählt werden, dass der Ausdruck
// für MILLISEC_BASE einen Wert kleiner als 128 ergibt
// MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
// soll.
//
#define PRESCALER 128
#define PRESCALER_BITS (1<<CS22) | ( 1 << CS20 )
#define MILLISEC_BASE ( F_CPU / PRESCALER / 1000 )
#define CENTER ( MILLISEC_BASE /2 )
//
// Konfiguration der Servoleitungen
//
#define NR_SERVOS 8
#define SERVO_DDR DDRC
#define SERVO_PORT PORTC
uint8_t ServoPuls[NR_SERVOS] = { 1<<PC0, 1<<PC1, 1<<PC2, 1<<PC3,
1<<PC4, 1<<PC5, 1<<PC6, 1<<PC7 };
//
// Werte für die Servoposition
// Gültige Werte laufen von 0 bis 2 * CENTER
// 0 ... ganz links
// CENTER ... Mittelstellung
// 2 * CENTER ... ganz rechts
//
volatile uint16_t ServoValue[NR_SERVOS];
ISR (TIMER2_COMP_vect)
{
static uint8_t ServoId = 0;
//
// den Puls des aktuellen Servos beenden
//
SERVO_PORT &= ~ServoPuls[ServoId];
//
// welches ist das nächste aktuelle Servo?
//
if( ++ServoId >= NR_SERVOS )
ServoId = 0;
//
// die Ausgangsleitung fuer dieses Servo auf 1; den Puls beginnen
//
SERVO_PORT |= ServoPuls[ServoId];
//
// den Timer so einstellen, dass bei Pulsende, die ISR erneut aufgerufen wird
//
OCR2 = MILLISEC_BASE + ServoValue[ServoId];
}
void InitServo()
{
uint8_t i;
//
// Die Servoleitungen auf Ausgang stellen
//
SERVO_DDR = ServoPuls[0] | ServoPuls[1] | ServoPuls[2] | ServoPuls[3] |
ServoPuls[4] | ServoPuls[5] | ServoPuls[6] | ServoPuls[7];
//
// Alle Servos in Mittelstellung
//
for( i = 0; i < NR_SERVOS; ++i )
ServoValue[i] = CENTER;
//
// Timer auf CTC Modus konfigurieren
//
OCR2 = MILLISEC_BASE + ServoValue[0];
TIMSK |= (1<<OCIE2);
TCCR2 = (1<<WGM21) | PRESCALER_BITS; // CTC mode
}
int main(void)
{
InitServo();
sei();
_delay_ms( 1000 );
//
// testweise einfach alle 8 Servos ansteuern
// jedes Servo soll sich unterschiedlich schnell bewegen
//
while( 1 ) {
ServoValue[0] += 2;
if( ServoValue[0] > 2*CENTER )
ServoValue[0] -= 2*CENTER;
ServoValue[1] += 1;
if( ServoValue[1] > 2*CENTER )
ServoValue[1] -= 2*CENTER;
ServoValue[2] += 2;
if( ServoValue[2] > 2*CENTER )
ServoValue[2] -= 2*CENTER;
ServoValue[3] += 3;
if( ServoValue[3] > 2*CENTER )
ServoValue[3] -= 2*CENTER;
ServoValue[4] += 1;
if( ServoValue[4] > 2*CENTER )
ServoValue[4] -= 2*CENTER;
ServoValue[5] += 3;
if( ServoValue[5] > 2*CENTER )
ServoValue[5] -= 2*CENTER;
ServoValue[6] += 2;
if( ServoValue[6] > 2*CENTER )
ServoValue[6] -= 2*CENTER;
ServoValue[7] += 1;
if( ServoValue[7] > 2*CENTER )
ServoValue[7] -= 2*CENTER;
_delay_ms( 400 );
}
}
Kann mir jemand meine Fehler aufzeigen? Bzw. mir einen Tipp geben?
Grüße High Light
High Light
15.04.2014, 17:57
Oder soll ich die Ansteuerung der Servos ganz anders machen?
Wie steuert ihr mehrere Servos mit einem Atmega an?
oberallgeier
15.04.2014, 18:44
... Wie steuert ihr mehrere Servos mit einem Atmega an?Für (m)einen Roboterkopf habe ich zehn Servos anzusteuern (https://www.roboternetz.de/community/threads/61379-Kopfsache-und-ein-m1284-etliche-Servos-viel-Alu?p=577672&viewfull=1#post577672) (zum Video runterscrollen). Die Eigenbauplatine bedient die Servos mit besser als 10 Bit Auflösung bei einer Servoperiode von rund 25 ms; es läuft darauf ein mega1284 mit 20 MHz. Mit knapp weniger als 20 ms könnten es dann immer noch acht Servos sein. Das Prinzip ist theoretisch in der Lage auch doppelt so viel Servos anzusteuern. Die Geschichte müsste in ähnlicher Weise auch mit nem mega32 gehen. Da der nur 16 MHz macht (oder?) muss man eben die Anzahl der bedienten Servos entsprechend niedriger ansetzen. Vermutlich sechs bei 20 ms Periodendauer und knapp acht (*gg*) bei 25 ms.
Das Timingprinzip ist hier in einem Codeauszug gezeigt: (https://www.roboternetz.de/community/threads/61379-Kopfsache-und-ein-m1284-etliche-Servos-viel-Alu?p=577715&viewfull=1#post577715) ein Timer mit einer Periodendauer von 2,5 ms startet jeweils einen umlaufend adressierten Servopuls (den Pin auf high) und einen zweiten Timer(kanal) dessen Laufzeit OCR1B den entsprechenden Servopin wieder zurücksetzt. Aus den zehn Perioden wird dann die Gesamtperiode für einen der zehn Servos. Servos die nicht adressiert werden laufen dann leer mit. Die Lösung ist also auch für nur einen oder zwei Servos geeignet, dann laufen die restlichen Teilperioden einfach leer.
Oder soll ich die Ansteuerung der Servos ganz anders machen?
Wie steuert ihr mehrere Servos mit einem Atmega an?
Vom Prinzip her schaut die Ansteuerung bei mir gleich aus wie bei dir. Also 1 timer im CTC Modus. Deine Werte sollten auch passen wenn ich sie mit meinen vergleiche. (ATmega8 @16MHz)
Tut sich gar nix?
EDIT: Du verwendest keinen Synchronisationszyklus. Am Anfang hast du eine Periode von nur knapp 8ms. Vielleicht ist das für die Servos schon zu wenig.
oberallgeier
16.04.2014, 09:05
... Ich denke, dass ich alle Hardware-Fehler beseitigt habe, jedoch funktioniert noch nichts ...Sorry, hab ich erst gerade jetzt gelesen. Dieser Satz, aber "dachte" - tempus perfectum, nicht praesens, wird häufig vor längeren Fehlersuchodysseen gesagt. So wie viele Unfallberichte beginnen mit "Nach Meinung aller Fachleute waren alle denkbaren Sicherheitsmaßnahmen getroffen worden ...".
WAS hast Du alles getan? GND von allen Baugruppen verbunden (siehe SchaltSCHEMA) (http://www.rn-wissen.de/index.php/Servo#Ansteuerung:_Signalform_und_Schaltung) - im Link mal runterscrollen. GND eher sternförmig, keine Ringschleife? Separate Servoversorgung, getrennt vom Controller, aber GND!! verbunden, welche Servos, ausreichend Energie für die Servos - manche ziehen im Stillstand satte zig MilliAmpere - PRO Stück, usf. ??? Zum Code, der ja wohl ähnlich angesetzt ist wie meiner, wäre ja auch ein Schaltbild hilfreich - wenn wir dann alle sicher sind, dass das Schaltbild den ISTzustand genau trifft *gg*.
High Light
16.04.2014, 09:39
Danke, für die Antworten.
In der Tat waren ein paar kleine Fehler auf der Platine, jedoch war das größte Problem, eine defekte Platine zur Programmierung...
Wenn ich heute Abend daheim bin, werde ich einen Schaltplan posten.
Getrennte Spannungsquellen und gemeinsame Masse ist umgesetzt.
Ich vermute, dass es an den Einstellungen des Timers 2 liegt....
oberallgeier
16.04.2014, 11:17
#define F_CPU 16000000UL
...
// für MILLISEC_BASE einen Wert kleiner als 128 ergibt
// MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
// soll.
//
#define PRESCALER 128
#define PRESCALER_BITS (1<<CS22) | ( 1 << CS20 )
#define MILLISEC_BASE ( F_CPU / PRESCALER / 1000 )
#define CENTER ( MILLISEC_BASE /2 )
...
Also ich rechne mal (und denk dabei nicht weiter nach):
( F_CPU / PRESCALER / 1000 )
16 000 000 [Hz] / 128 / 1000 = 125 [Hz]
1 ms sind - mal locker gesagt: ... 1000 Hz
Irre ich mich ??? (Hab grad den Kopf voll mit ungelösten Themen)
Die 1000Hz sind ja der zweite Divisor bei 16 000 000[Hz] / 128 / 1000 [Hz] = 125.
Die 125 sind der Wert für OCR2 um auf 1ms zukommen. 16 000 000[Hz] / 128 / 125 = 1000 [Hz].
oberallgeier
16.04.2014, 12:43
... Die 125 sind der Wert für OCR2 um auf 1ms zukommen ...Hast Recht, siad - ich muss mir das aber noch genauer durchsehen. Pingelig: ich denke dass die theoretische Mitte bei OCR2 124 ist (das sind dann 125 Schritte ab 0). Ich muss mir das Ganze mal in Ruhe durcharbeiten, denn nu kann doch OCR2 nicht mehr kleiner als 125 werden in OCR2 = MILLISEC_BASE + ServoValue[ServoId]; wegen uint16_t ServoValue[NR_SERVOS]. Also mehr bis später.
Hallo,
wurde auch die JTAGen-Fuse deaktiviert? Sonst gehen am PORTC beim ATmega32 einige Pins nicht richtig (PC2 bis 5).
Grüße, Bernhard
zschunky
16.04.2014, 16:07
Hallo,
Ist es so gewollt dass die Servoperiode sich ständig ändert? Denn die Servoperiode ist in dieser Implementierung die Summe aller Servowerte (ServoValue, min: 8*0, max:8*1ms). Nach meinen wissen sollte die schon fix auf ~25ms stehen. Dein PWM Signal wandert von 0ms bis 1ms. Bei Servo-Motoren sollte aber die Signaldauer bei 1ms-2ms liegen.
So wie ich das sehe geht die Periodendauer von 8-16ms. (1-2ms pro Servo).
Fix muss sie nicht sein sollte aber um die 20ms herum liegen. Ich steuere bei mir momentan 4 Servos nach dem gleichen Prinzip an. Danach kommt noch ein Intervall mit fixer Periode damit ich ungefähr auf die 20ms komme. Funktioniert ohne Probleme.
zschunky
16.04.2014, 17:17
ich hab das "MILLISEC_BASE +" übersehen.
High Light
16.04.2014, 18:21
Danke für den Hinweis mit der JTAGen-Fuse, diese war in der Tat nicht deaktiviert, jedoch, hat sich bisher nichts geändert.
Die Periodendauer ist wirklich im schlechtesten Fall nur 8 ms lang...:o und wie Siad ja schon geschrieben hat, sollte diese mindestens 20 ms lang sein.
Ich werde nachher ausprobieren, was passiert, wenn ich die Periodendauer länger mache... ich bin gespannt :-D
oberallgeier
16.04.2014, 19:16
... wie ich das sehe geht die Periodendauer von 8-16ms ... sollte aber um die 20ms ...Nöö. Sollte, vielleicht - aber der Standardwert kann mitunter weit über-/unterschritten werden.
Im RNWiki hatte ich ja ein paar Beispiele zur Ansteuerung (http://www.rn-wissen.de/index.php/Servo#Ansteuerung:_Signalform_und_Schaltung) vorgelegt, dass diese 20ms-Richtschnur zumindest für einige (analoge) Servos nicht wirklich gilt. Bis runter zu 8 ms Periodendauer habe ich noch nicht gehört, aber für etwa 10 bis 11 gibts Erfahrungswerte, leider ohne Nennung des Servotyps.
Seltsam kommt mir vor, dass Du nicht von markanten Änderungen nach Löschen des JTAG-Fuses schreibst. Sehr seltsam.
High Light
12.05.2014, 21:13
Ich melde mich auch wieder zurück...es wurde ruhig um das Projekt, da ich gerade in der Klausurphase bin, aber gut...
Bis jetzt habe ich den Quellcode noch nicht zum Laufen bekommen, und ich weiß um ehrlich zu sein auch nicht, was ich noch ausprobieren kann.
Jedoch habe ich auf eine ganz simple Art und Weise mit einem anderen Programm versucht nur einen Servo zu bewegen, was auch funktioniert hat.
Von daher liegt es anscheinend nicht an der Hardware.
Kann mir vlt jemand den Quellcode testen, ob er bei jemandem von euch funktioniert?
Grüße High Light
Wenn ich am Wochenende Zeit habe probiere ich es.
Ist jetzt vielleicht eine blöde Frage, aber wie sieht deine Spannungsversorgung aus? Hast du den Code schon einmal mit nur einem angeschlossenen Servo probiert oder gemessen ob du nicht etwa einen Spannungseinbruch hast?
High Light
16.05.2014, 19:41
Bin mir sicher, dass es an der Hardware nicht liegt.
Ich glaube, dass ich irgendwo die Register für den TIMER2 nicht richtig gesetzt oder vergessen habe.
Heute habe ich an der Hochschule den Code für den TIMER0 des Atmega88 angepasst und es funktionierte alles sofort wie gewünscht.
Ich würde die Ansteuerung mal anders, ohne CTC probieren.
Lass den Timer frei laufen und nimm beim Prescaler einen Wert von 8.
Somit ist ein Timer Takt von 0,5µs zu erreichen.
Die Comparematch Interrupts kannst Du dann so setzen, das der gewünschte Servowert zum aktuellen Zählerstand des TCNT Registers dazugezählt wird.
Der Vorherige Ausgang wird abgeschaltet, der gewünschte aktiviert und fertig ist der Lack.
Wenn du eine Variable definierst die 20ms entspricht, also 40000 und von dieser dann die gewünschten Servowerte jeweils abziehst und diesen Wert + TCNT als 9ten Wert in das Comparematch Register schreibst, ohne einen Ausgang zu aktivieren kommt auch alle 20ms ein Puls aus dem Controller raus.
Beim letzten Servo kann der allerdings um maximal 8 ms schwanken.
Nicht alle Servos packen eine Wiederholrate von 8ms.
Hier mal ein Codebeispiel für ein Servo.
PWMOUT ist der Servopin
ui_pulseout ist die Länge des gewünschten Servopulses in 1/2µs, also 2000 entspricht 1,0ms.
ui_pauslengh ist die gewünschte Pause allerdings hier ohne abzug des Servopulses, weil ja nur ein Servo bedient wird.
ub_highpulse ist ein Marker, ob 1 oder 0 am Ausgang anliegen, den kannst Du in eine Byte Variable umwandeln und Ihn dann zum Indizieren der Servoimpulsvariable verwenden.
// Timer 1 output compare A interrupt service routine - Servoimpulse ausgeben
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
if(ub_highpulse==0)
{
PWMOUT=1;
OCR1A=ui_pulseout+TCNT1;
ub_highpulse=1;
}
else
{
PWMOUT=0;
OCR1A=ui_pauslengh+TCNT1;
ub_highpulse=0;
};
}
Hier die Timer 1 Einstellungen Beim TIMSK wurde zusätzlich noch Timer 0 gestartet
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 2000,000 kHz = Prescaler 8
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x02;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
....
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x11;
Der Code läuft auf nem ATMEGA16, sollte also auch beim 32er gehen.
High Light
19.05.2014, 13:08
@wkrug: Danke für die Unterstützung, werde ich mir morgen Abend Mal genauer anschauen und ausprobieren.
@Siad: Hattest du Zeit den Code bei dir auszuprobieren?
Sorry, bin heute erst dazu gekommen. Schöne Frickelei den 32er auf einem mini Steckboard unterzukriegen. :D
Also bei mir funktioniert der Code. Wobei ich jetzt nur zwei Servos angehängt habe. Die restlichen Pins gehen derweil ins Leere.
High Light
23.05.2014, 11:29
Ich habe gerade den Code gedebuggt, und festgestellt, das weder die while-Schleife noch der Interrupt aufgerufen wird.
Sobald er in der Main zu sei() kommt, beginnt er die Main wieder von vorne.
Woran kann das liegen???
Hast Du die Interrupt.h included?
Offensichtlich wird ein Interrupt aufgerufen, der dann aber nicht richtig beendet wird.
Was mir auch ein wenig seltsam vorkommt ist das int main (void).
Ich weiss jetzt nicht wie das bei deinem Compiler gehandelt wird ich kenn's aber als void main (void).
Eventuell wärs auch möglich, das der Stack nicht richtig initialisiert wird, das wär aber dann ein Compilerproblem.
Also ich habe deinen code 1 zu 1 kopiert und mit avr studio 6.irgendwas kompiliert.
Hallo,
letzte Zeile: TIMSK=0x11; OCIE1A und der Overflow-Int von TC0 werden aktiviert, wenn keine ISR für letzteres vorhanden ist,
und das INT-Flag nicht resettet wird, steht beim CVAVR ein JMP RESET im asm, wenn Overflow TC0 nicht genutzt wird, mal TIMSK=0x10
probieren, bzw. INT-FLAGs vor sei(); resetten.
mfg
Achim
High Light
24.02.2015, 17:51
Hallo zusammen,
endlich habe ich Mal wieder zeitgefunden, an meinem Projekt weiter zu machen.
Die Änderung bei TIMSK, ändert nichts. Aber ich glaube auch dass ein Problem ab sei() ist.
Wenn ich das Programm debugge, läuft es nicht weiter als sei() in der Main.
Wie kann ich die INT-Flags reseten?
Grüße High Light
Wie kann ich die INT-Flags reseten?
Indem Du eine 1 in das entsprechende Flag schreibst.
Das löscht dann dieses Flag.
High Light
27.02.2015, 20:57
Noch immer kein Erfolg...
Also hab ich gedacht, Steckbrett her und aufbauen. Denn Siad hat den Code ja auch schon zum Laufen bekommen. Leider funktionierte der Aufbau nicht. Daraufhin hab ich ein einfaches Ein- und Ausschalten des PortC programmiert und geladen. Kein Signal am Ausgang. Ich habe den Reset über 47KOhm angeschlossen, Spannungsversorgung ist auch vorhanden. ISP-Verbindung über das STK500 funktioniert auch. Fuses sind auf 16MHz eingestellt und Quarz ist aufgesteckt.
Was kann ich jetzt noch probieren? Bzw. was habe ich falsch gemacht?
Mcgrizzly123
27.02.2015, 21:24
Hi,
bin mir nicht sicher aber hast Du in den Fuses JTAG deaktiviert.
EDIT :
Oh
Danke für den Hinweis mit der JTAGen-Fuse, diese war in der Tat nicht deaktiviert, jedoch, hat sich bisher nichts geändert.
hatte ich überlesen, vergiss es.
High Light
27.02.2015, 21:34
Jap sind deaktiviert.
Ich hatte für das Blink-Programm den falschen Atmega32 im AVR Studio ausgewählt. Das Programm funktioniert jetzt schon Mal :-D
Mit den Servos bin ich noch nicht weiter gekommen.
@Siad: Hattest du das Programm 1 zu 1 übernommen?
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.