Duda con el Serial.print

Hola amigos,
Tengo una duda, que os señalo con una flecha dentro del mismo código <--. El tema es que si sitúo el Serial.println dentro del "if" obtengo unos valores diferentes a si lo pongo fuera. ¿Saben por qué sucede? Lo que si me he dado cuenta es que si en vez de ser un "float rpm" es por ejemplo un "int rpm", ambos valores son iguales. El problema es que entonces el valor ya no es un float (no tengo decimales).

Este código, es para mostrarles mi duda. El código prácticamente sería igual, lo único que en vez de contar las veces que hace el Loop, cuenta los pasos de un motor DC.
El valor correcto es el que está dentro del "if" y lo necesitaría para aplicarlo en un PID.

Muchas gracias,

unsigned long timeold_L = 0;
float rpm_L = 0;
double contador;

void setup() {
  Serial.begin(9600);
}

void loop() {
  contador++;

  if (millis() - timeold_L >= 90) { 
    rpm_L = 0.0195 * (millis() - timeold_L) * contador; 
    timeold_L = millis(); 
    Serial.println(rpm_L); // <<---
    contador = 0; 
  }

  //Serial.println(rpm_L); // <<--


}

Es obvio que dentro del if tienes valores ocurridos cada 90 mseg y fuera tienes valores en todos los momentos entre 0 y 90 mseg.

Eso es lo que pregutnas?

Claramente debs esperar 90 mseg, por alguna razón que tu sabrás y no has dicho.
Pero el cálculo es el mismo dentro que fuera, solo que como dije, dentro ocurre una vez cada 90 mseg y fuera no tiene freno y se muesta en cada ciclo loop.

Perdona la redundancia de la expliación.

Muchas gracias por la respuesta.
Lo de los 90 ms forma parte de un código que he creado para unos motores con encoder. El 0.0195 es un coeficiente que utilizo para igualar las rpm de los motores a máxima velocidad a las rpm máximas según el catálogo del fabricante (210 rpm). La verdad es que tener o no decimales, no me preocupa.
El caso, es que necesito las rpm para un PID, por lo que, de alguna manera ¿existiría la posibilidad de extraer este valor de manera que los valores fueran iguales (o parecidos) dentro que fuera del if?
Muchas gracias de nuevo.

Tal vez no te comprendemos tu objetivo asi que olvida este tema y explica la naturaleza del PID que estas usando.
Cual es la variable a controlar? Supongo que 90 mseg es tu frecuencia de muestreo.
Intenta dar detalles de la plata y todo lo tecnico que permita darte buenas respuetas.

Gracias por la respuesta de nuevo. Sois geniales.

Hasta ahora no he querido dar el código completo para no marear a nadie. La verdad que el código es extenso, pero no tengo ningun problema en compartirlo completo. Quizás puede servir a los amigos del foro para sus proyectos. Así que si lo queréis os lo entrego encantado. Funciona prácticamente, incluso lee las rpm de los motores (eso si...dentro del if...).

Bueno. Estoy haciendo un robot autobalanceado. Y hasta ahora he conseguido que el robot se mantenga erguido. Esto lo he logrado mediante un único PID que se alimenta de los datos de un sensor de ángulo. Sin embargo el robot se mueve (sin caerse) y no está completamente parado. Es decir, va andando sin rumbo por la casa. Entonces, hay dos soluciones, o ponerle unos palitos :confused: o utilizar un segundo PID que modifique el SetPoint del primero, buscando siempre dos objetivos el ángulo 0 (o el deseado) y velocidad 0. Quizás hay otra solución, pero la desconozco.

En otras palabras: La idea es que el robot tenga una doble retroalimentación. Por un lado está el PID que se alimenta de los datos del sensor de ángulo (MPU 6050). Por otro lado (es mi intención), hacer el segundo PID con los datos de los encoders de los motores. La primera parte ya la he logrado y la segunda me está matando....

Quizás habréis visto muchos robots autobalanceados con Arduino. Y creo poder asegurar que lo único que hay (hasta dónde los resultados de búsqueda de google me han permitido) son robots con, o un único PID (que se alimenta del ángulo), o con un doble PID utilizando motores paso a paso. Pero no he encontrado ningún proyecto completo con un doble PID que utilize motores DC con encoders.

