Existe un punto donde ambas fuerzas están
equilibradas. Desgraciadamente dicho punto es inestable. Si el imán se aparta
un poco de la bobina la atracción magnética disminuye y el peso gana la
partida: el imán cae. Si el imán se acercara un poco a la bobina, la atracción
imán-tornillo es cada vez más fuerte, por lo que terminará pegándose
arriba. La idea es colocar un sensor de
flujo magnético justo debajo de la bobina. Usando este sensor el PIC puede
detectar la posición del imán y actuar en consecuencia: si el imán empieza a
caerse, hacemos pasar la corriente por el electroimán de forma que atraiga un
poco al imán y éste se recupere. Si se acerca, activamos la bobina en sentido
contrario para rechazar el imán y volverlo a llevar a la posición ideal de
equilibrio.
Podéis encontrar en internet bastantes
variantes de este esquema, aunque la mayoría de ellas no usan un
microcontrolador sino un integrado (MIC502) originalmente diseñado para
modificar la velocidad de un ventilador (mediante PWM) en función de la
temperatura de un sensor. La ventaja de un enfoque con un microcontrolador es que tenemos muchas más posibilidades de control (podríamos p.e. hacer oscilar a voluntad el imán). En este proyecto nos limitaremos a construir el programa básico.
El objetivo del proyecto es lograr algo parecido a lo mostrado en este corto video:
Al igual que en las entradas correspondientes a control de motores acompañamos el código del PIC con un código MATLAB para visualizar los resultados y jugar con los parámetros de control.
Código asociado:
maglev_4520.c (código PIC)
maglev.m (programa MATLAB de interfaz con el PC).
Fichero hex: maglev_4520.hex
maglev.m (programa MATLAB de interfaz con el PC).
Fichero hex: maglev_4520.hex
--------------------------------------------------------------------------------
Descripción del hardware:
Bobina arrollada sobre un tornillo: la bobina son unos 50 metros de hilo esmaltado de un diámetro aproximado de 0.25mm. La resistencia total son unos 16 ohmios. Estos datos son aproximados porque no esperaba documentar esto cuando forme la bobina. A posteriori medí su resistencia total (16 ohmios) y la compare con otros hilos de donde saque su diámetro aproximado y de ahí su longitud.
De todas formas hay tantos factores que influirán en la calibración final (fuerza y peso del imán, tipo de sensor usado, etc.) que no es crítico que la bobina sea exactamente como la mía. Los valores anteriores pueden ser un punto de referencia.
Lo que si es importante es que la resistencia total de la bobina sea lo
suficientemente alta para que la intensidad máxima (dependiente del voltaje
usado para la bobina) no supere las especificaciones del driver usado. En
funcionamiento normal el consumo es reducido (unos 20-30 mA) pero cuando
estamos haciendo pruebas muchas veces estaremos aplicando el voltaje al
completo.
La bobina se arrolla sobre un tornillo de 8mm de diámetro que forma el núcleo del electroiman.
Sensor de flujo magnético basado en
efecto Hall: he probado tanto un SS49E de Honeywell como un A1302 de Allegro. Lo importante es
que sea un sensor de medida continuo, no uno de detección de polos, que tienen
una respuesta binaria. Los sensores utilizados tienen 3 pines (V, GND y
OUTPUT). La alimentación ha sido con 5V y la salida (en ausencia de campo
magnético) es de V/2, lo que corresponderá a 512 una vez convertida en nuestro
ADC.
Alimentación de la bobina: uso una batería de litio de 7.5V.
Controlada con el PIC a través de un L293D, al igual que hicimos en el caso del
motor. En el peor de los casos puedo tener una intensidad de 7.5/16 = 0.5A, dentro de las posibilidades del driver (L293D) usado.

