Saltar al contenido
Control Automático Educación

Como Controlar un Servomotor con PIC usando Dos Estrategias

En esta entrada entenderemos como podemos controlar un Servomotor con PIC usando el compilador CCS C Compiler. Vamos a emplear un PIC 16F887, pero puedes usarlo también con un PIC 16F877A, 18F4550 o cualquier otro microcontrolador que tu desees usar. Los códigos aqui son adpatables a cualquier PIC.

Antes que nada, si todavia no has visto todo el curso gratuito de microcontroladores PIC desde cero usando lenguaje C, te invito a que le des un vistazo y te vuelvas un experto: 👉 CURSO DE PIC GRATIS

Y que te suscribas al canal de YouTube para seguir aprendiendo más sobre microcontroladores:

🤔 ¿Que es un Servomotor?

Un servomotor es básicamente un motor con su propio controlador interno, esto quiere decir que su giro va a ser proporcional a un comando de entrada, en lugar de estar girando continuamente como un motor convencional.

Servomotor PIC C

Como conectar un Servomotor

Un servomotor se compone de tres cables que son GND, Power (5v) y Control.

La siguiente ilustración muestra como conectar un servomotor:

servomotor con PIC
Conexión de Servomotor

La conexión del servomotor ya nos da una idea de como va a trabajar este componente electromecánico.

Como funciona el Servomotor

Para que el servomotor funcione, deben enviarse una secuencia de pulsos (PWM) por el cable de control, de esa forma el servomotor verifica su posición actual y actúa para ir hacia la posición deseada.

Para poder entender bien el concepto del funcionamiento de un servomotor, debemos dejar claro que este no se comporta como un motor cualquiera, como ya lo mencionamos antes, o sea que no va a girar libremente simplemente energizandolo, si no que por el contrario al ser un motor que tiene su propio controlador (PID) interno, el trabaja por referencias, y esta referencia es en Grados, lo que quiere decir que yo debo informarle si deseo que se vaya para 0°, o para 25°, 48°,100°, 180°, etc.

Entonces el cable de Control, es usado especificamente para eso, para informarle al motor a cual ángulo yo deseo que él se vaya.

Pero ¿Como le envío los ángulos al motor por medio de una secuencia de pulsos (PWM Servomotor)?

🎳 Control de Giro de un Servomotor 180 con PIC

El servomotor procesará la señal de pulso (PWM) y dependiendo del ancho del pulso, él sabrá cual es el angulo que estamos pidiéndole.

Generalmente un Servomotor procesa un pulso con un periodo de 20ms y valiendonos del datasheet del servomotor podremos saber cuanto es el ancho de pulso que necesitamos colocarle a la señal pwm para que el servomotor recorra la distancia deseada.

Un pulso de 1.5 ms, por ejemplo, va hacer con que el motor se detenga en la posición de 90 grados. Si el pulso es menor  que 1.5 ms, hará que el motor se detenga lo mas cerca posible de los 0° y si el pulso tiene una anchura mayor que 1.5 ms, el eje del servomotor girará lo mas cerca posible a 180 grados, tal y como lo podemos apreciar en la siguiente figura:

Servomotor con PIC


Nota que cada periodo, (cuando el pulso se vuelve positivo) ocurre a cada 20ms.

Teniendo un poco más claro el funcionamiento del servomotor, procederemos a controlar servos con PIC y el compilador CCS C.

Para este proyecto, vamos a usar el siguiente servomotor:

  1.  el servomotor MG996R de (120 grados, torque de 10Kg) Datasheet

Pero puede ser empleado cualquier otro servomotor como el MG995, SG90, etc.

Servomotor via PWM con PIC -modulo CCP

Como podemos apreciar en los datasheet de los servomotores, la frecuencia de un servomotor típicamente es de 50Hz.

Una de las formas de controlar un servomotor es configurando el PWM del PIC usando el modulo CCP, para usar el control de servomotor con pic 16F887 vamos a tener que configurar el PWM a esa frecuencia.

Oscilador Interno con PIC CCS

Como es una baja frecuencia vamos a tener que configurar el oscilador interno del PIC, para eso simplemente basta con configurar el #FUSE y usar la función de configuración del oscilador interno del PIC CCS

#fuses INTRC_IO 
#use delay(clock=500000) 
void main()
{
   setup_oscillator(OSC_500KHZ|OSC_INTRC); //Cristal interno de 500KHz
}