Los motores que yo utilizo son los siguientes:

Motor encoder 210 rpm

Efectivamente, los 90 ms son la frecuencia de muestreo. Se puede usar otra frecuencia, pero a mí los 90 ms me funcionan muy bien.
Sino utilizo interrupciones es porque, al MPU6050 no le sientan muy bien. Así que me las ha tenido que arreglar sin interrupciones.

A continuación adjunto el código para la lectura de las rpm para los dos motores con encoder Hall de cuadratura del robot.

Gracias de nuevo.

//lecturas digitales
const int channelPinA = 8; //Motor RH (CABLE VERDE)
const int channelPinB = 12; //Motor RH (CABLE AMARILLO)
const int channelPinC = 6; //Motor LH (CABLE AMARILLO)
const int channelPinD = 7; //Motor LH (CABLE VERDE)

unsigned char stateChannelA;//Motor RH. Lectura digital 1er canal del motor derecho RH
unsigned char stateChannelB;//Motor RH. Lectura digital 2º  canal del motor derecho RH
unsigned char stateChannelC;//Motor LH. Lectura digital 1er canal del motor izquierdo LH
unsigned char stateChannelD;//Motor LH. Lectura digital 2º  canal del motor izquierdo LH

float rpm;   //Revoluciones por minuto calculadas motot RH
float rpm_L = 0;// Revoluciones por minuto calculadas motot LH

int estado_previo, estado_dos;//Motor RH. Commparamos puntos de la señal escalón (que produce el sensor Hall) que son iguales a tiempos diferentes.
double value; //Motor RH. Pasos, es decir, cada vez que un polo del imán pasa por el sensor hall aumenta en uno o se reduce en uno, según sentido del giro.

int estado_previoL, estado_dosL;//Motor LH. Commparamos puntos de la señal escalón (que produce el sensor Hall) que son iguales a tiempos diferentes.
double valueL; //Motor LH. Pasos, es decir, cada vez que un polo del imán pasa por el sensor hall aumenta en uno o se reduce en uno, según sentido del giro.

unsigned long currentTime;  // Tiempo actual. Permite tomar lecturas en tiempos distintos.
unsigned long timeold = 0;  // Tiempo anterior para el motor RH.
unsigned long timeold_L = 0;// Tiempo anterior para el motor LH.

//Motor RH. Variables para los motores DC y módulo controlador L298N
int IN2 = 5;
int IN1 = 4;
int ENA = 3;

