Problemas con control PID en Arduino

Buenas , estoy realizando el control de apertura y cierre de una válvula(con un motor de 12V DC) , para controlar la cantidad de líquido en el depósito tengo conectado un potenciómetro multiuelta a una boya , de manera que cuando el líquido suba , el potenciómetro se mueva . El programa me funciona correctamente cuando se trata de valores máximos y mínimos , es decir el motor gira en la dirección correcta para abrir o cerrar la válvula hasta que el potenciómetro alcanza los valores máximos y mínimos.
Mi problema es que no soy capaz de establecer un cierto valor ( un valor entre el máximo y el mínimo) y la válvula se abra o se cierre hasta dicho punto sin llegar a los límites mencionados .

Estoy utilizando la libreria PID de Arduino , el driver que estoy utilizando para mover el motor es Ardumoto(basado en el L298P) . adjunto el trozo de código en el que no consigo realizar esto :

if(Output>0) //la válvula se abre
{

//AQUI PUEDES DAR VALORES A INPUT

//Si está dentro de 1000 el valor de apertura, ejecuta el PID . 1000 es el valor máximo superior para abrir la válvula
if(Input<1000)
{
aux=abs(Output);
driveArdumoto(MOTOR_B, FORWARD, aux); //función del driver , utilizando el motor_b , hacia la direccion derecha y a una velocidad marcada (-255,255)



}
//Si se pasa de 1000 al abrir, la válvula se para y
//manda un 0 a la salida
else
{
myPID.SetMode(MANUAL);
Output=0;
stopArdumoto(MOTOR_B);//Funcion para parar el motor b




}

//Función para mover el motor

void driveArdumoto(byte motor, byte dir, byte spd)
{
if (motor == MOTOR_B)
{
digitalWrite(DIRB, dir);
analogWrite(PWMB, spd);
}
}
 
Con solo leer los comentarios del codigo, y sabiendo que en la variable "Input" tienes el valor, ya tienes todo hecho.
Quizas usar un switch-case para valores predeterminado, o comparar con alguna entrada analogico (potenciometro en alguna de las entradas de ADC), y asi establecer el maximo y minimo segun "seteas" externamente.
 
Con solo leer los comentarios del codigo, y sabiendo que en la variable "Input" tienes el valor, ya tienes todo hecho.
Quizas usar un switch-case para valores predeterminado, o comparar con alguna entrada analogico (potenciometro en alguna de las entradas de ADC), y asi establecer el maximo y minimo segun "seteas" externamente.

No he entendido muy bien lo que has querido decirme , debido a que no llevo mucho manejando esto .
En la variable Input tengo guardado el valor del potenciómetro , que esta conectado a una boya , por lo que irá girando hasta llegar al valor deseado .
Utilizo una variable denominada Setpoint= mapeado , en la que guardo el valor obtenido en la función map.
Supongo que necesito conseguir una estructura que lea la variable Setpoint y la compare con la variable Input , y cuando ésta última llegue al Setpoint el motor se pare . ¿ Podrían ir por ahí los tiros ?
Gracias por tu respuesta
 
Si.
Por ejemplo
Podrias "mapear" ambos potenciometros, y que los resultados sean de 0% (vacio, o valor minimo) a 100% (lleno, o valor maximo).
Entonces comparas ambos resultados.
Otra cosa es ponerle margenes al potenciometro de ajuste externo, para que no haya un "rebote" apenas baje un poco el nivel (a menos que asi lo desees), ya que lo ideal seria algo asi.
Con valor minimo, empieza el llenado.
Suponiendl que el ajuste del nivel está en 35%.
Entonces al llegar a 35% para el llenado, y apaga todo.
Si baja, por ejemplo, a 30%, vuelve activar hasta los 35% seteados.

Eso me parece a mi, porque sino, apenas baje a 34%, se vuelve activar, y si la descarga es rapida estarias activando y desactivando el motor constantemente.

Los valores en porcentaje son a modo de ejemplo, luego lo adaptas a tus necesidades
 
Si.
Por ejemplo
Podrias "mapear" ambos potenciometros, y que los resultados sean de 0% (vacio, o valor minimo) a 100% (lleno, o valor maximo).
Entonces comparas ambos resultados.
Otra cosa es ponerle margenes al potenciometro de ajuste externo, para que no haya un "rebote" apenas baje un poco el nivel (a menos que asi lo desees), ya que lo ideal seria algo asi.
Con valor minimo, empieza el llenado.
Suponiendl que el ajuste del nivel está en 35%.
Entonces al llegar a 35% para el llenado, y apaga todo.
Si baja, por ejemplo, a 30%, vuelve activar hasta los 35% seteados.

Eso me parece a mi, porque sino, apenas baje a 34%, se vuelve activar, y si la descarga es rapida estarias activando y desactivando el motor constantemente.

Los valores en porcentaje son a modo de ejemplo, luego lo adaptas a tus necesidades
Ya te he entendido . El problema es que solo estoy utilizando un motor de corriente continua para abrir y cerrar la válvula y un potenciómetro que conectado a la boya marcará la posición actual del agua . Esto me funciona perfectamente para poder parar cuando el agua llega al punto máximo y al punto mínimo .
Para poder realizar esto que te comenté , ¿ Sería necesario contar con otro potenciómetro o componente para poder comparar ambas señales no ? No es posible realizarlo únicamente con estos dos componentes .
 
El control PID (Proporcional - Integral y Derivativo) es un poco mas complejo , por ahora eso sólo funciona por si o por no abriendo y cerrando totalmente la válvula , deberías agregar la parte Proporcional , o sea 100% abierta para tanque vacío , 80 % abierta para 20% de tanque , 50% abierta para 50% de tanque , 20 % abierta para 80 % de tanque y 0% abierta para tanque lleno (con todos sus pasos intermedios)

. . . Luego verás lo Integral y Derivativo . . .
 
El control PID (Proporcional - Integral y Derivativo) es un poco mas complejo , por ahora eso sólo funciona por si o por no abriendo y cerrando totalmente la válvula , deberías agregar la parte Proporcional , o sea 100% abierta para tanque vacío , 80 % abierta para 20% de tanque , 50% abierta para 50% de tanque , 20 % abierta para 80 % de tanque y 0% abierta para tanque lleno (con todos sus pasos intermedios)
. . . Luego verás lo Integral y Derivativo . . .
Para ponerlo mas claro:
Lo que está haciendo (bah, pretendiendo hacer en realidad) es un mísero control ON/OFF. Eso de PID no tiene absolutamente NADA.
Mi recomendación sería: flaco, agarrá un libro de control, estudiá y luego seguimos hablando de los PID..
 
Para ponerlo mas claro:
Lo que está haciendo (bah, pretendiendo hacer en realidad) es un mísero control ON/OFF. Eso de PID no tiene absolutamente NADA.
Mi recomendación sería: flaco, agarrá un libro de control, estudiá y luego seguimos hablando de los PID..
Obviamente el código compartido es en el que se realiza la apertura y cierre de la válvula y en el que solo busco que se detenga en un punto predeterminado que yo elija ( ahí el problema de mi programación) . La parte de código en la que configuro el PID con los valores ideales para la planta que estoy utilizando no está copiado , pues estoy casi seguro que esta programado correctamente .
Ojalá nacer aprendido como usted .
saludos .
 
No es posible realizarlo únicamente con estos dos componentes

Si, pero solo tendras maximo y minimo, o como dicen los colegas "ON-OFF".
Si quieres control total del volumen deseado, entonces haz lo que dice 2ME, o como te sugeri yo mas arriba.

Si quieres mas complejo, hasta le puedes poner una pantalla LCD que muestre el porcentanje, y pulsadores para setear los valores, hasta puedes setear el maximo y el minimo segun requieras
 
La parte de código en la que configuro el PID con los valores ideales para la planta que estoy utilizando no está copiado , pues estoy casi seguro que esta programado correctamente .
Entonces por que pones de titulo del tema "Problema control PID Arduino" si el problema es de otra cosa????
Y mostras un codigo que no tiene NADA que ver con lo que dice titulo... supones que nosotros tenemos que adivinar lo que te sucede???
No es cuestión de nacer aprendido sino de tratar de ser coherente.
 
Lo primero que debes plantearte mas alla de lo que te han dicho es que los que estamos del otro lado no sabemos que sabes y que no. Basamos nuestros comentarios en lo que expones.
Lo que expones es al menos muy liviano.
Se supone que debes entregar la mayor cantidad de información posible.. porque nosotros no hemos visto tu boya (como la nombras), no sabemos si tu boya o sensor potenciométrico se comporta linealmente con el nivel de líquido o no.
Has probado si esto es asi?. Se te ocurrio medir desde 0 y leer la salida del potenciometro... y luego tomar digamos 10 lecturas de nivel hasta completar el 100% y hacer una curva de como es la transferencia?
De lo contrario una cosa es creer que tienes algo lineal y otra es que no lo sea.

Ahora mira el código que has puesto, lo he identado para que se entienda (al menos a mi me hace falta)
Observa que es parcial, y no solo parcial sino que empieza y no sabemos si termina o no ya que lo primero no tiene llave de fin, pero luego hay otro procedimiento como driveArdumoto que esta claramente definido.



Código:
 // <= ACA NO SABEMOS DE DONDE VIENE
  if (Output>0) { //la válvula se abre
    //AQUI PUEDES DAR VALORES A INPUT

    //Si está dentro de 1000 el valor de apertura, ejecuta el PID . 1000 es el valor máximo superior para abrir la válvula
    if (Input<1000)   {
      aux=abs(Output);
      driveArdumoto(MOTOR_B, FORWARD, aux); //función del driver , utilizando el motor_b , hacia la direccion derecha y a una velocidad marcada (-255,255)
    }

    //Si se pasa de 1000 al abrir, la válvula se para y
    //manda un 0 a la salida
    else     {
      myPID.SetMode(MANUAL);
      Output = 0;
      stopArdumoto(MOTOR_B);//Funcion para parar el motor b
    }

  //Función para mover el motor
// <= ACA NO SABEMOS SI TERMINA ALGO?

void driveArdumoto(byte motor, byte dir, byte spd)  {
  if (motor == MOTOR_B)  {
      digitalWrite(DIRB, dir);
      analogWrite(PWMB, spd);
  }
}

Asi que tienes algunas directivas para trabajar que espero te sirvan y recuerda.
Aporta toda la información, código completo, esquema de conexiones aunque sea obvio (un bosquejo mano alzada sirve).
 
Última edición:
Tengo la siguiente duda: tengo un Arduino conectado al horno de Proteus, que se supone que con el potenciómetro envía el voltaje necesario para estabilizar la temperatura. Sin embargo, cuando le doy 0V, el horno se comporta de forma exponencial y crece en lugar de estabilizarse en 25C, que es la temperatura ambiente. Creo que tengo un error en el código de Arduino.problem2.pngconfig.png
 

Adjuntos

  • problem.png
    problem.png
    64.3 KB · Visitas: 12
  • honor_final.rar
    480 bytes · Visitas: 9
Si tu lo dices así será, o no.

Publica el código a ver.

Algo no me cuadra de tu montaje:
Si un pin de un Arduino calienta hornos es todo lo que necesitaba saber sobre ese simulador. No tengo palabras (buenas).
Bueno, si el horno es de unos pocos mm³ buen aislado entonces si.

Ya que estás pon varios hornos en paralelo y en serie a un pin a ver qué hace y echamos unas risas. (Más risas)


Así desde la ignorancia creo que no está bien configurado ese modelo.
 
Código:
#include <PID_v1.h>
#define PIN_INPUT 0
#define PIN_OUTPUT 11
#define PIN_GND 10
double setpoint; // Cambiamos el nombre de la variable
double input;
double output;
double kp = 401, ki = 10, kd = 0;
float tempc = 0;
PID my(&input, &output, &setpoint, kp, ki, kd, DIRECT); // Usamos el nuevo nombre de la variable

void setup() {
  Serial.begin(9600);
  tempc = analogRead(PIN_INPUT)*0.0048828125 * 100;
  input = tempc;
  setpoint =analogRead(A1); // Usamos el nuevo nombre de la variable
  my.SetMode(AUTOMATIC);
}

void loop() {
  tempc = analogRead(PIN_INPUT) * 0.0048828125 * 100;
  input = tempc;
  setpoint =analogRead(A1); // Usamos el nuevo nombre de la variable
  Serial.println("Temperatura regular a: ");
  Serial.println(setpoint);
  delay(100);
  my.Compute();
  analogWrite(PIN_OUTPUT, output);
}

Lo he sacado del .rar, antes no lo podía descomprimir en el teléfono.


Cosas que veo.
Primero decir que no he usado esa librería y a lo mejor alguna cosa es requerimiento de ella y está correcto aunque a mi me resulte raro.
¿Que hace ahí el bendito delay(100) aparte de pararlo todo todo el tiempo?
¿Por y para qué es output double si analogWrite solo puede tomar valores enteros de 0 a 255?, osea que no es ni int. Osea que gastas cuatro bytes para un número solo un byte.
Por cierto en arduino es inútil usar double: double - Arduino Reference
No me cuadra que input esté convertido a grados mientras que setpoint sea la lectura directa de la entrada analógica, lo mismo la librería PID se lo traga así, pero entiendo que si la "entrada" se convierte, "setpoint" debería de cuadrar en lo mismo
Yo esto:
tempc = analogRead(PIN_INPUT) * 0.0048828125 * 100;
Lo cambiaba directamente por:
input = analogRead(PIN_INPUT) * 0.48828125;
Es trabajo absurdo multiplicar por 100 si se puede arreglar sin mas.

A lo mejor el modelo del horno admite funcionar con 5V 30mA pero es bastante/absolutamente absurdo.

#define PIN_GND 10 ¿Esto que sentido tiene?, No lo entiendo.
 
Última edición:
Tengo la siguiente duda: tengo un Arduino conectado al horno de Proteus, que se supone que con el potenciómetro envía el voltaje necesario para estabilizar la temperatura. Sin embargo, cuando le doy 0V, el horno se comporta de forma exponencial y crece en lugar de estabilizarse en 25C, que es la temperatura ambiente. Creo que tengo un error en el código de Arduino.

Puede ser, también puede haber un error en los parámetros de simulación --> Lo mejor es que además del programa adjuntes el programa en Proteus. Al menos la hasta la versión que tengo no lo crea partiendo de una imagen.

-------------------------------
Hay un detalle que señaló Scooter con la multiplicación. Cuando se escribe:
Código:
tempc = analogRead(PIN_INPUT)*0.0048828125*100 ;
el compilador (avr-g++) multiplica primero analogRead(PIN_INPUT) por 0.0048828125 y luego al resultado por 100.
Y si hubiese mas constantes multiplicando/dividiendo sería peor, pues sería una cadena de lentas multiplicaciones en punto flotante.

Una manera es multiplicar directamente por la constante resultante (0.48828125) , pero también se puede dejar ese trabajo al compilador, sobre todo cuando la constante es resultado de una operación mas compleja.
Por ejemplo en:
Código:
#define Q1 1024./500.
tempc = analogRead(PIN_INPUT)*0.0048828125*100*Q1/(2*3.1416) ;
se genera una chorrera de multiplicaciones+una división (n) .
Pero si agregamos un paréntesis
Código:
#define Q1 1024./500.
tempc = analogRead(PIN_INPUT)*(0.0048828125*100*Q1/(2*3.1416)) ;
el compilador evalúa la constante entre paréntesis y se genera solo una multiplicación.

O si se escribe en este orden:
Código:
#define Q1 1024./500.
tempc = 0.0048828125*100*Q1/(2*3.1416)*analogRead(PIN_INPUT) ;
como el compilador analiza la operación de izquierda a derecha también genera una sola multiplicación.
 
Les adjunto el archivo de simulación de Proteus con el código de Arduino. Hice las modificaciones, pero sigo teniendo el mismo problema. Se supone que cuando tenga un setpoint de 0, debería responder la temperatura ambiente de 25C y no crecer exponencialmente.
 

Adjuntos

  • simulacion_proteus.zip
    259.4 KB · Visitas: 3
Les adjunto el archivo de simulación de Proteus con el código de Arduino. Hice las modificaciones, pero sigo teniendo el mismo problema. Se supone que cuando tenga un setpoint de 0, debería responder la temperatura ambiente de 25C y no crecer exponencialmente.

El problema es el modelo del horno, no está pensado para ser controlado por tensión,corriente ni nada que cumpla la física, sino que de acuerdo a si ve baja/alta impedancia en sus terminales calienta de acuerdo al ciclo de trabajo.
Por ejemplo, si le cortocircuitás los terminales sin conectarlo a nada --> te calienta al 100%

Algunos hacen la simulación poniendo un mosfet en lugar de usar directamente el pin del Arduino, pero no vale la pena , usá un VSWITCH con Roff=1T (sí, 1 Teraohm, con 1Megohm no alcanza)

oven.jpg
 
Esto sería parte de ese código:

double input;
float tempc = 0;
tempc = analogRead(PIN_INPUT)*0.0048828125 * 100;
input = tempc;

Y en él veo varias cosas:
-En otros lenguajes, INPUT es palabra reservada y no se puede usar como nombre de variable.
-Según la documentación de Arduino, AnalogRead() devuelve un valor de tipo entero (int) entre 0 y 1023 ...
Reads the value from the specified analog pin. Arduino boards contain a multichannel, 10-bit analog to digital converter. This means that it will map input voltages between 0 and the operating voltage(5V or 3.3V) into integer values between 0 and 1023.
... que, en ese código, se está asignando a una variable auxiliar de tipo float y, luego, se pasa a otra variable de tipo double.


Yo no se si todo eso se puede hacer en Arduino, así a pelo.

En otros lenguajes, saltaría un error como una casa por asignar el valor de una variable de un tipo a otra de otro tipo, sin una instrucción conversora de tipos de por medio.
Saltaría otro error, por asignar a una variable float el resultado de una función que devuelve un tipo int.
Y otro más, por usar una palabra reservada como nombre de variable.

Con todo ello, me pregunto:
¿Se puede usar INPUT como variable?
¿Admite Arduino asignar el valor de una variable de un tipo, a otra variable de distinto tipo, sin convertir los tipos?
¿Sus funciones son buena gente y te devuelven sus resultados en las variables del tipo que te dé la gana?
¿Son necesarios 10 decimales de precisión de la constante para mostrar una temperatura con un sólo decimal?
 
Última edición:
....
Con todo ello, me pregunto:
¿Se puede usar INPUT como variable?
INPUT no, input si. C es "case sensitive"

¿Admite Arduino asignar el valor de una variable de un tipo, a otra variable de distinto tipo, sin convertir los tipos?
La variable asigna no cambia su tipo.
La asignación se hace mientras sea compatible. Podemos asignar un float a un int (lo trunca a entero) pero no un float a una estructura.

¿Sus funciones son buena gente y te devuelven sus resultados en las variables del tipo que te dé la gana?
No, son mala gente.

¿Son necesarios 10 decimales de precisión de la constante para mostrar una temperatura con un sólo decimal?
Si multiplicás por un float da igual que tengas 1 o 10 decimales, por un lado el compilador carga en el float lo que entre de acuerdo a su tamaño.
Por otro, multiplicar por 0.7 demora lo mismo que por 0.733234.

Y como si eso fuera poco, como 0.48828125 = 1/2.048 , si escribís:
Código:
tempc = analogRead(PIN_INPUT) /2.048 ;
el compilador te genera una multiplicación por 0.48828125

------------------------------
Hay que aclarar que todo lo anterior es como te lo compila Arduino con los switchs habituales. Porque el código generado depende del nivel de optimización.
 
Última edición:
INPUT no, input si. C es "case sensitive"
Ok. Está más que clarísimo. Gracias.
Yo, es que me niego por costumbre a usar variables que sean palabras reservadas.

La asignación se hace mientras sea compatible. Podemos asignar un float a un int (lo trunca a entero) pero no un float a una estructura.
Muy claro también.
En este caso, sería lo contrario; asignar un valor tipo int devuelto así por la función, a una variable tipo float. Pero no habría problema tampoco. La variable quedaría como 2 bytes de mantisa como están + 2 bytes de exponente puestos a 0. Y tomarla como de 32 bits.

Más que nada lo decía porque hay lenguajes que protestan si no usas una función conversora de tipos, aunque la asignación sea compatible.

Muy claro también lo de los 10 decimales de la constante.

The float data type has only 6-7 decimal digits of precision. That means the total number of digits, not the number to the right of the decimal point.
Ahí el motivo.



No sabía que 0.48828125 venía de 1/2048.
Pero... si analogRead() devuelve un entero entre 0 y 1023... ¿no sería 1/1024 = 0,09765625 ? :unsure:
 
Última edición:
Atrás
Arriba