Sensor PING y librería Servo

Hola amigos.

Acabo de registrarme en el foro ya que soy un recién llegado al mundo Arduino. Tal es así que estoy dando mis primeros pasos en aprender a programar y en general a manejarme con esta plataforma. Está claro que por el momento preguntaré mucho y responderé poco.

Para hacerme con Arduino voy practicando con los ejemplos que trae y haciendo las variaciones que se me ocurren. En este caso, partiendo del ejemplo que toma lecturas del sensor de distancia PING de Parallax y la librería Servo, lo que pretendo es posicionar un servo en función de la distancia medida por el PING. La toma de lecturas es perfecta. Sin embargo en cuanto activo la instrucción write de la librería servo se producen lecturas incorrectas, muchas de ellas de lectura 0 (cero). Una secuencia de lecturas en estas condiciones podría ser (en microsegundos):

1200-1204-1202-0-1202-0-1202-0-1201-1200-1199-0-1198-0-1197-0...

Bajo mi entender hay algo en la instrucción write de la librería servo que de alguna manera interrumpe la secuencia de toma de lectura del sensor. No sé si tiene mucho sentido pero he probado a poner las instrucciones NoInterrupts() y interrupts() encerrando la secuencia de toma de lecturas pero el resultado es el mismo.

No sé si alguien ha experimentado esto o sabe qué puede ser. Si lo creéis necesario puedo pegar el código.

Gracias y saludos.

http://arduino.cc/playground/Es/Consejos

Si pega el código para poder ayudarte.

Gracias, aquí está el código.

#include <Servo.h>

#define BAUD 9600   

#define PINSERVO 9  

Servo MiServo;  

int TiempoMiServo;
int interval = 50;

const int pingPin = 7;
long oldduration;
long duration;
long mm;
unsigned long previousMillis = 0;



void setup() {
  Serial.begin(BAUD);
 
  MiServo.attach(PINSERVO); 
  MiServo.write(1500);
}

void loop () {

   unsigned long currentMillis = millis();
   
// Voy tomando lecturas del sensor cada 50 ms

  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;

  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  
  pinMode(pingPin, INPUT);

  duration = pulseIn(pingPin, HIGH);

  }
  
// Solo imprimo en pantalla y envio al servo las lecturas 
// que difieren como minimo 20 microsegundos 
  
 long diferencia = oldduration - duration;
 diferencia = abs(diferencia);
 
 if (diferencia > 20)
{

   long mm = 10 * duration / 29 / 2;

        Serial.print(mm);
        Serial.print(" - ");
        Serial.print(duration);
        Serial.print(" - ");
        Serial.println(diferencia);

        
oldduration = duration;
TiempoMiServo = map(duration, 300, 7000, 800, 2300);
TiempoMiServo = constrain (TiempoMiServo, 800, 2300);

// Si anulo la siguiente instruccion las lecturas en pantalla son correctas
// si no dan saltos

MiServo.writeMicroseconds(TiempoMiServo);

}

}

entonces esto:

        Serial.print(mm);
        Serial.print(" - ");
        Serial.print(duration);
        Serial.print(" - ");
        Serial.println(diferencia);

te da esto?

1200-1204-1202-0-1202-0-1202-0-1201-1200-1199-0-1198-0-1197-0...

no verdad?
puedes poner que sale por serie usando ese codigo

Disculpa, esa secuencia fue un ejemplo para intentar explicar lo que sucedía.

Por supuesto la secuencia es muy variable y depende de la distancia que está midiendo el sensor. Copio y pego una pequeña secuencia cuando el sensor está midiendo unos 80 cm y voy recortando hasta unos 70 cm.

