VARIABLES CON ERRORES EN PROYECTO DE DOSIFICACIÓN DE QÚIMICOS

Buenas noches amigos. Llevo unos días ideando un programa para dosificar producto químico en un depósito de mezcla y me he quedado atascado.
Agradecería que alguien me diera un empujoncito en el tema.

Os explico y adjunto el programa.

--- Funcionamiento del programa ----

En primer lugar, tenemos un caudalímetro, una tolva de sinfín para dosificar químicos y un depósito para realizar el mezclado con agua.

La alimentación de agua al depósito es manual mediante una llave.
Cada vez que pasa un litro de agua por el caudalímetro, la tolva dosifica una cantidad predeterminada de químico en el depósito.
Sabemos que para dosificar 5 gramos/litro de agua, la tolva debe estar funcionando 1´3 segundos.

Al tener bastante caudal de llenado, necesitamos que ese tiempo que estará la tolva dosificando por cada litro, se vaya acumulando en una variable, que es la que iremos comparando con cero, para saber cuando tiene que parar o accionarse de nuevo la tolva según vaya llenando el depósito.

Tengo en el programa una variable que se me desborda cuando llega a cero, que es timeacumulado y creo que tiene que ver con cómo estoy midiendo los tiempos o bien que me falta poner alguna variable a cero al entrar o salir del bucle, pero no doy con ello por más que lo miro.
Si alguien me pudiera orientar un poco con el tema, os lo agradecería, porque debe ser algo no demasiado complicado para alguien con experiencia en programación.

Os dejo aquí el programa, gracias de antemano

//****************************************************************************
// * PROGRAMA DE DOSIFICACIÓN DE DETERGENTE EN POLVO MEDIANTE SINFÍN *
//****************************************************************************

//DEFINICIÓN DE VARIABLES PARA PINES DE ENTRADA/SALIDA
const int caudalimetro = 2;             //Caudalimetro conectado a D2 (lectura mediante interrupciones)
const int motor = 3;                    //Motor tolva en D3

//DEFINICIÓN DE VARIABLES 
int pulsos;                             //Pulsos contados por el caudalímetro
int pulsosXlitro = 27;                  //Pulsos que genera el caudalimetro por cada litro que ha pasado por él
unsigned long timePredetMotor = 1300;   //Tiempo predeterminado en milisegundos para dosificación de 5 gramos/litro
unsigned long timeTotal;                //Tiempo total de dosificación (predeterminado+ajustable)
unsigned long timeAcumulado;            //Acumulador de tiempo de funcionamiento del motor.
unsigned long inicialMillis = 0;        //Variable para calcular tiempos
unsigned long ttranscurrido = 0;        //Variable para calcular tiempos
//****************************************************************************
//****************************************************************************
void setup() {
  Serial.begin(9600);
  pinMode (caudalimetro, INPUT_PULLUP);       //Caudalimetro como entrada
  pinMode (motor, OUTPUT);                    //Motor como salida
  attachInterrupt(0, leePulsosISR, RISING);   //Configuro la interrupción 0 (pin D2) por flanco ascendente
}
//****************************************************************************
//****************************************************************************
void loop() {

  //Aqui contamos cada litro que pasa por el caudalímetro.
  if (pulsos>=pulsosXlitro){                            //si pulsos>=27,.....hemos contado 1 litro en caudalimetro.                          
    timeAcumulado = timeAcumulado + timePredetMotor;          
    pulsos = 0;                                         //Pulsos = 0, para que empiece otra vez a contar desde 0 hasta 27 y poder contar otro litro.
    inicialMillis = millis();
    }

  //Mientras tiempo acumulado es >0, motorON, sino, motorOFF
  while (timeAcumulado>0){                              //Si hay tiempo en acumulador.... voy comparando con ttranscurrido, para encender o apagar el motor
    ttranscurrido = millis()-inicialMillis;             //Voy calculando el tiempo transcurrido entre inicial y actual
    timeAcumulado = timeAcumulado-ttranscurrido;        //
    digitalWrite(motor, HIGH);                          //Enciendo motor
    }  
    digitalWrite(motor, LOW);                           //Apago motor
}                                               
//****************************************************************************
//****************************************************************************

//CONTAMOS LOS PULSOS MEDIANTE INTERRUPCIONES 
    void leePulsosISR(){                                //A cada pulso del caudalímetro, sumo 1 a la variable pulsos.
    pulsos++;
    }

El fallo más grave que le veo a tu código está en las dos líneas:

    ttranscurrido = millis()-inicialMillis;             //Voy calculando el tiempo transcurrido entre inicial y actual
    timeAcumulado = timeAcumulado-ttranscurrido;        //

