Inicializar los servos en Arduino

Cuando se empieza con Arduino y se hace algún proyecto con servoa se llega el desesperante momento en que se reinicia la placa Arduino y los servos comienzan a dar bandazos hasta que llegan a la posición inicial que les marca el programa. ¿Como resolver esto?

Lo primero que hay que saber es que la mayoría de los ejemplos para aprender a usar la librería servo son incorrectos. No inicializan los servos y por se vuelvan locos. Veamoslo con un ejemplo sacado de la web de Arduino.

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/

#include <Servo.h>

Servo myservo; 

int pos = 0;

void setup() {
  myservo.attach(9);  
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) { 
    myservo.write(pos);
    delay(15);         
  }
  for (pos = 180; pos >= 0; pos -= 1) {
    myservo.write(pos);
    delay(15);  
  }
}

Lo primero es que el servo se vuelve “loco” porque toma los valores por defecto de la librería servo. Se pueden encontrar en el archivo Servo.h

#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo 
#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached

Veamos lo que significan. Los servos ajustan su posición en base a la duración de un pulso que se les envía. En este caso se ajusta el pulso para que tenga una duración mínima de 544 microsegundos y una duración máxima de 2400 microsegundos. Si estáis acostumbrados a posicionar los servos usando grados de 0 a 180 el valor de 0 grados corresponde con un pulso de duración 544 y 180 con uno de 2400. Para los valores intermedios se interpola el valor, de tal forma que para G grados el pulso tiene que durar P microsegundos:

(G * (MAX_PULSE_WIDTH – MIN_PULSE_WIDTH) / 180 ) + MIN_PULSE_WIDTH = P

Sacando cuentas podemos ver que DEFAULT_PULSE_WIDTH corresponde más o menos con 90 grados.

Lo que ocurre es lo siguiente:

  1. Al ejecutar myservo.attach(9) el servo se mueve hasta los 90 grados
  2. Al ejecutar myservo.write(pos) el servo se mueve a la posición indicada, en este caso 0 grados.

No es que el servo se vuelva “loco” es que le decimos que de esos bandazos.

La solución es cambiar el setup para que antes de ejecutar myservo.attach(9) le ponga el valor con el que queremos inicializar el servo.

void setup() {
  myservo.write(pos); //inicializamos la posicion del servo
  myservo.attach(9);  
}

Conservar el valor anterior del servo

Hay casos en que no tenemos un valor por defecto para un servo, tiene que conservar el valor con el que se quedo antes del reinicio. Como no podemos leer el valor del servo (read/readMicroseconds no leen el valor del servo devuelve el último valor pasado a write/writeMicroseconds). ¿Qué podemos hacer? Usar la EEPROM para almacenar los valores de los servos. Podemos almacenar el valor del servo con EEPROM.put() y recuperarlos con EEPROM.get(). Sin embargo esto tiene un problema, la memoria EEPROM del Arduino tiene una vida de unas 100.000 escrituras, que pueden ser poco para algunos casos. Hay algunas posibles soluciones:

  • Crear una posición de apagado de tal manera que antes de apagarse los servos vuelvan a esa posición. No sirve para fallos/reinicios repentinos.
  • Guardar la posición de los servos cada cierto tiempo. En casos donde los servos cambian rápidamente de posición no sirve.
  • Ir cambiando la dirección de memoria EEPROM donde se almacena el estado de los servos. Si cada byte de EEPROM se puede escribir 100000 veces, si rotamos la dirección de byte a lo largo de toda la memoria alargamos esa vida.
  • Guardar solo la posición si cambia lo suficiente. Se compara el valor almacenado con el valor del servo si la diferencia es pequeña no se cambia.

Veamos un ejemplo del ultimo caso.

Mover un servo.

void moveServo(Servo servo, byte angle, int addressEEPROM) {
    byte savedAngle = EEPROM.read(addressEEPROM, angle);//recuperamos el valor 
    if(abs(angle - savedAngle) > 10){//si más de 10 grados de diferencia se guarda
        EEPROM.update(addressEEPROM, angle);
    }
    servo.write(angle);
}

Se incian los servos con el valor recuparado de memoria.

void initServo(Servo servo, int addressEEPROM, uint8_t pin) {
    int savedAngle;
    EEPROM.get(addressEEPROM, saveAngle);//recuperamos el valor guardado
    servo.write(savedAngle);
    servo.attach(pin);
}