¿Cómo puedo controlar un servomotor con la salida PWM de un microcontrolador?

Quería controlar la dirección del servomotor con un interruptor. Estoy usando un Atmega32. Las salidas son PIND4 y PIND5 y la entrada es PINA3, pero los dos servomotores giran solo en 1 dirección.

 #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 ;

}
Si está utilizando servos RC, no puede hacerlo con su código. Copey algo como esto: arduino.cc/en/Tutorial/Sweep
Su declaración if(PORTA==0x08) debe ser if((PORTA & 0x08)==0x08) para probar solo el bit 3. Su declaración prueba todo el byte de PORTA. Del mismo modo si (PORTA == 0x00).
el mismo problema ...

Respuestas (1)

Necesitas aclarar. Son estos ...

  1. Servomotores RC aficionados que giran a una posición (0 ~ 180 grados)
  2. Servomotores RC para aficionados modificados que pueden dar revoluciones completas
  3. Servomotores industriales con retroalimentación de precisión

Los servomotores no funcionan igual que el uso de PWM para controlar la velocidad de un motor común o el brillo del LED.

Servomotor para aficionados

Este tipo de servomotor tiene un potenciómetro interno que proporciona retroalimentación a los circuitos internos a medida que gira el eje del motor, lo que le permite girar a una posición conocida. Usted controla el motor enviándole pulsos cronometrados muy específicamente. Los servomotores con los que he trabajado necesitan una señal de 50 Hz, por lo que un pulso cada 20 milisegundos. Este pulso se usa para crear un valor analógico en el servocontrolador que se compara con el valor que proviene del potenciómetro. Así es como el servo sabe detenerse en la posición correcta.

Los propios pulsos controlan la posición de rotación del motor. El rango de trabajo es típicamente de 1000 a 2000 microsegundos (1 - 2 ms) con la posición central (90 grados) justo en el medio a 1500 us. Algunos motores tienen un rango un poco extendido [0.5, 2.5us]. Vea esta imagen de Jameco :

Pulsos de servomotor

Estos motores tienen un sistema de circuito cerrado interno, pero en general son de circuito abierto. A menos que esté monitoreando la rotación externamente, no tiene forma de saber si el motor realmente giró a la posición correcta. Podría ser detenido por una fuerza externa, consumiendo demasiada corriente y dañando el controlador.

También hay "servos digitales" que funcionan de manera diferente internamente y, a veces, requieren un controlador más complicado.

Servomotor modificado

Los servomotores comunes para aficionados se pueden modificar internamente para permitir una rotación completa y un control de velocidad bastante preciso (con poca carga). Funcionan de la misma manera que antes, solo que ahora un ancho de pulso de 1500us es una velocidad de 0 rpm. Los anchos de pulso más grandes proporcionan una rotación positiva con velocidades crecientes, y los anchos de pulso más bajos proporcionan una rotación negativa con velocidades crecientes.

Servomotores industriales

Estos requieren conductores especiales y más conocimiento. No hablaré de esto a menos que sea realmente lo que está tratando de usar (lo cual dudo).

Conduciéndolos con un microcontrolador (MCU)

No puede alimentar un motor directamente desde la MCU, debe tener una fuente de alimentación externa. También tenga en cuenta que cuando el motor comienza a girar, puede consumir suficiente corriente para apagar la MCU (bajo voltaje) si comparten una fuente de alimentación. Use capacitancia a granel en las líneas de suministro, capacitores de derivación en los pines VCC y/o fuentes de alimentación/reguladores separados.

Debería estar manejando el tercer cable de un servomotor común con el tren de pulsos mencionado anteriormente. La mejor manera de hacer esto, permitiendo el control independiente de múltiples servos, es configurar un temporizador con un valor SUPERIOR establecido en 20 ms. Luego, configure un valor de comparación de salida para disparar en el tiempo de ancho de pulso deseado.