En la primera se está calculando el tiempo que lleva activo el motor desde que se puso en marcha. Y la segunda línea está descontando ese tiempo una y otra vez. Eso significa que si la primera vez lleva transcurrido 100 milisegundos se restan esos 100 milisegundos, y si la segunda vez lleva ya transcurrido 150 milisegundos desde que se puso en marcha, se le restan 150 milisegundos. Se habrán restado 250 milisegundos en total cuando en realidad sólo han transcurrido 150 milisegundos. En una tercera iteración habrá transcurrido, por ejemplo, 200 milisegundos desde el principio, con lo que se le restaría otros 200 milisegundos. Llevaría ya 450 milisegundos en total, cuando en realidad sólo han pasado 200 milisegundos. Todo porque la primera línea está calculando el tiempo total que lleva el motor encendido en lugar de los parciales y luego estás tratando de calcular el tiempo restante como si le quitaras tiempos parciales. Con lo que la solución podría ser calcular el tiempo parcial transcurrido entre cada iteración y restarlo al tiempo "acumulado" (cosa que no recomiendo por lo que a continuación comentaré) o simplemente verificar que el tiempo transcurrido es igual o mayor que el tiempo que ha de transcurrir (cosa que se hace en mi propuesta).

En principio el programa, tal como está, sólo haría que el tiempo calculado pasase mucho más rápido de lo que en realidad es, pero…

Pero en la segunda línea se está restando una cantidad arbitraria a una variable entera sin signo. ¿Qué pasa si, por ejemplo, la variable vale 100 y se le resta 150? ¿Que el resultado es -50? Pues no. Si no tiene signo no puede tener números negativos y su valor no será -50. La variable “se desborda” y pasa un tener un valor positivo muy elevado. Con lo que será mayor que cero y continuará dentro del bucle hasta que de la casualidad de que en una resta se le quite exactamente el valor que tiene. Entonces sí valdrá cero y saldrá del bucle… A saber si eso ocurrirá algún día. El efecto es que el motor nunca se para... o se parará por casualidad.

Aquí tienes mi propuesta:

//****************************************************************************
// * PROGRAMA DE DOSIFICACIÓN DE DETERGENTE EN POLVO MEDIANTE SINFÍN *
//****************************************************************************

//DEFINICIÓN DE CONSTANTES PARA PINES DE ENTRADA/SALIDA
const int caudalimetro = 2;             //Caudalimetro conectado a D2 (lectura mediante interrupciones)
const int motor = 3;                    //Motor tolva en D3

//DEFINICIÓN DE CONSTANTES
const int pulsosXlitro = 27;                  //Pulsos que genera el caudalimetro por cada litro que ha pasado por él
const unsigned long timePredetMotor = 1300;   //Tiempo predeterminado en milisegundos para dosificación de 5 gramos/litro

//DEFINICIÓN DE VARIABLES 
volatile int pulsos = 0;                //Pulsos contados por el caudalímetro (ha de ser volatile porque se modifica en una interrupción)
int dosis = 0;                          //Dosis que hay que aplicar (por si antes de completar la dosis ha pasado otro litro por el caudalímetro)
boolean dosificando = false;            //Indica si se está aplicando una dosis
unsigned long inicialMillis = 0;        //Variable para calcular tiempos

//CONTAMOS LOS PULSOS MEDIANTE INTERRUPCIONES 
void leePulsosISR(){                                    //A cada pulso del caudalímetro, sumo 1 a la variable pulsos.
  pulsos++;
}

//****************************************************************************
//****************************************************************************
void setup() {
  Serial.begin(9600);
  pinMode (caudalimetro, INPUT_PULLUP);       //Caudalimetro como entrada
  pinMode (motor, OUTPUT);                    //Motor como salida
  attachInterrupt(0, leePulsosISR, RISING);   //Configuro la interrupción 0 (pin D2) por flanco ascendente
}
//****************************************************************************
//****************************************************************************
void loop() {
  //Aqui contamos cada litro que pasa por el caudalímetro. Y vemos si ha de empezar a dosificar
  if (pulsos >= pulsosXlitro) {                         //si pulsos>=27,.....hemos contado 1 litro en caudalimetro.                          
    cli();                                              //Deshabilitamos momentáneamente las interrupciones porque vamos a manipular pulsos
    pulsos -= pulsosXlitro;                             //Descontamos los pulsos equivalentes a un litro (no lo ponemos a cero por si se ha generado algún pulso de más antes de detectar "el litro")
    sei();                                              //Habilitamos las interrupciones
    if (dosis == 0) {                                   //Si no había ninguna dosis pendiente...
      digitalWrite(motor, HIGH);                        //... Enciendo motor
      inicialMillis = millis();                         //y recordamos el instante del inicio de la dosis
    }
    dosis++;                                            //Añadimos una nueva dosis
  }
  //Controlamos la dosis
  if (dosis > 0) {                                      //Si hay alguna dosis pendiente
    if (millis() - inicialMillis >= timePredetMotor) {
      dosis--;                                          //Descontamos una dosis ya que ha transcurrido su tiempo
      if (dosis == 0) {
        digitalWrite(motor, LOW);                       //Apago motor si ya no quedan dosis
      }
      else {
        inicialMillis += timePredetMotor;               //Si queda pendiente alguna dosis, "actualizamos" el instante del inicio de la siguiente dosis (el 
      }
    }
  }

}                                               
//****************************************************************************
//****************************************************************************