El oscilador interno del PIC 16F887 al igual que el PIC16F877A  o el 18F4550 puede ser configurado con frecuencias desde 8MHz hasta 32KHz, para eso solo basta configurar en la directiva de la función setup_oscillator como:

  • OSC_8MHZ
  • OSC_4MHZ
  • OSC_2MHZ
  • OSC_1MHZ
  • OSC_500KHZ
  • OSC_250KHZ
  • OSC_125KHZ
  • OSC_32KHZ

Para otros microcontroladores PIC, debes ver en el datasheet si soporta estas mismas frecuencias, simplemente abres el datasheet y buscas por el registro “OSCCON”

Configuración del PWM para el servomotor

Vamos a obtener los 50Hz con el PWM, para eso vamos a configurar el oscilador interno a 500Khz y hacer los calculos del PWM que fueron explicados detalladamente en la entrada PWM con PIC CCSC.

El periodo (Inverso de la frecuencia) f=50Hz.

T_{PWM}=\dfrac{1}{50Hz}=0.02s
T_{Osc}=\dfrac{1}{500000Hz}=2\mu s

Desbordamiento del TIMER 2 (PR2) con preescaler de 16 y poscaler de 1:

T_{PWM}=(PR2+1)\cdot 4 \cdot(Preescaler) \cdot T_{Osc} \cdot Postcaler
PR2 =\dfrac{T_{PWM}}{4(Preescaler)T_{Osc}} - 1
PR2 =\dfrac{0.02}{4*16*0.000002} - 1=155

El valor de carga del Timer2 es PR2=155

El Timer 2 quedaría así:

setup_timer_2(T2_DIV_BY_16,155,1);

Ahora solo nos falta determinar el valor de resolución para saber cuanto es lo máximo que podremos variar el ancho de pulso, aplicando la formula de resolución tenemos:

n=\dfrac{log\left (\dfrac{500000}{50*16} \right )}{log(2)}=9.28

Así, el máximo ancho de pulso lo voy a obtener colocando la variable valor:

valor=2^{9.28}=625

Con esto sabemos que si colocamos 625 en el duty cicle, vamos a tener el PWM completamente en alto. Ahora bién como nosotros solo queremos inyectar máximo 2ms para hacer que el motor vaya a 180° y mínimo 1 ms para que el motor vaya a 0°, nuestro duty cilce haciendo una regla de tres simple sabiendo que 625 equivalen a 20ms, va a estar en el rango aproximado de

30\leq Duty\ Cicle\leq 63

Sin embargo cada Servomotor es fabricado de forma diferente y la configuración interna del controlador puede variar, asi que en la práctica se debe verificar si dichos valores de frecuencia realmente llevan el servomotor de 0°a 180°.

Por ejemplo, en mi caso para conseguir este barrido completo debo aumentar los limites del ancho del PWM en:

20\leq Duty\ Cicle\leq 80

Servomotor PIC C Compiler usando interrupción por TIMER

En este caso podremos controlar un mayor número de servomotores con un pic 16F877A, 18F4550, 16F84A, 16F887 o cualquier otro PIC configurando para que la interrupción por TIMER, salte a cada 20ms, por lo tanto aquí podremos utilizar normalmente nuestro cristal de cuarzo.

Vamos a realizar los calculos para que nuestro Timer 0, Recordemos que ya habíamos visto una entrada con las configuraciones del Timer 0 (Timer 0 con PIC C Compiler).

Sabemos que el servomotor necesita unos pulsos cada 20ms, sin embargo este valor no es totalmente extricto, como en este proyecto vamos a trabajar con un cristal de 4Mhz,  vamos a configurar nuestro Timer 0 para que se desborde aproximadamente cada 16.38ms

Para eso vamos a tomar un preescaler de 64, y sabiendo que el Timer 0 se desborda cuando llega a la carga de 256 pues vamos a conseguir el tiempo que queríamos:

DesbordeT=CargaTimer\cdot preescaler\cdot \dfrac{4}{F.Oscilador}=256\cdot 64\cdot \dfrac{4}{4000000}=0.01638s=16.38ms

Perfecto! con esto el PIC saltará a la interrupción del Timer 0 cada 16.38ms cuando cuente de 0 a 256. ¿Pero como vamos a mandar los pulsos de 1ms y 2ms? Pues hacemos una regla de 3 simple.

Valor mínimo:

x=\dfrac{256\cdot 1ms}{16.38ms }=15.62\approx 16

Valor máximo:

x=\dfrac{256\cdot 2ms}{16.38ms }=31.25\approx 32

