Servos de modelismo o RC
Control de un servo - Prueba 02: Control con interrupciones
por Eduardo J. Carletti
|
Servo utilizado: Hitec HS-311
|
Esta es la segunda etapa de un grupo de pruebas de laboratorio en las que
implementamos el control de servos de modelismo por medio de un microcontrolador PIC.
Si usted ha llegado directamente a este artículo, sepa que para tener una idea
completa de cómo venimos trabajando con este desarrollo debería ver antes la
primera parte de la serie, Control de un servo - Prueba 01: Control
básico.
Si bien el programa de la primera parte funciona y es simple, y permite
ver con claridad la manera de generar el pulso con los tiempos correctos, la verdad es
que esa manera de solucionar el control de servos tiene el defecto de que mientras se producen
los retardos, el microcontrolador está en un lazo cerrado, dedicado exclusivamente
a esperar que se cumplan los tiempos.
¿Cómo se soluciona esto? Haciendo que los retardos se produzcan mientras
el microcontrolador está haciendo otra cosa. Y para hacerlo se debe utilizar una de las
herramientas más potentes en un sistema que debe ser sensible a sucesos
producidos en el mundo real: las interrupciones.
El circuito que vamos a utilizar sigue siendo el mismo de la primera parte:
Segunda prueba de control de un servo RC
La foto que sigue nos permite apreciar la simplicidad del circuito.
Podemos ver un integrado de 18 patas, que es el
PIC16F628A
(sin cristal, porque
funciona con su generador interno de reloj de 4 MHz); el enchufe estándar de tres patas
para el servo; y tres pequeños pulsadores. Los resistores son para polarizar las
entradas del PIC que se utilizan para "leer" los pulsadores a un valor positivo, o valor
lógico 1. Los pulsadores llevan este nivel a lógico 0, o tierra, cuando
se los presiona.
Montaje del circuito básico de control de un servo RC
Al servo le hemos colocado un círculo de cartulina con una
línea que facilita la visualización de su posición. Los tres cuadrados
negros pequeños son los pulsadores, que corresponden, de izquierda a derecha, a
"pulsador 1", "pulsador 2" y "pulsador 3" en el circuito. El pulsador 1 servirá
para llevar al servo a su posición central, o "neutra". Con el pulsador 2 se
hará avanzar al servo en sentido antihorario, y con el pulsador 3 se lo
hará girar en sentido horario.
Programa de control de servos RC con interrupciones
;**********************************************************************
; MANEJO DE SERVOS - Programa Básico 2 - Manejo con interrupciones
; Por Eduardo J. Carletti, Robots Argentina, 2007
;**********************************************************************
list p=16F628A ; definir procesador
#include <p16F628A.inc> ; definiciones de variables específicas del procesador
ERRORLEVEL 1;-302 ; para evitar los mensajes de cambio de
; banco en el resultado del compilador
__CONFIG _CP_OFF & _WDT_OFF & _LVP_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT & _MCLRE_OFF
;***** DEFINICIÓN DEL NOMBRE DE ENTRADAS Y SALIDAS
#define SERVO1 PORTA,0 ; puerto de salida de servo 1
#define PULSADOR1 PORTA,1 ; puerto de entrada de pulsador 1
#define PULSADOR2 PORTA,2 ; puerto de entrada de pulsador 2
#define PULSADOR3 PORTA,3 ; puerto de entrada de pulsador 3
;***** VARIABLES
CBLOCK 0x20
acum_A ; variable momentánea
Posic ; posición servo
fase ; fase de programa para interrupción
Guardar_STATUS ; Guardar Status
Guardar_W ; Guardar W
ENDC
;***********************************************************************************
org 0x000
goto principal
org 0x004
goto interrupcion
;***********************************************************************************
; Principal
;***********************************************************************************
principal
movlw b'00000111' ; deshabilita comparadores. Esto es
movwf CMCON ; algo importante en el PIC 16F628A
clrf PORTA ; inicia ports
clrf PORTB ; inicia ports
bsf STATUS,RP0 ; Apunta a banco 1
movlw b'00001110' ; PORTA
movwf TRISA ; salidas menos 1, 2 y 3, entradas
MOVLW b'00000000' ; PORTB
movwf TRISB ; salidas
movlw b'00000010' ; Configuración para TMR0
movwf OPTION_REG ; preescaler 2, 1:8 con CLK interno (que es
; de 1 MHz si el oscilador es de 4 MHz).
; El contador cuenta cada 8 useg
bcf STATUS,RP0 ; Apunta a banco 0
clrf TMR0 ; inicia registro de timer en 0
movlw d'133' ; inicia valor de posición de servo (centro)
movwf Posic
clrf fase ; inicia para que comience con la fase 0
movlw b'10100000' ; Interrupciones habilitadas
; GIE (global), T0IE (timer 0)
movwf INTCON ; Interrupción General, Interrupción Timer 0
;***********************************************************************************
; Lazo principal
;***********************************************************************************
lazo
movf fase,f
bz fase0
goto lazo
;***********************************************************************************
; Rutina de apoyo
;***********************************************************************************
; Fase 0: Los servos actúan con un pulso de control que va entre 0,5 ms y 2,5 ms.
; En el bloque que sigue se inicia en 1 el pulso de control y se espera un
; retardo fijo de aproximadamente 0,5 ms, que es el valor mínimo del pulso.
; Con este valor de longitud de pulso y una longitud cero en la parte variable,
; el servo está parado al máximo hacia la izquierda (sentido antihorario).
fase0 bsf SERVO1 ; pone la señal de servo en 1
bcf INTCON,T0IF ; borra el flag de timer
movlw d'192' ; (256-192 = 64) 64 * 8 us = 0,512 ms
movwf TMR0 ; valor al registro de timer
incf fase,f ; pasa a fase 1
goto lazo
;***********************************************************************************
; Manejo interrupciones
;***********************************************************************************
interrupcion
; Salva todo
bcf INTCON,T0IE ; deshabilita interrupción de timer
movwf Guardar_W
swapf STATUS, w
movwf Guardar_STATUS
; distribuidor
movf fase,w
xorlw 0x01 ; ¿fase 1?
bz fase1 ; sí
movf fase,w
xorlw 0x02 ; ¿fase 2?
bz fase2 ; sí
movf fase,w
xorlw 0x03 ; ¿fase 3?
bz fase3 ; sí
goto salidaint ; ninguna de ellas, sale
; Fase 1: Aquí comienza el tiempo variable del pulso. Esto permite que
; el recorrido completo, de 180 grados (valores de pulso entre 0,5 ms
; y 2,5 ms), se divida en 256 segmentos (256 * 8 us = 2,048 ms).
; La parte variable del pulso de control varía entre 0 y 2 ms
fase1 movf Posic,w ; 256-nn x 8 uS = 1 ms, 1,5 ms, 2,5 ms
movwf TMR0 ; valor al registro de timer
incf fase,f ; pasa a fase 2
goto salidaint
; Fase 2: Retardo de 20 ms, que es el tiempo estándar que debe separar los pulsos
; de control para los servos comunes de RC
fase2 bcf SERVO1 ; pone la señal de servo en 0
bsf STATUS,RP0 ; Apunta a banco 1
movlw b'00000111' ; Configuración para TMR0
movwf OPTION_REG ; preescaler 7, 1:256 con CLK interno (que es
; de 1 MHz si el oscilador es de 4 MHz).
; El contador cuenta cada 256 useg
bcf STATUS,RP0 ; Apunta a banco 0
movlw d'249' ; (256-249 = 7) 7 * 256 us = 17,92 ms + 2 ms pulso
movwf TMR0 ; valor al registro de timer
incf fase,f ; pasa a fase 3
goto salidaint
; Fase 3: Como en el bloque de la fase 2 cambió el prescaler del timer a 7, ahora
; debe reponerlo al valor de un conteo cada 8 useg, y volver al estado de fase = 0,
; con lo cual el ciclo se reinicia. También hemos insertado en esta fase, que se
; ejecuta cada 20 ms aproximadamente, la lectura de los pulsadores.
fase3
bsf STATUS,RP0 ; Apunta a banco 1
movlw b'00000010' ; Configuración para TMR0
movwf OPTION_REG ; preescaler 2, 1:8 con CLK interno (que es
; de 1 MHz si el oscilador es de 4 MHz).
; El contador cuenta cada 8 useg
bcf STATUS,RP0 ; Apunta a banco 0
clrf fase ; pasa a fase 0
pulsadores ; lee órdenes a través de pulsadores
btfss PULSADOR1
goto iniciaPos ; servo en posición central
btfss PULSADOR2
goto incPos ; incrementa Posic
btfss PULSADOR3
goto decPos ; decrementa Posic
goto salidaint
;***********************************************************************************
; Rutinas de apoyo
;***********************************************************************************
iniciaPos movlw d'133' ; 256-133 = 123, 123 * 8 us = 984 us + 512 us
movwf Posic ; posición central en esta disposición
goto salidaint ; y sale
decPos movf Posic,f ; se fija si Posic ya es cero
bz sale1 ; si es cero no decrementa
decf Posic,f ; si no es cero, decrementa
sale1 goto salidaint ; y sale
incPos movlw 0xFF ; se fija si Posic no es 0xFF
xorwf Posic,w ; que es el valor máximo
bz sale2 ; si es el valor máximo, no incrementa
incf Posic,f ; si no es el valor máx, incrementa
sale2 goto salidaint ; y sale
; Regresa todo
salidaint
bcf INTCON,T0IF ; borra el flag de timer
swapf Guardar_STATUS, w
movwf STATUS
swapf Guardar_W, f
swapf Guardar_W, w
bsf INTCON,T0IE ; habilita interrupción de timer
retfie ; retorna
END
|
Bajar el programa en formato ASM (puede usar el botón derecho de su mouse)
Bajar el programa en formato HEX (puede usar el botón derecho de su mouse)
Por las dudas, el archivo incluido P16f628a.inc (puede usar el botón derecho de su mouse)
Para que el lector no deba buscar con lupa los cambios (aunque algunos
son muy evidentes), listaremos qué es lo que debimos cambiar para que el PIC funcione con
interrupciones.
- En primer lugar, se observa el agregado de tres variables: "fase", que nos
servirá para que cuando entremos a la rutina de manejo de interrupción podamos saber en qué
parte del pulso estamos, y las variables "Guardar_W" y "Guardar_STATUS", en las que nuestra
rutina de interrupción reservará estos dos importantes valores (registro STATUS y registro W,
o acumulador), para que cuando regrese al sitio de programa donde se produjo la
interrupción las cosas estén tal cual estaban al salir.
- En segundo lugar notaremos la declaración "org 0x004", seguida de
la instrucción "goto interrupcion". Con este valor se define (para el funcionamiento
interno del PIC) dónde está la rutina que se ejecutará cuando aparezca una interrupción. Nótese
que en la parte final del programa la palabra "interrupcion" es la etiqueta que señala la
rutina de manejo de las interrupciones.
- En tercer lugar encontramos, hacia el final de la parte de inicialización de sistema,
puertos y variables (justo antes de entrar al lazo principal), la habilitación del
funcionamiento en base a interrupciones, que se logra al definir valores del registro
de control de interrupciones, INTCON. El valor
que definimos tiene el bit 7 en 1, que significa que se habilita el funcionamiento de interrupción a
nivel gobal (el bit es GIE, Global Interrupt Enable); y el bit 5 en 1, que significa que
habilitamos la interrupción por sobrepaso (overflow) del timer 0 (cero). Esto significa una
interrupción cada vez que el registro del timer 0 cuente hasta su valor máximo, que
es 255 ó hexadecimal 0xFF, y pase de nuevo a cero. Nosotros usaremos esta característica
para colocar distintos valores en el registro, que avanza de valor a un ritmo conocido
por nosotros, de manera de lograr retardos controlados y exactos. Como se ve, con dos
bits solamente hemos cambiado del todo el funcionamiento de nuestro microcontrolador.
El siguiente cambio en el programa, éste sí muy evidente, es en la
rutina principal o lazo principal. Como se ve, consta de apenas tres instrucciones.
Es obvio que así el PIC también está desaprovechado, pero está claro que en este lugar
ubicaremos, en el futuro, programas más importantes.
A continuación de este lazo vemos una parte del programa anterior,
la que determina el encendido del pulso de control del servo a valor 1, y define el primer retardo,
básico, de unos 0,5 ms. Lo que se debe observar es que el retardo no se produce en
esta rutina, sino que sólo se "lanza" al timer, programando su registro o contador
con el valor necesario. Aquí no hay un lazo cerrado de espera.
¿Cómo se produce entonces el retardo? El retardo ha comenzado ya,
y transcurre durante todo el tiempo en que el programa se encuenta en fase 1.
Este pequeño bloque de programa, que inició el pulso y el retardo, también ha puesto el
programa en esta fase, poniendo a valor 1 la variable "fase".
¿Qué ocurre ahora? El PIC sigue con sus tareas habituales (en este
programa, bien, el PIC prácticamente no hace nada, pero podría estar comandando un
robot). El contador del timer 0 va contando, cada 8 microsegundos, hasta que llega a
sobrepasar su cuenta, rebasando su valor máximo, que es 0xFF (ó 255 en decimal) y
llegando de nuevo al valor 0x00. Aquí se produce la interrupción.
¿Qué significa esto? Significa que la lógica interna del PIC registra
el lugar exacto en el que está el programa (para luego volver a él), se fija en ese vector
0x004 que hemos programado para ver dónde debe ir a ejecutar su rutina de manejo de
la interrupción, y allí va...
Revisemos entonces la rutina de interrupción. A la entrada vemos que
lo primero que hace es inhibir una nueva interrupción a causa del timer. Esto es importante
y debe ser una práctica común a utilizar porque si se produce una nueva interrupción
mientras estamos atendiendo ésta, es decir, mientras estamos dentro de la rutina de
atención de interrupciones, algo fallará.
A continuación, la rutina se ocupa de guardar
el estado (STATUS) y el valor del acumulador (W). Esto es necesario para no afectar
el funcionamiento del programa cuando el PIC retorne al lugar donde se produjo la
interrupción. Ya veremos que a la salida de la rutina de manejo de la interrupción
los valores se reponen.
En la siguiente parte de la rutina de manejo de interrupción vemos
que ésta debe hacer diferentes cosas según en qué fase de programa nos encontremos.
A este punto del seguimiento estamos en fase 1, así que entrará a la rutina llamada "fase1".
fase1 es un fragmento del programa anterior, en el que definíamos el valor
de ancho, o retardo, del resto del pulso: la parte variable. Sólo se ha quitado, una
vez más, el lazo de espera sobre sí mismo (que es el que volvía esclavo al microcontrolador).
Como se observa en las líneas de programa, lo único que debe hacer esta rutina es colocar
un valor variable, la variable Posic, en el registro del timer, pasar el estado a
fase 2, y salir.
En la próxima interrupción (que se producirá cuando se cumpla el tiempo
de la segunda parte del pulso, o la parte variable del pulso de control), la rutina de
manejo de interrupción entrará a la parte de la rutina con la etiqueta fase2.
fase2 también es una parte del programa anterior, y también quedó sin
el lazo de espera. Aquí se pone a cero la señal de control del servo y se define la
siguiente espera, que es la separación entre pulsos de control. Avanza el estado del programa
a fase 3 y sale.
Si observamos el código, vemos que en esta parte estamos
cambiando algo en la configuración del timer. ¿Qué es esto? A no asustarse. Debido a que
en los retardos anteriores teníamos pulsos angostos, la velocidad de conteo del registro
del timer la habíamos definido en un pulso (o conteo) cada 8 microsegundos. Esto nos
daba una buena resolución para controlar el servo y nos permitía alcanzar los valores de
retardo necesario con sólo un byte para definirlos (la variable Posic). El máximo es de
256 x 8 us = 2048 microsegundos, ó 2,048 milisegundos. Pero en esta parte necesitamos un retardo
de 20 milisegundos. No nos alcanza. Por eso, cambiando el valor elegido de prescaler,
cambiamos el ritmo del contador para que cuente una vez cada 256 microsegundos. Un
valor de 7 en el contador (7 x 256 us) nos dará el valor deseado, de (aproximadamente)
18 milisegundos.
Cuando se cumpla este tiempo, se producirá una nueva interrupción y
entraremos a la parte etiquetada fase3 de la rutina de manejo de interrupciones.
En fase3 debemos preparar todo para un nuevo ciclo. Se repone el
prescaler anterior, que nos da un conteo cada 8 us, y se pone el estado del programa en
fase 0. Esta parte de la rutina es muy sencilla, como se ve.
Dentro de fase3 hemos incluido la lectura
de los pulsadores y, según su estado, la actualización de la posición del servo. Lo
colocamos aquí porque a esta parte de las fases entra una vez cada más o menos
20 milisegundos, y así, al actuar un pulsador, el avance y dismimución de la posición
que se produce en el movimiento del servo no es excesivamente veloz. Si dejásemos
esta parte de la rutina en el lazo principal, los pulsadores serían leídos tantas veces,
y a tanta velocidad, que el servo saltaría de un extremo al otro en lugar de moverse
gradualmente. Se nota aquí cómo los tiempos están mejor aprovechados, ya que el
movimiento del servo es más fluido.
En este punto regresamos a la fase 0. Al salir de la rutina
de manejo de interrupciones volvemos al lazo principal de programa y desde allí volveremos
a repetir, una vez más, el ciclo completo de fases.
Espero haber sido claro. Quizás sea necesario leer un par de veces
este programa, imprimiendo la rutina y haciendo marcas en los lugares que nos resulte
necesario.
Para facilitar el seguimiento, aquí van los diagramas de flujo:
Diagrama de flujo de la parte en el programa principal
Diagrama de flujo de la parte en la rutina de atención de las interrupciones
El próximo paso será manejar, con estos mismos conceptos, a más de un servo.
Muy pronto aportaremos la
tercera parte de este trabajo: Control de múltiples
servos.
Algunas cosas que observé en la parte 1 y 2
- Importante: el temblor y el calentamiento del servo crecen mucho si se supera el retardo de 20 ms entre pulsos de control.
Datos adicionales:
Microcontrolador PIC16F628A
PIC16F628A.
Servo de modelismo o RC
Utilizamos el servo Hitec HS-311 porque era el más barato disponible en ese tipo al
momento de encarar el proyecto.
Recomendamos ver, para completar esta información -> Servos, Características básicas
|