El planteamiento es un poco diferente. En lugar de ir calculando el tiempo que ha de continuar activo el motor según pasen los litros y el tiempo, lo que hace ahora es calcular las dosis que quedan por verter y descuenta cada dosis una vez transcurrido el tiempo timePredetMotor.

Si se completa un litro el motor se pondrá en marcha y se "toma nota" del instante en que se ha encendido si no había ninguna dosis pendiente (dosis == 0). Pero no hará nada de eso si ya habían dosis pendiente ya que eso ya se hizo con la primera dosis pendiente anterior y el motor ya se ha puso en marcha.

Una vez transcurrido el tiempo timePredetMotor se descuenta una dosis. Si aún queda alguna pendiente entonces se actualiza el instante en que se supone que empezó esta nueva dosis. Se supone que empieza cuando empezó la anterior más el tiempo que lleva verter la dosis, por lo que no hay que leer de nuevo millis(), sino que nos vale sólo con sumar timePredetMotor al inicio anterior.

Si al terminar el tiempo de la dosis y descontarse, ya no quedan más dosis por verter, entonces se para el motor y listo.

Un par de detalles sueltos: declarar como volatil la variable pulsos, que se cambia en la interrupción, “por cuestiones técnicas” que se pueden consultar en Google. Desactivar y activar las interrupciones momentáneamente cuando se manipula esa misma variable fuera de la interrupción (también por cuestiones técnicas). Tampoco poner a cero la variable pulsos cuando se ha completado “el litro”, sino restar el valor pulsosXlitro por si se ha contabilizado algún pulso de más antes de procesarlos. Si en lugar de 27 vale 28, al ponerlo a cero se pierde un pulso, mientras que restando 27 el pulso de más se conserva para contabilizar el siguiente litro.

Declarar como const las variables pulsosXlitro y timePredetMotor ya que sus valores han de permanecer constantes y esto evita que, al programar, un despiste por nuestra parte modifiquen sus valores. En ese caso el compilador se quejaría y eso nos dará idea de que algo hemos hecho mal.

Mi propuesta no tiene un while que retenga la ejecución del programa más de lo debido. Puede coexistir con más código que funcione a base de millis() (los delay() no deberían de usarse). Y es "resistente" a retardos "moderados" si al añadir más cosas al programa tarda en llegar la ejecución a la parte que controla la dosis ya que si hay pulsos de más no se pierden.

Tu problema es el Delay(). :slight_smile:
No, no estoy loco.
La siguiente estructura funciona exactamente como un delay.

while (timeAcumulado>0){                              //Si hay tiempo en acumulador.... voy comparando con ttranscurrido, para encender o apagar el motor
    ttranscurrido = millis()-inicialMillis;             //Voy calculando el tiempo transcurrido entre inicial y actual
    timeAcumulado = timeAcumulado-ttranscurrido;        //
    digitalWrite(motor, HIGH);                          //Enciendo motor
    }

Tu problema en el código es que el if de la primera parte no se ejecuta hasta que el while se completa.

Saludos

Ante todo, daros las gracias una y mil veces más por la ayuda desinteresada que prestáis, ya que no son dudas de ir preguntando a cualquiera, y a la rapidez de respuesta de IgnoranteAbsoluto y PeterKantTropus,.
Pues si, los problemas eran varios, y la verdad que todos solucionados con una claridad notoria por parte de IgnoranteAbsoluto, a quien no hace referencia su nombre a la vista está.... :wink:

He de decirte, que a parte de ir calculando los tiempo mal en mi programa, al principio tenía una estructura como la de tu propuesta con varios if, pero no sé por qué coloqué un while (gran error, porque a la vista está no era necesario).

Primer error: ir calculando un valor total y no parcial del tiempo, y después tenía un lio tremendo usando millis(). No encuentro por ningún sitio alguna librería que contenga funciones para calcular tiempo de una forma fácil, las que he probado son bastante engorrosas o no valen la pena usarlas porque le quitan claridad al código.

Otros errores: volatile, const (todas esas declaraciones que a veces se nos pasan cuando empezamos a hacer pruebas, depurando y cambiando partes del código como un loco a las....3 de la madrugada? ....

A parte de solucionarme todas las dudas que me tenían atorado con el programa, ha sido una clase magistral de programación y un enfoque nuevo al funcionamiento del programa en sí, haciendo lo mismo que yo tenía en la cabeza pero por otro camino, que también es muy instructivo

Saludos y muchas gracias por ese tiempo gastado tan desinteresadamente en los demás, haciendo que la electrónica y la programación no se vuelvan "un imposible".