Ahora dentro de la rutina de interrupción, modificaremos la carga del Timer 0 y colocaremos en alto o en bajo la salida del PIC donde esté conectado el servomotor.

Por ejemplo, si queremos el servomotor en 180° (2ms) sabemos que:

Servomotor PIC
  1. Pregunto si la salida está en alto o bajo
  2. Si esta en alto, cargo el timer0 con 32 y pongo la salida en bajo, de esa forma al timer0 solo tendra que contar desde 32 hasta 256, es decir 224 para volverse a desbordar.
  3. Si esta en bajo, cargo el timer0 con (255-32) y pongo la salida en alto, de esa forma al timer0 solo tendra que contar desde 223 hasta 256, es decir 32 para volverse a desbordar.

La ventaja de usar el timer 0 es que podremos usar el control de varios servomotores con nuestro PIC

Ejemplo de Control de Servomotor con PIC

Vamos a implementar el siguiente circuito con nuestro PIC, donde vamos a controlar 2 servomotores de 180° con 3 pulsadores. Los pulsadores tienen la siguiente función:

  1. incrementa los angulos del motor
  2. decrementa los angulos del motor
  3. selecciona el motor a controlar

El código será implementado de las dos formas:

  1. Controlando los Servomotores con PWM (Oscilador Interno)
  2. Controlando los Servomotores con Timer 0 (Cristal de 4MHz)

El siguiente esquema muestra como realizar el control del servo via pwm en el proteus, que será el mismo esquema para el control de servomotores en proteus via timer 0.

Servomotores CCS C

Es importante que verifiques el voltaje de alimentación de tu servomotor, ya que puedes tener un servomotor de 5v o un servomotor de 12v.

Asegurate de tener una buena fuente de voltaje si el servomotor va a manejar alguna carga.

Descargar Código

A continuación te dejo el código para que lo descargues junto con los archivos del Proteus 8.

Recuerda compartir este contenido en tus redes sociales para ayudar a más personas con este tema y permitir que este sitio web siga aportando más contenido de calidad.

Descarga de Archivos

>> Codigos Servomotor con PIC C Compiler <<

Servomotor PWM: CCP con PIC C Compiler

// ServoMotor Usando PWM con modulo CCP (Oscilador Interno a 500KHz)
// Por: Sergio Andres Castaño
// https://controlautomaticoeducacion.com/
//___________________________________________________________________________

#include <16f887.h>
//#include <18f4550.h>
#device ADC=10

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O

#use delay(internal=500000)
#byte porta=5
//Dirección de los puertos para PIC 18F
//#byte porta = 0xf80 // Identificador para el puerto A.

#define plus porta,0
#define minus porta,1
#define change porta,2

int16 ang1,ang2;
int i,servo=1;

void main()
{
   //20 y 80
   int lmin=30,lmax=65;
  
   //Configura el PWM a 500Khz
   setup_timer_2(t2_div_by_16,155,1);   //Configuracion de Timer 2 para establecer frec. PWM a 1kHz
   setup_ccp1(ccp_pwm);                //Configurar modulo CCP1 en modo PWM
   setup_ccp2(ccp_pwm);
   
   set_tris_a(0x07);
   
   //Define los angulos con el valor mínimo
   ang1=lmin;
   ang2=lmin;
   while(1)
   {    
      //Pregunta si se desea controlar el otro servo
      if(bit_test(change)){
        delay_ms(200);            //Anti-Rebote
        while(bit_test(change))   //Espera hasta dejar de presionar el botón
        {}
        //Si servo es 1 pongalo en 2, si es 2 pongalo 1
        servo= (servo==1) ? 2 : 1;  
      }
      
      //Pregunta por el botón de angulo positivo
      if(bit_test(plus)){
        delay_ms(200);
        if(servo==1)
            ang1++;  //Aumenta el angulo en el servo 1
        else
            ang2++;  //Aumenta el angulo en el servo 2
      }
      
      //Pregunta por el botón de angulo negativo
      if(bit_test(minus)){
        delay_ms(200);
        if(servo==1)
            ang1--;  //Decrementa el angulo en el servo 1
        else
            ang2--;  //Decrementa el angulo en el servo 2    
      }
      
      
      //Valida que los angulos estén en el PWM de lmin hasta lmax
      ang1= (ang1<lmin) ? lmin : ang1; 
      ang1= (ang1>lmax) ? lmax : ang1; 
      ang2= (ang2<lmin) ? lmin : ang2; 
      ang2= (ang2>lmax) ? lmax : ang2;
      
      //Configura el PWM
      set_pwm1_duty((int16)ang1);
      set_pwm2_duty((int16)ang2); 
   }
}

