INTRODUCCIÓN
A veces necesitamos hacer circuitos simples para tareas simples que antes hubieran sido muy complejos sin uC. En muchos casos para cualquier cosilla ahora vale la pena crear un pequeño circuito, pues los costes son ridículos .
Es verdad que ahora tenemos placas de prototipado como las de Raspery o Arduino, pero yo soy de la vieja escuela. Me gusta trastear y hacer yo los circuitos.
Por otro lado, es más divertido, si tienes tiempo, adaptar todo a tu gusto.
Para mover muchos servos no tiene sentido realizar el circuito en un microcontrolador, ya que ya existen circuitos PWM para ello que liberan al uC. El uC es mejor usalo para controlar el proceso y no para gestionar cosillas que consumen tantos recursos y para las que ya hay otros contoladores.
Si necesitais controalar más de un servo es mejor que useis como control de PWM un PCA9685. Con él dispondreis de 16 controladores de 12 bits, y lo podreis controlar por SPI.
Pero si solo queremos controlar sol un servo, sinos interesa hacerlo con el PIC.
FUNCIONAMIENTO
Hoy voy a poner un circuito para una acción muy simple. Mover un Servocontrol con PIC. Ya puse una entada para hacerlo desde Android, con Arduino a través de internet, pero en este caso voy a hacer algo más realista y más común de utilizar.
Supongamos que tengo un elemento en el que quiero ajustar la posición. Para hacerlo autónomo quiero utilizar un display. Para complicarme la vida, voy a usar un display gráfico. Así puedo mostrar los números mas grandes.
Vamos a colocar dos botones luminosos para mover el servo hacia arriba o abajo, pero como tengo que posicionarlo 180 grados, sería un rollo tener que pulsar 180 veces para pasar de abajo a arriba o viceversa.
Por ello usaré un truco, que consiste en ir aumentando los incrementos de paso a medida que pasa el tiempo. De esta forma cuando pulsemos un botón el servo irá saltando en pasos cada vez mayores. pero cuando soltemos volverá a ponerse en pasos de 1, para tener precisión. Esto no va a permitir movernos rápidamente en el intervalo y ser precisos al mismo tiempo.
En cuanto a control del servo lo haré, en este caso, mediante dos interrupciones para ajustar de reforma precisa el pulso al servo que utilizo.
Aquí voy a utilizar Timer 0 para generar los pulsos de 20ms y Timer 2 para los de control de posición.
En la prosima entrada utilizaré otro programa con un solo timer.
Además quiero que se posicione en pasos de 0,5 grados. Es decir que tenga 360 posiciones para 180 grados.
Por último he usado pulsadores luminosos para indicar los movimiento permitidos. El pulsador se apagará cuando no se pueda realizar el movimiento en ese sentido.
ESQUEMA
El circuito está hecho con un PIC16F882. Por qué tan pequeño... pues para hacerlo más difícil. Al tener solo 2K tendré que pensar más.
En este caso utilizaré el 96% de la memoria. Y para conseguir introducir el proceso en este micro, tendré que prescindir de algunas librerías estándar y crearme yo mi propia función. Con cualquier otro PIC16F88x no lo hubiera necesitado y podría haberlas utilizado, pero así hemos como resolver el problema.
El circuito es este.
Se podrá alimentar con tensión continua de 6V a 12V.
Lleva incorporado el conector ICSP para programarlo.
RV2 controlará el contraste del display.
Mediante los botones UP y DOWN, podremos mover el servo hacia arriba o abajo. Cuando estemos fuera del margen de movimiento admitido, el led correspondiente a su movimiento se apagará, indicando que no funcionará el pulsador correspondiente.
En este caso yo he utilizado pulsadores luminosos ,para conseguir ver la validación de la pulsación sobre el propio pulsador. Esto nos permitirá limitar los movimientos por programa a unos determinados grados, y que le usuario vea que ha llegado al tope de movimiento, sin tener que recordar cual es el máximo o mínimo de este.
TARJETA PROTOTIPO
PROGRAMA
El programa no es muy complejo. Lo podemos dividir en tres secciones.
CONTROL SERVO
El servo que utilizo es un Parallax standar. Este necesita 750ms para posición 0 y 2,25ms para posición 180.
En esta parte devinimos como vamos a mover el servo. En este caso generaremos los pulsos mediante dos interrupciones. TIMER0 nos servirá para generar pulsos de 20ms exactos. Como no uso cristal, sino que he uso el reloj interno del uP., programamos esta interrupción con divisor de 128, preseleccionando el timer a 100.
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_128);
set_rtcc(100);
Esto nos da una llamada cada 20ms exactos.
Cuando se ejecute esta interrupción. Esta lanzará la interrupción TIMER2. la cual pondrá a 750us, de forma que lance el tiempo inicial de posiconamiento 0 del servo.
setup_timer_2(T2_DIV_BY_4,124,1);
set_timer2(0);
Cada vez que se habilite la interrupción desde TIMER0 se producirá una ejecución de Timer2 casi instantánea, exactamente a los 67us. Esta llamada es realizada inmediatamente salga el procesador de la interrupción 0. Por lo tanto no habrán transcurrido los 750us programados.
Es por eso que para que cumpla los primeros 750us han de suceder realmente dos llamadas a Timer2.
Para controlar estas llamadas sucesivas, colocaré en TIMER2 un contador de secuencia, mediante el cual conoceré en que llamada está y podré decidir en consecuencia.
El Timer 2 es inicializado al principio desde TIMER0 a 124 con divisor 4. Es menos de los 750us deseados, pero el tiempo programado unido al tiempo gastado en la primera llamada más el procesado de los cases 2 y 3 en vacío, consumirá exactamente 750ms de tiempo hasta case3. Es decir desde que suceda TIMER0 hasta que llegue al case3 de TIMER2, y por lo tanto descienda la señal del servo pasarán 750ms en caso de que la posición del servo sea 0.
Dicho de otra manera, el TIMER2 siempre será llamado 4 veces. con un tiempo mínimo de 750us. Además después según lo que indiquemos en la variable POS_SERVO, se añadirán tiempo.
#int_RTCC
void RTCC_isr(void)
{disable_interrupts(INT_RTCC);
disable_interrupts(INT_TIMER2);
set_rtcc(100); //100 SON 50HZ- 20MS
TIMER2=0; //programa La posición del servo mediante la longitud del pulso de Timer1
setup_timer_2(T2_DIV_BY_4,124,1); //1er retardo 588us que sumados al los 67uS de la llamada inicial Y A LO QUE CONSUMEN LOS CASES 2 Y 3 dan 750uS de acceso al case 3(punto 0)
set_timer2(0); //INICIALIZACIÓN DEL TIMER antes de la llamada
enable_interrupts(INT_TIMER2); //HABILITA INTERRUPCIÓN EL TIMER2
output_high(PIN_SERVO); //PASA A ALTO LA SEÑAL DEL SERVO
enable_interrupts(INT_RTCC);
}
#int_TIMER2
void TIMER2_isr(void)
{
disable_interrupts(INT_TIMER2);
switch (TIMER2)
{case 0: //PRIMERA LLAMADA HECHA DE FORMA INSTANTANEA EN 67uS AL HABILITAR LA INTERUPCIÓN
TIMER2=1; //LA IGNORAMOS
enable_interrupts(INT_TIMER2);
break;
case 1:
TIMER2=2;
if(POS_SERVO<220)
setup_timer_2(T2_DIV_BY_4,POS_SERVO,1);
else
setup_timer_2(T2_DIV_BY_4,220,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
break;
case 2:
TIMER2=3;
if(POS_SERVO<220)
setup_timer_2(T2_DIV_BY_4,0,1);
else
setup_timer_2(T2_DIV_BY_4,POS_SERVO-220,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
break;
case 3: //este case es ejecutado a los 750us (punto cero) + el tiempo añadido en case 1
TIMER2=0;
output_low(PIN_SERVO); //PASA A BAJO LA SEÑAL DEL SERVO
//en este caso no habilitamos la interrupción
break;
}
}
Como se ve el case 0 es la entrada inicial nada más ser validada la interrupción por TIMER0. Case 1 es la entrada al primera parte de la temporización. Dado que vamos a temporizar 360 valores y el contador del timer solo tiene 255, tendremos que hacerlo en dos partes.
Seleccionamos un tiempo de 4us para el Tic del Timer2. Si la posición deseada es < de 220, usamos el case 1 para marcar el tiempo dejando el case 3 en 0.
En caso de superar 220 la posición deseada, hacemos los 220 primeros en el case 1. El resto desde 220 a 380, cuando se necesite lo realizará el case 2.
El case 3 será el que descienda la señal del control del servo.
CONTROL DE PULSADORES
Esta rutina incluida en el bucle main, es la que controla el proceso que deseamos.
En este caso simplemente vamos a ir t4esteando los pulsadores viendo si la posición seleccionada está dentro de los margenes Min y Max. Si está dentro nos irá subiendo o bajando el valor de POS, pero de forma acelerada, mientras mantengamos pulsado el botón.
//****************Por pulsación mantenida************
// permite mantener pulsado el mando de forma que vaya subiendo progresivamente la
// escala de incremento.
// de esta forma se podrá pasar de n·meros bajos a altos de forma rápida.
if(POS<Max){cuenta_pulsador=0;
while (!input(UP))
{ if (POS==Max) break;
if ((POS+cuenta_pulsador/2+1)<Max)
POS+= cuenta_pulsador/2+1;
else
POS++;
set_servo(POS);
cuenta_pulsador+=10;
}
}
if (POS>Min) {cuenta_pulsador=0;
while (!input(DOWN))
{ if (POS==Min) break;
if (POS>Min+cuenta_pulsador/2+1)
POS-= cuenta_pulsador/2+1;
else
POS--;
set_servo(POS);
cuenta_pulsador+=10;
}
}
REFRESCO
Para mover el servo simplemente debemos asignar a una variable la nueva posición para que las interrupciones hagan su trabajo. Pero si os habéis fijado, con el tiempo de tics, son 187*2 los tics necesarios para conseguir los 1,5ms que generan los 180 grados. esto produce un desfase entre lo desado y los tics. Exactamente 1.075.
Debido a que la memoria está tan al límite, no puedo utilizar la multiplicación con coma flotante, pues las rutina me comería la memoria, por lo que para conseguir la corrección lo haré mediante una multiplicación y una división simple. Esto me ahorra memoria de rom.
if (pos<=360 && pos>=0) POS_SERVO=pos+pos*76/1000; //*1.076;
La otra parte del refresco es la del display.
Igualmente que en el caso anterior, para mostrar el número en pantalla, necesitaré usar la función itoa(). Pero esto me obliga a incluir stdlib. Al hacerlo se me dispara la memoria rom necesaria, pues incluye funciones que no necesito. Por ello, y para ahorrar memoria, creo mi propia función itoa en el código. Esta además la adapto para colocar un decimal.
Dado que queremos 360 posiciones en 180 grados, tendremos una resolución de 0,5 grados, por lo que debemos mostrar esta resolución en la pantalla. En vez de usar coma flotante lo que veremos es si la división por 2 de la posición es entera y si no lo es colocaremos ".5" al final de la conversión del itoa().
CÓDIGO COMPLETO
Debemos tener en cuenta que hay que modificar el GLCD.c para definir los puertos y señales concretas para las conexiones que hemos hecho en el esquema.
/* Main.c
* Autor Jose Angel Moneo Fdez
* Created: lunes 23 de Febrero de 2015
* Processor: PIC16F882
* Compiler: CCS for PIC
*/
#include <16F882.h>
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOPUT //No Power Up Timer
#FUSES MCLR //Master Clear pin enabled
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOCPD //No EE protection
#FUSES NOBROWNOUT //No brownout reset
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOWRT //Program memory not write protected
#FUSES BORV40 //Brownout reset at 4.0V
#FUSES RESERVED //Used to set the reserved FUSE bits
#use delay(clock=4000000)
#define Max 360
#define Min 0
#include "GLCD.C"
//#include <math.h>
#define UP PIN_A3 // PULSADOR UP
#define DOWN PIN_A2 // PULSADOR DOWN
#define LED_UP PIN_A5
#define LED_DOWN PIN_A4
#define PIN_SERVO PIN_C7
#define pin_on output_high
#define pin_off output_low
int16 POS_SERVO=0; //POSICIËN SERVO
int TIMER2=0; //bandera TIMER2
/* Como el reloj es interno la frecuencia son 4Mhz
Para mover el servo,vamos a usar el timer0 para marcar los tiempo de 20 ms.
Cada vez que suceda se activarß el timer2, poniendolo a 750us
Después mediante llamadas sucesivas a timer2 se irá componiendo el tiempo de mando hasta los 1,5ms
El servo lo consideramos con 360puntos de resoluci¾n. Es decir 0.5 grados.
*/
#int_RTCC
void RTCC_isr(void)
{disable_interrupts(INT_RTCC);
disable_interrupts(INT_TIMER2);
set_rtcc(100); //100 SON 50HZ- 20MS
TIMER2=0; //programa La posici¾n del servo mediante la longitud del pulso de Timer1
setup_timer_2(T2_DIV_BY_4,124,1); //1er retardo 588us que sumados al los 67uS de la llamada inicial Y A LO QUE CONSUMEN MOS CASES 2 Y 3 dan 750uS de acceso al case 4(punto 0)
set_timer2(0); //INICIALIZACIËN DEL TIMER antes de la llamada
enable_interrupts(INT_TIMER2); //HABILITA INTERRUPCIónN EL TIMER2
output_high(PIN_SERVO); //PASA A ALTO LA SEñAL DEL SERVO
enable_interrupts(INT_RTCC);
}
#int_TIMER2
void TIMER2_isr(void)
{
disable_interrupts(INT_TIMER2);
switch (TIMER2)
{case 0: //PRIMERA LLAMADA HECHA DE FORMA INSTANTANEA EN 67uS AL HABILITAR LA INTERUPCIËN
TIMER2=1; //LA IGNORAMOS
enable_interrupts(INT_TIMER2);
break;
case 1: //Este caso se ejecuta a los 750uS de que RTCC habilite la interrupci¾n y consume 80uS
TIMER2=2;
if(POS_SERVO<220)
setup_timer_2(T2_DIV_BY_4,POS_SERVO,1);
else
setup_timer_2(T2_DIV_BY_4,220,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
break;
case 2: //Este caso se ejecuta a los 750uS de que RTCC habilite la interrupci¾n y consume 80uS
TIMER2=3;
if(POS_SERVO<220)
setup_timer_2(T2_DIV_BY_4,0,1);
else
setup_timer_2(T2_DIV_BY_4,POS_SERVO-220,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
break;
case 3: //este case es ejecutado a los 750us (punto cero) + el tiempo a±adido en case 1
TIMER2=0;
output_low(PIN_SERVO); //PASA A BAJO LA SEñAL DEL SERVO
break;
}
}
/*******itoa especifico para esta aplicaci¾n
Convierte un n·mero entero en texto en texto en base, considerando un bit de decimal.
Este bit es enviado por separado para colocar el ".5"
*********************************************/
char* itoa(signed int16 num, char* str, int base,int1 decimal)
{
int i = 0,h=0,j=0;
char temp[10];
if (num == 0)
{
str[i++] = '0';
str[i] = '\0';
return str;
}
// Si es negativo pone el signo y lo convierte en positivo
if (num<0) { str[i++]="-";
num=abs(num);
}
// Traducimos digito a digito
while (num != 0)
{
int rem = num % base;
temp[h++] = (rem > 9)? (rem-10) + 'a' : rem + '0';
num = num/base;
}
for (j=h-1;(j+1)>0;j--)
str[i++]=temp[j];
if (decimal==0) {
str[i++]=".";
str[i++]="5";
}
str[i] = '\0'; // a±adimos el final de cadena
return str;
}
// refresca los inicadores de permiso de botonera
void led_rfsh (int16 pos)
{ if(POS<Max) pin_on(LED_UP);
else pin_off(LED_UP);
if(POS>Min) pin_on(LED_DOWN);
else pin_off(LED_DOWN);
}
// envÝa orden de posici¾n al servo y refresca los pulsadores y el display
void set_servo(int16 pos)
{ char Texto[10]="";
char grado[10]="Grados";
if (pos<=360 && pos>=0) POS_SERVO=pos+pos*76/1000; //*1.076;
//refresca posici¾n en pantalla
itoa(pos/2, Texto,10,(pos==(pos/2)*2));
//prepara pantalla
glcd_fillScreen(OFF);
glcd_text57(30,45,grado,2,ON);
glcd_text57(10,10,Texto,4,ON);
led_rfsh(pos);
}
//INICIALIZACIONES
void Setup()
{
setup_wdt(WDT_OFF);
setup_timer_1(T1_DISABLED);
//timer 0 ... 20ms
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_128); //PREPARA TIMER 0
set_rtcc(100); //desde 157 tics = 20ms
//timer 2
setup_timer_2(T2_DIV_BY_4,175,1); //1er retardo 750us
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
//comparadores
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
set_tris_a(0b00001100);
set_tris_C(0x00);
set_tris_b(0x00);
glcd_init(ON);
glcd_fillScreen(OFF); // Must initialize the LCD
}
void main() {
INT16 POS=180;
// int1 upold,downold;
int8 cuenta_pulsador=0; //cuenta los avances de pulsador para aumentar su velociad
Setup();
set_servo(POS);
//COMIENZO DE CICLO
while(TRUE)
{
//****************Por pulsacion mantenida************
// permite mantener pulsado el mando de forma que vaya subiendo progresivamente la
// escala de incremento.
// de esta forma se podrß pasar de n·meros bajos a altos de forma rßpida.
if(POS<Max){cuenta_pulsador=0;
while (!input(UP))
{ if (POS==Max) break;
if ((POS+cuenta_pulsador/2+1)<Max)
POS+= cuenta_pulsador/2+1;
else
POS++;
set_servo(POS);
cuenta_pulsador+=10;
}
}
if (POS>Min) {cuenta_pulsador=0;
while (!input(DOWN))
{ if (POS==Min) break;
if (POS>Min+cuenta_pulsador/2+1)
POS-= cuenta_pulsador/2+1;
else
POS--;
set_servo(POS);
cuenta_pulsador+=10;
}
}
}
}
VIDEO