820 - 4760 - 3267
0 - 0 - 4760
808 - 4691 - 4691
0 - 0 - 4691
792 - 4597 - 4597
0 - 0 - 4597
452 - 2623 - 2623
777 - 4512 - 1889
0 - 0 - 4512
767 - 4454 - 4454
0 - 0 - 4454
757 - 4395 - 4395
0 - 0 - 4395
647 - 3754 - 3754
0 - 0 - 3754
745 - 4324 - 4324
0 - 0 - 4324
740 - 4295 - 4295
0 - 0 - 4295
735 - 4267 - 4267
0 - 0 - 4267
733 - 4253 - 4253
0 - 0 - 4253
731 - 4241 - 4241
0 - 0 - 4241
737 - 4277 - 4277
0 - 0 - 4277
737 - 4279 - 4279
0 - 0 - 4279
737 - 4279 - 4279
0 - 0 - 4279
482 - 2800 - 2800
737 - 4275 - 1475
0 - 0 - 4275
738 - 4282 - 4282
0 - 0 - 4282
315 - 1830 - 1830
737 - 4278 - 2448

Si hago la misma operación anulando la instrucción:

MiServo.writeMicroseconds(TiempoMiServo);

entonces las medidas son correctas:

803 - 4663 - 27
800 - 4642 - 21
793 - 4605 - 37
798 - 4634 - 29
794 - 4609 - 25
790 - 4585 - 24
795 - 4615 - 30
791 - 4589 - 26
787 - 4568 - 21
781 - 4533 - 35
776 - 4505 - 28
781 - 4535 - 30
777 - 4512 - 23
782 - 4537 - 25
776 - 4504 - 33
770 - 4468 - 36
775 - 4497 - 29
770 - 4468 - 29
766 - 4446 - 22
762 - 4425 - 21
758 - 4400 - 25
763 - 4426 - 26
758 - 4400 - 26
763 - 4426 - 26
759 - 4404 - 22
753 - 4371 - 33
757 - 4395 - 24
752 - 4363 - 32
747 - 4338 - 25
742 - 4307 - 31
750 - 4354 - 47
743 - 4315 - 39
740 - 4292 - 23
744 - 4317 - 25
737 - 4279 - 38
730 - 4239 - 40

no controlo mucho la librería de los servo, pero echando un vistazo rápido veo que maneja interrupciones con el timer.

Da la sensación de que no puedes ejecutar de manera integra el codigo de lectura del ping, haz esta modificación:

cli();
pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  
  pinMode(pingPin, INPUT);

  duration = pulseIn(pingPin, HIGH);
sei();

de este modo te aseguras que tu código no es interrumpido.

interesante aero !!

por si no lo entiendes lo que te ha puesto aero, lo que hace cli(); es deshabilitar las interrupciones y sei() vuelve a habilitarlas.

otra opción es filtrar los resultados donde duracion sea igual a cero. obviamente esto es "imposible" por lo que puedes descartar el calculo en este caso

Algo así imaginaba yo. Por eso intenté utilizar los códigos NoInterrupts() y interrupts() creyendo que precisamente hacían lo que indicáis que hacen cli() y sei().

Por cierto, ¿Y al deshabilitar las interrupciones no podría afectar al funcionamiento de la librería?

no se que hacen exactamente NoInterrupts() e interrupts() , pero cli() y sei() afectan a todas las interrupciones.

en cuanto a cuanto afecta a la ejecución... ni idea, hay que verlo in situ.

De todas formas prueba el código a ver el alcance que tiene.

Por otro lado te recomiendo usar la función pulseIn(pin, value, timeout), con un timeout de 20000-30000us, ya que si no hay rebote de señal, no se queda esperando.

De acuerdo, gracias. En cuanto pueda lo pruebo y os comento los resultados.

Pues no, a la librería Servo no le ha gustado nada que le quitemos las interrupciones.

El código ahora queda:

#include <Servo.h>

#define BAUD 9600   

#define PINSERVO 9  

Servo MiServo;  

int TiempoMiServo;
int interval = 50;

const int pingPin = 7;
long oldduration;
long duration;
long mm;
unsigned long previousMillis = 0;


void setup() {
  Serial.begin(BAUD);
 
  MiServo.attach(PINSERVO); 
  MiServo.writeMicroseconds(1500);
}

void loop () {

   unsigned long currentMillis = millis();
   
// Voy tomando lecturas del sensor cada 50 ms

  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
cli();
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  
  pinMode(pingPin, INPUT);

  duration = pulseIn(pingPin, HIGH, 20000);
sei();
  }
  
