Translate

miércoles, 20 de febrero de 2013

Proyecto: Levitador magnético

En este proyecto escribiremos un programa para el PIC que mantenga un imán suspendido en el aire. El PIC controlará la bobina de un electroimán a través de un L293D. Bajo este electroimán colocaremos un imán. En ausencia de corriente el imán siente dos fuerzas: la gravedad hacia abajo y la atracción magnética con el núcleo (un tornillo) del electroimán. 

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 


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

          
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.


Soporte para la bobina y el sensor: En el esquema adjunto podemos observar el montaje de la bobina + sensor en un soporte de madera. La bobina se sujeta al soporte aprovechando el propio tornillo que sirve como núcleo del electroimán. El sensor se encuentra posicionado justo debajo del tornillo, oculto en un receso de la madera. Debajo del sensor sería conveniente poner un material blando (un trozo de cámara de bici, espuma o similar) para que amotigüe los numerosos golpes que se llevará el sensor cuando el imán golpee contra él en la fase de pruebas. También se puede "cerrar" el receso de la madera con algún material (no magnético).

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.




31 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Hola que tal ant...

    Tengo una consulta sobre tu proyecto.

    Por que esos valores de Kp=28, Ki=40 y Kd=35 ?

    ResponderEliminar
    Respuestas
    1. 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.
      No 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.

      Eliminar
  3. hola tengo unas dudas, por que dice que hay un iman esta dentro de la implementacion o es el electroiman?
    el 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

    ResponderEliminar
    Respuestas
    1. 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.

      El 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

      Eliminar
  4. 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

    ResponderEliminar
    Respuestas
    1. He dejado disponible (al principio del post, junto con el fuente) el fichero .hex

      Gracias por el feedback, Antonio

      Eliminar
  5. Buenos Dias:

    Tengo 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

    ResponderEliminar
    Respuestas
    1. 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".

      Respecto 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.

      Eliminar
    2. hola gracias por responder:
      Sabes 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.

      Eliminar
    3. 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.

      El 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.

      Eliminar
  6. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  7. Disculpa, 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.

    ResponderEliminar
  8. Hola, 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.

    ResponderEliminar
    Respuestas
    1. No 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.
      Respecto 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

      Eliminar
    2. Este comentario ha sido eliminado por el autor.

      Eliminar
    3. Hola 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?

      Eliminar
  9. hola me podrían decir el modelo de pic que usas???

    ResponderEliminar
  10. Hola, 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:
    1)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

    ResponderEliminar
  11. Hola disculpa no puedo hacer el diseño de la placa, me podrias ayudar

    ResponderEliminar
  12. como le hiciste para evitar que el iman tratara de alinearse con el electroimán y por lo tanto se volteara?

    ResponderEliminar
  13. Buenas noches, disculpe no tendra lo que es el circuito en proteus, o algun diagrama?? tengo algunas dudas sobre las conexiones, muchas gracias.

    ResponderEliminar
  14. Y 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

    ResponderEliminar
  15. Amigo 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!!!

    ResponderEliminar
    Respuestas
    1. Primer vídeo: https://www.youtube.com/watch?v=3sLX0CEYPMo
      Segundo vídeo: https://www.youtube.com/watch?v=LdqZrnu--1k

      Eliminar
  16. 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.

    ResponderEliminar
  17. 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.

    ResponderEliminar
  18. Estoy 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

    ResponderEliminar
  19. Para 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).
    Si 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.

    ResponderEliminar
  20. bro programaste be mikroC ? o en Mplab x

    ResponderEliminar