Wie kann ich einen Servomotor mit dem PWM-Ausgang eines Mikrocontrollers steuern?

Ich wollte die Richtung des Servomotors mit einem Schalter steuern. Ich verwende einen Atmega32. Ausgänge sind PIND4 und PIND5 und der Eingang ist PINA3, aber die beiden Servomotoren drehen nur in 1 Richtung.

 #ifndef F_CPU
 #define F_CPU 1000000UL 
 #endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


int main(void)
{
DDRD= 0xFF ;
DDRA=0x00;
PORTA =0x00;
// FAST PWM
TCCR1A |= 1<<COM1A0 | 1<<COM1A1 | 1<<WGM11 |1<<COM1B0|1<<COM1B1 ;
TCCR1B= 1<<WGM12|1<<WGM13 ;
ICR1 = 19999 ;

TCCR1B |=1<<CS10 ;
while (1)
{
   if(PORTA==0x08) //if pina3 is 1
   {
    OCR1A = ICR1 -2000 ;   //turn servo 1
    _delay_ms(1000);
    
    OCR1B=ICR1-2000;    //turn servo 2
    _delay_ms(1000);
    }
    if(PORTA==0x00)// if pina3 is 0
    {
      OCR1A = ICR1 -900 ; //turn servo 1 in the other direction
      _delay_ms(1000); 
      
      OCR1B=ICR1-900;     //turn servo 2 in the other direction
      _delay_ms(1000);

    }
}

    
return 0 ;

}
Wenn Sie RC-Servos verwenden, können Sie dies nicht mit Ihrem Code tun. Kopieren Sie so etwas: arduino.cc/en/Tutorial/Sweep
Ihre if(PORTA==0x08)-Anweisung sollte if((PORTA & 0x08)==0x08) lauten, um nur Bit 3 zu testen. Ihre Anweisung testet das gesamte Byte von PORTA. Ebenso if(PORTA==0x00).
gleiches Problem ...

Antworten (1)

Sie müssen klären. Sind diese ...

  1. Hobby-RC-Servomotoren, die sich in eine Position drehen (0 ~ 180 Grad)
  2. Modifizierte Bastler-RC-Servomotoren, die volle Umdrehungen drehen können
  3. Industrielle Servomotoren mit präzisem Feedback

Servomotoren funktionieren bedingt nicht wie die Verwendung von PWM zur Steuerung einer gemeinsamen Motordrehzahl oder LED-Helligkeit.

Bastler-Servomotor

Diese Art von Servomotor verfügt über ein internes Potentiometer, das bei Drehung der Motorwelle eine Rückmeldung an die interne Schaltung liefert, sodass sie sich in eine bekannte Position drehen kann. Sie steuern den Motor, indem Sie ihm sehr zeitgesteuerte Impulse senden. Servomotoren, mit denen ich gearbeitet habe, wollen ein 50-Hz-Signal, also alle 20 Millisekunden einen Impuls. Dieser Impuls wird verwendet, um im Servotreiber einen analogen Wert zu erzeugen, der mit dem vom Potentiometer kommenden Wert verglichen wird. So weiß das Servo, an der richtigen Position anzuhalten.

Die Impulse selbst steuern die Drehposition des Motors. Der Arbeitsbereich beträgt typischerweise 1000 bis 2000 Mikrosekunden (1 - 2 ms), wobei die Mittelposition (90 Grad) genau in der Mitte bei 1500 us liegt. Einige Motoren haben einen etwas erweiterten Bereich [0,5, 2,5 us]. Siehe dieses Bild von Jameco :

Impulse des Servomotors

Diese Motoren haben ein internes Closed-Loop-System, sind aber insgesamt Open-Loop. Wenn Sie die Drehung nicht extern überwachen, haben Sie keine Möglichkeit zu wissen, ob sich der Motor tatsächlich in die richtige Position gedreht hat. Es könnte durch eine externe Kraft gestoppt werden, was zu viel Strom zieht und den Treiber beschädigt.

Es gibt auch "Digitalservos", die intern anders arbeiten und teilweise einen komplizierteren Treiber benötigen.

Modifizierter Servomotor