//motor LH. Variables para los motores DC y módulo controlador L298N
int IN3 = 11;
int IN4 = 10;
int ENB = 9;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(IN1, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N
  pinMode(IN2, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N
  pinMode(ENA, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N

  pinMode(IN3, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N
  pinMode(IN4, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N
  pinMode(ENB, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N

  pinMode(channelPinA, INPUT); // Entrada para las lecturas digitales del encoder.
  pinMode(channelPinB, INPUT); // Entrada para las lecturas digitales del encoder.
  pinMode(channelPinC, INPUT); // Entrada para las lecturas digitales del encoder.
  pinMode(channelPinD, INPUT); // Entrada para las lecturas digitales del encoder.

  //currentTime = millis();
  value = 0;
  rpm = 0;
  timeold = 0;
}

void loop() {
  digitalWrite(IN1, LOW); //Motor RH. Para el módulo controlador L298N.
  digitalWrite(IN2, HIGH); //Motor RH. Para el módulo controlador L298N.
  analogWrite(ENA, 255); //Motor RH. Para el módulo controlador L298N.

  digitalWrite(IN3, LOW); //Motor LH. Para el módulo controlador L298N.
  digitalWrite(IN4, HIGH); //Motor LH. Para el módulo controlador L298N.
  analogWrite(ENB, 255); //Motor LH. Para el módulo controlador L298N.

  //RPM para motor RH
  ContadorRH();
  ContadorLH();

  if (millis() - timeold >= 90) { // Se actualiza cada intervalo elegido. Muestreo.
    rpm = 0.0195 * (millis() - timeold) * value; // Calculamos las revoluciones por minuto (millis() - timeold) * value;
    timeold = millis(); // Almacenamos el tiempo actual.
    value = 0;  // Inicializamos los pulsos.
    Serial.println(rpm);
  }
  //RPM para motor LH
  if (millis() - timeold_L >= 90) { // Se actualiza cada intervalo elegido. Muestreo.
    rpm_L = 0.0195 * (millis() - timeold_L) * valueL; // Calculamos las revoluciones por minuto
    timeold_L = millis(); // Almacenamos el tiempo actual.
    valueL = 0;  // Inicializamos los pulsos.
    //Serial.println(rpm_L);
  }
  
}

void ContadorRH() { //Ánalisis del motor RH
  stateChannelA = digitalRead(channelPinA);
  stateChannelB = digitalRead(channelPinB);

  if ((estado_previo == stateChannelB) && estado_dos != stateChannelB) {
    if ((stateChannelA == LOW) && (stateChannelB == LOW)) { //Sentido horario
      value++;
    }
    if ((stateChannelB == LOW) && (stateChannelA == HIGH)) { //Sentido antihorario
      value--;
    }
    estado_previo = stateChannelB;
  }

  if (micros() - currentTime >= 1) { //reduce errores por debounce y fija el siguiente valor a comparar.
    estado_dos = stateChannelB;
    currentTime = micros();
  }
  //Serial.println(value);
}

void ContadorLH() { //Ánalisis del motor RH
  stateChannelC = digitalRead(channelPinC);
  stateChannelD = digitalRead(channelPinD);

  if ((estado_previoL == stateChannelD) && estado_dosL != stateChannelD) {
    if ((stateChannelC == LOW) && (stateChannelD == LOW)) { //Sentido horario
      valueL++;
    }
    if ((stateChannelD == LOW) && (stateChannelC == HIGH)) { //Sentido antihorario
      valueL--;
    }
    estado_previoL = stateChannelD;
  }

  if (micros() - currentTime >= 1) { //reduce errores por debounce y fija el siguiente valor a comparar.
    estado_dosL = stateChannelD;
    currentTime = micros();
  }
}

Ves como cambia el enfoque ahora que te explicaste con detalle?
Siempre se debe aportar esta información porque contextualiza el problema y de lo contrario los que te leemos no sabemos a que refieres.

Bien, veamos. Primera observación o comentario. Cuando dices que no usas interrupciones te refieres a los enconders quadratura?

Gracias por la respuesta.
Sí, efectivamente. Cuándo comento que no uso interrupciones, me refiero a los encoders de cuadratura. Me explico:

Te adjunto en este Post el código del robot autobalanceado sin la parte del código específica para los encoders. Es decir, cableando adecuadamente el robot con unos motores DC cualesquiera y sintonizando correctamente las constantes del PID (kp, ki, kd) el robot se mantendría erguido con el código que aquí he colgado.

En otras palabras, este código que aquí adjunto solo lee los datos del sensor de ángulo MPU 6050 y los vuelca en el PID. Ahora bien, si quisiéramos leer los datos del encoder hall de cuadratura mediante interrupciones, produciríamos conflictos con el MPU, dejando éste de tomar datos. El código se volvería ineficiente mostrando la advertencia de "FIFO overflow!".

En definitiva quiero fusionar los dos códigos que he colgado de manera que al final el SetPoint de este PID: "PID myPID(&Input, &U, &Setpoint, kp, ki, kd, DIRECT);" sea constantemente modificado por un segundo PID por ejemplo "PID myPID(&rpm, &nuevoSetPoint, 0, kp2, ki2, kd2, DIRECT);". La manera de hacerlo sería modificando el ángulo del primer PID de esta forma: Setpoint = 0º - nuevoSetPoint.

¿Porqué un robot autobalanceado nunca puede estar completamente parado con un sólo PID? Esto se debe a muchas razones, una es que es imposible establecer exactamente el ángulo de equilibrio perfecto. Luego los motores con reducción tienen un cierto juego, luego está evidentemente las pequeñísimas imperfecciones del suelo o fuerzas externas que pudieran existir (el aire...), etc.

Por eso es necesario tener un segundo PID que modifique el ángulo objetivo o SetPoint del primero. El SetPoint del segundo PID sería velocidad de los motores = 0.

En definitiva y si lo que pienso es correcto, si consiguiera sacar las rpm del if, creo que podría conseguirlo.

Gracias nuevamente!

#include <PID_v1.h>
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif

MPU6050 mpu;
bool blinkState = false;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorFloat gravity;    // [x, y, z]            gravity vector
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

// packet structure for InvenSense teapot demo
//uint8_t teapotPacket[14] = { '

, 0x02, 0,0, 0,0, 0,0, 0,0, 0x00, 0x00, '\r', '\n' };
volatile bool mpuInterrupt = false;    // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
  mpuInterrupt = true;
}

//Motor RH. Variables para los motores DC y módulo controlador L298N
int IN2 = 5;
int IN1 = 4;
int ENA = 3;

//motor LH. Variables para los motores DC y módulo controlador L298N
int IN3 = 11;
int IN4 = 10;
int ENB = 9;

// Variables PID
double  U, Setpoint = 6.0, Input; // nota: U = Output, es decir lo que controla el motor. Para mi, 6º es el equilibrio.
double kp = 140, ki = 15, kd = 90; //valores sintonizados manualmente

//Specify the links and initial tuning parameters
PID myPID(&Input, &U, &Setpoint, kp, ki, kd, DIRECT);

void setup()
{
  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  //Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
  TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif

Serial.begin(115200);

mpu.initialize();

devStatus = mpu.dmpInitialize();

// supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(-855);
  mpu.setYGyroOffset(-251);
  mpu.setZGyroOffset(-259);
  mpu.setZAccelOffset(1005); // 1688 factory default for my test chip

// make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // turn on the DMP, now that it's ready

mpu.setDMPEnabled(true);

// enable Arduino interrupt detection
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();

// set our DMP Ready flag so the main loop() function knows it's okay to use it
    dmpReady = true;

// get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();

myPID.SetOutputLimits(-255, 255); (mínimo 255 y máximo 255)
    myPID.SetSampleTime(5);
    myPID.SetMode(AUTOMATIC);

} else {
    // ERROR!
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
    // (if it's going to break, usually the code will be 1)
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }

// Motores
  pinMode(IN1, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N
  pinMode(IN2, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N
  pinMode(ENA, OUTPUT); //Motor RH. Salida para hacia el módulo controlador L298N

pinMode(IN3, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N
  pinMode(IN4, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N
  pinMode(ENB, OUTPUT); //Motor LH. Salida para hacia el módulo controlador L298N

//Ajuste de las variables con potenciómetro
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
}
void loop()
{
  if (!dmpReady) return;

while (!mpuInterrupt && fifoCount < packetSize) {

//Arrancamos la libería del PID
    myPID.Compute();
  }

// reset interrupt flag and get INT_STATUS byte
  mpuInterrupt = false;
  mpuIntStatus = mpu.getIntStatus();

// get current FIFO count
  fifoCount = mpu.getFIFOCount();

// check for overflow (this should never happen unless our code is too inefficient)
  if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
    // reset so we can continue cleanly
    mpu.resetFIFO();
    Serial.println(F("FIFO overflow!"));

// otherwise, check for DMP data ready interrupt (this should happen frequently)
  } else if (mpuIntStatus & 0x02) {
    // wait for correct available data length, should be a VERY short wait
    while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

// read a packet from FIFO
    mpu.getFIFOBytes(fifoBuffer, packetSize);

// track FIFO count here in case there is > 1 packet available
    // (this lets us immediately read more without waiting for an interrupt)
    fifoCount -= packetSize;

//display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    Input = ypr[2] * 180 / M_PI; (este es el ángulo que lee el MPU 6050)
  }

if (U == 0) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, 0);

digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
    analogWrite(ENB, 0);
  }

if (U > 0) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
    analogWrite(ENA, U);

digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
    analogWrite(ENB, U);
  }

if (U < 0) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, -U);

digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
    analogWrite(ENB, -U);
  }
 
}