// Solo imprimo en pantalla y envio al servo las lecturas 
// que difieren como minimo 20 microsegundos 
  
 long diferencia = oldduration - duration;
 diferencia = abs(diferencia);
 
 if (diferencia > 20)
{

   long mm = 10 * duration / 29 / 2;

        Serial.print(mm);
        Serial.print(" - ");
        Serial.print(duration);
        Serial.print(" - ");
        Serial.println(diferencia);

        
oldduration = duration;
TiempoMiServo = map(duration, 300, 7000, 800, 2300);
TiempoMiServo = constrain (TiempoMiServo, 800, 2300);

// Si anulo la siguiente instruccion las lecturas en pantalla son correctas
// si no dan saltos

MiServo.writeMicroseconds(TiempoMiServo);

}

}

Y la secuencia en el monitor la de abajo. La sensación es que ahora el servo se ha vuelto más loco. Antes si lo mantenía fijo a veces se congelaban las lecturas (por la diferencia entre lecturas de menos de 20 microsegundos). Ahora el servo no para de dar saltos a derecha e izquierda.
Yo creo que el problema sigue siendo que la librería funciona con interrupciones pero me quedo ahí, no sé ni para adónde apuntar para encontrar una solución.

973 - 5646 - 5646
0 - 0 - 5646
966 - 5604 - 5604
0 - 0 - 5604
948 - 5504 - 5504
0 - 0 - 5504
929 - 5390 - 5390
0 - 0 - 5390
897 - 5203 - 5203
0 - 0 - 5203
873 - 5069 - 5069
0 - 0 - 5069
849 - 4926 - 4926
0 - 0 - 4926
825 - 4789 - 4789
0 - 0 - 4789
807 - 4684 - 4684
0 - 0 - 4684
793 - 4605 - 4605
0 - 0 - 4605
784 - 4550 - 4550
0 - 0 - 4550
780 - 4527 - 4527
0 - 0 - 4527
789 - 4580 - 4580

te añado un una variable para comprobar si hay lectura nueva, esto debería eliminar los envios por serie del tipo 0-0-54525.

#include <Servo.h>

#define BAUD 9600   

#define PINSERVO 9  

Servo MiServo;  

int TiempoMiServo;
int interval = 50;

const int pingPin = 7;
long oldduration;
long duration;
long mm;
unsigned long previousMillis = 0;
boolean newRead = false;

void setup() {
  Serial.begin(BAUD);

  MiServo.attach(PINSERVO); 
  MiServo.writeMicroseconds(1500);
}

void loop () {

  unsigned long currentMillis = millis();

  // Voy tomando lecturas del sensor cada 50 ms

    if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    cli();
    pinMode(pingPin, OUTPUT);
    digitalWrite(pingPin, LOW);
    delayMicroseconds(2);
    digitalWrite(pingPin, HIGH);
    delayMicroseconds(5);
    digitalWrite(pingPin, LOW);

    pinMode(pingPin, INPUT);

    duration = pulseIn(pingPin, HIGH, 20000);
    newRead = true;
    sei();
  }

  // Solo imprimo en pantalla y envio al servo las lecturas 
  // que difieren como minimo 20 microsegundos 

  long diferencia = oldduration - duration;
  diferencia = abs(diferencia);

  if (diferencia > 20)
  {

    long mm = 10 * duration / 29 / 2;
    if(newRead==true){
      Serial.print(mm);
      Serial.print(" - ");
      Serial.print(duration);
      Serial.print(" - ");
      Serial.println(diferencia);


      oldduration = duration;
      TiempoMiServo = map(duration, 300, 7000, 800, 2300);
      TiempoMiServo = constrain (TiempoMiServo, 800, 2300);
      newRead=false;
    
    // Si anulo la siguiente instruccion las lecturas en pantalla son correctas
    // si no dan saltos

    MiServo.writeMicroseconds(TiempoMiServo);
}
  }

}

Se trata de newRead, al leer el ping lo ponemos true, y al usar la lectura lo ponemos false.

de hecho creo que debería eliminar el comportamiento errático del servo, que se debía a que le mandabas duration, que oscilaba de 800 a 2300 en cada orden

Acabo de probar el código con la variable newRead y siguen apareciendo las lecturas 0-0-xxx además que el resto de lecturas se han vuelto más dispares.

