-         

Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 10 von 20

Thema: servo pwm mit c erzeugen

  1. #1
    Neuer Benutzer Öfters hier
    Registriert seit
    19.07.2007
    Beiträge
    16

    servo pwm mit c erzeugen

    Anzeige

    Hi!
    Ich besitze ein RN-Control board und habe bis jetzt vergeblich versucht ein sauberes pwm signal für einen servo und einen brushless controller zu erzeugen.
    Beide verwenden wohl ein standard 20ms/50Hz pwm signal aber was ich bis jetzt gelesen habe deutet darauf hin, dass ich exakt dieses signal mit dem atmel wohl nicht erzeugen kann?
    Hab bis jetzt die beiden hardware pwms genutzt und noch nichts in software probiert.
    Gibt es eine gute hardware die ich zusammen mit RN-Control nutzen kann um mir eventuelles hick hack in meiner software zu ersparen oder gibt es einen einfachen weg so einen standard pwm zu realisieren?

  2. #2
    Hi JoSch,

    also PWM mit 50Hz mit nem Atmel zu erzeugen geht wohl. Auf folgender Seite wird beschrieben, wie man es hinbekommt. http://mil.ufl.edu/~achamber/servoPWMfaq.html
    Wichtig ist, dass du die richtigen Parameter(Register+Flags) setzt um deine gewünschte PWM Frequenz zu erhalten - die musst du natürlich selbst zusammenrechnen (je nach eingesetztem Quarz).

    Bin aber auch erst frisch in dem Thema.

  3. #3
    Erfahrener Benutzer Robotik Visionär Avatar von oberallgeier
    Registriert seit
    01.09.2007
    Ort
    Oberallgäu
    Beiträge
    7.551

    Re: servo pwm mit c erzeugen

    Zitat Zitat von JoSch1337
    ... RN-Control board ... pwm signal für einen servo ...
    Geht sicher problemlos für servos, zu brushless kann ich nix sagen (Erfahrung beschränkt sich auf die laufenden PC-Lüfter ),

    Zitat Zitat von JoSch1337
    ... bis jetzt vergeblich versucht ein sauberes pwm signal ...
    Was heisst "sauber" ? ? ?

    Zitat Zitat von JoSch1337
    ... RN-Control board ... dass ich exakt dieses signal mit dem atmel wohl nicht erzeugen kann?...
    Wie exakt??

    Die (analogen) Servos wollen in einem Zyklus von 20 ms (das sind die 50 Hz) ein positives Signal von 1 bis 2 ms für Anschlag eine Seite bis Anschlag andere Seite. Es gibt Aussagen, dass die Zykluszeit in einem weiten Bereich schwanken darf, vermutlich sogar mehr als 40 bis 60 Hz. Wichtig ist das Verhältnis von positivem Signal zur "Pause". Also würde ich an Deiner Stelle nicht die absolute Genauigkeit fordern - abgesehen davon, dass Du mit dem Quarz sowieso gut bestückt bist. Ich fahre Servos mit dem internen Oszillator (mit Abweichungen im Prozentbereich, je nach Temperatur) und es gibt null Probleme.

    Irgendwie habe ich rausgelesen, dass Du die PWM in Software realisieren willst. Dafür also :
    - mach Dir eine Interruptroutine, die in der Sekunde 50 mal aufgerufen wird.
    - In dieser Routine zähle eine Variable - sagen wir mal von 1000 - abwärts.
    - Zu Beginn der Zählerei schalte den Ausgang für den Servo auf high
    - Wenn Du bei dem gewünschten Signalanteil irgendwo zwischen 100 und 200 bist, dann schalte den Ausgang bis 1000 auf low.
    - bei 1000 setze die Variable wieder auf 0

    Der Ausgang kann aber den Servo NICHT antreiben, der schafft irgendwo um die 20 mA - der Servo braucht viel mehr. Der Ausgang bringt Dir nur das Signal. Also:

    RNControl_GND <--------> GND Servo
    RNControl_Pinx <--------> SIG Servo

    Netzteil o.ä. (+) <---------> (+) Servo
    Netzteil o.ä. (-) <---------> (-) Servo

    Du siehst, dass Du RNControl und Netzteil und Servo mit einem GND-Pegel fährst.

    Viel Erfolg,
    Ciao sagt der JoeamBerg

  4. #4
    Erfahrener Benutzer Robotik Einstein Avatar von wkrug
    Registriert seit
    17.08.2006
    Ort
    Dietfurt
    Beiträge
    1.892
    Mein Vorschlag wäre den Timer 1 mit einem Taktteiler von 8 bei einem 8MHz Quarz zu verwenden.
    Das gibt dann eine Auflösung von 1µs pro Zählerstand - TCNT1.

    Mit dem OCR1A ( 1...2ms ) kann man dann die Pulsweite einstellen mit dem OCR1B die Zykluszeit ( 20ms / 2(Kanäle) = 10ms ).
    Das Ein- und Ausschalten der entsprechenden Ports wird in den OCR Interrupts erledigt. Im OCR1B Interrupt wird das TCNT Register auf 0 gesetzt um einen neuen Zyklus zu starten.

    Das zu bearbeitende Servo wird in der OCR1B Routine hochgezählt und der entsprechende Ausgang eingeschaltet. Gleichzeitig wird die Impulsdauer aus dem Speicher für den zu bearbeitenden Kanal in das OCR1A geschrieben.
    Die OCR1A Routine schaltet dann den entsprechenden Ausgang einfach wieder ab.

    Das Ganze Spielchen kann dann durch ein paar Zeilen Code in den beiden Interruptroutinen am Laufen gehalten werden.
    Eine Erweiterung auf mehr Kanäle ist durch Anpassung der Zykluszeit OCR1B und den Überlauf des Kanalzählers leicht zu erreichen.
    Wenn man nicht über eine Zykuszeit von 20ms kommen will sind 8..9 Kanäle möglich. OCR1B muss immer größer sein als OCR1A !!!

    Da der Zähler ja mit µs Impulsen läuft ist auch die Berechnung der Impulslängen kein Hexenwerk, ein Zählerstand von 1500 entspricht somit 1,5ms = Servomitte. Man hat also zwischen dem minimalwert 1000 und dem maximalwert von 2000 - 1000 Stufen. So genau geht kein Servo.

  5. #5
    Erfahrener Benutzer Robotik Visionär Avatar von oberallgeier
    Registriert seit
    01.09.2007
    Ort
    Oberallgäu
    Beiträge
    7.551
    Hallo wkrug,
    Zitat Zitat von wkrug
    Mein Vorschlag wäre den Timer 1 ... Auflösung von 1µs pro Zählerstand - TCNT1 ... 2000 - 1000 Stufen. So genau geht kein Servo.
    ohhh - und ich schlage mich mit einer 8bittigen PWM herum, die für meine Zwecke (fast) zu grob ist.

    Folgende Initialisierung habe ich - vorzugsweise auf dem mega16 - laufen:
    Code:
    /* #####>>>>> Hier müssen die Motorports der RNControl umbenannt werden. Sie sind
    	definiert nach altem (RN/Mega32) und neuem (tiny2313) Stand:
    	Pin	    auf L293(D)		RN/Mega32			tiny2313
    	EN1, EN2	 1, 8	alt auf	PD4, PD5 (OC1B,OC1A)	neu:	PB4, PB3
    	IN1, IN2	 7, 2	alt auf PC6, PC7		neu:	PB2, PB5
    	IN3, IN4	10,15	alt auf PB0, PB1		neu:	PB0, PB1 << bleibt
       #### */
    
    void init_timer1(void)	//Initialisierung des Timers für Erzeugung des PWM-Signals
    {
       /* normale 8-bit PWM aktivieren ,
       Das Bit WGM10 wird im Datenblatt auch als PWM10 bezeichnet */
       TCCR1A = (1<<COM1A1)|(1<<COM1B1)|(1<<WGM10);
    
       /* Einstellen der PWM-Frequenz auf 14 kHz ( Prescaler = 1 ) */
       TCCR1B = (1<<CS10);
    
       /* Interrupts für Timer1 deaktivieren
       Achtung : Auch die Interrupts für die anderen Timer stehen in diesem Register */
    //   TIMSK &= ~0x3c;	//Versuche ÄHNLICHE Register zu setzten im tiny2313
    /* M32:   Bit	  7	  6	  5	  4	  3	  2	  1	  0
    		OCIE2	TOIE2	TICIE1	OCIE1A	OCIE1B	TOIE1	OCIE0	TOIE0
    im M32 gesetzt	  0	  0	  1	  1	  1	  1	  0	  0	entspr. 3C
       t2313	TOIE1	OCIE1A	OCIE1B	  -	ICIE1	OCIE0B	TOIE0	OCIE0A
    im tiny gesetzt	  1	  1	  1	  -	  ?	  ?	  0	  ?	entspr. E0 od. ED
    */
    //   TIMSK &= ~0xe8;	// dies gilt für tiny2313
       TIMSK &= ~0x3c;	// dies gilt für mega16(+32)
    }
    Das läuft ganz gut, mit etwa 30 µs Zyklusdauer. Aber ich kann eben nur in 254 Schritten differenzieren.
    Code:
    void setPWM1(uint8_t speed) 
    {OCR1BL = speed;}
    void setPWM2(uint8_t speed) 
    {OCR1AL = speed;}
    Wie bitte macht man das nach DEINER Methode? Hättest Du da bitte einen Vorschlag? Geht das dann auch mit dem L293D? Übrigens ist der Timer0 schon mit einer anderen Aufgabe belegt (50µs-Takt für eine Zykluszeitmessung zur Drehzahlbestimmung).
    Ciao sagt der JoeamBerg

  6. #6
    Erfahrener Benutzer Robotik Einstein Avatar von wkrug
    Registriert seit
    17.08.2006
    Ort
    Dietfurt
    Beiträge
    1.892
    Code:
    Chip type           : ATmega16
    Program type        : Application
    Clock frequency     : 8,000000 MHz
    Memory model        : Small
    External SRAM size  : 0
    Data Stack size     : 256
    *****************************************************/
    
    #include <mega16.h>
    
    volatile unsigned char uc_channel;
    volatile unsigned int ui_pwm[4];
    volatile unsigned int ui_cycle;
    
    // Timer 1 output compare A interrupt service routine
    interrupt [TIM1_COMPA] void timer1_compa_isr(void)
    {
    PORTC=0; // Port C abschalten = alle Ausgänge aus
    
    }
    
    // Timer 1 output compare B interrupt service routine
    interrupt [TIM1_COMPB] void timer1_compb_isr(void)
    {
    switch(uc_channel)
    {
        case 0:
        PORTC.0=1;           // Kanal 1 ein
        OCR1A=ui_pwm[0];
        break;
        
        
        case 1:
        PORTC.1=1;           // Kanal 2 ein
        OCR1A=ui_pwm[1];
        break;
        
        case 2:
        PORTC.2=1;           // Kanal 3 ein
        OCR1A=ui_pwm[2];
        break;
        
        case 3:
        PORTC.3=1;           // Kanal 4 ein
        OCR1A=ui_pwm[3];
        break;
    }; 
    TCNT1=0;
    uc_channel++;
    if ( uc_channel>3){uc_channel=0;};
    }
    
    // Declare your global variables here
    
    void main(void)
    {
    // Declare your local variables here
    
    // Input/Output Ports initialization
    // Port A initialization
    // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
    // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
    PORTA=0x00;
    DDRA=0x00;
    
    // Port B initialization
    // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
    // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
    PORTB=0x00;
    DDRB=0x00;
    
    // Port C initialization
    // Func7=In Func6=In Func5=In Func4=In Func3=Out Func2=Out Func1=Out Func0=Out 
    // State7=T State6=T State5=T State4=T State3=0 State2=0 State1=0 State0=0 
    PORTC=0x00;
    DDRC=0x0F;
    
    // Port D initialization
    // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
    // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
    PORTD=0x00;
    DDRD=0x00;
    
    // Timer/Counter 0 initialization
    // Clock source: System Clock
    // Clock value: Timer 0 Stopped
    // Mode: Normal top=FFh
    // OC0 output: Disconnected
    TCCR0=0x00;
    TCNT0=0x00;
    OCR0=0x00;
    
    // Timer/Counter 1 initialization
    // Clock source: System Clock
    // Clock value: 1000,000 kHz
    // 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: On
    TCCR1A=0x00;
    TCCR1B=0x02;
    TCNT1H=0x00;
    TCNT1L=0x00;
    ICR1H=0x00;
    ICR1L=0x00;
    OCR1AH=0x00;
    OCR1AL=0x00;
    OCR1BH=0x00;
    OCR1BL=0x00;
    
    // Timer/Counter 2 initialization
    // Clock source: System Clock
    // Clock value: Timer 2 Stopped
    // Mode: Normal top=FFh
    // OC2 output: Disconnected
    ASSR=0x00;
    TCCR2=0x00;
    TCNT2=0x00;
    OCR2=0x00;
    
    // External Interrupt(s) initialization
    // INT0: Off
    // INT1: Off
    // INT2: Off
    MCUCR=0x00;
    MCUCSR=0x00;
    
    // Timer(s)/Counter(s) Interrupt(s) initialization
    TIMSK=0x18;
    
    // Analog Comparator initialization
    // Analog Comparator: Off
    // Analog Comparator Input Capture by Timer/Counter 1: Off
    ACSR=0x80;
    SFIOR=0x00;
    
    uc_channel=0;
    ui_pwm[0]=1300;     // Pulsbreite Kanal 1
    ui_pwm[1]=1400;     // Pulsbreite Kanal 2
    ui_pwm[2]=1500;     // Pulsbreite Kanal 3
    ui_pwm[3]=1600;     // Pulsbreite Kanal 4
    ui_cycle=5000;      // 4 Kanale, jeder 4te Zyklus bediendt wieder den selben Kanal
    OCR1B=ui_cycle;
    // Global enable interrupts
    #asm("sei")
    
    while (1)
          {
          // Place your code here
    
          };
    }
    Probier den Code mal aus, sollte die Funktion so erfüllen wie ich es beschrieben hab. Also 8MHz Quarz mit 8/1 Taktteilung auf Timer 1.
    Der Code ist für die Bedienung von 4 Kanälen auf PortC 0 ... 3 ausgelegt.
    Die gewünschten PWM Werte sind in ui_pwm[0...3] abzulegen.
    Der Überlaufwert und der Zyklenzähler ist auf die Anzahl der Kanäle einzustellen.
    Ich hab den Code mal so auf die schnelle zusammengefügt - man möge mir bitte deshalb eventuelle Fehler verzeihen.
    Wenn man es noch schneller haben will, kann man die Interrupt Routinen auch in Assembler schreiben.
    Die Impulserzeugung läuft in Software, deshalb ist jeder beliebige Port als Ausgang verwendbar.

    @oberallgeier
    Mit dem L293D hab ich noch nichts gebastelt - ich kann dir also nicht sagen ob der Code auch damit geht.

  7. #7
    Moderator Robotik Visionär Avatar von radbruch
    Registriert seit
    27.12.2006
    Ort
    Stuttgart
    Alter
    54
    Beiträge
    5.781
    Blog-Einträge
    8
    Hallo

    In Anlehnung an den Servo-Code aus dem RN-Wissen steuert mein 8MHz-ATMega32 meine bis zu 5 Servos mit einem Timer im CTC-Mode mit ca. 100kHz:
    Code:
    ...
    	DDRA |= 16;
    	DDRC |= 3;
    
    	TCCR0 =  (0 << WGM00) | (1 << WGM01);					// CTC-Mode
    	TCCR0 |= (0 << COM00) | (0 << COM01);					// ohne OCR-Pin
    	TCCR0 |=	(0 << CS02)  | (1 << CS01) | (0 << CS00);	// prescaler /8
    	TIMSK =  (1 << OCIE0); 										// Interrupt ein
    	OCR0  = 9; // 100kHz?
    	sei();
    }
    ISR(TIMER0_COMP_vect)
    {
    	static uint16_t count=0;
    	if(count>x) PORTA &= ~16; else PORTA |= 16;
    	if(count>y) PORTC &= ~1;  else PORTC |= 1;
    	if(count>z) PORTC &= ~2;  else PORTC |= 2;
    	if(count<1000)count++; else count=0;
    };
    Warum meine Servos bei Werten von ca. 100 in der Mitte stehen, weiß ich nicht, ist aber nicht wirklich störend.

    Wichtig ist das Verhältnis von positivem Signal zur "Pause".
    Entscheidend ist die Pulslänge. Ja schneller die Wiederholung, umso "agressiver" bewegen sich die Servos.

    Gruß

    mic

    Atmel’s products are not intended, authorized, or warranted for use
    as components in applications intended to support or sustain life!

  8. #8
    Erfahrener Benutzer Robotik Einstein Avatar von wkrug
    Registriert seit
    17.08.2006
    Ort
    Dietfurt
    Beiträge
    1.892
    Entscheidend ist die Pulslänge. Ja schneller die Wiederholung, umso "agressiver" bewegen sich die Servos.
    Im Prinzip ist das schon richtig, aber nicht alle Servos kommen mit verkürzten Pausezeiten ( 10ms ) zurecht.
    Mit Hubschrauber Heckservos für Ansteuerung mit Kreisel sollten auch Pausen von 10ms noch OK sein.
    Um mit allen auf dem Markt üblichen Servos zurecht zu kommen sollte man aber trotzdem lieber bei 20ms Pausezeiten bleiben, wenn man die leicht erhöhte Reaktionszeit nicht unbedingt braucht.

  9. #9
    Neuer Benutzer Öfters hier
    Registriert seit
    19.07.2007
    Beiträge
    16
    wow! vielen, vielen dank für den code!!
    werde gleich alle drei ausprobieren und melde mich dann was jeweils passiert.

    EDIT: mein board hat übrigens nen atmega32 und einen 16MHz quartz

  10. #10
    Moderator Robotik Visionär Avatar von radbruch
    Registriert seit
    27.12.2006
    Ort
    Stuttgart
    Alter
    54
    Beiträge
    5.781
    Blog-Einträge
    8
    Hallo

    Bei 16MHz müßte bei meinem Code dann folgendes geändert werden:
    OCR0 = 9; würde zu OCR0 = 19;

    Weniger als 10ms mögen meine 5€ler auch nicht.

    Gruß

    mic

    Atmel’s products are not intended, authorized, or warranted for use
    as components in applications intended to support or sustain life!

Seite 1 von 2 12 LetzteLetzte

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •