Translate

viernes, 29 de noviembre de 2013

Control de múltiples servos con un único TIMER.



En esta entrada vamos a ver como conectar nuestro microcontrolador con otro dispositivo muy popular, un servomotor. Un servomotor es un motor DC, similar a los usados en las entradas que dedicamos al control de motores. Sin embargo, al contrario que los motores que manejamos allí, los servomotores incluyen su propio hardware de control, por lo que no tendremos que preocuparnos de esa parte.
En la primera parte de esta entrada describiremos los fundamentos de cómo controlar un servo con un microcontrolador, con una mínima sobrecarga para el PIC mediante el uso de un timer y sus interrupción asociada.

En la segunda parte extenderemos este enfoque para el caso de que necesitemos controlar muchos servos con un único PIC. Veremos como podemos controlar hasta 8/10 servos dedicando sólo un TIMER del microcontrolador.

Archivos de código asociados a esta entrada:  servo_calib.c  y  servo_n.c

--------------------------------------------------------------------------------------

Hardware :

Como hemos comentado, un servo (ver figura adjunta) es un simple motor DC con su propia electrónica de control. Normalmente el parámetro controlado es la posición: una orden nuestra hace que el servo vaya hasta una cierta posición y permanezca en ella.

Aunque menos comunes, podemos encontrarnos servos cuyo parámetro controlado es la velocidad. En este caso, nuestras ordenes causan que el servo gire uniformemente a la velocidad indicada.

En esta entrada nos centraremos en los servos de posición. Típicamente constan de un motor DC normal con una gran reductora entre el eje del motor y el eje de salida. A este eje de salida está conectado algún tipo de sensor (normalmente un potenciómetro) a través de cuya medida el servo conoce que ha llegado a la posición deseada y puede detener el motor.

La mayoría de los servos están limitados a moverse dentro de una fracción de vuelta, por lo que en vez de posiciones hablaremos del ángulo del servo. Un parámetro importante de un servo es el rango máximo que puede girar ("throw" en ingles). Así tendremos servos de media vuelta (180º), una vuelta completa (360º) o incluso un cuarto (90º) de vuelta.

Los servos de media vuelta, con un rango máximo de 180º, son muy comunes y en ellos basaré la explicación de los conceptos básicos.

Un servo suele contar con 3 hilos. Dos de ellos, típicamente rojo (V+) y negro (GND), le proporcionan la alimentación requerida, así como un nivel de referencia común (masa) con el micro. El tercer hilo (naranja es una opción muy usada para este hilo) es el que el micro usará para control, es decir, para decirle al servo a que posición (ángulo) debe girar.

La forma de enviar esa información en muy sencilla. Técnicamente se denomina modulación por ancho de pulso y consiste en que el PIC debe mandar una serie de pulsos al servo con una frecuencia constante (del orden de 50 Hz). La separación entre pulsos por lo tanto es siempre de unos 20 msec. Las ordenes se las damos a través del ancho de los pulsos, que suele oscilar entre 1 y 2 milisegundos.

En la figura adjunta podemos ver gráficamente esta situación:




El caso ilustrado corresponde a un servo con un rango de ½ vuelta o 180º. Un ancho del pulso de 1.5 milisegundos le dice al servo que debe ir a su posición central o neutra. Si acortamos o alargamos el pulso giraremos a la izquierda o a la derecha. En el ejemplo mostrado el servo alcanza sus extremos (0 y 180º) para 1 y 2 milisegundos.

Como se observa, independientemente del ancho de los pulsos, la separación entre pulsos es constante y debe ser del orden de 20 milisegundos, lo que corresponde a la frecuencia antes mencionada de 50 Hz. Esta frecuencia no es crítica: frecuencias entre 40 y 65 Hz funcionarán posiblemente (periodos entre 15-25 msec).

Es importante resaltar que los valores anteriores son aproximados y deben considerarse un punto de partida.

Es fundamental no sobrepasar los valores máximo y mínimo, porque el servo intentará ir más allá y sus "topes" se lo impedirán. El resultado, dependiendo de la fuerza del servo y la calidad de los engranajes podría ser un servo quemado o con sus engranajes "pulidos" (sobre todo si son de plástico, como ocurre en muchos servos baratos).

Debido a esto, es muy importante verificar los servos con los que vamos a trabajar antes de usarlos en nuestra aplicación. Para ello podemos ir incrementando/decrementando el ancho del pulso hasta encontrar sus límites. El punto de partida será la posición neutra "nominal": un ancho de 1.5 msec. Puede que no corresponda al punto central exactamente pero seguro que cae en la zona permitida.

El siguiente esquema refleja las conexiones entre el micro y el servo. El hilo de control (naranja?) se conecta directamente a cualquier pin (modo salida digital) del PIC. En nuestro primer ejemplo hemos usado RC0, pero cualquier otro valdría. Los otros dos hilos del servo se conectan a la alimentación (rojo?) y a masa (negro/marrón?).


Recalcar que es necesario que la masa del PIC y la del servo estén conectadas para que tengan una referencia común de voltaje. 

Respecto al voltaje de alimentación para el servo habrá que consultar las especificaciones, pero muchos de ellos funcionarán razonablemente con una alimentación entre 4 y 8V. Si nos quedamos cortos lo único que puede pasar es que el servo no se mueva o gire más lentamente.

Como 5V es un voltaje adecuado para muchos servos podríamos estar tentados de usar la misma alimentación para el servo y el PIC. Esto es una mala idea, ya que las demandas de corriente del servo al moverse pueden hacer caer el voltaje de la alimentación, con el consiguiente riesgo de reseteo del PIC.

En mi montaje uso la placa EasyPIC, alimentada a través del USB (5V) del ordenador. Sin embargo, uso otra batería independiente (7V) para alimentar al servo.

Finalmente, en el programa usaremos un par de pulsadores (conectados a RA1 y RA2) para variar el ancho del pulso (y por lo tanto mover el servo). Vemos en el esquema adjunto cómo cada pin está atado a tierra con una resistencia de pull-down. Su estado natural es LOW (0). Sólo al pulsar pasan a un nivel alto.


 Software :

Nuestro principal objetivo es programar una señal de 50 Hz con un ancho de pulso que podamos variar en el rango indicado.

Lo primero que podemos pensar es que la señal esperada por el servo (periodo fijo, ancho del pulsi variable) se parece mucho a una señal PWM. De hecho eso es exactamente lo que es (antes la hemos llamado codificación por ancho de pulso, pero eso en inglés es justamente PWM, Pulse Width Modulation).

Nuestra primera idea sería usar una línea PWM para controlar un servo. Se trataría de conseguir una frecuencia PWM del orden de 50 Hz y variar el ancho del pulso con el duty_cicle. Esto es factible, pero en mi opinión, no es una buena idea por varias razones:

1.  Las líneas PWM son escasas (típicamente sólo 2 en un PIC). ¿Qué pasa si queremos controlar más servos? Además, las líneas PWM tienen otras funciones (modo de CAPTURE o COMPARE) y pueden no estar disponibles.

2.  Aunque no hubiese problemas de disponibilidad, las características del PWM de un PIC no se prestan a su uso como control de un servo. Veamos las razones.

Si recordamos la entrada de introducción al PWM la frecuencia PWM que puede obtenerse para una cierta frecuencia del oscilador es:

                             Fpwm (mínima) = Fosc / 16384

Para poder llegar a la frecuencia deseada de 50 Hz deberíamos bajar a un oscilador de 1 MHz o menor, lo que posiblemente haga muy lento el sistema para otras tareas.

Además PWM está diseñado para variar el ciclo de trabajo (% on) entre 0 y 100%. En cambio, para controlar un servo el tiempo on (ancho del pulso) oscila como hemos visto entre 1 y 2 msec dentro del periodo de 20 msec. Traducido a ciclo de trabajo correspondería a movernos entre un 5% (1/20) y un 10% (2/20). Perderemos mucha "resolución" si estamos limitados a un intervalo tan pequeño.

En vez de usar el módulo PWM es mucho mejor usar un simple TIMER (y su correspondiente interrupción) para crear la señal de control. Se nos puede criticar que estamos cambiando un recurso (PWM) por otro (TIMER) pero eso no sería correcto, ya que usar el PWM conlleva también el gasto del timer asociado (TMR2).

Otra posible crítica es que si necesitamos controlar muchos servos se nos acabarán los timers. Lo bueno de este enfoque es que, como explicaremos en esta entrada, podemos controlar hasta 8 servos con un único  timer (el número exacto depende de las características de los servos usados, podrían ser alguno más o menos).

En esta entrada veremos dos programas: en el primero presentaremos los fundamentos de la interfaz con el servo usando un timer. En el segundo ampliaremos el concepto al uso de múltiples servos sin que necesitemos más recursos del PIC.


PROGRAMA1 (servo_calib.c): 

Ilustrará los conceptos básicos sobre como configurar un TIMER para generar la señal de control sin apenas sobrecarga para el PIC. Usaremos un par de pulsadores para aumentar/reducir el ancho del pulso.

Este programa también puede servirnos como calibración de un servo cuyas especificaciones detalladas desconozcamos. Podemos partir de un ancho medio (1500 msec) correspondiente a la posición neutra e ir subiendo/bajando hasta notar (chirridos, vibraciones) que hemos alcanzado el tope. De esta forma podemos obtener el ancho mínimo y máximo del pulso para ese servo y su posición neutra. Nos vendrá bien además para verificar el verdadero rango del servo (su movimiento en grados). Es fácil adquirir servos baratos supuestamente con un rango de 180º, cuyo verdadero rango está más cerca de ¼ de vuelta (90º).

El programa es muy sencillo. Veamos el código de la rutina principal.

void main()
{
  T0CON = 0b00000000; 
  enable_global_ints; enable_TMR0_int;
  start_TMR0;
 
  ADCON1=0x0F;      // No PORTS used as AD
  TRISA=0b00000110; // RA1,RA2 inputs
  TRISB=0; PORTB=0; TRISC=0;  PORTC=0; TRISD=0; PORTD=0; // All pins down to 0
  
  while(1)
   {
    if (PORTAbits.RA1==1) if (pulse<2100) pulse++; // Increments pulse (max 2.1 msec)
    if (PORTAbits.RA2==1) if (pulse>500) pulse--;  // Decrements pulse (min 0.5 msec)
    PORTB = pulse/10;  // Show current pulse in PORTB
    Delay10TCYx(100);  // Wait a while
  
}

Algunos comentarios:

  • La inicialización a 0 de T0CON pone al timer TMR0 en modo de 16 bits, contando ciclos de instrucción y con un pre-scaler 1:2. En este proyecto estoy usando un cristal de 8 MHz, por lo que la frecuencia de instrucción es de Fosc/4 = 2 MHz. Con un preescaler 1:2 hago que el contador del TMR0 se incremente cada microsegundo, lo que resulta muy conveniente para "traducir" los delay programados.
  • Habilitamos las interrupciones globales y la interrupción asociada a TMR0.
  • Usaremos todos los pines como de entrada/salida digital (ADCON1=0x0F)
  • Declaramos RA1 y RA2 entradas (bits correspondientes de TRISA = 1).


Previamente hemos declarado un par de macros para arrancar el timer TMR0 y resetera su contador (16 bits):

#define set_TMR0(x) {TMR0H=(x>>8); TMR0L=(x&0x00FF);}
#define start_TMR0 T0CONbits.TMR0ON=1;


El resto del main() es simplemente un bucle infinito donde monitorizamos si se han pulsado los pulsadores asociados a RA1 y RA2. Si es así se incrementa o decrementa la variable pulse (uint16) que gobernará el ancho del pulso de control. El valor de dicha variable puede variar entre 0.5 y 2.1 milisegundos, que son los extremos que he detectado para el servo usado.

Evidentemente toda la acción sucede durante la interrupción del TMR0. Veamos su código:

#define SERVO_LINE LATCbits.LATC0

uint16 TMR0_ini,pulse=1325;    // in microsec
 
// High priority interruption
#pragma interrupt high_ISR
void high_ISR (void)
{
 if (TMR0_flag)  // ISR de la interrupcion de TMR0
  {
   SERVO_LINE=1-SERVO_LINE;
   if (SERVO_LINE) TMR0_ini= 25-pulse; //65536-(pulse-25);
     else TMR0_ini= 45536+pulse+25;    //65536-(20000-(pulse-25))
   set_TMR0(TMR0_ini);
   TMR0_flag=0;
  }
}


  • Con el define #SERVO_LINE indico que pin voy a usar para controlar el servo. En este caso es RC0.
  • Declaro la variable pulse (uint16) que guardará el ancho del pulso deseado, y la inicializo con 1325, que corresponde a 1.325 milisegundos (que resulta ser la posición neutra de mi servo). 

Examinemos ahora con más detalle el código de la interrupción:

1.  Lo primero que hacemos es cambiar el estado de la línea. Si estaba baja se levanta y viceversa.

2.  Si la línea está alta (tras la operación anterior) es que estamos en la fase del pulso, que debe durar el ancho del pulso (valor de pulse en microsegundos). Como los contadores de los timers en los PIC se incrementan siempre y la interrupción se producirá al llegar a 65536, debo programar un valor nominal inicial para TMR0:
 
         TMR0_ini = 65536 – pulse

para que pasados pulse microsegundos rebose el contador y se produzca la siguiente interrupción (donde se cambiará el estado de la línea, acabándose el pulso.
     
Sin embargo, debido a los retrasos introducidos en la llamada a la interrupción y a los cálculos previos he comprobado que es más exacto inicializar el contador un poco más adelante de su valor nominal. En particular, una corrección de 25 ciclos es muy adecuada:

       TMR0_ini = 65536 – pulse  + 25 =  (para un uint16)  = 25 - pulse

      y ese es el valor que introduzco en la variable TMR0_ini.

3.  Si tras el cambio la línea esta baja es que estamos en la fase de "descanso" hasta el siguiente pulso. La duración de esa fase será de 20 msec – ancho_pulso = 20000 – pulse. Inicializaremos TMR0_ini con su
    complementario:

                   TMR0_ini = 65536 – 20000 + pulse = 45536 + pulse.

    Al igual que antes para compensar retrasos en la ejecución podemos adelantar un poco dicho contador. Si introducimos la misma corrección de antes tendremos:

             TMR0_ini = 45536 + pulse + 25

4.  Tras hacer los cálculos anteriores pongo en hora el contador TMR0 con set_TMR0(TMR0_ini).


Y ya está todo. La interrupción saltará alternadamente cada pulse (en msec) y cada 20-pulse (msec). Trás el intervalo largo (20-pulso) se subirá la línea y tras el corto (pulse) se bajará, obteniéndose así la señal de control deseada.

Como pulse es una variable puede cambiarse en cualquier momento (en nuestro caso a través de los pulsadores en el bucle principal) modificando el ancho del pulso y la posición del servo.


En el video adjunto se muestra el resultado cuando conectamos RC0 a la línea de control de un servo:



Una vez entendidos los fundamentos para controlar un servo, ampliaremos el concepto al caso de querer gobernar múltiples servos.


PROGRAMA 2 (servo_n.c)

En este segundo ejemplo ilustraremos como usar múltiples servos con un único TIMER del PIC.

Además del usoi de un timer necesitaremos (en principio) 8 líneas de control, una para cada servo. Sin embargo veremos que con este enfoque en cada instante sólo tenemos activa una línea de un servo, por lo que sería posible usar un demultiplexador (tipo 74HC237) para gobernar 8 servos con sólo 3 pines del PIC. En este código no se ha usado esta opción pero sería fácil de adaptar. 

Podemos controlar varios servos sin usar timers adicionales porque el tiempo que cada servo esta alto (1-2 msec) es muy pequeño comparado con el periodo (20 msec). La idea es aprovechar el tiempo en que la línea de un servo está baja para dedicarnos al resto de los servos.

Veamos el código, que está escrito suponiendo 5 servos a controlar, aunque es fácilmente modificable para potro número. Empezaremos con la declaración de los defines y variables globales a usar:
  
#define N_SERVO 5
#define SERVO_0 LATCbits.LATC0
#define SERVO_1 LATCbits.LATC1
#define SERVO_2 LATCbits.LATC2
#define SERVO_3 LATCbits.LATC3
#define SERVO_4 LATCbits.LATC4

uint8 servo_active=0;
uint16 pulse[N_SERVO]={600,900,1200,1500,1800};    // in microsec
uint16 TMR0_ini;


Muy similar al caso anterior, solo que ahora tenemos 5 servos (N_SERVO) a controlar, por lo que tenemos que declarar no 1 sino 5 pines de control, desde RC0 a RC4.

Tenemos que tener también una nueva variable (servo_activo) que nos indicará de que servo se está ocupando el timer.

Finalmente tenemos nuestras conocidas variables TMR0_ini y pulse, solo que ahora pulse es un array de tamaño 5, ya que tenemos 5 servos y cada uno puede tener un ancho de pulso (posción) diferente. Los pulsos para los cinco servos se inicializan a 0.6, 0.9 , 1.2, 1.5 y 1.8 msec.

Dentro de la función main()  tenemos un sistema parecido al anterior para poder cambiar los anchos de los pulsos:


ADCON1=0x0F;  // No PORTS used as AD
TRISA=0b00000111;
while(1)
  {
   if (PORTAbits.RA0==1) {k++; if (k==N_SERVO) k=0; PORTB=k;}
   if (PORTAbits.RA1==1) if (pulse[k]<2100) pulse[k]++;
   if (PORTAbits.RA2==1) if (pulse[k]>500) pulse[k]--;
   Delay10TCYx(100);
  }


Además de los pulsadores RA1 y RA2 de antes para cambiar el tamaño de un pulso, tengo que tener una variable (k, inicializada a 0) que me indica cuál de los servos estoy modificando. Dicha variable cambia al pulsar RA0, por lo que puedo modificar el ancho de pulso de cualquiera de las señales de control. Para saber qué servo estoy modificando el programa me muestra el valor actual de k en PORTB.

Ya sólo queda ver el código de la interrupción donde se hace el trabajo:


#define slot   (20000/N_SERVO)   // Time slot allocated to each servo
#define c_slot (65536-slot+30)   // Complementary (16 bits) of c_slot
 
// High priority interruption
#pragma interrupt high_ISR
void high_ISR (void)
{
 PORTDbits.RD0=1;
 if (TMR0_flag) // ISR de la interrupcion de TMR1
  {
   switch (servo_active)
    {
     case 0:
       // if HIGH program pulse[0] delay, else // else (slot-pulse[0]) delay
       if (SERVO_0) TMR0_ini= 25-pulse[0];
       else {TMR0_ini= c_slot+pulse[0]; servo_active++;}
       break;
     case 1:
       SERVO_1=1-SERVO_1;
       if (SERVO_1) TMR0_ini= 25-pulse[1];
       else {TMR0_ini= c_slot+pulse[1]; servo_active++; }
       break;
     case 2:
       SERVO_2=1-SERVO_2;
       if (SERVO_2) TMR0_ini= 25-pulse[2];
       else {TMR0_ini= c_slot+pulse[2]; servo_active++; }
       break;
     case 3:
       SERVO_3=1-SERVO_3;
       if (SERVO_3) TMR0_ini= 25-pulse[3];
       else {TMR0_ini= c_slot+pulse[3]; servo_active++; }
       break;
     case 4:
       SERVO_4=1-SERVO_4;
       if (SERVO_4) TMR0_ini= 25-pulse[4];
       else {TMR0_ini= c_slot+pulse[4]; servo_active=0; }
       break;
    }
   set_TMR0(TMR0_ini);
   TMR0_flag=0;
  }
 PORTDbits.RD0=0;
}

A través de servo_active podemos ver que servo está "activo". Dentro de cada servo, el código es muy similar al de antes:

       SERVO_0=1-SERVO_0;  // Changes states of SERVO_0
       // if HIGH program delay = pulse[0] else =(slot-pulse[0])
       if (SERVO_0) TMR0_ini= 25-pulse[0];
       else {TMR0_ini= c_slot+pulse[0]; servo_active++;}
       break;

Como antes cambiamos el estado de la línea. Si tras el cambio está alta es que estamos en el periodo del pulso y programamos el timer para que salte dentro de pulse[0] microsegundos.

La diferencia es que ahora, si la línea está baja en vez de esperar 20 msec (– pulso), esperamos 20 msec/5 (- pulso). Es lo que en el programa defino como un slot. La idea es que ahora los N servos deben repartirse los 20 milisegundos de los que disponemos, por lo que cada uno sólo cuenta con 20/5 = 4 msec antes de ceder el paso al siguiente pulso.
Eso se hace incrementando la variable servo_active, salvo en el último caso (case 4:) donde se resetea a 0 para volver a empezar el ciclo.

Si conectasemos la salida de las 5 líneas a un analizador lógico veríamos algo así como lo mostrado en la figura adjunta. 


En cada segmento de 20/5 = 4 msec la variable servo_active toma un valor distinto y los comandos de levantar/bajar la línea afectan a distintos pines. Una vez que ha acabado el pulso y el pin ha vuelto a su nivel bajo no hay nada más que hacer para ese servo (hasta dentro de 20 msec), por lo que puedo dedicarme al siguiente.

Solo falta ver cuantos servos podríamos llegar a manejar. La respuesta es sencilla. El problema de tener muchos servos es que puede que todavía estuviesemos haciendo la ronda cuando se cumplieran los 20 milisegundos. En ese momento tendríamos que volver a ocuparnos de preparar el pulso del 1er servo.

Si nuestros servos tienen un ancho máximo de unos 2 msec con asignar un slot de 2.5 msec a cada servo tendríamos suficiente. 20 msec entre 2.5msec dan un total de 8 slots. Incluso podríamos acomodar alguno más usando el hecho de que la frecuencia de 50 Hz no es crítica. De ser necesario podríamos seguramente incrementar el periodo de los pulsos a 25 msec (40 Hz), lo que nos daría espacio para 2 slots adicionales.

Otro consideración es que % del PIC queda libre para otras tareas. Y la respuesta es que prácticamente todo. Si monitorizamos cuanto nos demoramos en la interrupción veremos que es del orden de 25 usec (justo la compensación que tuvimos que usar para ajustar más exactamente los timings).

Por cada timer que tengamos TMR0 salta 2 veces, una para subir y otra para bajar la línea. En el caso máximo de tener 8 timers tendríamos 8 x 2 = 16 interrupciones durante el periodo de 20 msec. 16 interrupciones nos ocuparán
16 x 25 = 400 usec = 0.4 msec. El tanto por ciento que se come la interrupción es por lo tanto de:

                                            0.4/20 = 2%

Luego el PIC tiene libre un 98% de sus ciclos para dedicarlos a sus otras tareas.











13 comentarios:

  1. Me ha sido de gran ayuda, ahora intentaré aplicarlo. Aprendí micros con una STM32F3 y es una bestia, tiene varios Timers con hasta 4 canales con PWM, es una maravilla, sin embargo ahora que ando aprendiendo PIC veo que es dificil contar con tantos recursos en hardware, así que veo muy útil utilizar soft, claro, habrá aplicaciones que requieran alta precición y por lo tanto requieran puro PWD por hard, sin embargo si así lo amerita la implementación pues mejor invertir en un micro mas grande, el pwm por soft nos permite llevar a cabo la gran mayoría de los proyectos "de nivel medio"

    ResponderEliminar
  2. hola. muy bueno todo el proyecto, se agradece. estoy aprendiendo a prog pics y me ha sido de gran ayuda. lo único que no tengo claro es la ultima parte referente a "% que se come la interrupción =2%". ¿quiere decir esto que si yo quiero ocupar el pic con otras funciones (por ej: leer un pin, leer un canal analógico, manejar un lcd, etc) tengo un 98% disponible?....de ser asi, ¿el programa se interrumpe incondicionalmente con el valor cargado en el timer ?. y la ultima pregunta y la mas importante para mi es ¿se puede utilizar paralelamente los timers del pic ? saludos y gracias.

    ResponderEliminar
  3. Gracias por todos los aportes, me he leído la mayoría de tus archivos tratando de aprender a programar mi pic32.
    Y he aprendido mucho con tu Información, GRACIAS!!

    ResponderEliminar
  4. hola , una pregunta , la alimentación del pic y de los servos son independientes, pero cuando dices que tengan las mismas masas , es que todas las tierras vayan a un solo lugar , osea que compartan las tierras el pic y los servos ?

    ResponderEliminar
  5. Bro tus enlaces de descarga están mal.. los podrías arreglarlos porfa..!!

    ResponderEliminar
  6. Hice un codigo en ccs pero controlar la dirección de los motores pero solo funciona con uno ya que al conectar otro y oprimir cualquier push button (derecha o izquieda) siempre se dirige hacia un mismo lado ¿alguien puede decirme porque sucede esto?

    ResponderEliminar
  7. Alguien ya logró hacer funcionar el código ?

    ResponderEliminar
  8. Hola son nuevo en programar pic ¿el codigo se puede adaptar para un 16f88?

    ResponderEliminar
  9. Hola yo estoy esperando a una niña para Julio y tengo que manejar dos servos de forma independiente con dos potenciometros. Estoy manejando un 16F628A, pero el codigo no me vale para PCW de CCS. En que esta programado este ejemplo??

    ResponderEliminar
  10. como puedo hacer esto con los 2 pulsadores, 1 servo y arduino???
    muchas gracias

    ResponderEliminar
  11. Excelente información con la explicación que da el buen amigo se puede programar en cualquier microcontrolador solo hace falta tener logica. Saludos desde Mexico.

    ResponderEliminar
  12. Muy bien explicado amigo, la verdad muchas gracias logre hacer mi libreria para un PIC18F2550 con tu blog, le hice mejores y reduci el codigo, la verdad muchas gracias por la explicacion, Saludos desde Peru

    ResponderEliminar