Lo que también me parece raro es que al hacer búsquedas no he encontrado nada sobre esto (también puede ser que esté un poco torpe en esto) y es algo que yo creo que debe de usar bastante gente: compaginar las lecturas de un sensor de este tipo e instrucciones de la librería servo, ya sea para controlar un servo o para controlar la velocidad de un motor brushless (a través de un esc).

948 - 5501 - 5501
0 - 0 - 5501
367 - 2131 - 2131
0 - 0 - 2131
334 - 1942 - 1942
0 - 0 - 1942
584 - 3389 - 3389
0 - 0 - 3389
530 - 3074 - 3074
0 - 0 - 3074
500 - 2900 - 2900
0 - 0 - 2900
876 - 5085 - 5085
0 - 0 - 5085
424 - 2464 - 2464
0 - 0 - 2464
400 - 2324 - 2324
0 - 0 - 2324
765 - 4442 - 4442
0 - 0 - 4442
337 - 1956 - 1956
0 - 0 - 1956
322 - 1873 - 1873
0 - 0 - 1873
311 - 1805 - 1805
0 - 0 - 1805
309 - 1795 - 1795

Acabo de comprobar una cosa. Estando en funcionamiento dando saltos las lecturas apareciendo continuamente las de tipo 0-0-xxx, si desconecto físicamente el servo instantáneamente desaparecen los errores y las lecturas son correctas. No sé si será porque la librería servo detecta que no tiene nada conectado o por qué puede ser.

Alimentas el servo desde el arduino???

Cheyenne no te compliques, si la duración es CERO haz que descarte la lectura y ya esta, todo solucionado.
esta midiendo en microsegundos,no? o igual en millis. bueno da igual, por muy cerca que este el objeto es bastante improbable que "duration" sea CERO.
En caso que necesites de verdad detectar ese CERO absoluto de tiempo en el ping, pues haces que cuando la lectura sea CERO que realice 2 lecturas mas para confirmarlo, si las tres lecturas son cero pues ...sera verdad.

por supuesto lo ideal es llegar al fondo de porque ocurren estas lecturas, pero como parece que no vamos muy encaminados, con esta solución puedes seguir con tu proyecto.
Yo tengo un sensor de estos midiendo el nivel de un tanque cada 10 segundos (exagerado pero bueno) y me dio muchos problemas ya que a veces tenia lecturas erroneas y me estropeaba todas las estadisticas ya que los datos se guardan en MYSQL. lo que hice fue implementar un filtro modal y punto.
lo que digo con esto, es que a veces hay que buscar otro camino jeje

Sergegsx, las lecturas CERO es el síntoma más visible pero si se miran bien los datos aparecen muchas más lecturas incorrectas que difícilmente se pueden filtrar.

aero_yo, estoy alimentando el servo desde el Arduino. La verdad que no he comprobado el consumo y lo que puede suministrar el Arduino pero el servo es muy pequeño (2,2 kg*cm) y por supuesto lo hago funcionar en vacío. Además este mismo servo lo probé con el ejemplo en el que se posiciona en función de la lectura de un potenciómetro y funcionaba perfectamente.

In the face!!! jeje

vale pues entonces ni idea, has probado a alimentarlo con trafo externo en lugar de USB?
y si pones un delay exagerado entre el movimiento del servo y la captura de datos, para probar si afecta el realizar las dos cosas a la vez.

Prueba a alimentar los motores con una fuente externa que no sea el USB arduino, a mi me paso lo mismo y se soluciona alimentando los motores a parte.
aprovecho este post, ya que no encuentro otro, yo uso la libreria Ultrasonic.h para leer la medición del sensor de ultrasonido, con un arduino duamilanove me va perfecto pero con un arduino nano, no me funciona, me da lectura erronea, sabéis si esa libreria no es compatible con esta placa?

Finalmente ha sido algo tan "sencillo" como alimentar el servo con una fuente externa. Nunca pensé que pudiera ir por ahí la cosa ya que el ejemplo Knob con el que se controla un servo mediante un potenciómetro me funcionó bien estando alimentado el servo desde la placa Arduino.

Gracias a todos.