Controlando la velocidad de los motores.

En un robot diferencial los giros se realizan cuando las ruedas de un lado se mueven a distinta velocidad que las ruedas del otro lado. Para ello podemos dejar paradas las ruedas de un lado mientras se mueven las del otro, o se puede hacer que las ruedas de cada lado giren en un sentido, lo que provocará que el robot gire sobre sí mismo.

Lo más práctico suele ser que las ruedas de cada lado se pueden mover en el mismo sentido pero a distinta velocidad, cuanto mayor sea la diferencia entre la velocidad de las ruedas de cada lado mayor es el ángulo de giro del robot. Por ejemplo si queremos girar a la izquierda, las ruedas del lado derecho se deben mover más rápido que las del izquierdo.

Para controlar la velocidad de un motor se suele utilizar lo que se llama PWM (modulación por ancho de pulso), que consiste en meter un tren de pulsos de una determinada frecuencia al driver que controla el motor, y dependiendo del tiempo que el pulso está a 1 dentro del período, el motor se mueve más o menos rápido.

Para controlar un motor la Baby Orangutan como driver lleva dos puentes en h, un puente en h está formado por 4 transistores:

Encendiendo y apagando los transistores podemos controlar la dirección y velocidad del motor. Por ejemplo cuando estén encendidos los dos transistores de la imagen de arriba por los que pasa la flecha, que representa la intensidad, el motor gira en un sentido. Cuando esos dos transistores estén apagados y se enciendan los otros dos el motor gira en el sentido inverso al anterior. Encendiendo sólo los dos transistores de debajo conseguimos que el motor frene.

El microcontrolador se encarga de proporcionar al circuito integrado con los puentes en h las señales para el control de la velocidad y sentido de los motores, según la siguiente tabla:



Si dejamos M1A a cero y metemos un 1 en M1B, el motor gira en un sentido. Si en lugar de meter un 1 (5V) metemos una señal de este tipo (señal pwm):

El motor gira a una velocidad que es función del tiempo dentro del período en el que el pulso está a 1, es decir variando el ancho de pulso de la señal cuadrada que le proporcionamos en M1B conseguimos que el motor gire más lento o más rápido. Cuanto mayor sea la frecuencia de la señal cuadrada que metemos en M1B más se acerca a un valor constante la intensidad que atraviesa el motor, teniendo como límite una frecuencia máxima en la que empezamos a tener pérdidas.

Si el microcontrolador tuviera que estar poniendo a 1 M1B, contando el tiempo que lleva a 1 para generar el ancho de pulso y depués ponerlo a cero y además llevar la cuenta del tiempo transcurrido antes de entrar en el siguiente período, pues implicaría consumir mucho tiempo del microcontrolador en realizar esta tarea, lo que nos podría impedir hacer otras cosas. Para ello el microcontrolador tiene hasta 6 salidas pwm. Son módulos hardware que sólo debemos activar, indicar la frecuencia de la señal cuadrada y su ancho de pulso, y estos módulos se encargarán de generar de manera automática la señal pwm por un pin del microcontrolador, liberandole a éste de tal tarea para que pueda dedicar su tiempo a hacer otras cosas.

No voy a entrar con los registros del microcontrolador encargados de la tarea de pwm, ni con los distintos modos de pwm que podemos generar ya que está fuera del propósito de este robot, pero si lo haré para la sección deArduino/AVR de la página, debido a que las señales pwm se usan con bastante frecuencia en robótica.

Voy a aprovechar el código hecho para la Baby Orangutan en la página de Pololu, donde encontramos las funciones para inicializar el hardware del microcontrolados para crear la señales pwm, y las funciones para cambiar el ancho de pulso de cada señal pwm generada por los módulos, y por tanto para poder regular en velocidad los motores. Sólo anotar que el modo de pwm seleccionado es el denominado Fast PWM entre los disponibles en el microcontrolador, que no es el más indicado para control de motores, teniendo otros modos disponibles que corrigen la fase de la señal cuando cambia el ancho de pulso.  Pero es el utilizado por el robot 3pi y para el que se encuentra el código disponible, y por tanto el que se va a usar.

En la siguiente página: http://www.pololu.com/docs/pdf/0J15/motor_driver_application_note.pdf encontramos el código para controlar los motores que conectemos a la Baby Orangutan. Las funciones que nos interesan son las siguiente:

#include <avr/io.h>
// Motor Control Functions -- pwm is an 8-bit value
// (i.e. ranges from 0 to 255)
void M1_forward(unsigned char pwm)
{
OCR0A = 0;
OCR0B = pwm;
}
void M1_reverse(unsigned char pwm)
{
OCR0B = 0;
OCR0A = pwm;
}
void M2_forward(unsigned char pwm)
{
OCR2A = 0;
OCR2B = pwm;
}
void M2_reverse(unsigned char pwm)
{
OCR2B = 0;
OCR2A = pwm;
}

Incluyendo estas funciones en nuestro código podremos controlar en velocidad y sentido los motores. La funciones Mx_forward y Mx_reverse hacen que el motor giren en un sentido o en el inverso, a la función le pasamos un valor en la variable pwm, ese valor está comprendido entre 0 y 255 y se corresponde con la velocidad de giro del motor.

// Motor Initialization routine -- this function must be called
// before you use any of the above functions
void motors_init()
{
// configure for inverted PWM output on motor control pins:
// set OCxx on compare match, clear on timer overflow
// Timer0 and Timer2 count up from 0 to 255
TCCR0A = TCCR2A = 0xF3;
// use the system clock/8 (=2.5 MHz) as the timer clock
TCCR0B = TCCR2B = 0x02;
// initialize all PWMs to 0% duty cycle (braking)
OCR0A = OCR0B = OCR2A = OCR2B = 0;
// set PWM pins as digital outputs (the PWM signals will not
// appear on the lines if they are digital inputs)
DDRD |= (1 << PORTD3) | (1 << PORTD5) | (1 << PORTD6);
DDRB |= (1 << PORTB3);
}