Übliche Bastler-Servomotoren können intern modifiziert werden, um eine volle Drehung und eine ziemlich genaue Geschwindigkeitssteuerung (unter geringer Last) zu ermöglichen. Sie funktionieren genauso wie zuvor, nur dass jetzt eine Impulsbreite von 1500us eine Geschwindigkeit von 0 U / min ist. Größere Impulsbreiten sorgen für eine positive Drehung bei zunehmender Geschwindigkeit, und niedrigere Impulsbreiten für eine negative Drehung bei zunehmenden Geschwindigkeiten.

Industrieller Servomotor

Diese erfordern spezielle Fahrer und mehr Wissen. Ich werde nicht darüber sprechen, es sei denn, es ist tatsächlich das, was Sie zu verwenden versuchen (was ich bezweifle).

Ansteuerung mit einem Mikrocontroller (MCU)

Sie können einen Motor nicht direkt über die MCU mit Strom versorgen, er muss über eine externe Stromversorgung verfügen. Beachten Sie auch, dass der Motor, wenn er sich zu drehen beginnt, genug Strom ziehen kann, um die MCU herunterzufahren (Unterspannung), wenn sie sich eine Stromversorgung teilen. Verwenden Sie Bulk-Kapazität an den Versorgungsleitungen, Bypass-Kondensatoren an den VCC-Pins und/oder separate Netzteile/Regler.

Sie sollten den dritten Draht eines gewöhnlichen Servomotors mit der oben erwähnten Impulsfolge ansteuern. Der beste Weg, dies zu tun, um eine unabhängige Steuerung mehrerer Servos zu ermöglichen, besteht darin, einen Timer mit einem TOP-Wert von 20 ms zu konfigurieren. Stellen Sie dann einen Ausgangsvergleichswert ein, um bei der gewünschten Impulsbreitenzeit auszulösen.

Sie könnten die Ausgangspins direkt mit den Timern bei Vergleichsübereinstimmung verbinden oder Sie könnten die Interrupt-Service-Routinen (ISR) für den TOP (Überlauf) und den Ausgangsvergleich aktivieren. Stellen Sie die Servoimpulsstifte oben ein und löschen Sie sie beim Ausgangsvergleich.

Sie können separate Timer für jeden Motor, separate Ausgangsvergleichsregister für einen einzelnen Timer oder einen Algorithmus in Ihrem Vergleichs-ISR verwenden, um sich selbst auf den nächstniedrigeren Vergleichswert in einem Array von Servosteuerwerten einzustellen.

Code-Probleme

Es gibt zahlreiche Probleme mit Ihrem Code. Für den Anfang prüfen Sie eine Eingabe mit PINx, nicht mit PORTx. Bei Eingängen steuert PORTx typischerweise die Pullup-Widerstände.

if(PORTA==0x08) //if pina3 is 1

sollte sein

if(PINA & _BV(PA3)) //if pina3 is 1

Übrigens bevorzuge ich das Makro "_BV(bit)". Es ist definiert als (1 << (Bit)), also ist dies äquivalent zu:

if(PINA & 0x08) //if pina3 is 1

Ich muss davon ausgehen, dass dies nicht Ihr gesamter Code ist, da Ihnen andere wichtige Includes, Definitionen und Konfigurationscode fehlen. Außerdem benötigen Sie keine Rückkehr in der Hauptsache. Es ist ein eingebettetes System. Wohin würde es zurückkehren?

Als nächstes, und das ist der Kicker, können Sie auf diese Weise keine Servos steuern. Ich habe keine Ahnung, was Ihre CPU-Frequenz ist. Der Standardwert für AVR ist 1 MHz (8 MHz / 8) und Arduino war 16 MHz (extern xtal). Ich bin mit dem Mega32 nicht vertraut, daher weiß ich nicht, ob Sie den Timer für PWM richtig einstellen, aber vorausgesetzt, Sie sind es und Ihr PWM arbeitet mit der erforderlichen 50-Hz-Frequenz (was ich stark bezweifle). Sie stellen eine Pulsbreite von 18ms ein. Das ist ein Arbeitszyklus von 90 %: (19999 - 2000) / 19999 * 100 % = 90 %. (für Servo 1). Dann lässt du es 1 Sekunde laufen, bevor du Servo 2 einschaltest. Das ist nur ... nein.

Außerdem STOPPEN Sie niemals die Servos. Sie müssen den Timer stoppen oder OCR1A/B auf 0 setzen.

