En la imagen se ve el control de dos servos, el amarillo y el verde. En general se recomienda mandar a cada servo su pulso de control cada 20 mili segundos (ms), pero este valor no es crítico ya que los pulsos pueden ir bastante más separados y los servos reaccionan bien. 20 ms son cuatro cuadrados de la rejilla en la imagen.
Los pulsos deben oscilar entre 0.5 ms (servo a la izquierda) y 2.5 ms (servo a la derecha) aunque estos valores dependen ligeramente del fabricante. Pero para mantener el servo en la misma posición hay que mandar los pulsos siempre idénticos o de lo contrario el servo intentará seguir cada una de sus variaciones, haciendo el clásico ruidito (cric-cric) y envejeciendo más aprisa.
Lo más práctico es usar dos interrupciones asociadas al Timer1 del Arduino. La idea es que ese contador (línea roja) produzca una interrupción cada 3.33 ms, que aprovecharemos para encender (poner 'high') el pin de control del servo que toque, y otra a intervalos variables para apagarlo (ponerlo 'low').
La primera interrupción se produce cuando el contador Timer1 (línea roja) alcanza el valor fijo programado en el registro ICR1 y el contador vuelve a cero. La segunda cuando el contador alcanza el valor programado en OCR1A, que podemos cambiar desde el programa cuando queramos.
El código que hace esto es el siguiente:
Código: Seleccionar todo
/* Este programa explica cómo configurar correctamente las interrupciones del Timer1
* para controlar un servo conectado al pin 13 con precisión de µs
*
* By Norber 2016
*/
void setup() {
pinMode(13, OUTPUT);
cli();
TCCR1A = 0;
TCCR1B = 0;
TCCR1A |= (1 << WGM11); // Turn on Fast PWM mode & TOP = ICR1 (overflow int)
TCCR1B |= (1 << WGM13)|(1 << WGM12); // Turn on Fast PWM mode
TCCR1B |= (1 << CS11); // Set 8 prescaler (0.5 µs) on Timer1 - Servos
ICR1 = 6660; // Set PWM fixed frequency
TIMSK1 = 0;
TIMSK1 |= (1 << OCIE1A)|(1<<TOIE1); // Enable Timer1 interrupts for Compare Match A & Overflow mode
sei();
}
ISR(TIMER1_OVF_vect) { // Timer1 Overflow interrupt for servos each SLOTuS/2 µS
digitalWrite(13, HIGH);
OCR1A = 4000;
}
ISR(TIMER1_COMPA_vect) { // Timer1 Match Compare A interrupt for servo duty cyle
digitalWrite(13, LOW);
}
void loop() {
}
Aunque lo he comentado en inglés, el significado es el anterior: escribiendo los valores apropiados en los registros del Arduino llamados TCCR1A y TCCR1B se habilita el contador para que adopte la forma de la curva roja. Escribiendo 6660 en el registro ICR1 la curva roja cae a cero cada 3.33 ms (3.33 x 2000 = 6660). Los valores escritos en el registro TIMSK1 habilitan las dos interrupciones que necesitamos:
- La primera ejecutará la función ISR(TIMER1_OVF_vect) que debe siempre llamarse así. En ella encendemos el pin 13 y programamos el valor de OCR1A en 2.0 ms (2.0 x 2000 = 4000).
- La segunda interrupción ejecutará la función ISR(TIMER1_COMPA_vect) que siempre debe llamarse así. En ella se apaga el pin 13 y el pulso está completo.
Como el pin 13 controla el encendido del led que los Arduinos tienen montado en la placa, puede comprobarse sin tener osciloscopio que valores bajos de OCR1A causan que el led luzca menos (está menos tiempo encendido y más en reposo) y al revés. Evidentemente, OCR1A siempre debe ser menor que ICR1 (esto se entiende viendo el gráfico).
A partir de este código básico se puede hacer de todo con los servos y funcionarán sin ruiditos porque el control con interrupciones es lo más preciso que hay:
- Un pulsador puede hacer que OCR1A pase de valer 1500 (servo a la izquierda) a valer 4000 (servo a la derecha).
- Si hay que controlar varios servos, cada interrupción ISR(TIMER1_OVF_vect) encenderemos un pin distinto, el correspondiente a cada uno, que luego apagaremos con la ISR(TIMER1_COMPA_vect). Y habrá varios pulsadores lógicamente, cada uno causando la variación del único OCR1A cuando toque
Esto es lo que hace el código que puse algunos mensajes más atrás.
Suerte!