Motors_init() se encarga de inicializar el hardware del microcontrolador que va a generar las señales de pwm, la frecuencia de la señal pwm se establece a 9.8 KHz. El puente en H de la placa Baby Orangutan admite hasta frecuencias de 80 KHz, por lo que nos puede interesar cambiar esta configuración, para poner una frecuencia mayor fuera del rango que escuchamos y que probablemente mejore el rendimiento de los motores. De momento se va a dejar así ya que es como viene.

Se escribe un código  en el AVR Studio para comprobar el funcionamiento de todo lo anterior:

#define F_CPU 20000000UL    // Baby Orangutan frequency (20MHz)
#include <avr/io.h>
#include <util/delay.h>

//Leds. Salidas.
#define LEDP PORTD1
#define    LEDV PORTB1
#define    LEDR PORTD0
//Interruptores. Entradas.
#define SW1    PORTB0
#define SW2 PORTB2
//Sensores. Entradas y salidas.
#define    S0 PORTD7
#define S1 PORTD4
#define S2 PORTD2
#define S3 PORTB5
#define S4 PORTB4
#define S5 PORTC3
#define S6 PORTC2
#define S7 PORTC1
#define LED_ON PORTC0

void inicializar_puertos(void);
void reset(void);
void M1_forward(unsigned char pwm);
void M1_reverse(unsigned char pwm);
void M2_forward(unsigned char pwm);
void M2_reverse(unsigned char pwm);
void motors_init();

int main( void )
{
    inicializar_puertos();
    motors_init();
    reset();

    PORTD |= (1<<LEDR);        //Diodos encendidos.
    PORTB |= (1<<LEDV);

    M1_forward(64);        //Motor derecho.
    M2_forward(192);    //Motor izquierdo.  

    while ( 1 )
    {        
    }
    return 0;
}

void inicializar_puertos(void)
{
   DDRD=0x6A;     //0110 1011  0,1,3,5,6 Salidas
   PORTD=0x00;  
   DDRB=0x0A;     //0000 1010  1,3 Salidas
   PORTB=0x00;
   DDRC=0x01;     //0000 0001  0 Salida
   PORTC=0x00; 
}

void reset(void)
{
    PORTD |= (1<<LEDP); //Encendemos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);//Apagamos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
}

//Funciones para controlar la velocidad y dirección de los
//motores. PWM controla la velocidad, valor entre 0-255.
void M1_reverse(unsigned char pwm)
{
    OCR0A = 0;
    OCR0B = pwm;
}
void M1_forward(unsigned char pwm)
{
    OCR0B = 0;
    OCR0A = pwm;
}
void M2_reverse(unsigned char pwm)
{
    OCR2A = 0;
    OCR2B = pwm;
}
void M2_forward(unsigned char pwm)
{
    OCR2B = 0;
    OCR2A = pwm;
}

//Configuración del hardware del micro que controla los motores.
void motors_init()
{
    // configure for inverted PWM output on motor control pins:
    // set OCxx on compare match, clear on timer overflow
    // Timer0 and Timer2 count up from 0 to 255
    TCCR0A = TCCR2A = 0xF3;
    // use the system clock/8 (=2.5 MHz) as the timer clock
    TCCR0B = TCCR2B = 0x02;
    // initialize all PWMs to 0% duty cycle (braking)
    OCR0A = OCR0B = OCR2A = OCR2B = 0;
    // set PWM pins as digital outputs (the PWM signals will not
    // appear on the lines if they are digital inputs)
    DDRD |= (1 << PORTD3) | (1 << PORTD5) | (1 << PORTD6);  //Ya inicializados en otra función, se puede quitar.
    DDRB |= (1 << PORTB3); //Ya inicializado en otra función, se puede quitar.
}

Se compila y se graba en el micro igual que en los ejemplos anteriores. Lo único que hace este código es generar dos señales pwm que salen por los pines del microcontrolador y llegan a las entradas del puente en h que controla los motores. En el motor derecho (M1) una señal con un ancho de pulso del 25% y en el motor izquierdo (M2) una señal con un ancho de pulso del 75%, lo que provoca que el motor izquierdo gire bastante más rápido que el derecho.

        M1_forward(64);        //Motor derecho.
        M2_forward(192);    //Motor izquierdo.   

La velocidad de cada motor y su sentido de giro se establece en estas dos llamadas a las funciones, al derecho le asignamos el valor 64 que es poco más del 25% de 255, y al izquierdo le pasamos 192 que se corresponde con el 75% del valor máximo a pasar, 255. Si queremos que nuestro robot vaya a la velocidad máxima habrá que pasar 255 a cada motor. Si un motor gira en el sentido inverso al esperado, es decir que retroceda en lugar de avanzar para la función foward, cambiando los cables de la conexión del motor uno por otro se soluciona (el motor no tiene polaridad), o cambiamos los nombres de reverse por forward en las funciones.

Si conectamos un osciloscopio al borne a mayor tensión de cada motor podemos observar la siguiente señal, amarillo M1, azul M2.


Esta es la tensión a la que están conectados el terminal positivo de cada motor en el tiempo, se puede ver como los resultados se corresponden con los valores pasados en las funciones M1_foward y M2_foward que hemos copiado de pololu, y por tanto su funcionamiento es correcto. Es decir cuanto mayor sea nuestro ancho de pulso mayor será la tensión eficaz que le proporcionemos al motor, por lo que más rápido girará.

Video del funcionamiento del código anterior.


Para cualquier duda, comentario, idea: foro.