Conexiones: del soporte anterior salen 5 conexiones. Tres de ellas corresponden al sensor: alimentación V (5V), tierra GND y la salida del sensor S. Las dos primeras van directamente a Vcc y GND de la placa EasyPIC, mientras que la salida del sensor la llevamos al pin que usaremos como entrada analógica, en nuestro caso RA0.
Las otras
dos conexiones (B1 y B2) corresponden a la bobina. Al igual que pasaba con un
motor, no podemos controlar directamente la bobina desde el PIC. Como deseamos
tener la capacidad de cambiar el sentido de la corriente (para que el electro
atraiga o rechace el imán) usaremos un puente H como se indicó en el tema
anterior.
Hemos
optado de nuevo por usar (la mitad de) un L293D. De esta forma las conexiones
de la bobina (B1,B2) irán a dos de las salidas del L293D (OUT1 y OUT2).
Respecto a
las líneas de control desde el PIC, hemos optado por la configuración que llamábamos
de tipo B) en la entrada sobre control de motores. En dicha configuración, la
salida PWM del PIC (por ejemplo RC2) se lleva directamente a una (IN1) de las
entradas del driver. El complementario de la señal PWM (obtenida con un
inversor constituido con un simple transistor) se lleva a la otra entrada IN2.
Como
vimos, de esta forma, un duty cicle del 50% en la señal PWM supone que no
estamos actuando sobre el electroiman. Por el contrario, valores de 0 o 1023 suponen una máxima
atracción/repulsa del imán.
En cuanto
al pin ENABLE del L293D podríamos dejarlo atado a un nivel alto (5V)
continuamente. De esta forma el driver estaría siempre "encendido". Sin embargo hemos
preferido conectarlo a otro pin (RC0) del PIC, para poder desconectar/conectar
el electro desde el programa.
Aunque opcional, esta conexión es muy conveniente. Si por ejemplo el imán se
cae, el sensor detectaría que hay que subirlo y el PIC pondría a la bobina en
estado de máxima atracción. Como el electro no va a tener fuerza para levantar
al imán de la mesa, la situación se va a mantener. La bobina va a estar tirando todo el rato, tratando
(inútilmente) de recuperar el imán. Lo
mismo pasa si el imán llega a pegarse al núcleo del imán. Por mucho que lo
intente el electro no puede vencer dicha fuerza y el imán seguirá pegado al
tornillo.
El
programa puede detectar estas situaciones a través del sensor (un error grande mantenido mucho
tiempo, positivo en un caso, negativo en otro) y en esos casos puede cortar la
corriente a la bobina, poniendo a 0 el pin ENABLE del L293D. En cuanto
volvamos a acercar el imán al punto adecuado, el error volverá a tener un valor
razonable y se pondrá a 1 el
ENABLE, recuperando la funcionalidad del sistema
De esta
forma sólo estaremos gastando batería cuando el imán esté levitando y no cuando
se caiga o lo quitemos.
Puerto serie: usaremos el puerto serie (pines RC6,RC7) para mandar los resultados y recibir órdenes del PC. Como en la placa EsayPIC disponemos de un conversos de niveles y un conector DB9 no tendremos que preocuparnos de esa parte.
Descripción del software:
En este proyecto vamos a usar casi todo lo que
hemos visto hasta ahora:
·
Temporizadores e interrupciones para tomar medidas y controlar el
electroimán cada cierto tiempo (unas 500 veces por segundo)
·
Uso del ADC para leer datos de un sensor analógico (usando la
correspondiente interrupción).
·
Uso del PWM para controlar un electroimán a través de un puente en
H.
·
Uso del puerto serie para mandar datos al PC y representarlos
usando MALAB.
·
Algoritmo tipo PID para el control de la bobina en función de los
datos del sensor.
Lo bueno es que los detalles de todos los puntos
anteriores ya están explicados en entradas anteriores, por lo que podemos
limitarnos a ver como se enlazan en el programa final.
Interrupción TX del puerto serie:
Tras los
#includes y #pragmas habituales para definir la configuración, empezamos reservando
espacio para el buffer de escritura del puerto serie (ref entrada UART INTS).
Ya comentamos en su momento que debido a la
estructura de la memoria del PIC en bancos de 256 bytes podríamos tener
dificultades si necesitabamos reservar un array de más de 256 bytes o si una de
nuestras variables cruza una frontera de 256 bytes. Es posible hacerlo pero no
es inmediato. En este programa ilustramos como se hace. En nuestro programa
deseamos reservar un array (tx_buf) de tamaño 256 bytes. Para ello debemos hacer dos cosas:
1) Modificar
el archivo 184520_g.lkr con la información sobre la distribución de la memoria
del modelo de PIC usado. Lo mejor es hacer una copia del original en el
directorio de nuestro proyecto y modificarla. Hay que acordarse de añadirla al
proyecto en la sección de Linker Scripts
como se muestra en la imagen adjunta. Si no se hace así el compilador
usara la copia original de su directorio y conseguiremos linkar el programa. Buscar
la sección del fichero donde se describen la posición de los distintos bancos
de memoria. El original será algo así como:
ORIGINAL:
DATABANK NAME=gpr0 START=0x80 END=0xFF
DATABANK NAME=gpr1 START=0x100 END=0x1FF
DATABANK NAME=gpr2 START=0x200 END=0x2FF
DATABANK NAME=gpr3 START=0x300 END=0x3FF
DATABANK NAME=gpr4 START=0x400 END=0x4FF
Y lo
cambiaremos por esto:
DATABANK NAME=gpr0 START=0x80 END=0xFF
DATABANK NAME=gpr1 START=0x100 END=0x1FF
DATABANK NAME=gpr2 START=0x200 END=0x2FF
DATABANK NAME=gpr3 START=0x300 END=0x3FF
DATABANK NAME=gpr4 START=0x400 END=0x4FF PROTECTED
SECTION NAME=buffer_maglev RAM=gpr4
Lo único
que hemos hecho es declarar un banco (gpr4, de 256 bytes desde la posición 0x400 a
la 0x4FF) como PROTECTED, de forma que el compilador no intente meter ninguna
variable en esa zona, salvo las que explícitamente declaremos que están en ese
banco. En la línea añadida (SECTION NAME) simplemente damos un nombre
(buffer_maglev) al banco reservado para poder referirnos a él en nuestro
programa.
En el
programa principal reservaremos nuestra variable tx_buf[256] de la siguiente
forma:
// USART TX BUFFER
#define BUF_SIZE 256
#pragma udata buffer_maglev
static uint8 array_1[BUF_SIZE];
#pragma udata
uint8
*tx_buf=&array_1[0];
uint8 tx_next=0; uint8
tx_sent=0; // TX indexes
Usando
#pragma udata reservamos un array del tamaño adecuado y le indicamos al
compilador que lo posicione en la sección buffer_maglev que hemos definido en
el fichero anterior. Luego apuntamos nuestro puntero tx_buf al inicio de dicho
array. A partir de entonces ya podemos usar tx_buf[k] en nuestro programa.
Las líneas
siguientes inicializan los punteros que se moverán sobre el buffer. Recordad
que tx_next lo incrementaremos nosotros cuando pongamos datos en el buffer. El
puntero tx_sent lo incrementara la interrupción de TX cada vez que pase un
carácter al módulo USART para que lo envíe. El código de la ISR de la
interrupción será:
// TX_isr gets called
when TX_flag is set. That means that the port is ready to tramsmit.
// If
tx_next==tx_sent, there is nothing to send and TX_INT is disabled.
// If tx_next!=tx_sent,
next byte in TX buffer is loaded in TXREG, and the
// pointer is
incremented, making sure it remains within the [0,BUF_SIZE-1] range
void TX_isr(void)
{
if (tx_sent==tx_next) disable_TX_int;
else { PORTCbits.RC5=1; TXREG=tx_buf[tx_sent++]; PORTCbits.RC5=0;
}
}
Estos
buffers son circulares, por lo que al llegar al final los punteros tx_sent y
tx_next deberían pasar a 0. Como en este caso el tamaño de los buffers es de
256 bytes y ambos punteros son de tipo uint8, no tenemos que hacer nada especial
pues pasarán de forma natural a 0 al ser incrementados desde 255.
Lo único
que queda respecto al puerto serie es inicializarlo (en el main) para una
comunicación a 57600 baudios:
void main()
{
...
//
USART setup (clock 8 MHz, BRGH=1, SP = 8) --> 57600 bauds
OpenUSART(USART_TX_INT_OFF&
USART_RX_INT_OFF & USART_ASYNCH_MODE &
USART_EIGHT_BIT &
USART_BRGH_HIGH , 8);
...
}
Configuración del ADC:
void main()
{
...
ADCON0 = 0b00000001;
ADCON1 = 0b00001010;
ADCON2 = 0b10010001;
...
}
Estas tres
líneas configuran los registros del ADC (ver entrada sobre ADC). Entre las opciones configuradas:
·
Reloj
del ADC = Fosc/8, lo que me da una frecuencia de 1 MHz para un reloj de 8 MHz.
Esto corresponde a un Tad de 1 microsegundo, que resulta compaible con las
especificaciones del 4520 (Tad > 0.75 usec).
·
Programación
automática de un Tacq igual a 4 Tad = 4 usec, que es superior al recomendado de
unos 2-3 usecs.
·
PORTA dedicado a entradas
analógicas.
·
No
se van a usar voltajes de referencia distintos de 0 y 5V.
·
Formato
a la derecha del resultado final en los registros ADRESH:ADRESL
Configuración del PWM:
Usando la
función OpenPWM1 junto OpenTimer2 configuramos (también en el main) la
frecuencia del PWM:
void main()
{
...
// PWM setup (Fpwm = (Fosc/4)/(256) = 7.81250 KHz
//
TMR2 with 1:16
POSTscaler --> Fservo = Fpwm/16 = 488 Hz
OpenPWM1(255);
OpenTimer2(TIMER_INT_OFF & T2_POST_1_16
& T2_PS_1_1);
...
}
Configuraramos
PWM con PR2=255, PRE del TMR2=1 lo que nos da (entrada PWM) para un reloj
de 8 MHz una frecuencia de:
Fpwm =
(Fosc/4)/(PRE x (PR2+1)) = 7.8 KHz
Luego
necesitaremos un timer para tomar medidas cada cierto tiempo y actuar sobre la
bobina. Podríamos usar cualquier timer, pero aprovechamos que TMR2 está
corriendo (ya que es usado en la generación del PWM) para usar su interrupción
para programar la adquisición de datos del sensor. En la inicialización
anterior del TMR2 programamos un POSTscaler de 1:16 para que la interrupción
salte cada 16 rebosamientos. Esto corresponde a 16 veces el periodo del PWM o
2.048 msec. Aproximadamente tendremos una frecuencia de actuación (servo) de
unos 500 Hz, adecuada para nuestro proyecto.
Para
modificar el duty cicle de la onda PWM usaremos la siguiente función (o
alternativamente la del compilador C18)
// Set duty cicle (10
bits) for PWM1 (RC2)
void set_pwm1(unsigned duty )
{
CCP1CONbits.DC1B0=(duty& 0x01);
duty>>=1;
CCP1CONbits.DC1B1=(duty& 0x01);
duty>>=1;
CCPR1L=duty;
}
Interrupción Timer TMR2:
El código
de la interrupción del TMR2 es muy sencillo:
// TMR2 INT (every
2.048 msec with a 8 MHz clock)
// Starts the sampling
500 times per second
void timer2_int(void)
{
PORTCbits.RC4=1;;
ADCON0 = 0b00000011; // Canal 0 GO=1
PORTCbits.RC4=0;
}
La 1ª y 3ª
línea modificando PORTC son simplemente un control para verificar con el
osciloscopio que todo esta funcionando correctamente y pueden ser eliminadas
sin problemas. El código es simplemente una línea donde seleccionamos el canal
(AN0 = RA0) y lanzamos el proceso de la conversión analógica digital.
Como el
tiempo de adquisición está programado no hay que preocuparse de nada: el PIC
esperará a que pase los 4 usec programados y lanzará la conversión. Una vez
lista se generará la interrupción del ADC, que es donde hacemos la mayor parte
del trabajo.
Interrupción ADC:
Dentro de
la interrupción del ADC es donde se hace el trabajo principal. En ella se lee
la medida del sensor y junto con el historial de errores pasados se estima la
integral y derivada del error. A partir de ellas se calcula el parámetro de
control (duty cycle del PWM) usando un controlador PID y se modifica la señal
PWM que controla la bobina (a través del driver L293D).
#define h 16
#define MAX_DUTY 511
#define error_max 51
#define TOPE 3200 //e_max*NPAST
// ADC int: reads
sensor output and computes PID
void adc_int(void)
{
uint8 p;
uint8 JUMP;
uint16 res;
PORTCbits.RC4=1;
res=ADRESH; res<<=8; res+=ADRESL; // sensor measurement
// Current
error
error=(res-Kpid[0]);
if
(error>=error_max) error=error_max;
else if
(error<=-error_max) error=-error_max;
// Integral
error
error_int+=
(error-error_past[next]);
if ((error_int>=TOPE) ||
(error_int<=-TOPE)) L293_ENABLE=0; else L293_ENABLE=1;
//
EStimation of derivative of error using f' = (3f(x) - 4f(x-h) + f(x-2h) )/(2h)
JUMP=255-h; JUMP++;
error_der = 3*error;
p = next-h; if (p>=NPAST) p-=JUMP; error_der-=4*error_past[p];
p = p-h;
if
(p>=NPAST) p-=JUMP; error_der+=error_past[p];
//
Substitute current value for oldest value in table
error_past[next]=error;
next++; next &= (NPAST-1);
// Computes PID factor
duty = (Kpid[1]*error) +
((Kpid[3]*error_der)/h)*4 + (error_int/Kpid[2]);
// Makes sure it is in the [-512,512] range
if (duty > MAX_DUTY) duty = MAX_DUTY; else if (duty <
-MAX_DUTY) duty = -MAX_DUTY;
//
Translate it to the [0 1023] range and use it to set duty cicle.
duty+=512; set_pwm1((uint16)duty);
#ifdef TERMINAL
#else
create_send_msg(); //Creates and send a message with info
#endif
PORTCbits.RC4=0;
}
Algunos
comentarios sobre el código anterior:
·
Los
datos del PID (objetivo + constantes Kp, Ki, Kd ) están guardados en un array
Kpid en el orden citado. La única peculiaridad es que para poder operar con
aritmética entera, trabajamos con el inverso de la variable Ki, de forma que al
sumar los términos del PID dividiremos (en lugar de multiplicar) por Ki.
·
Tras
recoger los datos de ADRESH, ADRESL en la variable res, calculamos el error de
la medida respecto al objetivo o target, guardado en Kpid[0].
·
Para
calcular el error integral mantenemos un "historico" de errores
pasados. En este caso usamos 64 valores antiguos en el array error_past[]. La
variable next indica la posición de la tabla conteniendo el error más antiguo.
·
De
nuevo, para sumar el error integral, restamos el error más antiguo (que va a
ser eliminado) y sumamos el que acabamos de medir. A la suma resultante
deberíamos multiplicarla por dt (intervalo entre muestras = 0.002048 segundos)
para estimar la integral del error. Con objeto de evitar tener que usar
aritmética en coma flotante, dicha constante se encuentra integrada en la
constante Ki (entera) usada.
·
Si
el error instantáneo es mayor que un cierto valor (en este caso 50) se usa un
valor máximo. Esto nos sirve para evitar posibles problemas de desbordamiento
en la suma de los errores pasados. De todas formas se puede comprobar que
errores mayores que +/- 30 son irrecuperables por el sistema (nos hemos
apartado tanto del equilibrio que la bobina no puede volver a llevar al imán de
vuelta al punto de equilibrio).
·
Si
el error integral es mayor (en valor absoluto) que un cierto umbral significa
que el imán se ha caido (o se ha pegado al electro) por lo que ponemos a cero
el pin definido en L293_ENABLE (RC0). Dicho pin está conectado al
correspondiente pin del L293D y detiene la actuación sobre la bobina. En caso
contrario ponemos a 1 dicho pin.
·
Estimamos
la derivada del error usando la fórmula: e(n) -
4e(n-h) + e(n-2h) , donde h es un salto en muestras (hemos usado un valor de h=16). Notad que para una correcta estimación
de la derivada del error deberíamos
dividir por (2*h*dt) donde dt es el intervalo de toma de datos (2.048 msec). De
nuevo, dichos términos están incluidos dentro de la constante Kd para evitar
operaciones en coma flotante.
·
Finalmente,
antes de pasar a calcular el parámetro PID de control, guardamos el último
error en la posición indicada (next) machacando la más antigua. El contador
next se incrementa.
Una vez
que disponemos del error instantáneo, error integral y derivada del error, los
combinamos usando las correspondientes constantes Kp=Kpid[1], Ki=1/Kpid[2] y Kd=Kpid[3],
obteniendo así el valor de control a usar (entre -511 y 511). Si nos pasamos de ese intervalo lo situamos en
el valor límite -511 o +511.
Sumándole
512 a dicha cantidad estamos listos para
usarla como duty cicle del PWM.
Finalmente,
tras concluir el proceso de control, mandamos un mensaje binario a través del puerto
serie con el error medido. Como comentamos en la
entrada anterior dicho mensaje contiene un contador para verificar la
integridad de los datos recibidos en el PC. Para ello usamos la función:
void create_send_msg(void)
{
int8 e;
uint8 *ptr;
tx_buf[tx_next++]='A'; // 'A', start of packet
e = (int8)error; tx_buf[tx_next++]=e; // error (1 byte)
tx_buf[tx_next++]=msg_cont++; // message counter
tx_buf[tx_next++]='Z'; // 'Z', end of packect
enable_TX_int;
}
que escribe directamente sobre el buffer TX del puerto serie, incrementando el contador correspondiente.
Previamente
se han declarado el array de errores pasados y las variables donde guardamos
los distintos tipos de error:
#define L293_ENABLE PORTCbits.RC0
#define NPAST 64
int16 error_past[NPAST];
uint8 next=0;
int16 error, error_der, error_int=0;
int16 duty;
int16 Kpid[4];
#define reset_Kpid
{Kpid[0]=410; Kpid[1]=30; Kpid[2]=50; Kpid[3]=5; Kpid[4]=16;}
Finalmente,
nos queda definir la ISR combinando las tres interrupciones usadas:
// Interruption
Service
#pragma interruptlow high_ISR
void high_ISR (void)
{
if (TX_flag) {
TX_isr(); TX_flag=0; }
if (AD_flag) {
adc_int(); AD_flag=0; return; }
if (TMR2_flag) { timer2_int(); TMR2_flag=0; return; }
}
// Code @ 0x0008 ->
Jump to ISR for High priority interruption
#pragma code high_vector = 0x0008
void code_0x0008(void) {_asm goto high_ISR _endasm}
#pragma code
Con esto
ya tenemos descrito casi todo el código usado. En el main() además de las
configuraciones del ADC, PWM y USART comentadas debemos habilitar las correspondientes
interrupciones y declarar RA0 como entrada:
void main()
{
...
reset_Kpid;
// Reset PID to initial state
c=0; while(c<=NPAST) error_past[c++]=0 // past
errors = 0
...
L293_ENABLE=0;
TRISB=0x0; PORTB=0; TRISC=0; PORTC=0;
TRISA = 0xFF; // PORTA
input, PORTB,PORTC outputs
...
enable_AD_int; enable_TMR2_int; //
Enable AD & TMR2 INTs
enable_global_ints; enable_perif_ints;
while(1);
}
Puesta en marcha:
Lo primero es comprobar que la secuencia de muestreo/actuación/envío de información se cumple correctamente cada 2 msec (aprox). Como hemos
comentado, los pines RC4 y RC5 se usan para poder comprobar en el osciloscopio
que todo está funcionando correctamente. RC4 se alza mientras estamos
procesando la interrupción del TMR2 y la del ADC. RC5 lo alzamos cada vez que
la interrupción TX da salida a un byte hacia el módulo USART para ser
transmitido.
El
siguiente pantallazo del osciloscopio muestra la secuencia (RC4 en canal 1,
arriba, y RC5 en canal 2, abajo):
Vemos que primero entra la interrupción del TMR2 lanzando el proceso del ADC. Es casi instantánea ya que solo tiene que escribir el valor del correspondiente registro ADCON. Al terminar la conversión AD viene la interrupción del ADC, que es la que más tiempo se demora, al tener que calcular los diferentes errores, combinarlos en el PID y fijar el duty cicle. Finalmente, (canal 2 abajo) una sucesión de picos nos indican la salida de los sucesivos bytes que componen cada mensaje que se envía al PC. Podemos comprobar que para la velocidad del puerto serie usada (57600) estamos bastante al límite de lo que podemos enviar antes de que se nos venga encima la siguiente toma de datos.
Comunicación con el PC:
En el
main() anterior terminábamos con un bucle while en vacío. Dicho programa
funcionaría, pero al igual que antes nos interesaría poder modificar los
parámetros del PID desde el PC. Por ello hemos incluido un simple chequeo del
puerto serie y en función del carácter recibido incrementamos/decrementamos las
constantes del PID o el objetivo. Esto es
especialmente importante en este caso, donde previsiblemente tendremos que
trastear un rato antes de conseguir hacer levitar el imán.
Hay implementadas dos formas de comunicación. Para habilitar la primera de ellas basta establecer el siguiente #define al principio del programa:
#define TERMINAL
En este caso se deshabilita el envio de información constante y se usa un protocolo sencillo que es fácil usar desde cualquier terminal serie. La apariencia de dicha interfaz es la siguiente:
Usando '*' seleccionamos el parámetro (0,1,2,3) a modificar y lo incrementamos/decrementamos con +/-.
Con '0' reseteamos los valores de target y constantes a los valores iniciales. Cada vez que hay un cambio el PIC nos vuelca los nuevos valores y continuamene nos da información sobre los errores.
Esta interfaz es adecuada para calibrar el sistema si no disponemos o no deseamos usar MATLAB.
Si comentamos el #define anterior usaremos una nueva interfaz que está pensada para ser usada con el correspondiente programa MATLAB para recoger y visualizar los datos.
El programa es maglev.m y tiene un funcionamiento similar a los ya vistos. Podemos abrir el puerto serie y si parseamos los mensajes veremos aparecer gráficas con los distintos errores. Dada las limitaciones comentadas antes sobre la capacidad de mandar datos de forma continua el PIC solo envía los datos de error instantáneo. Los errores integrales y derivada se obtienen dentro del programa de MATLAB con un cálculo análogo al usado dentro del PIC.
Disponemos también de un "teclado" de 12 pulsadores que nos permiten incrementar/decrementar o resetear el objetivo y cada una de las constantes del PID. Al pulsar una tecla se mandará un caracter al PIC y este modificará la correspondiente entrada del array Kpid[].
En este video final se ilustra el uso de la interfaz de MATLAB y las gráficas obtenidas, al mismo tiempo que podemos ver como levita el imán.
Este comentario ha sido eliminado por el autor.
ResponderEliminarHola que tal ant...
ResponderEliminarTengo una consulta sobre tu proyecto.
Por que esos valores de Kp=28, Ki=40 y Kd=35 ?
Los valores de las constantes del PID los encontré tanteando. Ahí es muy conveniente la interfaz con el PC para poder modificar los valores y observar comportamiento.
EliminarNo son en absoluto fundamentales y si intentas replicar el proyecto tendrás que determinar tus propios valores, que dependerán de tu bobina, alimentación, peso y fuerza del imán usado, etc.
Antonio.
hola tengo unas dudas, por que dice que hay un iman esta dentro de la implementacion o es el electroiman?
ResponderEliminarel controlador que esta implementado en el pic es digital o analogo?.
y cuales fueron los criterios de diseño de la bobina, de verdad esta siendo controlada por un L293B? a que voltaje? he visto otras implementaciones pero las hacen a voltajes y corrientes muy altas, quisiera saber hasta que fuerza puede ejercer esta bobina y quedar en equilibrio.ya que me parece muy interesante que se pueda levitar con corrientes y voltajes pequeños
Con lo del imán me refiero a que si cambias el imán (peso/fuerza) vas a tener que cambiar los parámetros de control.
EliminarEl controlador es digital. Toma lecturas con el ADC del sensor Hall y modula el duty de un PWM en función de lo que dicte un controlador PID.
No use ningún criterio de diseño de la bobina. Fue simplemente la que me salió bobinando un tanto de hilo que tenía a mano.
El driver usado es efectivamente un L293D (sin diodos). Es más que suficiente porque la bobina se alimenta con 7V (bateria litio) y en equilibrio consume unos 30 mA. Lo he llegado a hacer funcionar alimentando la bobina + el PIC a 5V, directamente del USB del PC.
Una bobina mayor te da más margen para corregir desequilibrios, ya que puede tirar o empujar con más fuerza. Este diseño trata de mantener al imán en el punto donde se equilibran su peso y la atracción magnetica por el núcleo (tornillo) de la bobina. La bobina solo tiene que corregir desviaciones, de ahí su escaso gasto.
Espero que te haya aclarado algo,
Antonio
hola,,, excelentes post,,, he probado varios de tus proyectos y me han ayudado bastante,,, pero en este si he tenido varios problemas creo q podria ser mi compilador y quisiera si fuera posible q me facilitaras el .hex del pic para verificar o comprobar donde tengo el error,, si es el pic u otro componente,, he usado todos los accesorios descrito en tu post,, de antemano gracias,, buen aporte
ResponderEliminarHe dejado disponible (al principio del post, junto con el fuente) el fichero .hex
EliminarGracias por el feedback, Antonio
Buenos Dias:
ResponderEliminarTengo una pregunta, lo que pasa es que estoy haciendo el mismo proyecto pero con arduino, mi duda es sobre el sensor de efecto hall A1302, yo estoy usando el sensor de efecto hall ugn3503 y mi pregunta es si se puede ocupar igual que el A1302 o tiene distinto funcionamiento.
Mi otra consulta es que cuando energizo la bobina con el sensor, esta me indica unos valores que corresponden a la distancia del objeto, pero cuando cambio el sentido de la corriente me cambian los valores del sensado y no puedo determinar la distancia.
Me podrías decir como estableciste la distancia para todos los cambios que sufre el sistema.
Muchas Gracias
Respecto al sensor el UGN3503 también da una salida proporcional al campo por lo que debería funcionar igualmente. De hecho en las primeras versiones use un UGNxxx (aunque no recuerdo si era justo el 3503) y me funciono correctamente. Lo de pasar a usar un A1302 no fue por ninguna razón especial. Simplemente encontré una oferta en eBay para comprar 10 o así y como iba a usar este proyecto para proponerselo a mis alumnos en un curso sobre micros quería tener "reservas".
EliminarRespecto a tu segunda pregunta yo también me planteé el problema de como influiría el campo de la bobina en las medidas. Mi razonamiento fue que en una primera aprox podría ignorarlo, ya que idealmente el sistema trabaja en el punto en el que el imán se equilibra por su propia atracción al núcleo de la bobina. Por lo tanto la bobina no debería (casi) estar en funcionamiento.
Por lo tanto, como una primera aprox. al valor objetivo para la salida del sensor lo que hice fue montar el sistema y sacar la respuesta del sensor Hall por el LCD o puerto serie, pero con la bobina desconectada (sin actuar sobre ella).
Luego fui acercando el imán desde abajo hacia el conjunto bobina + tornillo y anote el valor dado por el sensor cuando el imán parecía querer saltar hacia el tornillo (peso = atracción magnética). Ese fue el punto de partida de mis pruebas.
Lo que encontré muy útil para refinar los paráetros es tener una interfaz con el PC que te permita ir modificando los valores e ir viendo el comportamiento, sin tener que recompilar y volver a flashear el PIC.
Un saludo, Antonio.
hola gracias por responder:
EliminarSabes hice lo que me dijiste y empiezo a tomar las mediciones con la bobina apagada y cuando veo que empieza a tirar establezco desde allí el control, pero el tema es que al principio, cuando esta la bobina apagada me empieza a medir un valor pequeño y cuando sube del punto deseado yo activo la bobina para que invierta la corriente y así aleje el imán de la bobina, pero sigue pasando lo que le decía que me cambian los valores una vez que se activa la bobina y no se logra mantener suspendida.
Yo estoy haciendo este proyecto con lógica difusa y establezco las reglas para que funcione pero sigo teniendo el problema del sensor y no puedo controlarlo.
por favor algún consejo de como arreglar esto,gracias.
Cuando dices que activas la bobina, ¿lo haces de alguna forma proporcional? Si es así cuando estás cerca del objetivo la bobina estaría "casi" apagada y no debería influir tanto.
EliminarEl problema es que no se muy bien como funciona tu controlador con lógica difusa.
Una cosa que podrías hacer (para asegurar que todo el montaje, sensor, etc. están correctos) sería implementar un controlador PID como explico en el artículo. Si te funciona en ese caso sabrías que el problema está en el diseño de tu controlador.
Un saludo, Antonio.
Este comentario ha sido eliminado por el autor.
ResponderEliminarDisculpa, estoy intentando realizar este proyecto pero tengo unas dudas, la programación compiló bien pero a la hora de quemar el pic (uso el master prog) no me detectaba los bits de configuracion del hex, así que tuve que exportarlos del mplab para que el pic se quemara sin problemas (aunque el hex aumentó de peso). ¿Afectaría eso? He tenido problemas a la hora de ver los datos en MATLAB, porque cuando lo puse en la proto de repente enviaba datos si se movía un poco la tierra del sensor pero por unos segundos, luego dejo de mandar datos por completo (se queda en puerto COM conectado). Lo emplaque pensando que la proto hacia falso pero igual sigue sin verse los datos correr en MATLAB. ¿Podrías aconsejarme al respecto? Estoy usando un MAX232 y un cable USB serial.
ResponderEliminarHola, yo he realizado un levitador magnético con un PID implementado con opams, ahora quiero hacerlo con un controlador digital. Mi pregunta es: ¿Conoces algún método para caracterizar la planta? ,¿usaste alguno de ellos o fue experimental? y ¿por qué?.También me gustaría saber ¿Cuántas vueltas aproximadamente tiene tu bobina? y ¿De qué longitud es tu tornillo? Gracias.
ResponderEliminarNo use ningún método para caracterizar el sistema. Todo fue totalmente experimental (que es la forma bonita de decir chapuzero). Si tengo tiempo si que me gustaría volver sobre este proyecto con un enfoque de teoría de control, con un modelo de la planta, etc. Si haces algo en esa línea te agradecería el feedback.
EliminarRespecto a la bobina, como comento en el post, no pensaba documentar el proyecto, así que no tengo los datos exactos, pero son como 50 metros de 0.25mm2 arrollados en un tornillo de 6mm de diametro y 8 cm de longitud.
De todas formas no es crítico. He hecho algún otro también con otra bobina determinada por la cantidad de hilo que tenía suelto, con un tornillo de cabeza redondeada (en vez de hexagonal) y también funcionó. Es la ventaja del enfoque experimental. Si como dices, estuvieramos haciendo un modelo del sistema, todos esos parámetros habría que determinarlos con suficiente exactitud.
Espero haberte servido de algo, Antonio
Este comentario ha sido eliminado por el autor.
EliminarHola Antonio me llamo Carlos, soy profesor de física de secundaria estoy realizando este mismo proyecto con arduino, ¿serias tan amable de pasarme el código que utilizaste?
Eliminarhola me podrían decir el modelo de pic que usas???
ResponderEliminarHola, buen día. Muy bueno el proyecto, lo felicito. Yo estoy haciendo un proyecto igual, con un electroimán un poco más grande y un PIC 18F2550. Ya he realizado casi todos los cálculos y simulaciones y en este momento estoy en la etapa de sintonización del PID. Aprovechando su experiencia en la materia, quisiera evacuar algunas dudas que me surgieron:
ResponderEliminar1)Podría explicarme como "trabaja" la integral y la derivada del error en el programa? La fórmula utilizada para calcular el error derivativo, ayuda a eliminar el ruido en la señal? y porque esa fórmula?.
Desde ya le agradezco.
Carlos
Hola disculpa no puedo hacer el diseño de la placa, me podrias ayudar
ResponderEliminarcomo le hiciste para evitar que el iman tratara de alinearse con el electroimán y por lo tanto se volteara?
ResponderEliminarBuenas noches, disculpe no tendra lo que es el circuito en proteus, o algun diagrama?? tengo algunas dudas sobre las conexiones, muchas gracias.
ResponderEliminarY ttambien como puedo incluir las librerias delays.h , timers.h , usart.h y pwm.h porque me manda error de que debo declarar variables, gracias
ResponderEliminarAmigo por favor nos puedes dar el link del video? Lo podemos encontrar el youtube o algo asi??? es que no nos cargó el video de esta pagina... Muchas gracias!!!
ResponderEliminarPrimer vídeo: https://www.youtube.com/watch?v=3sLX0CEYPMo
EliminarSegundo vídeo: https://www.youtube.com/watch?v=LdqZrnu--1k
Antonio buen día!! me gustaría saber en que compilador realizaste el programa para controlador el microprocesador y como creas la interfaz gráfica en el matlab. De antemano muchas gracias.
ResponderEliminarAntonio buen día!! me gustaría saber en que compilador realizaste el programa para controlador el microprocesador y como creas la interfaz gráfica en el matlab. De antemano muchas gracias.
ResponderEliminarEstoy haciendo el mismo proyecto y he probado el código de la interfaz en matlab y el codigo para controlar el micro lo he puesto en el compilador mplab y ambos me crean errores. La verdad es que nose como lo has creado, si me puedes ayudar en eso te lo agradecería
ResponderEliminarnota: matlab y mplab son compiladores diferente
EliminarPara resolver tu pregunta el libro del autor Ogata, Control digital viene explicado a mayor detalle sobre la formula y el uso del PID. (Proportional Integral and Derivative).
ResponderEliminarSi recuerdo bien Proportional = Ganancia, Integral = Hacer el error lo mas cercano a 0 y Derivative = Hacer que el circuito reaccione rapido a las perturbaciones que pudiera tener.
bro programaste be mikroC ? o en Mplab x
ResponderEliminar