Wenn PA3 niedrig ist, stellen Sie die Servoimpulsbreite auf 19 ms ein (wieder unter der Annahme einer korrekten 50-Hz-Frequenz). Das ist eine Einschaltdauer von 95 %. Wenn sich die Servos jemals drehen würden, würden sie sich weiterhin in dieselbe Position, in dieselbe Richtung und mit derselben Geschwindigkeit drehen, da die maximale Impulsbreite, die ihnen wichtig wäre, 2,5 ms beträgt, ein Tastverhältnis von 12,5 %.

Einschlägiges Beispiel

Es ist eigentlich einfach, 8 Servos mit einem einzigen Timer anzusteuern, wenn man weiß, dass der maximale Impuls für einen von ihnen 2500 us beträgt und dass 8 * 2500 us = 20 ms - die Gesamtimpulsperiode. Teilen Sie diesen Zeitraum konzeptionell in 8 Blöcke auf und aktivieren Sie, falls gewünscht, zu Beginn jedes 2,5-ms-Blocks ein Servo, das bei der gewünschten Impulsbreite deaktiviert wird.

Hier sind einige Code-Bits, die ich vor langer Zeit für einen TWI-fähigen ATtiny24-Servotreiber geschrieben habe. Ich habe einen externen 16-MHz-Quarz mit deaktivierter Taktteilung verwendet. Ohne eine zuverlässige Taktquelle werden die Servos sehr zittrig sein. Es sollte nicht schwer sein, dies auf einen anderen AVR zu portieren.

// Set up Timer 1 for 2.5ms counter (20ms period shared by 8 servos)
TCCR1A = 0x00;      // Outputs Disabled
TCCR1B = 
    _BV(WGM13) |        // TOP - ICR1
    _BV(WGM12) |        // CTC Mode
    _BV(CS11);          // Prescaler = 8
ICR1 = 4999;            // Top = (16Mhz * 2.5ms / 8) -1
OCR1A = 5000;           // Ensure Timer 1 Compare Match A does not happen first
TIMSK1 = 
    _BV(ICIE1) |        // Enable Input Capture Interrupt
    _BV(OCIE1A);        // Enable Output Compare Match A Interrupt

Jetzt ist hier das Fleisch ... Ich habe globale Arrays der aktuellen und nächsten Impulsbreiten und eine Zählung des aktuellen Servos deklariert:

static volatile uint8_t servo_cnt = 8;      // Signifies current servo [0,7]
static volatile uint16_t    currPos[8];     // Current Position - Updated after movement
static volatile uint16_t    nextPos[8];     // Next Position - Updated by BUS Master

Die erste ISR wird alle 2,5 ms auf den Timer-Überlauf getriggert. Es sollte servo[x] einschalten, wobei x gleich [0,7] ist. Wie Sie dies tun, hängt von Ihrer Schaltung ab (setzen Sie einen Pin, verschieben Sie ein Byte in ein Register usw.). Zu diesem Zeitpunkt müssen Sie auch den Wert des Ausgangsvergleichsregisters einstellen, um den Impuls zu beenden.

ISR(TIM1_CAPT_vect)
{
  // Set servo[x] control pin HI
  // Shift values out to register
  // etc

    /*** UPDATE OCR1A ***
    The "nextPos" value is first multipled by 2 because when the timer counts to N, 
    N/2 us have actually passed. This is a factor of the TIMER1 prescale value.     */
  currPos[--servo_cnt] = nextPos[servo_cnt];            // Update Current Position
  OCR1A = currPos[servo_cnt] << 1;  // Tell counter when to turn outputs OFF
  if(0 == servo_cnt) servo_cnt = 8; // Shift control to the next of 8 outputs (compare to 0 is more efficient)
}

Das letzte Stück ist der Vergleichs-ISR, der auslöst, wenn es Zeit ist, ein Servo auszuschalten.

ISR(TIM1_COMPA_vect)
{
  // Shut OFF current servo output (if applicable) - set pin LO
}

Ich habe dann die Werte für nextPos mit TWI aktualisiert. Der Grund für zwei Positionsarrays besteht darin, kompliziertere Impulse zu berechnen, um zu steuern, wie schnell sich das Servo tatsächlich zur nächsten Position dreht.