Servomotor usando TIMER 0 con PIC C Compiler

// ServoMotor Usando  Timer 0
// Por: Sergio Andres Castaño Giraldo
// https://controlautomaticoeducacion.com/
//___________________________________________________________________________
#include <16F887.h>
//#include <18F4550.h>
#device ADC=8
#fuses XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#use delay(clock=4000000)  
//Dirección de los puertos para PIC 16F
#byte porta=5
#byte portc=7
//Dirección de los puertos para PIC 18F
//#byte porta = 0xf80 // Identificador para el puerto A.  
//#byte portc = 0xf82 // Identificador para el puerto C. 
#define plus porta,0
#define minus porta,1
#define change porta,2
#define servo2 portc,1
#define servo1 portc,2
unsigned char ang1,ang2,cont=1;
int i,servo=1;
//Rutina de interrupción por RTCC (TIMER)
#INT_RTCC 
void pulse()
{
switch(servo){
case 1:
if(bit_test(servo1))  //La salida del servo 1 está en alto?
{
set_rtcc(ang1);
bit_clear(servo1);
}
else
{
set_rtcc(255-ang1);
bit_set(servo1);
}
break;
case 2:
if(bit_test(servo2))  //La salida del servo 1 está en alto?
{
set_rtcc(ang2);
bit_clear(servo2);
}
else
{
set_rtcc(255-ang2);
bit_set(servo2);
}
break;
}
}
void main()
{
//6 y 36
int lmin=14,lmax=32; //Limites max y min en 100us
set_tris_a(0x07);  //Define RA0, RA1, RA2 como entradas
set_tris_c(0);     //Define puerto C como salida
portc=0;
//SET_RTCC(246);    //Cargo valor inicial del timer para ejecutarse cada 100us
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_64|RTCC_8_bit); //Configura interrupcion del timer
ENABLE_INTERRUPTS(INT_RTCC);                 //Activa interrupcion del timer
ENABLE_INTERRUPTS(GLOBAL);                   //Activa las interrupciones globales
ang1=lmin;
ang2=lmin;
while(TRUE)
{
//Pregunta si se desea controlar el otro servo
if(bit_test(change)){
delay_ms(200);            //Anti-Rebote
while(bit_test(change))   //Espera hasta dejar de presionar el botón
{}
//Si servo es 1 pongalo en 2, si es 2 pongalo 1
servo= (servo==1) ? 2 : 1;  
}
//Pregunta por el botón de angulo positivo
if(bit_test(plus)){
delay_ms(200);
if(servo==1)
ang1++;  //Aumenta el angulo en el servo 1
else
ang2++;  //Aumenta el angulo en el servo 2
}
//Pregunta por el botón de angulo negativo
if(bit_test(minus)){
delay_ms(200);
if(servo==1)
ang1--;  //Decrementa el angulo en el servo 1
else
ang2--;  //Decrementa el angulo en el servo 2    
}
//Valida que los angulos estén en el PWM de lmin hasta lmax
ang1= (ang1<lmin) ? lmin : ang1; 
ang1= (ang1>lmax) ? lmax : ang1; 
ang2= (ang2<lmin) ? lmin : ang2; 
ang2= (ang2>lmax) ? lmax : ang2;   
}
}

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Comentarios (5)

como esta mi estimado, soy nuevo en este campo y queria saber si puede crear un tutorial de movimiento de un servo con teclado matricial y lcd para controlar los 360 grados. si no dispone de tiempo quisiera hablar en privado con usted …

Responder

Hola Sergio, he tenido problemas para solo controlar un servo, quiero saber donde declaras la variable servo que hacer que cambie de valor o como es que funciona?

Responder

La variable servo está declarada antes del main(), donde dice int i,servo;
Esa variable se usa como una bandera, cuando es 1 usa el primer servo, cuando es 2 usa el segundo servo. Se modifica en la parte donde estan las instrucciones cuando se presiona el botón. A través de un if-else de una sola linea (servo= (servo==1) ? 2 : 1;)

Responder

¿como puedo manipular esto (servo= (servo==1) ? 2 : 1;) para aumentar a 3 servos?

Responder

un IF/ELSE tradicional, o un SWITCH/CASE. O haces un contador, depende de la lógica que desees implementar. Con contador sería algo como:
servo++;
if(servo>3)
servo=1;

Responder