Puede conectar los pines de salida directamente a los temporizadores en la coincidencia de comparación, o puede habilitar las rutinas de servicio de interrupción (ISR) para el TOP (desbordamiento) y la comparación de salida. Establezca los pines de pulso del servo en la parte superior y límpielos en la comparación de salida.

Puede usar temporizadores separados para cada motor, registros de comparación de salida separados en un solo temporizador o un algoritmo en su ISR de comparación para establecerse en el siguiente valor de comparación más bajo en una matriz de valor de control de servo.

Problemas de código

Hay numerosos problemas con su código. Para empezar, verifica una entrada usando PINx, no PORTx. Con las entradas, PORTx normalmente controla las resistencias pullup.

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

debiera ser

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

Por cierto, prefiero la macro "_BV(bit)". Se define como (1 << (bit)), por lo que esto es equivalente a:

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

Debo suponer que este no es todo su código, ya que le faltan otras inclusiones, definiciones y código de configuración importantes. Además, no necesita un retorno en main. Es un sistema embebido. ¿A dónde volvería?

A continuación, y este es el truco, no puedes controlar los servos de esta manera. No tengo idea de cuál es la frecuencia de su CPU. El valor predeterminado para AVR es 1MHz (8Mhz / 8) y Arduino fue 16MHz (xtal externo). No estoy familiarizado con el mega32, por lo que no sé si está configurando el temporizador correctamente para PWM, pero suponiendo que lo esté, y suponiendo que su PWM esté funcionando a la frecuencia necesaria de 50 Hz (que lo dudo mucho), Está configurando un ancho de pulso de 18ms. Eso es un ciclo de trabajo del 90 %: (19999 - 2000) / 19999 * 100 % = 90 %. (para servo 1). Luego lo dejas funcionar durante 1 segundo antes de encender el servo 2. Eso es solo... no.

Además, nunca DETIENES el funcionamiento de los servos. Deberá detener el temporizador o establecer OCR1A/B en 0.

Si PA3 es bajo, configura los anchos de pulso del servo en 19 ms (nuevamente, suponiendo una frecuencia correcta de 50 Hz). Eso es un ciclo de trabajo del 95%. Si los servos estuvieran girando alguna vez, continuarían girando a la misma posición, en la misma dirección, a la misma velocidad, porque el ancho de pulso máximo que les interesaría es de 2,5 ms, un ciclo de trabajo del 12,5 %.

Ejemplo relevante

En realidad, es fácil manejar 8 servos con un solo temporizador sabiendo que el pulso máximo para cualquiera de ellos será de 2500 us, y que 8 * 2500 us = 20 ms, el período total del pulso. Divida conceptualmente ese período en 8 fragmentos y habilite un servo al comienzo de cada fragmento de 2,5 ms si lo desea, deshabilitándolo en el ancho de pulso deseado.

Aquí hay algunos fragmentos de código que escribí hace mucho tiempo para un servocontrolador ATtiny24 habilitado para TWI. Usé un cristal externo de 16 MHz con la división de reloj deshabilitada. Sin una fuente de reloj confiable, los servos estarán muy nerviosos. No debería ser difícil portar esto a cualquier otro AVR.

// 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

Ahora aquí está la carne... He declarado matrices globales de anchos de pulso actuales y siguientes, y un conteo del servo actual:

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

El primer ISR se activa en el desbordamiento del temporizador cada 2,5 ms. Debería encender el servo[x], donde x es [0,7]. La forma en que lo haga depende de su circuito (establezca un pin, cambie un byte a un registro, etc.). En este momento, también debe configurar el valor del registro de comparación de salida para finalizar el pulso.

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)
}

La última pieza es el ISR de comparación que se activa cuando es el momento de apagar un servo.

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

Luego actualicé los valores para nextPos usando TWI. El motivo de las matrices de dos posiciones es calcular pulsos más complicados para controlar la rapidez con la que el servo gira realmente a la siguiente posición.